mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
14 Commits
v25.0.0-ni
...
v25.0.0-ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8f3ef39f1e | ||
|
|
48e13fde80 | ||
|
|
bb6648b79e | ||
|
|
2b9dae4b06 | ||
|
|
2e1f803f37 | ||
|
|
4c6092e151 | ||
|
|
b72f81ab5b | ||
|
|
97b19a7946 | ||
|
|
b27e4cae21 | ||
|
|
1e106c8aa4 | ||
|
|
fda8ea9277 | ||
|
|
6a6908c4c8 | ||
|
|
42e7cd9b3f | ||
|
|
8cf03f5661 |
@@ -970,26 +970,13 @@ step-ts-compile: &step-ts-compile
|
||||
# List of all steps.
|
||||
steps-electron-gn-check: &steps-electron-gn-check
|
||||
steps:
|
||||
- *step-checkout-electron
|
||||
- *step-depot-tools-get
|
||||
- *step-depot-tools-add-to-path
|
||||
- install-python2-mac
|
||||
- *step-setup-env-for-build
|
||||
- *step-setup-goma-for-build
|
||||
- *step-generate-deps-hash
|
||||
- *step-touch-sync-done
|
||||
- maybe-restore-portaled-src-cache
|
||||
- run:
|
||||
name: Ensure src checkout worked
|
||||
command: |
|
||||
if [ ! -d "src/third_party/blink" ]; then
|
||||
echo src cache was not restored for an unknown reason
|
||||
exit 1
|
||||
fi
|
||||
- run:
|
||||
name: Wipe Electron
|
||||
command: rm -rf src/electron
|
||||
- *step-checkout-electron
|
||||
- checkout-from-cache
|
||||
- *step-setup-env-for-build
|
||||
- *step-wait-for-goma
|
||||
- *step-gn-gen-default
|
||||
- *step-gn-check
|
||||
|
||||
steps-electron-ts-compile-for-doc-change: &steps-electron-ts-compile-for-doc-change
|
||||
steps:
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -11,4 +11,5 @@ patches/**/.patches merge=union
|
||||
*.ts text eol=lf
|
||||
*.py text eol=lf
|
||||
*.ps1 text eol=lf
|
||||
*.html text eol=lf
|
||||
*.md text eol=lf
|
||||
|
||||
7
.github/workflows/update_appveyor_image.yml
vendored
7
.github/workflows/update_appveyor_image.yml
vendored
@@ -40,7 +40,9 @@ jobs:
|
||||
if: ${{ env.APPVEYOR_IMAGE_VERSION }}
|
||||
uses: mikefarah/yq@1c7dc0e88aad311c89889bc5ce5d8f96931a1bd0 # v4.27.2
|
||||
with:
|
||||
cmd: yq '.image = "${{ env.APPVEYOR_IMAGE_VERSION }}"' "appveyor.yml" > "appveyor2.yml"
|
||||
cmd: |
|
||||
yq '.image = "${{ env.APPVEYOR_IMAGE_VERSION }}"' "appveyor.yml" > "appveyor2.yml"
|
||||
yq '.image = "${{ env.APPVEYOR_IMAGE_VERSION }}"' "appveyor-woa.yml" > "appveyor-woa2.yml"
|
||||
- name: (Optionally) Generate Commit Diff
|
||||
if: ${{ env.APPVEYOR_IMAGE_VERSION }}
|
||||
run: |
|
||||
@@ -66,4 +68,5 @@ jobs:
|
||||
delete-branch: true
|
||||
title: 'build: update appveyor image to latest version'
|
||||
body: |
|
||||
This PR updates appveyor.yml to the latest baked image, ${{ env.APPVEYOR_IMAGE_VERSION }}.
|
||||
This PR updates appveyor.yml to the latest baked image, ${{ env.APPVEYOR_IMAGE_VERSION }}.
|
||||
Notes: none
|
||||
2
DEPS
2
DEPS
@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'113.0.5668.0',
|
||||
'113.0.5670.0',
|
||||
'node_version':
|
||||
'v18.15.0',
|
||||
'nan_version':
|
||||
|
||||
@@ -804,7 +804,7 @@ editor. Please refer to [Apple's documentation][CFBundleURLTypes] for details.
|
||||
**Note:** In a Windows Store environment (when packaged as an `appx`) this API
|
||||
will return `true` for all calls but the registry key it sets won't be accessible
|
||||
by other applications. In order to register your Windows Store application
|
||||
as a default protocol handler you must [declare the protocol in your manifest](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol).
|
||||
as a default protocol handler you must [declare the protocol in your manifest](https://learn.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol).
|
||||
|
||||
The API uses the Windows Registry and `LSSetDefaultHandlerForURLScheme` internally.
|
||||
|
||||
|
||||
@@ -243,6 +243,8 @@ it is not allowed to add or remove a custom header.
|
||||
* `encoding` string (optional)
|
||||
* `callback` Function (optional)
|
||||
|
||||
Returns `this`.
|
||||
|
||||
Sends the last chunk of the request data. Subsequent write or end operations
|
||||
will not be allowed. The `finish` event is emitted just after the end operation.
|
||||
|
||||
|
||||
@@ -65,8 +65,8 @@ 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)
|
||||
* `input` string | [GlobalRequest](https://nodejs.org/api/globals.html#request)
|
||||
* `init` [RequestInit](https://developer.mozilla.org/en-US/docs/Web/API/fetch#options) & { bypassCustomProtocolHandlers?: boolean } (optional)
|
||||
|
||||
Returns `Promise<GlobalResponse>` - see [Response](https://developer.mozilla.org/en-US/docs/Web/API/Response).
|
||||
|
||||
@@ -101,9 +101,23 @@ Limitations:
|
||||
* The `.type` and `.url` values of the returned `Response` object are
|
||||
incorrect.
|
||||
|
||||
Requests made with `net.fetch` can be made to [custom protocols](protocol.md)
|
||||
as well as `file:`, and will trigger [webRequest](web-request.md) handlers if
|
||||
present.
|
||||
By default, requests made with `net.fetch` can be made to [custom
|
||||
protocols](protocol.md) as well as `file:`, and will trigger
|
||||
[webRequest](web-request.md) handlers if present. When the non-standard
|
||||
`bypassCustomProtocolHandlers` option is set in RequestInit, custom protocol
|
||||
handlers will not be called for this request. This allows forwarding an
|
||||
intercepted request to the built-in handler. [webRequest](web-request.md)
|
||||
handlers will still be triggered when bypassing custom protocols.
|
||||
|
||||
```js
|
||||
protocol.handle('https', (req) => {
|
||||
if (req.url === 'https://my-app.com') {
|
||||
return new Response('<body>my app</body>')
|
||||
} else {
|
||||
return net.fetch(req, { bypassCustomProtocolHandlers: true })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
### `net.isOnline()`
|
||||
|
||||
|
||||
@@ -8,15 +8,11 @@ An example of implementing a protocol that has the same effect as the
|
||||
`file://` protocol:
|
||||
|
||||
```javascript
|
||||
const { app, protocol } = require('electron')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
const { app, protocol, net } = require('electron')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
protocol.registerFileProtocol('atom', (request, callback) => {
|
||||
const filePath = url.fileURLToPath('file://' + request.url.slice('atom://'.length))
|
||||
callback(filePath)
|
||||
})
|
||||
protocol.handle('atom', (request) =>
|
||||
net.fetch('file://' + request.url.slice('atom://'.length)))
|
||||
})
|
||||
```
|
||||
|
||||
@@ -38,14 +34,15 @@ to register it to that session explicitly.
|
||||
```javascript
|
||||
const { session, app, protocol } = require('electron')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const partition = 'persist:example'
|
||||
const ses = session.fromPartition(partition)
|
||||
|
||||
ses.protocol.registerFileProtocol('atom', (request, callback) => {
|
||||
const url = request.url.substr(7)
|
||||
callback({ path: path.normalize(`${__dirname}/${url}`) })
|
||||
ses.protocol.handle('atom', (request) => {
|
||||
const path = request.url.slice('atom://'.length)
|
||||
return net.fetch(url.pathToFileURL(path.join(__dirname, path)))
|
||||
})
|
||||
|
||||
mainWindow = new BrowserWindow({ webPreferences: { partition } })
|
||||
@@ -109,7 +106,74 @@ The `<video>` and `<audio>` HTML elements expect protocols to buffer their
|
||||
responses by default. The `stream` flag configures those elements to correctly
|
||||
expect streaming responses.
|
||||
|
||||
### `protocol.registerFileProtocol(scheme, handler)`
|
||||
### `protocol.handle(scheme, handler)`
|
||||
|
||||
* `scheme` string - scheme to handle, for example `https` or `my-app`. This is
|
||||
the bit before the `:` in a URL.
|
||||
* `handler` Function<[GlobalResponse](https://nodejs.org/api/globals.html#response) | Promise<GlobalResponse>>
|
||||
* `request` [GlobalRequest](https://nodejs.org/api/globals.html#request)
|
||||
|
||||
Register a protocol handler for `scheme`. Requests made to URLs with this
|
||||
scheme will delegate to this handler to determine what response should be sent.
|
||||
|
||||
Either a `Response` or a `Promise<Response>` can be returned.
|
||||
|
||||
Example:
|
||||
|
||||
```js
|
||||
import { app, protocol } from 'electron'
|
||||
import { join } from 'path'
|
||||
import { pathToFileURL } from 'url'
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
scheme: 'app',
|
||||
privileges: {
|
||||
standard: true,
|
||||
secure: true,
|
||||
supportsFetchAPI: true
|
||||
}
|
||||
}
|
||||
])
|
||||
|
||||
app.whenReady().then(() => {
|
||||
protocol.handle('app', (req) => {
|
||||
const { host, pathname } = new URL(req.url)
|
||||
if (host === 'bundle') {
|
||||
if (pathname === '/') {
|
||||
return new Response('<h1>hello, world</h1>', {
|
||||
headers: { 'content-type': 'text/html' }
|
||||
})
|
||||
}
|
||||
// NB, this does not check for paths that escape the bundle, e.g.
|
||||
// app://bundle/../../secret_file.txt
|
||||
return net.fetch(pathToFileURL(join(__dirname, pathname)))
|
||||
} else if (host === 'api') {
|
||||
return net.fetch('https://api.my-server.com/' + pathname, {
|
||||
method: req.method,
|
||||
headers: req.headers,
|
||||
body: req.body
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
See the MDN docs for [`Request`](https://developer.mozilla.org/en-US/docs/Web/API/Request) and [`Response`](https://developer.mozilla.org/en-US/docs/Web/API/Response) for more details.
|
||||
|
||||
### `protocol.unhandle(scheme)`
|
||||
|
||||
* `scheme` string - scheme for which to remove the handler.
|
||||
|
||||
Removes a protocol handler registered with `protocol.handle`.
|
||||
|
||||
### `protocol.isProtocolHandled(scheme)`
|
||||
|
||||
* `scheme` string
|
||||
|
||||
Returns `boolean` - Whether `scheme` is already handled.
|
||||
|
||||
### `protocol.registerFileProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -130,7 +194,7 @@ path or an object that has a `path` property, e.g. `callback(filePath)` or
|
||||
By default the `scheme` is treated like `http:`, which is parsed differently
|
||||
from protocols that follow the "generic URI syntax" like `file:`.
|
||||
|
||||
### `protocol.registerBufferProtocol(scheme, handler)`
|
||||
### `protocol.registerBufferProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -154,7 +218,7 @@ protocol.registerBufferProtocol('atom', (request, callback) => {
|
||||
})
|
||||
```
|
||||
|
||||
### `protocol.registerStringProtocol(scheme, handler)`
|
||||
### `protocol.registerStringProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -170,7 +234,7 @@ The usage is the same with `registerFileProtocol`, except that the `callback`
|
||||
should be called with either a `string` or an object that has the `data`
|
||||
property.
|
||||
|
||||
### `protocol.registerHttpProtocol(scheme, handler)`
|
||||
### `protocol.registerHttpProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -185,7 +249,7 @@ Registers a protocol of `scheme` that will send an HTTP request as a response.
|
||||
The usage is the same with `registerFileProtocol`, except that the `callback`
|
||||
should be called with an object that has the `url` property.
|
||||
|
||||
### `protocol.registerStreamProtocol(scheme, handler)`
|
||||
### `protocol.registerStreamProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -234,7 +298,7 @@ protocol.registerStreamProtocol('atom', (request, callback) => {
|
||||
})
|
||||
```
|
||||
|
||||
### `protocol.unregisterProtocol(scheme)`
|
||||
### `protocol.unregisterProtocol(scheme)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
|
||||
@@ -242,13 +306,13 @@ Returns `boolean` - Whether the protocol was successfully unregistered
|
||||
|
||||
Unregisters the custom protocol of `scheme`.
|
||||
|
||||
### `protocol.isProtocolRegistered(scheme)`
|
||||
### `protocol.isProtocolRegistered(scheme)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
|
||||
Returns `boolean` - Whether `scheme` is already registered.
|
||||
|
||||
### `protocol.interceptFileProtocol(scheme, handler)`
|
||||
### `protocol.interceptFileProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -261,7 +325,7 @@ Returns `boolean` - Whether the protocol was successfully intercepted
|
||||
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
|
||||
which sends a file as a response.
|
||||
|
||||
### `protocol.interceptStringProtocol(scheme, handler)`
|
||||
### `protocol.interceptStringProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -274,7 +338,7 @@ Returns `boolean` - Whether the protocol was successfully intercepted
|
||||
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
|
||||
which sends a `string` as a response.
|
||||
|
||||
### `protocol.interceptBufferProtocol(scheme, handler)`
|
||||
### `protocol.interceptBufferProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -287,7 +351,7 @@ Returns `boolean` - Whether the protocol was successfully intercepted
|
||||
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
|
||||
which sends a `Buffer` as a response.
|
||||
|
||||
### `protocol.interceptHttpProtocol(scheme, handler)`
|
||||
### `protocol.interceptHttpProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -300,7 +364,7 @@ Returns `boolean` - Whether the protocol was successfully intercepted
|
||||
Intercepts `scheme` protocol and uses `handler` as the protocol's new handler
|
||||
which sends a new HTTP request as a response.
|
||||
|
||||
### `protocol.interceptStreamProtocol(scheme, handler)`
|
||||
### `protocol.interceptStreamProtocol(scheme, handler)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
* `handler` Function
|
||||
@@ -313,7 +377,7 @@ Returns `boolean` - Whether the protocol was successfully intercepted
|
||||
Same as `protocol.registerStreamProtocol`, except that it replaces an existing
|
||||
protocol handler.
|
||||
|
||||
### `protocol.uninterceptProtocol(scheme)`
|
||||
### `protocol.uninterceptProtocol(scheme)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
|
||||
@@ -321,7 +385,7 @@ Returns `boolean` - Whether the protocol was successfully unintercepted
|
||||
|
||||
Remove the interceptor installed for `scheme` and restore its original handler.
|
||||
|
||||
### `protocol.isProtocolIntercepted(scheme)`
|
||||
### `protocol.isProtocolIntercepted(scheme)` _Deprecated_
|
||||
|
||||
* `scheme` string
|
||||
|
||||
|
||||
@@ -784,9 +784,23 @@ Limitations:
|
||||
* The `.type` and `.url` values of the returned `Response` object are
|
||||
incorrect.
|
||||
|
||||
Requests made with `ses.fetch` can be made to [custom protocols](protocol.md)
|
||||
as well as `file:`, and will trigger [webRequest](web-request.md) handlers if
|
||||
present.
|
||||
By default, requests made with `net.fetch` can be made to [custom
|
||||
protocols](protocol.md) as well as `file:`, and will trigger
|
||||
[webRequest](web-request.md) handlers if present. When the non-standard
|
||||
`bypassCustomProtocolHandlers` option is set in RequestInit, custom protocol
|
||||
handlers will not be called for this request. This allows forwarding an
|
||||
intercepted request to the built-in handler. [webRequest](web-request.md)
|
||||
handlers will still be triggered when bypassing custom protocols.
|
||||
|
||||
```js
|
||||
protocol.handle('https', (req) => {
|
||||
if (req.url === 'https://my-app.com') {
|
||||
return new Response('<body>my app</body>')
|
||||
} else {
|
||||
return net.fetch(req, { bypassCustomProtocolHandlers: true })
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### `ses.disableNetworkEmulation()`
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
* `isDefault` boolean - whether or not a given printer is set as the default printer on the OS.
|
||||
* `options` Object - an object containing a variable number of platform-specific printer information.
|
||||
|
||||
The number represented by `status` means different things on different platforms: on Windows its potential values can be found [here](https://docs.microsoft.com/en-us/windows/win32/printdocs/printer-info-2), and on Linux and macOS they can be found [here](https://www.cups.org/doc/cupspm.html).
|
||||
The number represented by `status` means different things on different platforms: on Windows its potential values can be found [here](https://learn.microsoft.com/en-us/windows/win32/printdocs/printer-info-2), and on Linux and macOS they can be found [here](https://www.cups.org/doc/cupspm.html).
|
||||
|
||||
## Example
|
||||
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
|
||||
* `type` 'file' - `file`.
|
||||
* `filePath` string - Path of file to be uploaded.
|
||||
* `offset` Integer - Defaults to `0`.
|
||||
* `length` Integer - Number of bytes to read from `offset`.
|
||||
* `offset` Integer (optional) - Defaults to `0`.
|
||||
* `length` Integer (optional) - Number of bytes to read from `offset`.
|
||||
Defaults to `0`.
|
||||
* `modificationTime` Double - Last Modification time in
|
||||
number of seconds since the UNIX epoch.
|
||||
* `modificationTime` Double (optional) - Last Modification time in
|
||||
number of seconds since the UNIX epoch. Defaults to `0`.
|
||||
|
||||
@@ -269,9 +269,9 @@ Returns `boolean` - Whether double click events will be ignored.
|
||||
|
||||
Displays a tray balloon.
|
||||
|
||||
[NIIF_NOSOUND]: https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa#niif_nosound-0x00000010
|
||||
[NIIF_LARGE_ICON]: https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa#niif_large_icon-0x00000020
|
||||
[NIIF_RESPECT_QUIET_TIME]: https://docs.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa#niif_respect_quiet_time-0x00000080
|
||||
[NIIF_NOSOUND]: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa#niif_nosound-0x00000010
|
||||
[NIIF_LARGE_ICON]: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa#niif_large_icon-0x00000020
|
||||
[NIIF_RESPECT_QUIET_TIME]: https://learn.microsoft.com/en-us/windows/win32/api/shellapi/ns-shellapi-notifyicondataa#niif_respect_quiet_time-0x00000080
|
||||
|
||||
#### `tray.removeBalloon()` _Windows_
|
||||
|
||||
|
||||
@@ -19,6 +19,36 @@ const contents = win.webContents
|
||||
console.log(contents)
|
||||
```
|
||||
|
||||
## Navigation Events
|
||||
|
||||
Several events can be used to monitor navigations as they occur within a `webContents`.
|
||||
|
||||
### Document Navigations
|
||||
|
||||
When a `webContents` navigates to another page (as opposed to an [in-page navigation](web-contents.md#in-page-navigation)), the following events will be fired.
|
||||
|
||||
* [`did-start-navigation`](web-contents.md#event-did-start-navigation)
|
||||
* [`will-frame-navigate`](web-contents.md#event-will-frame-navigate)
|
||||
* [`will-navigate`](web-contents.md#event-will-navigate) (only fired when main frame navigates)
|
||||
* [`will-redirect`](web-contents.md#event-will-redirect) (only fired when a redirect happens during navigation)
|
||||
* [`did-redirect-navigation`](web-contents.md#event-did-redirect-navigation) (only fired when a redirect happens during navigation)
|
||||
* [`did-frame-navigate`](web-contents.md#event-did-frame-navigate)
|
||||
* [`did-navigate`](web-contents.md#event-did-navigate) (only fired when main frame navigates)
|
||||
|
||||
Subsequent events will not fire if `event.preventDefault()` is called on any of the cancellable events.
|
||||
|
||||
### In-page Navigation
|
||||
|
||||
In-page navigations don't cause the page to reload, but instead navigate to a location within the current page. These events are not cancellable. For an in-page navigations, the following events will fire in this order:
|
||||
|
||||
* [`did-start-navigation`](web-contents.md#event-did-start-navigation)
|
||||
* [`did-navigate-in-page`](web-contents.md#event-did-navigate-in-page)
|
||||
|
||||
### Frame Navigation
|
||||
|
||||
The [`will-navigate`](web-contents.md#event-will-navigate) and [`did-navigate`](web-contents.md#event-did-navigate) events only fire when the [mainFrame](web-contents.md#contentsmainframe-readonly) navigates.
|
||||
If you want to also observe navigations in `<iframe>`s, use [`will-frame-navigate`](web-contents.md#event-will-frame-navigate) and [`did-frame-navigate`](web-contents.md#event-did-frame-navigate) events.
|
||||
|
||||
## Methods
|
||||
|
||||
These methods can be accessed from the `webContents` module:
|
||||
@@ -225,7 +255,7 @@ Returns:
|
||||
* `frameProcessId` Integer _Deprecated_
|
||||
* `frameRoutingId` Integer _Deprecated_
|
||||
|
||||
Emitted when a user or the page wants to start navigation. It can happen when
|
||||
Emitted when a user or the page wants to start navigation on the main frame. It can happen when
|
||||
the `window.location` object is changed or a user clicks a link in the page.
|
||||
|
||||
This event will not emit when the navigation is started programmatically with
|
||||
@@ -237,6 +267,34 @@ this purpose.
|
||||
|
||||
Calling `event.preventDefault()` will prevent the navigation.
|
||||
|
||||
#### Event: 'will-frame-navigate'
|
||||
|
||||
Returns:
|
||||
|
||||
* `details` Event<>
|
||||
* `url` string - The URL the frame is navigating to.
|
||||
* `isMainFrame` boolean - True if the navigation is taking place in a main frame.
|
||||
* `frame` WebFrameMain - The frame to be navigated.
|
||||
* `initiator` WebFrameMain (optional) - The frame which initiated the
|
||||
navigation, which can be a parent frame (e.g. via `window.open` with a
|
||||
frame's name), or null if the navigation was not initiated by a frame. This
|
||||
can also be null if the initiating frame was deleted before the event was
|
||||
emitted.
|
||||
|
||||
Emitted when a user or the page wants to start navigation in any frame. It can happen when
|
||||
the `window.location` object is changed or a user clicks a link in the page.
|
||||
|
||||
Unlike `will-navigate`, `will-frame-navigate` is fired when the main frame or any of its subframes attempts to navigate. When the navigation event comes from the main frame, `isMainFrame` will be `true`.
|
||||
|
||||
This event will not emit when the navigation is started programmatically with
|
||||
APIs like `webContents.loadURL` and `webContents.back`.
|
||||
|
||||
It is also not emitted for in-page navigations, such as clicking anchor links
|
||||
or updating the `window.location.hash`. Use `did-navigate-in-page` event for
|
||||
this purpose.
|
||||
|
||||
Calling `event.preventDefault()` will prevent the navigation.
|
||||
|
||||
#### Event: 'did-start-navigation'
|
||||
|
||||
Returns:
|
||||
@@ -777,20 +835,24 @@ Returns:
|
||||
* `callback` Function
|
||||
* `deviceId` string
|
||||
|
||||
Emitted when bluetooth device needs to be selected on call to
|
||||
`navigator.bluetooth.requestDevice`. To use `navigator.bluetooth` api
|
||||
`webBluetooth` should be enabled. If `event.preventDefault` is not called,
|
||||
first available device will be selected. `callback` should be called with
|
||||
`deviceId` to be selected, passing empty string to `callback` will
|
||||
cancel the request.
|
||||
Emitted when a bluetooth device needs to be selected when a call to
|
||||
`navigator.bluetooth.requestDevice` is made. `callback` should be called with
|
||||
the `deviceId` of the device to be selected. Passing an empty string to
|
||||
`callback` will cancel the request.
|
||||
|
||||
If no event listener is added for this event, all bluetooth requests will be cancelled.
|
||||
If an event listener is not added for this event, or if `event.preventDefault`
|
||||
is not called when handling this event, the first available device will be
|
||||
automatically selected.
|
||||
|
||||
Due to the nature of bluetooth, scanning for devices when
|
||||
`navigator.bluetooth.requestDevice` is called may take time and will cause
|
||||
`select-bluetooth-device` to fire multiple times until `callback` is called
|
||||
with either a device id or an empty string to cancel the request.
|
||||
|
||||
```javascript title='main.js'
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let win = null
|
||||
app.commandLine.appendSwitch('enable-experimental-web-platform-features')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow({ width: 800, height: 600 })
|
||||
@@ -800,6 +862,9 @@ app.whenReady().then(() => {
|
||||
return device.deviceName === 'test'
|
||||
})
|
||||
if (!result) {
|
||||
// The device wasn't found so we need to either wait longer (eg until the
|
||||
// device is turned on) or cancel the request by calling the callback
|
||||
// with an empty string.
|
||||
callback('')
|
||||
} else {
|
||||
callback(result.deviceId)
|
||||
|
||||
@@ -823,6 +823,28 @@ this purpose.
|
||||
|
||||
Calling `event.preventDefault()` does __NOT__ have any effect.
|
||||
|
||||
### Event: 'will-frame-navigate'
|
||||
|
||||
Returns:
|
||||
|
||||
* `url` string
|
||||
* `isMainFrame` boolean
|
||||
* `frameProcessId` Integer
|
||||
* `frameRoutingId` Integer
|
||||
|
||||
Emitted when a user or the page wants to start navigation anywhere in the `<webview>`
|
||||
or any frames embedded within. It can happen when the `window.location` object is
|
||||
changed or a user clicks a link in the page.
|
||||
|
||||
This event will not emit when the navigation is started programmatically with
|
||||
APIs like `<webview>.loadURL` and `<webview>.back`.
|
||||
|
||||
It is also not emitted during in-page navigation, such as clicking anchor links
|
||||
or updating the `window.location.hash`. Use `did-navigate-in-page` event for
|
||||
this purpose.
|
||||
|
||||
Calling `event.preventDefault()` does __NOT__ have any effect.
|
||||
|
||||
### Event: 'did-start-navigation'
|
||||
|
||||
Returns:
|
||||
|
||||
@@ -12,6 +12,55 @@ 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 (25.0)
|
||||
|
||||
### Deprecated: `protocol.{register,intercept}{Buffer,String,Stream,File,Http}Protocol`
|
||||
|
||||
The `protocol.register*Protocol` and `protocol.intercept*Protocol` methods have
|
||||
been replaced with [`protocol.handle`](api/protocol.md#protocolhandlescheme-handler).
|
||||
|
||||
The new method can either register a new protocol or intercept an existing
|
||||
protocol, and responses can be of any type.
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 25
|
||||
protocol.registerBufferProtocol('some-protocol', () => {
|
||||
callback({ mimeType: 'text/html', data: Buffer.from('<h5>Response</h5>') })
|
||||
})
|
||||
|
||||
// Replace with
|
||||
protocol.handle('some-protocol', () => {
|
||||
return new Response(
|
||||
Buffer.from('<h5>Response</h5>'), // Could also be a string or ReadableStream.
|
||||
{ headers: { 'content-type': 'text/html' } }
|
||||
)
|
||||
})
|
||||
```
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 25
|
||||
protocol.registerHttpProtocol('some-protocol', () => {
|
||||
callback({ url: 'https://electronjs.org' })
|
||||
})
|
||||
|
||||
// Replace with
|
||||
protocol.handle('some-protocol', () => {
|
||||
return net.fetch('https://electronjs.org')
|
||||
})
|
||||
```
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 25
|
||||
protocol.registerFileProtocol('some-protocol', () => {
|
||||
callback({ filePath: '/path/to/my/file' })
|
||||
})
|
||||
|
||||
// Replace with
|
||||
protocol.handle('some-protocol', () => {
|
||||
return net.fetch('file:///path/to/my/file')
|
||||
})
|
||||
```
|
||||
|
||||
## Planned Breaking API Changes (24.0)
|
||||
|
||||
### API Changed: `nativeImage.createThumbnailFromPath(path, size)`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Updating an Appveyor Azure Image
|
||||
|
||||
Electron CI on Windows uses AppVeyor, which in turn uses Azure VM images to run. Occasionally, these VM images need to be updated due to changes in Chromium requirements. In order to update you will need [PowerShell](https://docs.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-6) and the [Azure PowerShell module](https://docs.microsoft.com/en-us/powershell/azure/install-az-ps?view=azps-1.8.0&viewFallbackFrom=azurermps-6.13.0).
|
||||
Electron CI on Windows uses AppVeyor, which in turn uses Azure VM images to run. Occasionally, these VM images need to be updated due to changes in Chromium requirements. In order to update you will need [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell?view=powershell-7.3&viewFallbackFrom=powershell-6) and the [Azure PowerShell module](https://learn.microsoft.com/en-us/powershell/azure/install-az-ps?view=azps-9.5.0&viewFallbackFrom=azps-1.8.0).
|
||||
|
||||
Occasionally we need to update these images owing to changes in Chromium or other miscellaneous build requirement changes.
|
||||
|
||||
|
||||
@@ -94,7 +94,7 @@ out [this video tutorial][procmon-instructions] provided by Microsoft.
|
||||
## Using WinDbg
|
||||
<!-- TODO(@codebytere): add images and more information here? -->
|
||||
|
||||
It's possible to debug crashes and issues in the Renderer process with [WinDbg](https://docs.microsoft.com/en-us/windows-hardware/drivers/debugger/getting-started-with-windbg).
|
||||
It's possible to debug crashes and issues in the Renderer process with [WinDbg](https://learn.microsoft.com/en-us/windows-hardware/drivers/debugger/getting-started-with-windbg).
|
||||
|
||||
To attach to a debug a process with WinDbg:
|
||||
|
||||
|
||||
@@ -79,7 +79,7 @@ the Node.js source tree.
|
||||
|
||||
#### Missing fonts
|
||||
|
||||
[Some Windows 10 devices](https://docs.microsoft.com/en-us/typography/fonts/windows_10_font_list) do not ship with the Meiryo font installed, which may cause a font fallback test to fail. To install Meiryo:
|
||||
[Some Windows 10 devices](https://learn.microsoft.com/en-us/typography/fonts/windows_10_font_list) do not ship with the Meiryo font installed, which may cause a font fallback test to fail. To install Meiryo:
|
||||
|
||||
1. Push the Windows key and search for _Manage optional features_.
|
||||
2. Click _Add a feature_.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
<h1>Web Bluetooth API</h1>
|
||||
|
||||
<button id="clickme">Test Bluetooth</button>
|
||||
<button id="cancel">Cancel Bluetooth Request</button>
|
||||
|
||||
<p>Currently selected bluetooth device: <strong id="device-name""></strong></p>
|
||||
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
const {app, BrowserWindow, ipcMain} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
let bluetoothPinCallback
|
||||
let selectBluetoothCallback
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
@@ -12,11 +15,23 @@ function createWindow () {
|
||||
|
||||
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
|
||||
event.preventDefault()
|
||||
if (deviceList && deviceList.length > 0) {
|
||||
callback(deviceList[0].deviceId)
|
||||
}
|
||||
selectBluetoothCallback = callback
|
||||
const result = deviceList.find((device) => {
|
||||
return device.deviceName === 'test'
|
||||
})
|
||||
if (result) {
|
||||
callback(result.deviceId)
|
||||
} else {
|
||||
// The device wasn't found so we need to either wait longer (eg until the
|
||||
// device is turned on) or until the user cancels the request
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('cancel-bluetooth-request', (event) => {
|
||||
selectBluetoothCallback('')
|
||||
})
|
||||
|
||||
|
||||
// Listen for a message from the renderer to get the response for the Bluetooth pairing.
|
||||
ipcMain.on('bluetooth-pairing-response', (event, response) => {
|
||||
bluetoothPinCallback(response)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
cancelBluetoothRequest: (callback) => ipcRenderer.send('cancel-bluetooth-request', callback),
|
||||
bluetoothPairingRequest: (callback) => ipcRenderer.on('bluetooth-pairing-request', callback),
|
||||
bluetoothPairingResponse: (response) => ipcRenderer.send('bluetooth-pairing-response', response)
|
||||
})
|
||||
@@ -7,6 +7,12 @@ async function testIt() {
|
||||
|
||||
document.getElementById('clickme').addEventListener('click',testIt)
|
||||
|
||||
function cancelRequest() {
|
||||
window.electronAPI.cancelBluetoothRequest()
|
||||
}
|
||||
|
||||
document.getElementById('cancel').addEventListener('click', cancelRequest)
|
||||
|
||||
window.electronAPI.bluetoothPairingRequest((event, details) => {
|
||||
const response = {}
|
||||
|
||||
|
||||
@@ -1,128 +1,128 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Customize Menus</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Customize Menus</h1>
|
||||
|
||||
<h3>
|
||||
The <code>Menu</code> and <code>MenuItem</code> modules can be used to
|
||||
create custom native menus.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
There are two kinds of menus: the application (top) menu and context
|
||||
(right-click) menu.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/menu"
|
||||
>full API documentation<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Create an application menu</h2>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
The <code>Menu</code> and <code>MenuItem</code> modules allow you to
|
||||
customize your application menu. If you don't set any menu, Electron
|
||||
will generate a minimal menu for your app by default.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you click the 'View' option in the application menu and then the
|
||||
'App Menu Demo', you'll see an information box displayed.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Know operating system menu differences.</strong>
|
||||
<p>
|
||||
When designing an app for multiple operating systems it's
|
||||
important to be mindful of the ways application menu conventions
|
||||
differ on each operating system.
|
||||
</p>
|
||||
<p>
|
||||
For instance, on Windows, accelerators are set with an
|
||||
<code>&</code>. Naming conventions also vary, like between
|
||||
"Settings" or "Preferences". Below are resources for learning
|
||||
operating system specific standards.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://developer.apple.com/macos/human-interface-guidelines/menus/menu-anatomy/"
|
||||
>macOS<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/bb226797"
|
||||
>Windows<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://developer.gnome.org/hig/stable/menu-bars.html.en"
|
||||
>Linux<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Create a context menu</h2>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<button id="context-menu">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
A context, or right-click, menu can be created with the
|
||||
<code>Menu</code> and <code>MenuItem</code> modules as well. You can
|
||||
right-click anywhere in this app or click the demo button to see an
|
||||
example context menu.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this demo we use the <code>ipcRenderer</code> module to show the
|
||||
context menu when explicitly calling it from the renderer process.
|
||||
</p>
|
||||
<p>
|
||||
See the full
|
||||
<a
|
||||
href="https://electronjs.org/docs/api/web-contents/#event-context-menu"
|
||||
>context-menu event documentation</a
|
||||
>
|
||||
for all the available properties.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Customize Menus</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Customize Menus</h1>
|
||||
|
||||
<h3>
|
||||
The <code>Menu</code> and <code>MenuItem</code> modules can be used to
|
||||
create custom native menus.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
There are two kinds of menus: the application (top) menu and context
|
||||
(right-click) menu.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/menu"
|
||||
>full API documentation<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Create an application menu</h2>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
The <code>Menu</code> and <code>MenuItem</code> modules allow you to
|
||||
customize your application menu. If you don't set any menu, Electron
|
||||
will generate a minimal menu for your app by default.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
If you click the 'View' option in the application menu and then the
|
||||
'App Menu Demo', you'll see an information box displayed.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Know operating system menu differences.</strong>
|
||||
<p>
|
||||
When designing an app for multiple operating systems it's
|
||||
important to be mindful of the ways application menu conventions
|
||||
differ on each operating system.
|
||||
</p>
|
||||
<p>
|
||||
For instance, on Windows, accelerators are set with an
|
||||
<code>&</code>. Naming conventions also vary, like between
|
||||
"Settings" or "Preferences". Below are resources for learning
|
||||
operating system specific standards.
|
||||
</p>
|
||||
<ul>
|
||||
<li>
|
||||
<a
|
||||
href="https://developer.apple.com/macos/human-interface-guidelines/menus/menu-anatomy/"
|
||||
>macOS<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://learn.microsoft.com/en-us/previous-versions/windows/desktop/bb226797"
|
||||
>Windows<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
<li>
|
||||
<a
|
||||
href="https://developer.gnome.org/hig/stable/menu-bars.html.en"
|
||||
>Linux<span
|
||||
>(opens in new window)</span
|
||||
></a
|
||||
>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Create a context menu</h2>
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<button id="context-menu">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
A context, or right-click, menu can be created with the
|
||||
<code>Menu</code> and <code>MenuItem</code> modules as well. You can
|
||||
right-click anywhere in this app or click the demo button to see an
|
||||
example context menu.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this demo we use the <code>ipcRenderer</code> module to show the
|
||||
context menu when explicitly calling it from the renderer process.
|
||||
</p>
|
||||
<p>
|
||||
See the full
|
||||
<a
|
||||
href="https://electronjs.org/docs/api/web-contents/#event-context-menu"
|
||||
>context-menu event documentation</a
|
||||
>
|
||||
for all the available properties.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,73 +1,73 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Keyboard Shortcuts</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Keyboard Shortcuts</h1>
|
||||
|
||||
<h3>The <code>globalShortcut</code> and <code>Menu</code> modules can be used to define keyboard shortcuts.</h3>
|
||||
|
||||
<p>
|
||||
In Electron, keyboard shortcuts are called accelerators.
|
||||
They can be assigned to actions in your application's Menu,
|
||||
or they can be assigned globally so they'll be triggered even when
|
||||
your app doesn't have keyboard focus.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the full documentation for the
|
||||
<a href="https://electronjs.org/docs/api/menu">Menu</a>,
|
||||
<a href="https://electronjs.org/docs/api/accelerator">Accelerator</a>,
|
||||
and
|
||||
<a href="https://electronjs.org/docs/api/global-shortcut">globalShortcut</a>
|
||||
APIs in your browser.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
To try this demo, press <kbd>CommandOrControl+Alt+K</kbd> on your
|
||||
keyboard.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Global shortcuts are detected even when the app doesn't have
|
||||
keyboard focus, and they must be registered after the app's
|
||||
`ready` event is emitted.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Avoid overriding system-wide keyboard shortcuts.</strong>
|
||||
<p>
|
||||
When registering global shortcuts, it's important to be aware of
|
||||
existing defaults in the target operating system, so as not to
|
||||
override any existing behaviors. For an overview of each
|
||||
operating system's keyboard shortcuts, view these documents:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><a
|
||||
href="https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/Keyboard.html">macOS</a>
|
||||
</li>
|
||||
<li><a
|
||||
href="http://windows.microsoft.com/en-us/windows-10/keyboard-shortcuts">Windows</a></li>
|
||||
<li><a
|
||||
href="https://developer.gnome.org/hig/stable/keyboard-input.html.en">Linux</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Keyboard Shortcuts</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Keyboard Shortcuts</h1>
|
||||
|
||||
<h3>The <code>globalShortcut</code> and <code>Menu</code> modules can be used to define keyboard shortcuts.</h3>
|
||||
|
||||
<p>
|
||||
In Electron, keyboard shortcuts are called accelerators.
|
||||
They can be assigned to actions in your application's Menu,
|
||||
or they can be assigned globally so they'll be triggered even when
|
||||
your app doesn't have keyboard focus.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the full documentation for the
|
||||
<a href="https://electronjs.org/docs/api/menu">Menu</a>,
|
||||
<a href="https://electronjs.org/docs/api/accelerator">Accelerator</a>,
|
||||
and
|
||||
<a href="https://electronjs.org/docs/api/global-shortcut">globalShortcut</a>
|
||||
APIs in your browser.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<div>
|
||||
<p>
|
||||
To try this demo, press <kbd>CommandOrControl+Alt+K</kbd> on your
|
||||
keyboard.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Global shortcuts are detected even when the app doesn't have
|
||||
keyboard focus, and they must be registered after the app's
|
||||
`ready` event is emitted.
|
||||
</p>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Avoid overriding system-wide keyboard shortcuts.</strong>
|
||||
<p>
|
||||
When registering global shortcuts, it's important to be aware of
|
||||
existing defaults in the target operating system, so as not to
|
||||
override any existing behaviors. For an overview of each
|
||||
operating system's keyboard shortcuts, view these documents:
|
||||
</p>
|
||||
|
||||
<ul>
|
||||
<li><a
|
||||
href="https://developer.apple.com/library/mac/documentation/UserExperience/Conceptual/OSXHIGuidelines/Keyboard.html">macOS</a>
|
||||
</li>
|
||||
<li><a
|
||||
href="http://windows.microsoft.com/en-us/windows-10/keyboard-shortcuts">Windows</a></li>
|
||||
<li><a
|
||||
href="https://developer.gnome.org/hig/stable/keyboard-input.html.en">Linux</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Error Dialog</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Error Dialog</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="error-dialog">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
error dialog.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can use an error dialog before the app's
|
||||
<code>ready</code> event, which is useful for showing errors upon
|
||||
startup.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const errorBtn = document.getElementById('error-dialog')
|
||||
|
||||
errorBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('open-error-dialog')
|
||||
})
|
||||
</code></pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('open-error-dialog', (event) => {
|
||||
dialog.showErrorBox('An Error Message', 'Demonstrating an error message.')
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Error Dialog</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Error Dialog</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="error-dialog">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
error dialog.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can use an error dialog before the app's
|
||||
<code>ready</code> event, which is useful for showing errors upon
|
||||
startup.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const errorBtn = document.getElementById('error-dialog')
|
||||
|
||||
errorBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('open-error-dialog')
|
||||
})
|
||||
</code></pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('open-error-dialog', (event) => {
|
||||
dialog.showErrorBox('An Error Message', 'Demonstrating an error message.')
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,104 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Information Dialog</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="section-wrapper">
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Information Dialog</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="information-dialog">
|
||||
View Demo
|
||||
</button>
|
||||
<span id="info-selection"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
information dialog. Options may be provided for responses which can
|
||||
then be relayed back to the renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note: The <code>title</code> property is not displayed in macOS.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
An information dialog can contain an icon, your choice of buttons,
|
||||
title and message.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const informationBtn = document.getElementById('information-dialog')
|
||||
|
||||
informationBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('open-information-dialog')
|
||||
})
|
||||
|
||||
ipcRenderer.on('information-dialog-selection', (event, index) => {
|
||||
let message = 'You selected '
|
||||
if (index === 0) message += 'yes.'
|
||||
else message += 'no.'
|
||||
document.getElementById('info-selection').innerHTML = message
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('open-information-dialog', (event) => {
|
||||
const options = {
|
||||
type: 'info',
|
||||
title: 'Information',
|
||||
message: "This is an information dialog. Isn't it nice?",
|
||||
buttons: ['Yes', 'No']
|
||||
}
|
||||
dialog.showMessageBox(options, (index) => {
|
||||
event.sender.send('information-dialog-selection', index)
|
||||
})
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Information Dialog</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="section-wrapper">
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Information Dialog</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="information-dialog">
|
||||
View Demo
|
||||
</button>
|
||||
<span id="info-selection"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
information dialog. Options may be provided for responses which can
|
||||
then be relayed back to the renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note: The <code>title</code> property is not displayed in macOS.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
An information dialog can contain an icon, your choice of buttons,
|
||||
title and message.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const informationBtn = document.getElementById('information-dialog')
|
||||
|
||||
informationBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('open-information-dialog')
|
||||
})
|
||||
|
||||
ipcRenderer.on('information-dialog-selection', (event, index) => {
|
||||
let message = 'You selected '
|
||||
if (index === 0) message += 'yes.'
|
||||
else message += 'no.'
|
||||
document.getElementById('info-selection').innerHTML = message
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('open-information-dialog', (event) => {
|
||||
const options = {
|
||||
type: 'info',
|
||||
title: 'Information',
|
||||
message: "This is an information dialog. Isn't it nice?",
|
||||
buttons: ['Yes', 'No']
|
||||
}
|
||||
dialog.showMessageBox(options, (index) => {
|
||||
event.sender.send('information-dialog-selection', index)
|
||||
})
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,108 +1,108 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Open File or Directory</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="section-wrapper">
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Open a File or Directory</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="select-directory">View Demo</button>
|
||||
<span id="selected-file"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
open file (or directory) dialog. If a file is selected, the main
|
||||
process can send that information back to the renderer process.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const selectDirBtn = document.getElementById('select-directory')
|
||||
|
||||
selectDirBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('open-file-dialog')
|
||||
})
|
||||
|
||||
ipcRenderer.on('selected-directory', (event, path) => {
|
||||
document.getElementById('selected-file').innerHTML = `You selected: ${path}`
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('open-file-dialog', (event) => {
|
||||
dialog.showOpenDialog({
|
||||
properties: ['openFile', 'openDirectory']
|
||||
}, (files) => {
|
||||
if (files) {
|
||||
event.sender.send('selected-directory', files)
|
||||
}
|
||||
})
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>The sheet-style dialog on macOS.</strong>
|
||||
<p>
|
||||
On macOS you can choose between a "sheet" dialog or a default
|
||||
dialog. The sheet version descends from the top of the window. To
|
||||
use sheet version, pass the <code>window</code> as the first
|
||||
argument in the dialog method.
|
||||
</p>
|
||||
<pre><code class="language-js">const ipc = require('electron').ipcMain
|
||||
const dialog = require('electron').dialog
|
||||
const BrowserWindow = require('electron').BrowserWindow
|
||||
|
||||
|
||||
ipc.on('open-file-dialog-sheet', function (event) {
|
||||
const window = BrowserWindow.fromWebContents(event.sender)
|
||||
const files = dialog.showOpenDialog(window, { properties: [ 'openFile' ]})
|
||||
})</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Open File or Directory</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="section-wrapper">
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Open a File or Directory</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="select-directory">View Demo</button>
|
||||
<span id="selected-file"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
open file (or directory) dialog. If a file is selected, the main
|
||||
process can send that information back to the renderer process.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const selectDirBtn = document.getElementById('select-directory')
|
||||
|
||||
selectDirBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('open-file-dialog')
|
||||
})
|
||||
|
||||
ipcRenderer.on('selected-directory', (event, path) => {
|
||||
document.getElementById('selected-file').innerHTML = `You selected: ${path}`
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('open-file-dialog', (event) => {
|
||||
dialog.showOpenDialog({
|
||||
properties: ['openFile', 'openDirectory']
|
||||
}, (files) => {
|
||||
if (files) {
|
||||
event.sender.send('selected-directory', files)
|
||||
}
|
||||
})
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>The sheet-style dialog on macOS.</strong>
|
||||
<p>
|
||||
On macOS you can choose between a "sheet" dialog or a default
|
||||
dialog. The sheet version descends from the top of the window. To
|
||||
use sheet version, pass the <code>window</code> as the first
|
||||
argument in the dialog method.
|
||||
</p>
|
||||
<pre><code class="language-js">const ipc = require('electron').ipcMain
|
||||
const dialog = require('electron').dialog
|
||||
const BrowserWindow = require('electron').BrowserWindow
|
||||
|
||||
|
||||
ipc.on('open-file-dialog-sheet', function (event) {
|
||||
const window = BrowserWindow.fromWebContents(event.sender)
|
||||
const files = dialog.showOpenDialog(window, { properties: [ 'openFile' ]})
|
||||
})</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,91 +1,91 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Save Dialog</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Save Dialog</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button button id="save-dialog">View Demo</button>
|
||||
<span id="file-saved"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
save dialog. It returns the path selected by the user which can be
|
||||
relayed back to the renderer process.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const saveBtn = document.getElementById('save-dialog')
|
||||
|
||||
saveBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('save-dialog')
|
||||
})
|
||||
|
||||
ipcRenderer.on('saved-file', (event, path) => {
|
||||
if (!path) path = 'No path'
|
||||
document.getElementById('file-saved').innerHTML = `Path selected: ${path}`
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('save-dialog', (event) => {
|
||||
const options = {
|
||||
title: 'Save an Image',
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }
|
||||
]
|
||||
}
|
||||
dialog.showSaveDialog(options, (filename) => {
|
||||
event.sender.send('saved-file', filename)
|
||||
})
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Save Dialog</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Use system dialogs</h1>
|
||||
|
||||
<h3>
|
||||
The <code>dialog</code> module in Electron allows you to use native
|
||||
system dialogs for opening files or directories, saving a file or
|
||||
displaying informational messages.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
This is a main process module because this process is more efficient
|
||||
with native utilities and it allows the call to happen without
|
||||
interrupting the visible elements in your page's renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/dialog/">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Save Dialog</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button button id="save-dialog">View Demo</button>
|
||||
<span id="file-saved"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, the <code>ipc</code> module is used to send a message
|
||||
from the renderer process instructing the main process to launch the
|
||||
save dialog. It returns the path selected by the user which can be
|
||||
relayed back to the renderer process.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const saveBtn = document.getElementById('save-dialog')
|
||||
|
||||
saveBtn.addEventListener('click', (event) => {
|
||||
ipcRenderer.send('save-dialog')
|
||||
})
|
||||
|
||||
ipcRenderer.on('saved-file', (event, path) => {
|
||||
if (!path) path = 'No path'
|
||||
document.getElementById('file-saved').innerHTML = `Path selected: ${path}`
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain, dialog} = require('electron')
|
||||
|
||||
ipcMain.on('save-dialog', (event) => {
|
||||
const options = {
|
||||
title: 'Save an Image',
|
||||
filters: [
|
||||
{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }
|
||||
]
|
||||
}
|
||||
dialog.showSaveDialog(options, (filename) => {
|
||||
event.sender.send('saved-file', filename)
|
||||
})
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,76 +1,76 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Drag and drop files</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Drag and drop files</h1>
|
||||
<div>Supports: Win, macOS, Linux <span>|</span> Process: Both</div>
|
||||
<h3>
|
||||
Electron supports dragging files and content out from web content into
|
||||
the operating system's world.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/tutorial/native-file-drag-drop">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Dragging files</h2>
|
||||
<div>
|
||||
<div>
|
||||
<a href="#" id="drag-file-link">Drag Demo</a>
|
||||
</div>
|
||||
<p>
|
||||
Click and drag the link above to copy the renderer process
|
||||
javascript file on to your machine.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this demo, the <code>webContents.startDrag()</code> API is called
|
||||
in response to the <code>ondragstart</code> event.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre><code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const dragFileLink = document.getElementById('drag-file-link')
|
||||
|
||||
dragFileLink.addEventListener('dragstart', (event) => {
|
||||
event.preventDefault()
|
||||
ipcRenderer.send('ondragstart', __filename)
|
||||
})
|
||||
</code></pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
ipcMain.on('ondragstart', (event, filepath) => {
|
||||
const iconName = 'codeIcon.png'
|
||||
event.sender.startDrag({
|
||||
file: filepath,
|
||||
icon: path.join(__dirname, iconName)
|
||||
})
|
||||
})
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Drag and drop files</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Drag and drop files</h1>
|
||||
<div>Supports: Win, macOS, Linux <span>|</span> Process: Both</div>
|
||||
<h3>
|
||||
Electron supports dragging files and content out from web content into
|
||||
the operating system's world.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/tutorial/native-file-drag-drop">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Dragging files</h2>
|
||||
<div>
|
||||
<div>
|
||||
<a href="#" id="drag-file-link">Drag Demo</a>
|
||||
</div>
|
||||
<p>
|
||||
Click and drag the link above to copy the renderer process
|
||||
javascript file on to your machine.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In this demo, the <code>webContents.startDrag()</code> API is called
|
||||
in response to the <code>ondragstart</code> event.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre><code>
|
||||
const {ipcRenderer} = require('electron')
|
||||
|
||||
const dragFileLink = document.getElementById('drag-file-link')
|
||||
|
||||
dragFileLink.addEventListener('dragstart', (event) => {
|
||||
event.preventDefault()
|
||||
ipcRenderer.send('ondragstart', __filename)
|
||||
})
|
||||
</code></pre>
|
||||
<h5>Main Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const {ipcMain} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
ipcMain.on('ondragstart', (event, filepath) => {
|
||||
const iconName = 'codeIcon.png'
|
||||
event.sender.startDrag({
|
||||
file: filepath,
|
||||
icon: path.join(__dirname, iconName)
|
||||
})
|
||||
})
|
||||
</code></pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,104 +1,104 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Open external links and the file manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>
|
||||
Open external links and the file manager
|
||||
</h1>
|
||||
<h3>
|
||||
The <code>shell</code> module in Electron allows you to access certain
|
||||
native elements like the file manager and default web browser.
|
||||
</h3>
|
||||
|
||||
<p>This module works in both the main and renderer process.</p>
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/shell">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Open Path in File Manager</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="open-file-manager">
|
||||
View Demo
|
||||
</button>
|
||||
</div>
|
||||
<p>
|
||||
This demonstrates using the <code>shell</code> module to open the
|
||||
system file manager at a particular location.
|
||||
</p>
|
||||
<p>
|
||||
Clicking the demo button will open your file manager at the root.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Open External Links</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="open-ex-links">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
If you do not want your app to open website links
|
||||
<em>within</em> the app, you can use the <code>shell</code> module
|
||||
to open them externally. When clicked, the links will open outside
|
||||
of your app and in the user's default web browser.
|
||||
</p>
|
||||
<p>
|
||||
When the demo button is clicked, the electron website will open in
|
||||
your browser.
|
||||
</p>
|
||||
<p></p>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Open all outbound links externally.</strong>
|
||||
<p>
|
||||
You may want to open all <code>http</code> and
|
||||
<code>https</code> links outside of your app. To do this, query
|
||||
the document and loop through each link and add a listener. This
|
||||
app uses the code below which is located in
|
||||
<code>assets/ex-links.js</code>.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const shell = require('electron').shell
|
||||
|
||||
const links = document.querySelectorAll('a[href]')
|
||||
|
||||
Array.prototype.forEach.call(links, (link) => {
|
||||
const url = link.getAttribute('href')
|
||||
if (url.indexOf('http') === 0) {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
shell.openExternal(url)
|
||||
})
|
||||
}
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Open external links and the file manager</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>
|
||||
Open external links and the file manager
|
||||
</h1>
|
||||
<h3>
|
||||
The <code>shell</code> module in Electron allows you to access certain
|
||||
native elements like the file manager and default web browser.
|
||||
</h3>
|
||||
|
||||
<p>This module works in both the main and renderer process.</p>
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/shell">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Open Path in File Manager</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="open-file-manager">
|
||||
View Demo
|
||||
</button>
|
||||
</div>
|
||||
<p>
|
||||
This demonstrates using the <code>shell</code> module to open the
|
||||
system file manager at a particular location.
|
||||
</p>
|
||||
<p>
|
||||
Clicking the demo button will open your file manager at the root.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Open External Links</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="open-ex-links">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
If you do not want your app to open website links
|
||||
<em>within</em> the app, you can use the <code>shell</code> module
|
||||
to open them externally. When clicked, the links will open outside
|
||||
of your app and in the user's default web browser.
|
||||
</p>
|
||||
<p>
|
||||
When the demo button is clicked, the electron website will open in
|
||||
your browser.
|
||||
</p>
|
||||
<p></p>
|
||||
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Open all outbound links externally.</strong>
|
||||
<p>
|
||||
You may want to open all <code>http</code> and
|
||||
<code>https</code> links outside of your app. To do this, query
|
||||
the document and loop through each link and add a listener. This
|
||||
app uses the code below which is located in
|
||||
<code>assets/ex-links.js</code>.
|
||||
</p>
|
||||
<h5>Renderer Process</h5>
|
||||
<pre>
|
||||
<code>
|
||||
const shell = require('electron').shell
|
||||
|
||||
const links = document.querySelectorAll('a[href]')
|
||||
|
||||
Array.prototype.forEach.call(links, (link) => {
|
||||
const url = link.getAttribute('href')
|
||||
if (url.indexOf('http') === 0) {
|
||||
link.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
shell.openExternal(url)
|
||||
})
|
||||
}
|
||||
})
|
||||
</code>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,67 +1,67 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Desktop notifications</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Desktop notifications</h1>
|
||||
<h3>
|
||||
The <code>notification</code> module in Electron allows you to add basic
|
||||
desktop notifications.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Electron conveniently allows developers to send notifications with the
|
||||
<a href="https://notifications.spec.whatwg.org/">HTML5 Notification API</a>,
|
||||
using the currently running operating system’s native notification
|
||||
APIs to display it.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Note:</b> Since this is an HTML5 API it is only available in the
|
||||
renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/all/#notifications-windows-linux-macos">
|
||||
full API documentation<span>(opens in new window)</span>
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Basic notification</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="basic-noti">View demo</button>
|
||||
</div>
|
||||
<p>This demo demonstrates a basic notification. Text only.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Notification with image</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="advanced-noti">View demo</button>
|
||||
</div>
|
||||
<p>
|
||||
This demo demonstrates a basic notification. Both text and a image
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Desktop notifications</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Desktop notifications</h1>
|
||||
<h3>
|
||||
The <code>notification</code> module in Electron allows you to add basic
|
||||
desktop notifications.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Electron conveniently allows developers to send notifications with the
|
||||
<a href="https://notifications.spec.whatwg.org/">HTML5 Notification API</a>,
|
||||
using the currently running operating system’s native notification
|
||||
APIs to display it.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Note:</b> Since this is an HTML5 API it is only available in the
|
||||
renderer process.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/all/#notifications-windows-linux-macos">
|
||||
full API documentation<span>(opens in new window)</span>
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Basic notification</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="basic-noti">View demo</button>
|
||||
</div>
|
||||
<p>This demo demonstrates a basic notification. Text only.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Notification with image</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="advanced-noti">View demo</button>
|
||||
</div>
|
||||
<p>
|
||||
This demo demonstrates a basic notification. Both text and a image
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,47 +1,47 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Tray</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Tray</h1>
|
||||
<h3>
|
||||
The <code>tray</code> module allows you to create an icon in the
|
||||
operating system's notification area.
|
||||
</h3>
|
||||
<p>This icon can also have a context menu attached.</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/tray">
|
||||
full API documentation
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Tray support in Linux.</strong>
|
||||
<p>
|
||||
On Linux distributions that only have app indicator support, users
|
||||
will need to install <code>libappindicator1</code> to make the
|
||||
tray icon work. See the
|
||||
<a href="https://electronjs.org/docs/api/tray">
|
||||
full API documentation
|
||||
</a>
|
||||
for more details about using Tray on Linux.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Tray</title>
|
||||
</head>
|
||||
<body>
|
||||
<div>
|
||||
<h1>Tray</h1>
|
||||
<h3>
|
||||
The <code>tray</code> module allows you to create an icon in the
|
||||
operating system's notification area.
|
||||
</h3>
|
||||
<p>This icon can also have a context menu attached.</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/tray">
|
||||
full API documentation
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
<div>
|
||||
<div>
|
||||
<h2>ProTip</h2>
|
||||
<strong>Tray support in Linux.</strong>
|
||||
<p>
|
||||
On Linux distributions that only have app indicator support, users
|
||||
will need to install <code>libappindicator1</code> to make the
|
||||
tray icon work. See the
|
||||
<a href="https://electronjs.org/docs/api/tray">
|
||||
full API documentation
|
||||
</a>
|
||||
for more details about using Tray on Linux.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,77 +1,77 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Frameless window</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Create and Manage Windows</h1>
|
||||
|
||||
<h3>
|
||||
The <code>BrowserWindow</code> module in Electron allows you to create a
|
||||
new browser window or manage an existing one.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Each browser window is a separate process, known as the renderer
|
||||
process. This process, like the main process that controls the life
|
||||
cycle of the app, has full access to the Node.js APIs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Create a frameless window</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="frameless-window">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
A frameless window is a window that has no
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Chrome">
|
||||
"chrome"
|
||||
</a>
|
||||
, such as toolbars, title bars, status bars, borders, etc. You can
|
||||
make a browser window frameless by setting <code>frame</code> to
|
||||
<code>false</code> when creating the window.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Windows can have a transparent background, too. By setting the
|
||||
<code>transparent</code> option to <code>true</code>, you can also
|
||||
make your frameless window transparent:
|
||||
</p>
|
||||
<pre>
|
||||
<code class="language-js">var win = new BrowserWindow({
|
||||
transparent: true,
|
||||
frame: false
|
||||
})</code></pre>
|
||||
|
||||
<p>
|
||||
For more details, see the
|
||||
<a href="https://electronjs.org/docs/tutorial/window-customization/">
|
||||
Window Customization
|
||||
|
||||
</a>
|
||||
documentation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Frameless window</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Create and Manage Windows</h1>
|
||||
|
||||
<h3>
|
||||
The <code>BrowserWindow</code> module in Electron allows you to create a
|
||||
new browser window or manage an existing one.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Each browser window is a separate process, known as the renderer
|
||||
process. This process, like the main process that controls the life
|
||||
cycle of the app, has full access to the Node.js APIs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Create a frameless window</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="frameless-window">View Demo</button>
|
||||
</div>
|
||||
<p>
|
||||
A frameless window is a window that has no
|
||||
<a href="https://developer.mozilla.org/en-US/docs/Glossary/Chrome">
|
||||
"chrome"
|
||||
</a>
|
||||
, such as toolbars, title bars, status bars, borders, etc. You can
|
||||
make a browser window frameless by setting <code>frame</code> to
|
||||
<code>false</code> when creating the window.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Windows can have a transparent background, too. By setting the
|
||||
<code>transparent</code> option to <code>true</code>, you can also
|
||||
make your frameless window transparent:
|
||||
</p>
|
||||
<pre>
|
||||
<code class="language-js">var win = new BrowserWindow({
|
||||
transparent: true,
|
||||
frame: false
|
||||
})</code></pre>
|
||||
|
||||
<p>
|
||||
For more details, see the
|
||||
<a href="https://electronjs.org/docs/tutorial/window-customization/">
|
||||
Window Customization
|
||||
|
||||
</a>
|
||||
documentation.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,64 +1,64 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Manage window state</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Create and Manage Windows</h1>
|
||||
|
||||
<h3>
|
||||
The <code>BrowserWindow</code> module in Electron allows you to create a
|
||||
new browser window or manage an existing one.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Each browser window is a separate process, known as the renderer
|
||||
process. This process, like the main process that controls the life
|
||||
cycle of the app, has full access to the Node.js APIs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Manage window state</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="manage-window">View Demo</button>
|
||||
<span id="manage-window-reply"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo we create a new window and listen for
|
||||
<code>move</code> and <code>resize</code> events on it. Click the
|
||||
demo button, change the new window and see the dimensions and
|
||||
position update here, above.
|
||||
</p>
|
||||
<p>
|
||||
There are a lot of methods for controlling the state of the window
|
||||
such as the size, location, and focus status as well as events to
|
||||
listen to for window changes. Visit the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
documentation (opens in new window)
|
||||
</a>
|
||||
for the full list.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Manage window state</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Create and Manage Windows</h1>
|
||||
|
||||
<h3>
|
||||
The <code>BrowserWindow</code> module in Electron allows you to create a
|
||||
new browser window or manage an existing one.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Each browser window is a separate process, known as the renderer
|
||||
process. This process, like the main process that controls the life
|
||||
cycle of the app, has full access to the Node.js APIs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Manage window state</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="manage-window">View Demo</button>
|
||||
<span id="manage-window-reply"></span>
|
||||
</div>
|
||||
<p>
|
||||
In this demo we create a new window and listen for
|
||||
<code>move</code> and <code>resize</code> events on it. Click the
|
||||
demo button, change the new window and see the dimensions and
|
||||
position update here, above.
|
||||
</p>
|
||||
<p>
|
||||
There are a lot of methods for controlling the state of the window
|
||||
such as the size, location, and focus status as well as events to
|
||||
listen to for window changes. Visit the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
documentation (opens in new window)
|
||||
</a>
|
||||
for the full list.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,58 +1,58 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Window events</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Create and Manage Windows</h1>
|
||||
|
||||
<h3>
|
||||
The <code>BrowserWindow</code> module in Electron allows you to create a
|
||||
new browser window or manage an existing one.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Each browser window is a separate process, known as the renderer
|
||||
process. This process, like the main process that controls the life
|
||||
cycle of the app, has full access to the Node.js APIs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Window events</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="listen-to-window">View Demo</button>
|
||||
<button id="focus-on-modal-window">
|
||||
Focus on Demo
|
||||
</button>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, we create a new window and listen for
|
||||
<code>blur</code> event on it. Click the demo button to create a new
|
||||
modal window, and switch focus back to the parent window by clicking
|
||||
on it. You can click the <i>Focus on Demo</i> button to switch focus
|
||||
to the modal window again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<title>Window events</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div>
|
||||
<h1>Create and Manage Windows</h1>
|
||||
|
||||
<h3>
|
||||
The <code>BrowserWindow</code> module in Electron allows you to create a
|
||||
new browser window or manage an existing one.
|
||||
</h3>
|
||||
|
||||
<p>
|
||||
Each browser window is a separate process, known as the renderer
|
||||
process. This process, like the main process that controls the life
|
||||
cycle of the app, has full access to the Node.js APIs.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open the
|
||||
<a href="https://electronjs.org/docs/api/browser-window">
|
||||
full API documentation (opens in new window)
|
||||
</a>
|
||||
in your browser.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div>
|
||||
<h2>Window events</h2>
|
||||
<div>
|
||||
<div>
|
||||
<button id="listen-to-window">View Demo</button>
|
||||
<button id="focus-on-modal-window">
|
||||
Focus on Demo
|
||||
</button>
|
||||
</div>
|
||||
<p>
|
||||
In this demo, we create a new window and listen for
|
||||
<code>blur</code> event on it. Click the demo button to create a new
|
||||
modal window, and switch focus back to the parent window by clicking
|
||||
on it. You can click the <i>Focus on Demo</i> button to switch focus
|
||||
to the modal window again.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// You can also require other files to run in this process
|
||||
require("./renderer.js");
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -234,7 +234,7 @@ embedded content.
|
||||
[context isolation]: tutorial/context-isolation.md
|
||||
[mac app store submission guide]: tutorial/mac-app-store-submission-guide.md
|
||||
[main]: #main-process
|
||||
[msi]: https://docs.microsoft.com/en-us/windows/win32/msi/windows-installer-portal
|
||||
[msi]: https://learn.microsoft.com/en-us/windows/win32/msi/windows-installer-portal
|
||||
[Native Node Modules]: tutorial/using-native-node-modules.md
|
||||
[offscreen rendering]: tutorial/offscreen-rendering.md
|
||||
[process sandboxing]: tutorial/sandbox.md
|
||||
|
||||
@@ -200,4 +200,4 @@ See the [Windows Store Guide][].
|
||||
[windows store guide]: ./windows-store-guide.md
|
||||
[maker-squirrel]: https://www.electronforge.io/config/makers/squirrel.windows
|
||||
[maker-msi]: https://www.electronforge.io/config/makers/wix-msi
|
||||
[signtool.exe]: https://docs.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
|
||||
[signtool.exe]: https://learn.microsoft.com/en-us/dotnet/framework/tools/signtool-exe
|
||||
|
||||
@@ -84,7 +84,7 @@ the `sandbox: false` preference in the [`BrowserWindow`][browser-window] constru
|
||||
app.whenReady().then(() => {
|
||||
const win = new BrowserWindow({
|
||||
webPreferences: {
|
||||
sandbox: true
|
||||
sandbox: false
|
||||
}
|
||||
})
|
||||
win.loadURL('https://google.com')
|
||||
|
||||
@@ -81,10 +81,13 @@ the exact dependency versions to install.
|
||||
"version": "1.0.0",
|
||||
"description": "Hello World!",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jane Doe",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "19.0.0"
|
||||
"electron": "23.1.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -137,13 +140,14 @@ script in the current directory and run it in dev mode.
|
||||
"version": "1.0.0",
|
||||
"description": "Hello World!",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Jane Doe",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"start": "electron ."
|
||||
},
|
||||
"devDependencies": {
|
||||
"electron": "^19.0.0"
|
||||
"electron": "23.1.3"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -465,7 +469,7 @@ privileged APIs and how to communicate between processes.
|
||||
[repl]: ./repl.md
|
||||
[webpack]: https://webpack.js.org
|
||||
[window-all-closed]: ../api/app.md#event-window-all-closed
|
||||
[wsl]: https://docs.microsoft.com/en-us/windows/wsl/about#what-is-wsl-2
|
||||
[wsl]: https://learn.microsoft.com/en-us/windows/wsl/about#what-is-wsl-2
|
||||
|
||||
<!-- Tutorial links -->
|
||||
|
||||
|
||||
@@ -88,7 +88,7 @@ After completing all of the above, open your cross-compilation command prompt an
|
||||
|
||||
## Debugging native modules
|
||||
|
||||
Debugging native modules can be done with Visual Studio 2017 (running on your development machine) and corresponding [Visual Studio Remote Debugger](https://docs.microsoft.com/en-us/visualstudio/debugger/remote-debugging-cpp?view=vs-2019) running on the target device. To debug:
|
||||
Debugging native modules can be done with Visual Studio 2017 (running on your development machine) and corresponding [Visual Studio Remote Debugger](https://learn.microsoft.com/en-us/visualstudio/debugger/remote-debugging-cpp?view=vs-2019) running on the target device. To debug:
|
||||
|
||||
1. Launch your app `.exe` on the target device via the _Command Prompt_ (passing `--inspect-brk` to pause it before any native modules are loaded).
|
||||
2. Launch Visual Studio 2017 on your development machine.
|
||||
|
||||
@@ -89,7 +89,7 @@ app.setUserTasks([])
|
||||
> NOTE: The user tasks will still be displayed even after closing your
|
||||
application, so the icon and program path specified for a task should exist until your application is uninstalled.
|
||||
|
||||
[msdn-jumplist]: https://docs.microsoft.com/en-us/windows/win32/shell/taskbar-extensions#tasks
|
||||
[msdn-jumplist]: https://learn.microsoft.com/en-us/windows/win32/shell/taskbar-extensions#tasks
|
||||
|
||||
### Thumbnail Toolbars
|
||||
|
||||
@@ -156,7 +156,7 @@ const win = new BrowserWindow()
|
||||
win.setThumbarButtons([])
|
||||
```
|
||||
|
||||
[msdn-thumbnail]: https://docs.microsoft.com/en-us/windows/win32/shell/taskbar-extensions#thumbnail-toolbars
|
||||
[msdn-thumbnail]: https://learn.microsoft.com/en-us/windows/win32/shell/taskbar-extensions#thumbnail-toolbars
|
||||
|
||||
### Icon Overlays in Taskbar
|
||||
|
||||
@@ -196,7 +196,7 @@ const win = new BrowserWindow()
|
||||
win.setOverlayIcon('path/to/overlay.png', 'Description for overlay')
|
||||
```
|
||||
|
||||
[msdn-icon-overlay]: https://docs.microsoft.com/en-us/windows/win32/shell/taskbar-extensions#icon-overlays
|
||||
[msdn-icon-overlay]: https://learn.microsoft.com/en-us/windows/win32/shell/taskbar-extensions#icon-overlays
|
||||
|
||||
### Flash Frame
|
||||
|
||||
@@ -230,7 +230,7 @@ win.flashFrame(true)
|
||||
In the above example, it is called when the window comes into focus,
|
||||
but you might use a timeout or some other event to disable it.
|
||||
|
||||
[msdn-flash-frame]: https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindow#remarks
|
||||
[msdn-flash-frame]: https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-flashwindow#remarks
|
||||
|
||||
[setthumbarbuttons]: ../api/browser-window.md#winsetthumbarbuttonsbuttons-windows
|
||||
[setusertaskstasks]: ../api/app.md#appsetusertaskstasks-windows
|
||||
|
||||
@@ -13,7 +13,7 @@ function createDeferredPromise<T, E extends Error = Error> (): { promise: Promis
|
||||
return { promise, resolve: res!, reject: rej! };
|
||||
}
|
||||
|
||||
export function fetchWithSession (input: RequestInfo, init: RequestInit | undefined, session: SessionT): Promise<Response> {
|
||||
export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypassCustomProtocolHandlers?: boolean}) | undefined, session: SessionT): Promise<Response> {
|
||||
const p = createDeferredPromise<Response>();
|
||||
let req: Request;
|
||||
try {
|
||||
@@ -84,6 +84,8 @@ export function fetchWithSession (input: RequestInfo, init: RequestInit | undefi
|
||||
redirect: req.redirect
|
||||
}));
|
||||
|
||||
(r as any)._urlLoaderOptions.bypassCustomProtocolHandlers = !!init?.bypassCustomProtocolHandlers;
|
||||
|
||||
// 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);
|
||||
@@ -104,6 +106,7 @@ export function fetchWithSession (input: RequestInfo, init: RequestInit | undefi
|
||||
status: resp.statusCode,
|
||||
statusText: resp.statusMessage
|
||||
});
|
||||
(rResp as any).__original_resp = resp;
|
||||
p.resolve(rResp);
|
||||
});
|
||||
|
||||
|
||||
@@ -1,33 +1,130 @@
|
||||
import { app, session } from 'electron/main';
|
||||
import { ProtocolRequest, session } from 'electron/main';
|
||||
import { createReadStream } from 'fs';
|
||||
import { Readable } from 'stream';
|
||||
import { ReadableStream } from 'stream/web';
|
||||
|
||||
// Global protocol APIs.
|
||||
const protocol = process._linkedBinding('electron_browser_protocol');
|
||||
const { registerSchemesAsPrivileged, getStandardSchemes, Protocol } = process._linkedBinding('electron_browser_protocol');
|
||||
|
||||
// Fallback protocol APIs of default session.
|
||||
Object.setPrototypeOf(protocol, new Proxy({}, {
|
||||
get (_target, property) {
|
||||
if (!app.isReady()) return;
|
||||
const ERR_FAILED = -2;
|
||||
const ERR_UNEXPECTED = -9;
|
||||
|
||||
const protocol = session.defaultSession!.protocol;
|
||||
if (!Object.prototype.hasOwnProperty.call(protocol, property)) return;
|
||||
const isBuiltInScheme = (scheme: string) => scheme === 'http' || scheme === 'https';
|
||||
|
||||
// Returning a native function directly would throw error.
|
||||
return (...args: any[]) => (protocol[property as keyof Electron.Protocol] as Function)(...args);
|
||||
},
|
||||
function makeStreamFromPipe (pipe: any): ReadableStream {
|
||||
const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
|
||||
return new ReadableStream({
|
||||
async pull (controller) {
|
||||
try {
|
||||
const rv = await pipe.read(buf);
|
||||
if (rv > 0) {
|
||||
controller.enqueue(buf.subarray(0, rv));
|
||||
} else {
|
||||
controller.close();
|
||||
}
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ownKeys () {
|
||||
if (!app.isReady()) return [];
|
||||
return Reflect.ownKeys(session.defaultSession!.protocol);
|
||||
},
|
||||
function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): RequestInit['body'] {
|
||||
if (!uploadData) return null;
|
||||
// Optimization: skip creating a stream if the request is just a single buffer.
|
||||
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') return uploadData[0].bytes;
|
||||
|
||||
has: (target, property: string) => {
|
||||
if (!app.isReady()) return false;
|
||||
return Reflect.has(session.defaultSession!.protocol, property);
|
||||
},
|
||||
const chunks = [...uploadData] as any[]; // TODO: types are wrong
|
||||
let current: ReadableStreamDefaultReader | null = null;
|
||||
return new ReadableStream({
|
||||
pull (controller) {
|
||||
if (current) {
|
||||
current.read().then(({ done, value }) => {
|
||||
controller.enqueue(value);
|
||||
if (done) current = null;
|
||||
}, (err) => {
|
||||
controller.error(err);
|
||||
});
|
||||
} else {
|
||||
if (!chunks.length) { return controller.close(); }
|
||||
const chunk = chunks.shift()!;
|
||||
if (chunk.type === 'rawData') { controller.enqueue(chunk.bytes); } else if (chunk.type === 'file') {
|
||||
current = Readable.toWeb(createReadStream(chunk.filePath, { start: chunk.offset ?? 0, end: chunk.length >= 0 ? chunk.offset + chunk.length : undefined })).getReader();
|
||||
this.pull!(controller);
|
||||
} else if (chunk.type === 'stream') {
|
||||
current = makeStreamFromPipe(chunk.body).getReader();
|
||||
this.pull!(controller);
|
||||
}
|
||||
}
|
||||
}
|
||||
}) as RequestInit['body'];
|
||||
}
|
||||
|
||||
getOwnPropertyDescriptor () {
|
||||
return { configurable: true, enumerable: true };
|
||||
}
|
||||
}));
|
||||
Protocol.prototype.handle = function (this: Electron.Protocol, scheme: string, handler: (req: Request) => Response | Promise<Response>) {
|
||||
const register = isBuiltInScheme(scheme) ? this.interceptProtocol : this.registerProtocol;
|
||||
const success = register.call(this, scheme, async (preq: ProtocolRequest, cb: any) => {
|
||||
try {
|
||||
const body = convertToRequestBody(preq.uploadData);
|
||||
const req = new Request(preq.url, {
|
||||
headers: preq.headers,
|
||||
method: preq.method,
|
||||
referrer: preq.referrer,
|
||||
body,
|
||||
duplex: body instanceof ReadableStream ? 'half' : undefined
|
||||
} as any);
|
||||
const res = await handler(req);
|
||||
if (!res || typeof res !== 'object') {
|
||||
return cb({ error: ERR_UNEXPECTED });
|
||||
}
|
||||
if (res.type === 'error') { cb({ error: ERR_FAILED }); } else {
|
||||
cb({
|
||||
data: res.body ? Readable.fromWeb(res.body as ReadableStream<ArrayBufferView>) : null,
|
||||
headers: Object.fromEntries(res.headers),
|
||||
statusCode: res.status,
|
||||
statusText: res.statusText,
|
||||
mimeType: (res as any).__original_resp?._responseHead?.mimeType
|
||||
});
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
cb({ error: ERR_UNEXPECTED });
|
||||
}
|
||||
});
|
||||
if (!success) throw new Error(`Failed to register protocol: ${scheme}`);
|
||||
};
|
||||
|
||||
Protocol.prototype.unhandle = function (this: Electron.Protocol, scheme: string) {
|
||||
const unregister = isBuiltInScheme(scheme) ? this.uninterceptProtocol : this.unregisterProtocol;
|
||||
if (!unregister.call(this, scheme)) { throw new Error(`Failed to unhandle protocol: ${scheme}`); }
|
||||
};
|
||||
|
||||
Protocol.prototype.isProtocolHandled = function (this: Electron.Protocol, scheme: string) {
|
||||
const isRegistered = isBuiltInScheme(scheme) ? this.isProtocolIntercepted : this.isProtocolRegistered;
|
||||
return isRegistered.call(this, scheme);
|
||||
};
|
||||
|
||||
const protocol = {
|
||||
registerSchemesAsPrivileged,
|
||||
getStandardSchemes,
|
||||
registerStringProtocol: (...args) => session.defaultSession.protocol.registerStringProtocol(...args),
|
||||
registerBufferProtocol: (...args) => session.defaultSession.protocol.registerBufferProtocol(...args),
|
||||
registerStreamProtocol: (...args) => session.defaultSession.protocol.registerStreamProtocol(...args),
|
||||
registerFileProtocol: (...args) => session.defaultSession.protocol.registerFileProtocol(...args),
|
||||
registerHttpProtocol: (...args) => session.defaultSession.protocol.registerHttpProtocol(...args),
|
||||
registerProtocol: (...args) => session.defaultSession.protocol.registerProtocol(...args),
|
||||
unregisterProtocol: (...args) => session.defaultSession.protocol.unregisterProtocol(...args),
|
||||
isProtocolRegistered: (...args) => session.defaultSession.protocol.isProtocolRegistered(...args),
|
||||
interceptStringProtocol: (...args) => session.defaultSession.protocol.interceptStringProtocol(...args),
|
||||
interceptBufferProtocol: (...args) => session.defaultSession.protocol.interceptBufferProtocol(...args),
|
||||
interceptStreamProtocol: (...args) => session.defaultSession.protocol.interceptStreamProtocol(...args),
|
||||
interceptFileProtocol: (...args) => session.defaultSession.protocol.interceptFileProtocol(...args),
|
||||
interceptHttpProtocol: (...args) => session.defaultSession.protocol.interceptHttpProtocol(...args),
|
||||
interceptProtocol: (...args) => session.defaultSession.protocol.interceptProtocol(...args),
|
||||
uninterceptProtocol: (...args) => session.defaultSession.protocol.uninterceptProtocol(...args),
|
||||
isProtocolIntercepted: (...args) => session.defaultSession.protocol.isProtocolIntercepted(...args),
|
||||
handle: (...args) => session.defaultSession.protocol.handle(...args),
|
||||
unhandle: (...args) => session.defaultSession.protocol.unhandle(...args),
|
||||
isProtocolHandled: (...args) => session.defaultSession.protocol.isProtocolHandled(...args)
|
||||
} as typeof Electron.protocol;
|
||||
|
||||
export default protocol;
|
||||
|
||||
@@ -163,6 +163,16 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
});
|
||||
});
|
||||
|
||||
// Dispatch guest's frame navigation event to embedder.
|
||||
guest.on('will-frame-navigate', function (event: Electron.WebContentsWillFrameNavigateEventParams) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'will-frame-navigate', {
|
||||
url: event.url,
|
||||
isMainFrame: event.isMainFrame,
|
||||
frameProcessId: event.frame.processId,
|
||||
frameRoutingId: event.frame.routingId
|
||||
});
|
||||
});
|
||||
|
||||
// Notify guest of embedder window visibility when it is ready
|
||||
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
|
||||
guest.on('dom-ready', function () {
|
||||
|
||||
@@ -124,5 +124,6 @@ chore_introduce_blocking_api_for_electron.patch
|
||||
chore_patch_out_partition_attribute_dcheck_for_webviews.patch
|
||||
expose_v8initializer_codegenerationcheckcallbackinmainthread.patch
|
||||
chore_patch_out_profile_methods_in_profile_selections_cc.patch
|
||||
add_gin_converter_support_for_arraybufferview.patch
|
||||
chore_defer_usb_service_getdevices_request_until_usb_service_is.patch
|
||||
revert_roll_clang_rust_llvmorg-16-init-17653-g39da55e8-3.patch
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jeremy Rose <japthorp@slack-corp.com>
|
||||
Date: Wed, 8 Mar 2023 14:53:17 -0800
|
||||
Subject: add gin::Converter support for ArrayBufferView
|
||||
|
||||
This should be upstreamed.
|
||||
|
||||
diff --git a/gin/converter.cc b/gin/converter.cc
|
||||
index 4eb8c3d8c8392512eeb235bc18012589549b872b..d0432f6fff09cdcebed55ccf03a6524a445ef346 100644
|
||||
--- a/gin/converter.cc
|
||||
+++ b/gin/converter.cc
|
||||
@@ -18,6 +18,7 @@
|
||||
#include "v8/include/v8-value.h"
|
||||
|
||||
using v8::ArrayBuffer;
|
||||
+using v8::ArrayBufferView;
|
||||
using v8::External;
|
||||
using v8::Function;
|
||||
using v8::Int32;
|
||||
@@ -244,6 +245,20 @@ bool Converter<Local<ArrayBuffer>>::FromV8(Isolate* isolate,
|
||||
return true;
|
||||
}
|
||||
|
||||
+Local<Value> Converter<Local<ArrayBufferView>>::ToV8(Isolate* isolate,
|
||||
+ Local<ArrayBufferView> val) {
|
||||
+ return val.As<Value>();
|
||||
+}
|
||||
+
|
||||
+bool Converter<Local<ArrayBufferView>>::FromV8(Isolate* isolate,
|
||||
+ Local<Value> val,
|
||||
+ Local<ArrayBufferView>* out) {
|
||||
+ if (!val->IsArrayBufferView())
|
||||
+ return false;
|
||||
+ *out = Local<ArrayBufferView>::Cast(val);
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
Local<Value> Converter<Local<External>>::ToV8(Isolate* isolate,
|
||||
Local<External> val) {
|
||||
return val.As<Value>();
|
||||
diff --git a/gin/converter.h b/gin/converter.h
|
||||
index eb704fcd56dee861e18e9cd64a857d68dea6f415..d32a8c26403cf32f3333ed85c23292915e6f0681 100644
|
||||
--- a/gin/converter.h
|
||||
+++ b/gin/converter.h
|
||||
@@ -180,6 +180,15 @@ struct GIN_EXPORT Converter<v8::Local<v8::ArrayBuffer> > {
|
||||
v8::Local<v8::ArrayBuffer>* out);
|
||||
};
|
||||
|
||||
+template<>
|
||||
+struct GIN_EXPORT Converter<v8::Local<v8::ArrayBufferView> > {
|
||||
+ static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
+ v8::Local<v8::ArrayBufferView> val);
|
||||
+ static bool FromV8(v8::Isolate* isolate,
|
||||
+ v8::Local<v8::Value> val,
|
||||
+ v8::Local<v8::ArrayBufferView>* out);
|
||||
+};
|
||||
+
|
||||
template<>
|
||||
struct GIN_EXPORT Converter<v8::Local<v8::External> > {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
@@ -49,7 +49,7 @@ index b7bff829d779036ce0341b52ce9adc28eac91fa2..79b48d028ff6742d0d43ac6d32242f50
|
||||
// its owning reference back to our owning LocalFrame.
|
||||
client_->Detached(type);
|
||||
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
|
||||
index 296fd54e6aa8f678d21ee3547cbc2a91eacfcaad..ce83dac248559ff3779f957527eb56aec95ed3d9 100644
|
||||
index 9cf36959fc6afeb56a8a03e88698515df9b7b1c0..176039ff509633ba771b820012f85fc19f914d91 100644
|
||||
--- a/third_party/blink/renderer/core/frame/local_frame.cc
|
||||
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
|
||||
@@ -622,10 +622,6 @@ bool LocalFrame::DetachImpl(FrameDetachType type) {
|
||||
|
||||
@@ -33,10 +33,10 @@ index cf53c168b31fecca8fd07f167d1e3523bc3be568..b13ffdfb9ffdaea0908b0b4b8260666f
|
||||
"//base",
|
||||
"//build:branding_buildflags",
|
||||
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
|
||||
index dd3ed42ff8b59f9346d864e96f9f20cf19c8dbdc..2bee1495ba31a255b66b048acf122a86836ba1d4 100644
|
||||
index 717724a0736dfa27e61697cbfb2df9040647898b..21982376d6d9537b62ad8ba6b751b46db1fea62c 100644
|
||||
--- a/chrome/browser/BUILD.gn
|
||||
+++ b/chrome/browser/BUILD.gn
|
||||
@@ -4552,7 +4552,7 @@ static_library("browser") {
|
||||
@@ -4550,7 +4550,7 @@ static_library("browser") {
|
||||
|
||||
# On Windows, the hashes are embedded in //chrome:chrome_initial rather
|
||||
# than here in :chrome_dll.
|
||||
@@ -46,10 +46,10 @@ index dd3ed42ff8b59f9346d864e96f9f20cf19c8dbdc..2bee1495ba31a255b66b048acf122a86
|
||||
sources += [ "certificate_viewer_stub.cc" ]
|
||||
}
|
||||
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
|
||||
index 03bbf4e354c5612a31a35c66999645c322d3ea3c..8e1ad9be9edfb20d734423fc37a7c74dc6196543 100644
|
||||
index 2a859f1f0646e4a95af08b7d6d871857cdb9bddc..feefed02d976eadcad82c74b4349e5529b725a91 100644
|
||||
--- a/chrome/test/BUILD.gn
|
||||
+++ b/chrome/test/BUILD.gn
|
||||
@@ -6370,7 +6370,6 @@ test("unit_tests") {
|
||||
@@ -6383,7 +6383,6 @@ test("unit_tests") {
|
||||
|
||||
deps += [
|
||||
"//chrome:other_version",
|
||||
@@ -57,7 +57,7 @@ index 03bbf4e354c5612a31a35c66999645c322d3ea3c..8e1ad9be9edfb20d734423fc37a7c74d
|
||||
"//chrome//services/util_win:unit_tests",
|
||||
"//chrome/app:chrome_dll_resources",
|
||||
"//chrome/app:win_unit_tests",
|
||||
@@ -6396,6 +6395,10 @@ test("unit_tests") {
|
||||
@@ -6409,6 +6408,10 @@ test("unit_tests") {
|
||||
"//ui/resources",
|
||||
]
|
||||
|
||||
@@ -68,7 +68,7 @@ index 03bbf4e354c5612a31a35c66999645c322d3ea3c..8e1ad9be9edfb20d734423fc37a7c74d
|
||||
ldflags = [
|
||||
"/DELAYLOAD:api-ms-win-core-winrt-error-l1-1-0.dll",
|
||||
"/DELAYLOAD:api-ms-win-core-winrt-l1-1-0.dll",
|
||||
@@ -7318,7 +7321,6 @@ test("unit_tests") {
|
||||
@@ -7334,7 +7337,6 @@ test("unit_tests") {
|
||||
}
|
||||
|
||||
deps += [
|
||||
@@ -76,7 +76,7 @@ index 03bbf4e354c5612a31a35c66999645c322d3ea3c..8e1ad9be9edfb20d734423fc37a7c74d
|
||||
"//chrome/browser/apps:icon_standardizer",
|
||||
"//chrome/browser/apps/app_service",
|
||||
"//chrome/browser/apps/app_service:test_support",
|
||||
@@ -7394,6 +7396,10 @@ test("unit_tests") {
|
||||
@@ -7410,6 +7412,10 @@ test("unit_tests") {
|
||||
"//ui/webui/resources/js/browser_command:mojo_bindings",
|
||||
]
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ Subject: build: only use the mas build config in the required components
|
||||
Before landing this patch should be split into the relevant MAS patches, or at least the patch this one partially reverts
|
||||
|
||||
diff --git a/base/BUILD.gn b/base/BUILD.gn
|
||||
index e3518fb8c518de4874da9a6288bac4acd0ee6cec..ac3f79b63fb25c2f0fb15692b4d8bc46ab73a94f 100644
|
||||
index 80f6eaf01c022b979ff8c3049e7b1d66aaa605c3..c472e10fdc5c9cce7934e584f2848e74aecd8801 100644
|
||||
--- a/base/BUILD.gn
|
||||
+++ b/base/BUILD.gn
|
||||
@@ -1035,6 +1035,7 @@ component("base") {
|
||||
@@ -55,7 +55,7 @@ index 0625f07f317de46af619fdb279be78d9ecdc0029..5897820839d6d57ada22a83fe753e3a6
|
||||
"alert.h",
|
||||
"alert.mm",
|
||||
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
|
||||
index 17b6cfd529e971cfe6683531d14daa121f531bb1..5ba0aacf84177274935bfdd22e26a299ced2e629 100644
|
||||
index a808f967a95fca4847588f1d88a210bfd7f53767..7c3c71cac9162b8f565494fdad16fb06aa9563d5 100644
|
||||
--- a/components/viz/service/BUILD.gn
|
||||
+++ b/components/viz/service/BUILD.gn
|
||||
@@ -314,6 +314,7 @@ viz_component("service") {
|
||||
@@ -76,7 +76,7 @@ index 17b6cfd529e971cfe6683531d14daa121f531bb1..5ba0aacf84177274935bfdd22e26a299
|
||||
|
||||
if (is_win) {
|
||||
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
|
||||
index 04c3f2e02fa5b1c06f92d565f1acc725ca1b37ec..cae80ff987a962029d57a2c990e3a1d0fb5a1bed 100644
|
||||
index fccddda5803b6838bcfde366b4812308fce5522e..6fa821c72d385579bbc6f1b86443323138e32f77 100644
|
||||
--- a/content/browser/BUILD.gn
|
||||
+++ b/content/browser/BUILD.gn
|
||||
@@ -56,6 +56,7 @@ source_set("browser") {
|
||||
@@ -161,7 +161,7 @@ index a8193d411da14f4ff0087fba81e63809832d1806..94c410b59ab7b40654fb214a6a9d8281
|
||||
|
||||
# The standard API of net/dns.
|
||||
diff --git a/sandbox/mac/BUILD.gn b/sandbox/mac/BUILD.gn
|
||||
index b078486d595ec38e5db1462486ab31a2951430e0..5cafd91460d5f98a20caa884834cc3edb116d9b2 100644
|
||||
index 5e9fc18352d1bf0939f8366d2282b49aeb307994..69dcc2cafa27b3d8bdf3fe8d0a22a98050bb3cc6 100644
|
||||
--- a/sandbox/mac/BUILD.gn
|
||||
+++ b/sandbox/mac/BUILD.gn
|
||||
@@ -37,6 +37,7 @@ component("seatbelt") {
|
||||
@@ -265,7 +265,7 @@ index af0e7466f1a265c9d55ba81f3b2c9c68dafa7767..d991513ee82f4406f578a4751bf30fac
|
||||
if (is_win) {
|
||||
sources += [
|
||||
diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn
|
||||
index fc60060c434bfa09b4dea97d1452a7bb8480ba39..4d6cddae21fd884bb56c43adbdb389203bb3b617 100644
|
||||
index 14b9424f12ae3d3978f834e2bb765d9f18892ce5..7ad880f66fa720b3b708d0f4fe8ee4f81d98d10c 100644
|
||||
--- a/ui/views/BUILD.gn
|
||||
+++ b/ui/views/BUILD.gn
|
||||
@@ -673,6 +673,7 @@ component("views") {
|
||||
|
||||
@@ -9,10 +9,10 @@ potentially prevent a window from being created.
|
||||
TODO(loc): this patch is currently broken.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index b1d5cfa7f38a45b2e4bc47ced22a81a58203d017..2d7451eed6fc95388a8118514ba899da7aeda772 100644
|
||||
index 7773acfa5ee3bae6d17a391d873abf399839eb25..3da982748c96b6e774d57af7ee3c873a5d474b9f 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -7634,6 +7634,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
@@ -7653,6 +7653,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
last_committed_origin_, params->window_container_type,
|
||||
params->target_url, params->referrer.To<Referrer>(),
|
||||
params->frame_name, params->disposition, *params->features,
|
||||
@@ -66,10 +66,10 @@ index c7cc09531bd7b2c67085a7a3d9f98ab060a62ccb..faa0a7cd254b84af686f0c321f4d718f
|
||||
|
||||
// Operation result when the renderer asks the browser to create a new window.
|
||||
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
|
||||
index 5339daee92f0d840fa40999efcfa9c3e7a0be769..96f6ae76972060d409146b01e782d0e5ca19c032 100644
|
||||
index cd77eefb577e610612e5af8cd7b89c064b6b6fe6..789df626f7b0592868c77c2e2ddc35522865be46 100644
|
||||
--- a/content/public/browser/content_browser_client.cc
|
||||
+++ b/content/public/browser/content_browser_client.cc
|
||||
@@ -630,6 +630,8 @@ bool ContentBrowserClient::CanCreateWindow(
|
||||
@@ -636,6 +636,8 @@ bool ContentBrowserClient::CanCreateWindow(
|
||||
const std::string& frame_name,
|
||||
WindowOpenDisposition disposition,
|
||||
const blink::mojom::WindowFeatures& features,
|
||||
@@ -79,7 +79,7 @@ index 5339daee92f0d840fa40999efcfa9c3e7a0be769..96f6ae76972060d409146b01e782d0e5
|
||||
bool opener_suppressed,
|
||||
bool* no_javascript_access) {
|
||||
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
|
||||
index 24e6a9599f178858cb39d3bb2a78764d97c538bd..04259a39040c47912da72cccf2a223555590d173 100644
|
||||
index 5500a0cec01ec6b48c0f8faadf1528ccc9eea120..552714d86b77b2346371b86ad6087db4bb54f9e5 100644
|
||||
--- a/content/public/browser/content_browser_client.h
|
||||
+++ b/content/public/browser/content_browser_client.h
|
||||
@@ -164,6 +164,7 @@ class NetworkService;
|
||||
@@ -90,7 +90,7 @@ index 24e6a9599f178858cb39d3bb2a78764d97c538bd..04259a39040c47912da72cccf2a22355
|
||||
} // namespace network
|
||||
|
||||
namespace sandbox {
|
||||
@@ -1023,6 +1024,8 @@ class CONTENT_EXPORT ContentBrowserClient {
|
||||
@@ -1031,6 +1032,8 @@ class CONTENT_EXPORT ContentBrowserClient {
|
||||
const std::string& frame_name,
|
||||
WindowOpenDisposition disposition,
|
||||
const blink::mojom::WindowFeatures& features,
|
||||
|
||||
@@ -20,7 +20,7 @@ to deal with color spaces. That is being tracked at
|
||||
https://crbug.com/634542 and https://crbug.com/711107.
|
||||
|
||||
diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc
|
||||
index f6ce27a1611961e4e854967d7a8bb58cb4e19628..a9e0e73f380d449b5cb398be06d3ef51c742fe87 100644
|
||||
index 89f3522136226ee007a7d142bac48ac3f3132718..a8e86133abeba844332b37873385a331ea97b8ae 100644
|
||||
--- a/cc/trees/layer_tree_host_impl.cc
|
||||
+++ b/cc/trees/layer_tree_host_impl.cc
|
||||
@@ -1888,6 +1888,10 @@ void LayerTreeHostImpl::SetIsLikelyToRequireADraw(
|
||||
@@ -93,7 +93,7 @@ index 98e21ef6e75cb86e3408514fb42124949e611b64..a699285a41537932b5c1182cafc1024b
|
||||
sandbox::policy::switches::kGpuSandboxAllowSysVShm,
|
||||
sandbox::policy::switches::kGpuSandboxFailuresFatal,
|
||||
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
|
||||
index 9dfe0c331f97b11f285179881cd8c7820b4b61a5..7d0417c08cfabc466d6586ad382135f2a3feff0b 100644
|
||||
index 43987c78c7d1248f429c6bed4336e0db2e151c2c..bf9595d303fc91a855e125036ee7f0f53afd4cbb 100644
|
||||
--- a/content/browser/renderer_host/render_process_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_process_host_impl.cc
|
||||
@@ -3317,6 +3317,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer(
|
||||
|
||||
@@ -367,7 +367,7 @@ index 68db1862fd51f9b6c8c8c9fbd4055490fccd5f9a..5fb93015c780124ced9781ff5ae3e9cb
|
||||
#if BUILDFLAG(USE_ZYGOTE)
|
||||
ZygoteCommunication* UtilitySandboxedProcessLauncherDelegate::GetZygote() {
|
||||
diff --git a/content/browser/utility_sandbox_delegate.h b/content/browser/utility_sandbox_delegate.h
|
||||
index 7df4b4752e92fbc94c3d31cd4e0668d237bfd093..4c1d57a491a5f1044ebe1ad481809f3b088fd460 100644
|
||||
index aaf1549544051226c83e78d5d6dedac6f03618c9..517f1c05ed2bcf726a3f5aaed932adcd310a5e82 100644
|
||||
--- a/content/browser/utility_sandbox_delegate.h
|
||||
+++ b/content/browser/utility_sandbox_delegate.h
|
||||
@@ -26,7 +26,9 @@ class UtilitySandboxedProcessLauncherDelegate
|
||||
@@ -381,7 +381,7 @@ index 7df4b4752e92fbc94c3d31cd4e0668d237bfd093..4c1d57a491a5f1044ebe1ad481809f3b
|
||||
~UtilitySandboxedProcessLauncherDelegate() override;
|
||||
|
||||
sandbox::mojom::Sandbox GetSandboxType() override;
|
||||
@@ -45,25 +47,25 @@ class UtilitySandboxedProcessLauncherDelegate
|
||||
@@ -46,25 +48,25 @@ class UtilitySandboxedProcessLauncherDelegate
|
||||
ZygoteCommunication* GetZygote() override;
|
||||
#endif // BUILDFLAG(USE_ZYGOTE)
|
||||
|
||||
@@ -568,10 +568,10 @@ index 9bb4b30ba0f5d37ec2b28f0848d94f34c24f9423..b614fef01ee5cdf81b7112be721b851c
|
||||
|
||||
} // namespace content
|
||||
diff --git a/content/public/common/sandboxed_process_launcher_delegate.cc b/content/public/common/sandboxed_process_launcher_delegate.cc
|
||||
index 8656215a709012eef80532e7aac197818ac292df..6ee68149f140e475b11cfc02a25ade11ba9022b7 100644
|
||||
index 9c1aa450f32b6812d4a87cd0b9ee0dfb1a9557f4..3360302b4511ed914ac2d5756dcc7edf7e675631 100644
|
||||
--- a/content/public/common/sandboxed_process_launcher_delegate.cc
|
||||
+++ b/content/public/common/sandboxed_process_launcher_delegate.cc
|
||||
@@ -64,11 +64,17 @@ ZygoteCommunication* SandboxedProcessLauncherDelegate::GetZygote() {
|
||||
@@ -68,11 +68,17 @@ ZygoteCommunication* SandboxedProcessLauncherDelegate::GetZygote() {
|
||||
}
|
||||
#endif // BUILDFLAG(USE_ZYGOTE)
|
||||
|
||||
@@ -592,7 +592,7 @@ index 8656215a709012eef80532e7aac197818ac292df..6ee68149f140e475b11cfc02a25ade11
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
|
||||
diff --git a/content/public/common/sandboxed_process_launcher_delegate.h b/content/public/common/sandboxed_process_launcher_delegate.h
|
||||
index 4b2ea0e2680c552b853fcbe4f5e4765a908f8915..b8815f846e2307c5c5841636da37262e6a75e7ca 100644
|
||||
index cb43aa14c9742f3788ae58c3e49b890cd532f327..6a738f7aade504f2ff3bb6647a0da8f8d1933de2 100644
|
||||
--- a/content/public/common/sandboxed_process_launcher_delegate.h
|
||||
+++ b/content/public/common/sandboxed_process_launcher_delegate.h
|
||||
@@ -6,6 +6,7 @@
|
||||
@@ -603,7 +603,7 @@ index 4b2ea0e2680c552b853fcbe4f5e4765a908f8915..b8815f846e2307c5c5841636da37262e
|
||||
#include "base/files/scoped_file.h"
|
||||
#include "base/process/process.h"
|
||||
#include "build/build_config.h"
|
||||
@@ -56,10 +57,14 @@ class CONTENT_EXPORT SandboxedProcessLauncherDelegate
|
||||
@@ -57,10 +58,14 @@ class CONTENT_EXPORT SandboxedProcessLauncherDelegate
|
||||
virtual ZygoteCommunication* GetZygote();
|
||||
#endif // BUILDFLAG(USE_ZYGOTE)
|
||||
|
||||
@@ -621,10 +621,10 @@ index 4b2ea0e2680c552b853fcbe4f5e4765a908f8915..b8815f846e2307c5c5841636da37262e
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
// Whether or not to disclaim TCC responsibility for the process, defaults to
|
||||
diff --git a/sandbox/policy/win/sandbox_win.cc b/sandbox/policy/win/sandbox_win.cc
|
||||
index 544bf0087d9703e912914686ef37f8ab76584bf1..772e4048a1ce0e4588d84708f922377fe282f9f0 100644
|
||||
index d663390faa8e2a00e5fc3926384fe13b6d9653da..2e6e68de87fffeaeebc0f4693ed8b7697e34c142 100644
|
||||
--- a/sandbox/policy/win/sandbox_win.cc
|
||||
+++ b/sandbox/policy/win/sandbox_win.cc
|
||||
@@ -739,11 +739,9 @@ ResultCode GenerateConfigForSandboxedProcess(const base::CommandLine& cmd_line,
|
||||
@@ -723,11 +723,9 @@ ResultCode GenerateConfigForSandboxedProcess(const base::CommandLine& cmd_line,
|
||||
// command line flag.
|
||||
ResultCode LaunchWithoutSandbox(
|
||||
const base::CommandLine& cmd_line,
|
||||
@@ -637,7 +637,7 @@ index 544bf0087d9703e912914686ef37f8ab76584bf1..772e4048a1ce0e4588d84708f922377f
|
||||
// Network process runs in a job even when unsandboxed. This is to ensure it
|
||||
// does not outlive the browser, which could happen if there is a lot of I/O
|
||||
// on process shutdown, in which case TerminateProcess can fail. See
|
||||
@@ -970,7 +968,7 @@ bool SandboxWin::InitTargetServices(TargetServices* target_services) {
|
||||
@@ -954,7 +952,7 @@ bool SandboxWin::InitTargetServices(TargetServices* target_services) {
|
||||
ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
|
||||
const base::CommandLine& cmd_line,
|
||||
const std::string& process_type,
|
||||
@@ -646,7 +646,7 @@ index 544bf0087d9703e912914686ef37f8ab76584bf1..772e4048a1ce0e4588d84708f922377f
|
||||
SandboxDelegate* delegate,
|
||||
TargetPolicy* policy) {
|
||||
const base::CommandLine& launcher_process_command_line =
|
||||
@@ -984,7 +982,7 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
|
||||
@@ -968,7 +966,7 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
|
||||
}
|
||||
|
||||
// Add any handles to be inherited to the policy.
|
||||
@@ -655,7 +655,7 @@ index 544bf0087d9703e912914686ef37f8ab76584bf1..772e4048a1ce0e4588d84708f922377f
|
||||
policy->AddHandleToShare(handle);
|
||||
|
||||
if (!policy->GetConfig()->IsConfigured()) {
|
||||
@@ -999,6 +997,13 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
|
||||
@@ -983,6 +981,13 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
|
||||
// have no effect. These calls can fail with SBOX_ERROR_BAD_PARAMS.
|
||||
policy->SetStdoutHandle(GetStdHandle(STD_OUTPUT_HANDLE));
|
||||
policy->SetStderrHandle(GetStdHandle(STD_ERROR_HANDLE));
|
||||
@@ -669,7 +669,7 @@ index 544bf0087d9703e912914686ef37f8ab76584bf1..772e4048a1ce0e4588d84708f922377f
|
||||
#endif
|
||||
|
||||
if (!delegate->PreSpawnTarget(policy))
|
||||
@@ -1011,7 +1016,7 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
|
||||
@@ -995,7 +1000,7 @@ ResultCode SandboxWin::GeneratePolicyForSandboxedProcess(
|
||||
ResultCode SandboxWin::StartSandboxedProcess(
|
||||
const base::CommandLine& cmd_line,
|
||||
const std::string& process_type,
|
||||
@@ -678,7 +678,7 @@ index 544bf0087d9703e912914686ef37f8ab76584bf1..772e4048a1ce0e4588d84708f922377f
|
||||
SandboxDelegate* delegate,
|
||||
base::Process* process) {
|
||||
const base::ElapsedTimer timer;
|
||||
@@ -1019,13 +1024,13 @@ ResultCode SandboxWin::StartSandboxedProcess(
|
||||
@@ -1003,13 +1008,13 @@ ResultCode SandboxWin::StartSandboxedProcess(
|
||||
// Avoid making a policy if we won't use it.
|
||||
if (IsUnsandboxedProcess(delegate->GetSandboxType(), cmd_line,
|
||||
*base::CommandLine::ForCurrentProcess())) {
|
||||
|
||||
@@ -87,7 +87,7 @@ index 8af69cac78b7488d28f1f05ccb174793fe5148cd..9f74e511c263d147b5fbe81fe100d217
|
||||
private:
|
||||
const HWND hwnd_;
|
||||
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
|
||||
index dd6a99304fe86f8aea36d27d919ad621afd59292..17b6cfd529e971cfe6683531d14daa121f531bb1 100644
|
||||
index 27c944acb2e84a1abb0b03336b427afe89109804..a808f967a95fca4847588f1d88a210bfd7f53767 100644
|
||||
--- a/components/viz/service/BUILD.gn
|
||||
+++ b/components/viz/service/BUILD.gn
|
||||
@@ -137,6 +137,8 @@ viz_component("service") {
|
||||
@@ -513,7 +513,7 @@ index d4d4c1444e7a169d154bb9062f09f2270e7e9734..01943e14de567afd7b14f6a92eec651d
|
||||
waiting_on_draw_ack_ = true;
|
||||
|
||||
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
|
||||
index fc5d840c76d9d7006f030b93fd5c6d9df44fbdd9..2be7a63fe86a3758d5a0cba6bf46ef352a161207 100644
|
||||
index 84ab6b2b44c612cc15b7a2eb9493d2e143fe1d77..2c69746cc1a680ecbf4ee646022e0cc50d65fc1e 100644
|
||||
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
|
||||
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
|
||||
@@ -89,7 +89,8 @@ RootCompositorFrameSinkImpl::Create(
|
||||
|
||||
@@ -17,10 +17,10 @@ policy->CanCommitOriginAndUrl.
|
||||
Upstreamed at https://chromium-review.googlesource.com/c/chromium/src/+/3856266.
|
||||
|
||||
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
|
||||
index ddc4da15221e992201e6b98a90188a49d56ae242..a076988e5ea7338e723f5c2bf38bc20fefd1e086 100644
|
||||
index ea2dc4ac452b1566f0f02fd4aed27d1b1ad905ef..bc3c2664eb057ec2ace8365aa0ae553d7406adc8 100644
|
||||
--- a/content/browser/renderer_host/navigation_request.cc
|
||||
+++ b/content/browser/renderer_host/navigation_request.cc
|
||||
@@ -7206,10 +7206,11 @@ NavigationRequest::GetOriginForURLLoaderFactoryAfterResponseWithDebugInfo() {
|
||||
@@ -7192,10 +7192,11 @@ NavigationRequest::GetOriginForURLLoaderFactoryAfterResponseWithDebugInfo() {
|
||||
if (IsForMhtmlSubframe())
|
||||
return origin_with_debug_info;
|
||||
|
||||
@@ -37,10 +37,10 @@ index ddc4da15221e992201e6b98a90188a49d56ae242..a076988e5ea7338e723f5c2bf38bc20f
|
||||
}
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.h b/content/browser/renderer_host/render_frame_host_impl.h
|
||||
index 95ad4547efb132874179786553797974f7873961..1c6d8787d8c4e0ebc0c6e0c7ad6ade567bd9dbfa 100644
|
||||
index 24bd1304f14003e53615ed248eeac45b20ea7ecd..c29d2df1672e4d8509116800f24ee229c819c48a 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.h
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.h
|
||||
@@ -2892,6 +2892,17 @@ class CONTENT_EXPORT RenderFrameHostImpl
|
||||
@@ -2891,6 +2891,17 @@ class CONTENT_EXPORT RenderFrameHostImpl
|
||||
// last committed document.
|
||||
CookieChangeListener::CookieChangeInfo GetCookieChangeInfo();
|
||||
|
||||
@@ -58,7 +58,7 @@ index 95ad4547efb132874179786553797974f7873961..1c6d8787d8c4e0ebc0c6e0c7ad6ade56
|
||||
// Sets a ResourceCache in the renderer. `remote` must have the same process
|
||||
// isolation policy.
|
||||
// TODO(https://crbug.com/1414262): Add checks to ensure the preconditions.
|
||||
@@ -3228,17 +3239,6 @@ class CONTENT_EXPORT RenderFrameHostImpl
|
||||
@@ -3227,17 +3238,6 @@ class CONTENT_EXPORT RenderFrameHostImpl
|
||||
// relevant.
|
||||
void ResetWaitingState();
|
||||
|
||||
|
||||
@@ -64,10 +64,10 @@ index 4cd668a127a50e5462e3878c3f1dcb7384926768..dfbec49249404df8f8ebdbd26e6e865c
|
||||
|
||||
#endif // THIRD_PARTY_BLINK_PUBLIC_WEB_WEB_SCRIPT_EXECUTION_CALLBACK_H_
|
||||
diff --git a/third_party/blink/renderer/core/frame/local_frame.cc b/third_party/blink/renderer/core/frame/local_frame.cc
|
||||
index ce83dac248559ff3779f957527eb56aec95ed3d9..d837a66929d6e8d3e305d4bbf5ef4ac5e2700990 100644
|
||||
index 176039ff509633ba771b820012f85fc19f914d91..ba76b6f74f3c7f2c77b104f6e7aaed0da537f593 100644
|
||||
--- a/third_party/blink/renderer/core/frame/local_frame.cc
|
||||
+++ b/third_party/blink/renderer/core/frame/local_frame.cc
|
||||
@@ -2690,6 +2690,7 @@ void LocalFrame::RequestExecuteScript(
|
||||
@@ -2697,6 +2697,7 @@ void LocalFrame::RequestExecuteScript(
|
||||
mojom::blink::EvaluationTiming evaluation_timing,
|
||||
mojom::blink::LoadEventBlockingOption blocking_option,
|
||||
WebScriptExecutionCallback callback,
|
||||
@@ -75,7 +75,7 @@ index ce83dac248559ff3779f957527eb56aec95ed3d9..d837a66929d6e8d3e305d4bbf5ef4ac5
|
||||
BackForwardCacheAware back_forward_cache_aware,
|
||||
mojom::blink::WantResultOption want_result_option,
|
||||
mojom::blink::PromiseResultOption promise_behavior) {
|
||||
@@ -2720,7 +2721,8 @@ void LocalFrame::RequestExecuteScript(
|
||||
@@ -2727,7 +2728,8 @@ void LocalFrame::RequestExecuteScript(
|
||||
PausableScriptExecutor::CreateAndRun(
|
||||
ToScriptState(DomWindow(), *world), std::move(script_sources),
|
||||
execute_script_policy, user_gesture, evaluation_timing, blocking_option,
|
||||
@@ -86,7 +86,7 @@ index ce83dac248559ff3779f957527eb56aec95ed3d9..d837a66929d6e8d3e305d4bbf5ef4ac5
|
||||
|
||||
void LocalFrame::SetEvictCachedSessionStorageOnFreezeOrUnload() {
|
||||
diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h
|
||||
index 14f11b7ad67250f191956aeab9f9cc506be70381..fc52c45cab54480c0a37205f48baeae930ababaa 100644
|
||||
index b85c9aa090851134a9b357c10c396b7303a2e6d3..e932a99632d53edb595a8c8a696cdd528b6e7d99 100644
|
||||
--- a/third_party/blink/renderer/core/frame/local_frame.h
|
||||
+++ b/third_party/blink/renderer/core/frame/local_frame.h
|
||||
@@ -778,6 +778,7 @@ class CORE_EXPORT LocalFrame final
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: frame_host_manager.patch
|
||||
Allows embedder to intercept site instances created by chromium.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_manager.cc b/content/browser/renderer_host/render_frame_host_manager.cc
|
||||
index 9ffeee895d26666a3bf47f374f7b77818316d446..48f1431287e151106cca4c2a20b1d22105a28837 100644
|
||||
index fd8779c9a7032b318f61653dfc9bcbc17451d403..3041cd1bb26da0571cbf91786ba82b619daeb1ea 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_manager.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_manager.cc
|
||||
@@ -3734,6 +3734,9 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest(
|
||||
@@ -3735,6 +3735,9 @@ RenderFrameHostManager::GetSiteInstanceForNavigationRequest(
|
||||
request->ResetStateForSiteInstanceChange();
|
||||
}
|
||||
|
||||
@@ -20,7 +20,7 @@ index 9ffeee895d26666a3bf47f374f7b77818316d446..48f1431287e151106cca4c2a20b1d221
|
||||
}
|
||||
|
||||
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
|
||||
index 04259a39040c47912da72cccf2a223555590d173..8cbedf0e2de675c04c5a64ba8b1dce14a132fad9 100644
|
||||
index 552714d86b77b2346371b86ad6087db4bb54f9e5..64c0354cfc574661214c9fb71c81c6d840cc23cc 100644
|
||||
--- a/content/public/browser/content_browser_client.h
|
||||
+++ b/content/public/browser/content_browser_client.h
|
||||
@@ -275,6 +275,11 @@ class CONTENT_EXPORT ContentBrowserClient {
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: gritsettings_resource_ids.patch
|
||||
Add electron resources file to the list of resource ids generation.
|
||||
|
||||
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
|
||||
index d635208d00ae6b19eb401227bd24f29c3b83380d..628afef66ee41952499b841772ffec768e7522a2 100644
|
||||
index 03c4a6a04c1231c3f169a8ddb1880b95df84ea09..02609fc28b8cdd410049c365ea001227ec8eed3f 100644
|
||||
--- a/tools/gritsettings/resource_ids.spec
|
||||
+++ b/tools/gritsettings/resource_ids.spec
|
||||
@@ -1111,6 +1111,11 @@
|
||||
@@ -1115,6 +1115,11 @@
|
||||
"includes": [4960],
|
||||
},
|
||||
|
||||
|
||||
@@ -133,7 +133,7 @@ index bbd2aa78722fc0a14ac815ca0243b83965ad8d7c..b6e0a2fce3a0fb9c449aa1bef6a0f970
|
||||
const GURL& document_url,
|
||||
const WeakDocumentPtr& weak_document_ptr,
|
||||
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
|
||||
index fa0d5afe801d83c6bcbe3e64acb43353ee413107..0d2fedc58a7e0ec968764ebfba33e4a4aa11c177 100644
|
||||
index cba4284de4f48dc0b2efa93f0571cad6611fdc06..045cd18f85d2ccecb94d97251b0aeab132a22050 100644
|
||||
--- a/content/browser/renderer_host/render_process_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_process_host_impl.cc
|
||||
@@ -2113,7 +2113,7 @@ void RenderProcessHostImpl::CreateNotificationService(
|
||||
|
||||
@@ -818,10 +818,10 @@ index 146fbcb2e6bd4348110ecc3220d6ac0ac59babf3..eecc3118033ef7fe1f17aba48cd19b17
|
||||
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
|
||||
// Set options for print preset from source PDF document.
|
||||
diff --git a/content/browser/BUILD.gn b/content/browser/BUILD.gn
|
||||
index 1c99ce5b8dfa6bb302bc11240fd3276ec77c6df4..04c3f2e02fa5b1c06f92d565f1acc725ca1b37ec 100644
|
||||
index 591334520ada8346eed65f879a84c68e9ec92339..fccddda5803b6838bcfde366b4812308fce5522e 100644
|
||||
--- a/content/browser/BUILD.gn
|
||||
+++ b/content/browser/BUILD.gn
|
||||
@@ -2835,8 +2835,9 @@ source_set("browser") {
|
||||
@@ -2837,8 +2837,9 @@ source_set("browser") {
|
||||
"//ppapi/shared_impl",
|
||||
]
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: scroll_bounce_flag.patch
|
||||
Patch to make scrollBounce option work.
|
||||
|
||||
diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc
|
||||
index 7da94f2cff710ea2d7e52bbca685f176a0531c54..c7dfa527a1308bb8623db9653cd5d3482a8d441c 100644
|
||||
index 3d3645fa251316cb1668352a2ee1e17bc15dc6e3..688ad18bf9e6bc036734bd3ac4c6b2c1a2ac22c4 100644
|
||||
--- a/content/renderer/render_thread_impl.cc
|
||||
+++ b/content/renderer/render_thread_impl.cc
|
||||
@@ -1341,7 +1341,7 @@ bool RenderThreadImpl::IsLcdTextEnabled() {
|
||||
@@ -1342,7 +1342,7 @@ bool RenderThreadImpl::IsLcdTextEnabled() {
|
||||
}
|
||||
|
||||
bool RenderThreadImpl::IsElasticOverscrollEnabled() {
|
||||
|
||||
@@ -22,13 +22,13 @@ However, the patch would need to be reviewed by the security team, as it
|
||||
does touch a security-sensitive class.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc
|
||||
index 0d2fedc58a7e0ec968764ebfba33e4a4aa11c177..9dfe0c331f97b11f285179881cd8c7820b4b61a5 100644
|
||||
index 045cd18f85d2ccecb94d97251b0aeab132a22050..43987c78c7d1248f429c6bed4336e0db2e151c2c 100644
|
||||
--- a/content/browser/renderer_host/render_process_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_process_host_impl.cc
|
||||
@@ -1794,9 +1794,15 @@ bool RenderProcessHostImpl::Init() {
|
||||
std::unique_ptr<SandboxedProcessLauncherDelegate> sandbox_delegate =
|
||||
std::make_unique<RendererSandboxedProcessLauncherDelegateWin>(
|
||||
*cmd_line, IsJitDisabled());
|
||||
*cmd_line, IsPdf(), IsJitDisabled());
|
||||
+#else
|
||||
+#if BUILDFLAG(USE_ZYGOTE)
|
||||
+ bool use_zygote = !cmd_line->HasSwitch(switches::kNoZygote);
|
||||
@@ -42,7 +42,7 @@ index 0d2fedc58a7e0ec968764ebfba33e4a4aa11c177..9dfe0c331f97b11f285179881cd8c782
|
||||
|
||||
auto file_data = std::make_unique<ChildProcessLauncherFileData>();
|
||||
diff --git a/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.cc b/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.cc
|
||||
index ef00d5539e5b859d23c6d137c179a88c7c41fd4c..aa2372ec406bdbd9b1b9a672784ca2df704892ff 100644
|
||||
index c297e3fc2ffabb53e94908d7021ef3e750d4c0f1..4b723edd68f9c5668c5e8f3b12984a6f18392252 100644
|
||||
--- a/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.cc
|
||||
+++ b/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.cc
|
||||
@@ -33,6 +33,9 @@ namespace content {
|
||||
@@ -55,10 +55,10 @@ index ef00d5539e5b859d23c6d137c179a88c7c41fd4c..aa2372ec406bdbd9b1b9a672784ca2df
|
||||
const base::CommandLine& browser_command_line =
|
||||
*base::CommandLine::ForCurrentProcess();
|
||||
base::CommandLine::StringType renderer_prefix =
|
||||
@@ -63,6 +66,9 @@ RendererSandboxedProcessLauncherDelegateWin::
|
||||
GetContentClient()->browser()->IsRendererCodeIntegrityEnabled()),
|
||||
renderer_app_container_disabled_(
|
||||
GetContentClient()->browser()->IsRendererAppContainerDisabled()) {
|
||||
@@ -67,6 +70,9 @@ RendererSandboxedProcessLauncherDelegateWin::
|
||||
is_pdf_renderer_(is_pdf_renderer) {
|
||||
// PDF renderers must be jitless.
|
||||
CHECK(!is_pdf_renderer || is_jit_disabled);
|
||||
+#if BUILDFLAG(USE_ZYGOTE)
|
||||
+ use_zygote_ = !cmd_line->HasSwitch(switches::kNoZygote);
|
||||
+#endif
|
||||
@@ -66,7 +66,7 @@ index ef00d5539e5b859d23c6d137c179a88c7c41fd4c..aa2372ec406bdbd9b1b9a672784ca2df
|
||||
dynamic_code_can_be_disabled_ = true;
|
||||
return;
|
||||
diff --git a/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.h b/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.h
|
||||
index bd21850f7c700c537445cb6541446b2e9793750e..0d86702e124014b518382500c6ce1dac1a9a5276 100644
|
||||
index 00038da2c15696b361aea1469ccf73307e44963e..7ccfbf11ecfd56fd165915baa85919eaf2e923b9 100644
|
||||
--- a/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.h
|
||||
+++ b/content/browser/renderer_host/renderer_sandboxed_process_launcher_delegate.h
|
||||
@@ -18,6 +18,11 @@ class CONTENT_EXPORT RendererSandboxedProcessLauncherDelegate
|
||||
|
||||
@@ -6,7 +6,7 @@ Subject: unsandboxed_ppapi_processes_skip_zygote.patch
|
||||
Unsandboxed ppapi processes should skip zygote.
|
||||
|
||||
diff --git a/content/browser/ppapi_plugin_sandboxed_process_launcher_delegate.cc b/content/browser/ppapi_plugin_sandboxed_process_launcher_delegate.cc
|
||||
index f2b5c3b52a2c16448bb4d7b8f35dd110f66da0f4..9b4e8205139af69a15e7a64bf7a9fd08733d3f0b 100644
|
||||
index b141cfe3cc2e6e6563af03249bf5ec849e26d819..a1cc958e16162eee9686c73c207effe4880d50c7 100644
|
||||
--- a/content/browser/ppapi_plugin_sandboxed_process_launcher_delegate.cc
|
||||
+++ b/content/browser/ppapi_plugin_sandboxed_process_launcher_delegate.cc
|
||||
@@ -10,6 +10,7 @@
|
||||
@@ -17,7 +17,7 @@ index f2b5c3b52a2c16448bb4d7b8f35dd110f66da0f4..9b4e8205139af69a15e7a64bf7a9fd08
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include "sandbox/policy/win/sandbox_win.h"
|
||||
@@ -54,6 +55,9 @@ bool PpapiPluginSandboxedProcessLauncherDelegate::InitializeConfig(
|
||||
@@ -58,6 +59,9 @@ bool PpapiPluginSandboxedProcessLauncherDelegate::AllowWindowsFontsDir() {
|
||||
ZygoteCommunication* PpapiPluginSandboxedProcessLauncherDelegate::GetZygote() {
|
||||
const base::CommandLine& browser_command_line =
|
||||
*base::CommandLine::ForCurrentProcess();
|
||||
|
||||
@@ -14,10 +14,10 @@ Note that we also need to manually update embedder's
|
||||
`api::WebContents::IsFullscreenForTabOrPending` value.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index 2d7451eed6fc95388a8118514ba899da7aeda772..4eaef7517af1dc4cbae9f6c54c626dc4bb151955 100644
|
||||
index 3da982748c96b6e774d57af7ee3c873a5d474b9f..94f6b4527bbcc336830b5d5b0df6120321b54d07 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -6868,6 +6868,17 @@ void RenderFrameHostImpl::EnterFullscreen(
|
||||
@@ -6887,6 +6887,17 @@ void RenderFrameHostImpl::EnterFullscreen(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -26,10 +26,10 @@ index 77f2c4fbf8c886c5c622f41d0b9da67d09b7d331..7aa2059f33dfa92828beec95f4716775
|
||||
// An empty URL is returned if the URL is not overriden.
|
||||
virtual GURL OverrideFlashEmbedWithHTML(const GURL& url);
|
||||
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
|
||||
index 53bfdfdc340596b1bf7eee664e5667f8e74d6a7a..91147e2b16c2f8f4c80e0ab2262b89d18cff59af 100644
|
||||
index decef7b3d86c0538bc1bede5c761438ade86e0d4..df22e32a5a9bed8457f8db9dbe2f8cd093a4c06e 100644
|
||||
--- a/content/renderer/renderer_blink_platform_impl.cc
|
||||
+++ b/content/renderer/renderer_blink_platform_impl.cc
|
||||
@@ -821,6 +821,12 @@ void RendererBlinkPlatformImpl::WillStopWorkerThread() {
|
||||
@@ -824,6 +824,12 @@ void RendererBlinkPlatformImpl::WillStopWorkerThread() {
|
||||
WorkerThreadRegistry::Instance()->WillStopCurrentWorkerThread();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ index 53bfdfdc340596b1bf7eee664e5667f8e74d6a7a..91147e2b16c2f8f4c80e0ab2262b89d1
|
||||
const v8::Local<v8::Context>& worker) {
|
||||
GetContentClient()->renderer()->DidInitializeWorkerContextOnWorkerThread(
|
||||
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
|
||||
index beb3fab6e4c3a8b74482d0fc85f22af4b9e2cbd9..b1c2949e621d210c88a8eae812cc42f3d4c5ac0d 100644
|
||||
index a15ca0214e694e9eda2ef88c0100ba26f306d9dc..0bf1ebaafaf8147350aa0d5daccd2eabe7bb0ab9 100644
|
||||
--- a/content/renderer/renderer_blink_platform_impl.h
|
||||
+++ b/content/renderer/renderer_blink_platform_impl.h
|
||||
@@ -180,6 +180,7 @@ class CONTENT_EXPORT RendererBlinkPlatformImpl : public BlinkPlatformImpl {
|
||||
@@ -55,7 +55,7 @@ index beb3fab6e4c3a8b74482d0fc85f22af4b9e2cbd9..b1c2949e621d210c88a8eae812cc42f3
|
||||
const blink::WebSecurityOrigin& script_origin) override;
|
||||
blink::ProtocolHandlerSecurityLevel GetProtocolHandlerSecurityLevel()
|
||||
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
|
||||
index 7b45e20cd208ffe01005898b5345a478b74fb96a..0933ffefcb4e736607f42729d334b161544c4bdc 100644
|
||||
index 17e2a2876789c11dcdf7d850e845d73fcf637434..931d174c6f5f843d8c29b89ec678c10ef06ce0de 100644
|
||||
--- a/third_party/blink/public/platform/platform.h
|
||||
+++ b/third_party/blink/public/platform/platform.h
|
||||
@@ -632,6 +632,7 @@ class BLINK_PLATFORM_EXPORT Platform {
|
||||
|
||||
@@ -35,10 +35,10 @@ index 7aa2059f33dfa92828beec95f4716775e7291fed..df919b894d4f29274696806444d034d4
|
||||
// from the worker thread.
|
||||
virtual void WillDestroyWorkerContextOnWorkerThread(
|
||||
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
|
||||
index 91147e2b16c2f8f4c80e0ab2262b89d18cff59af..34b87e12629e24ab6dd44fc42a60e5e8395ba363 100644
|
||||
index df22e32a5a9bed8457f8db9dbe2f8cd093a4c06e..538e7be679804c536ff4c124926345487437d150 100644
|
||||
--- a/content/renderer/renderer_blink_platform_impl.cc
|
||||
+++ b/content/renderer/renderer_blink_platform_impl.cc
|
||||
@@ -833,6 +833,12 @@ void RendererBlinkPlatformImpl::WorkerContextCreated(
|
||||
@@ -836,6 +836,12 @@ void RendererBlinkPlatformImpl::WorkerContextCreated(
|
||||
worker);
|
||||
}
|
||||
|
||||
@@ -52,7 +52,7 @@ index 91147e2b16c2f8f4c80e0ab2262b89d18cff59af..34b87e12629e24ab6dd44fc42a60e5e8
|
||||
const blink::WebSecurityOrigin& script_origin) {
|
||||
return GetContentClient()->renderer()->AllowScriptExtensionForServiceWorker(
|
||||
diff --git a/content/renderer/renderer_blink_platform_impl.h b/content/renderer/renderer_blink_platform_impl.h
|
||||
index b1c2949e621d210c88a8eae812cc42f3d4c5ac0d..d1e7646bee788f261352265b79627e0c3c2a666a 100644
|
||||
index 0bf1ebaafaf8147350aa0d5daccd2eabe7bb0ab9..57ad67acccbe08fe3a551026fcf512db79353567 100644
|
||||
--- a/content/renderer/renderer_blink_platform_impl.h
|
||||
+++ b/content/renderer/renderer_blink_platform_impl.h
|
||||
@@ -180,6 +180,8 @@ class CONTENT_EXPORT RendererBlinkPlatformImpl : public BlinkPlatformImpl {
|
||||
@@ -65,7 +65,7 @@ index b1c2949e621d210c88a8eae812cc42f3d4c5ac0d..d1e7646bee788f261352265b79627e0c
|
||||
bool AllowScriptExtensionForServiceWorker(
|
||||
const blink::WebSecurityOrigin& script_origin) override;
|
||||
diff --git a/third_party/blink/public/platform/platform.h b/third_party/blink/public/platform/platform.h
|
||||
index 0933ffefcb4e736607f42729d334b161544c4bdc..a36fbc621b0fe0a85a718425653f2ac48a9c385f 100644
|
||||
index 931d174c6f5f843d8c29b89ec678c10ef06ce0de..24d16db94e628b73ab80c4d9d62198b91b85a554 100644
|
||||
--- a/third_party/blink/public/platform/platform.h
|
||||
+++ b/third_party/blink/public/platform/platform.h
|
||||
@@ -632,6 +632,8 @@ class BLINK_PLATFORM_EXPORT Platform {
|
||||
|
||||
@@ -36,10 +36,10 @@ index b22591ee606ef449817aef1f9bd5ff0c024d1c05..a14c0e0a381ae838d44d4c7f2cc252c2
|
||||
import '../../ui/legacy/components/source_frame/source_frame-meta.js';
|
||||
import '../../panels/console_counters/console_counters-meta.js';
|
||||
diff --git a/front_end/ui/legacy/BUILD.gn b/front_end/ui/legacy/BUILD.gn
|
||||
index 660dde0a60378278f43040ab1bf3f279ffa85349..dd165e9edca8b217977e6f326d2be288026a0d7d 100644
|
||||
index fe5d16d9935cad8e78a050d8db10bb6b7b355eee..fd21618411a388c53a2a508663fb9349a51a6a58 100644
|
||||
--- a/front_end/ui/legacy/BUILD.gn
|
||||
+++ b/front_end/ui/legacy/BUILD.gn
|
||||
@@ -182,5 +182,6 @@ devtools_entrypoint("legacy") {
|
||||
@@ -183,5 +183,6 @@ devtools_entrypoint("legacy") {
|
||||
visibility = [
|
||||
"../..:legacy_entrypoints",
|
||||
"../../legacy_test_runner/*",
|
||||
|
||||
@@ -9,10 +9,10 @@ necessary for native modules to load.
|
||||
Also, some fixes relating to mksnapshot on ARM.
|
||||
|
||||
diff --git a/BUILD.gn b/BUILD.gn
|
||||
index ac155fd467f5a4b56bc861e8fc62c20b02e3d247..534b7c3165e549f87f16c931ccfe87f855ac550f 100644
|
||||
index 8f9377940796a550558552ea48fa0ba1bc4de59b..2875b365ee3187b46bed18e8d874b61a54e576c4 100644
|
||||
--- a/BUILD.gn
|
||||
+++ b/BUILD.gn
|
||||
@@ -691,7 +691,7 @@ config("internal_config") {
|
||||
@@ -699,7 +699,7 @@ config("internal_config") {
|
||||
":cppgc_header_features",
|
||||
]
|
||||
|
||||
@@ -21,7 +21,7 @@ index ac155fd467f5a4b56bc861e8fc62c20b02e3d247..534b7c3165e549f87f16c931ccfe87f8
|
||||
defines += [ "BUILDING_V8_SHARED" ]
|
||||
}
|
||||
|
||||
@@ -6384,7 +6384,7 @@ if (current_toolchain == v8_generator_toolchain) {
|
||||
@@ -6397,7 +6397,7 @@ if (current_toolchain == v8_generator_toolchain) {
|
||||
"src/interpreter/bytecodes.h",
|
||||
]
|
||||
|
||||
|
||||
@@ -12,10 +12,10 @@ This patch can be safely removed if, when it is removed, `node.lib` does not
|
||||
contain any standard C++ library exports (e.g. `std::ostringstream`).
|
||||
|
||||
diff --git a/BUILD.gn b/BUILD.gn
|
||||
index 3fdd0beb746b2739689e08a741c387c87ad99ded..83ac0a1a2abe2e917f314bb88e28a27f71019101 100644
|
||||
index 47ca2ef8de86da8ee73c4b1b37f9eceb74cd7ce1..9d30971e9bd7e4b366f2e5592b921a9870944db4 100644
|
||||
--- a/BUILD.gn
|
||||
+++ b/BUILD.gn
|
||||
@@ -691,6 +691,10 @@ config("internal_config") {
|
||||
@@ -699,6 +699,10 @@ config("internal_config") {
|
||||
":cppgc_header_features",
|
||||
]
|
||||
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: expose_mksnapshot.patch
|
||||
Needed in order to target mksnapshot for mksnapshot zip.
|
||||
|
||||
diff --git a/BUILD.gn b/BUILD.gn
|
||||
index 534b7c3165e549f87f16c931ccfe87f855ac550f..3fdd0beb746b2739689e08a741c387c87ad99ded 100644
|
||||
index 2875b365ee3187b46bed18e8d874b61a54e576c4..47ca2ef8de86da8ee73c4b1b37f9eceb74cd7ce1 100644
|
||||
--- a/BUILD.gn
|
||||
+++ b/BUILD.gn
|
||||
@@ -6396,7 +6396,6 @@ if (current_toolchain == v8_generator_toolchain) {
|
||||
@@ -6409,7 +6409,6 @@ if (current_toolchain == v8_generator_toolchain) {
|
||||
|
||||
if (current_toolchain == v8_snapshot_toolchain) {
|
||||
v8_executable("mksnapshot") {
|
||||
|
||||
@@ -273,9 +273,17 @@ gin::Handle<Protocol> Protocol::Create(
|
||||
isolate, new Protocol(isolate, browser_context->protocol_registry()));
|
||||
}
|
||||
|
||||
gin::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::Wrappable<Protocol>::GetObjectTemplateBuilder(isolate)
|
||||
// static
|
||||
gin::Handle<Protocol> Protocol::New(gin_helper::ErrorThrower thrower) {
|
||||
thrower.ThrowError("Protocol cannot be created from JS");
|
||||
return gin::Handle<Protocol>();
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::ObjectTemplate> Protocol::FillObjectTemplate(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> tmpl) {
|
||||
return gin::ObjectTemplateBuilder(isolate, "Protocol", tmpl)
|
||||
.SetMethod("registerStringProtocol",
|
||||
&Protocol::RegisterProtocolFor<ProtocolType::kString>)
|
||||
.SetMethod("registerBufferProtocol",
|
||||
@@ -304,7 +312,8 @@ gin::ObjectTemplateBuilder Protocol::GetObjectTemplateBuilder(
|
||||
.SetMethod("interceptProtocol",
|
||||
&Protocol::InterceptProtocolFor<ProtocolType::kFree>)
|
||||
.SetMethod("uninterceptProtocol", &Protocol::UninterceptProtocol)
|
||||
.SetMethod("isProtocolIntercepted", &Protocol::IsProtocolIntercepted);
|
||||
.SetMethod("isProtocolIntercepted", &Protocol::IsProtocolIntercepted)
|
||||
.Build();
|
||||
}
|
||||
|
||||
const char* Protocol::GetTypeName() {
|
||||
@@ -333,6 +342,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
void* priv) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("Protocol", electron::api::Protocol::GetConstructor(context));
|
||||
dict.SetMethod("registerSchemesAsPrivileged", &RegisterSchemesAsPrivileged);
|
||||
dict.SetMethod("getStandardSchemes", &electron::api::GetStandardSchemes);
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "gin/handle.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "shell/browser/net/electron_url_loader_factory.h"
|
||||
#include "shell/common/gin_helper/constructible.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -37,15 +38,19 @@ enum class ProtocolError {
|
||||
};
|
||||
|
||||
// Protocol implementation based on network services.
|
||||
class Protocol : public gin::Wrappable<Protocol> {
|
||||
class Protocol : public gin::Wrappable<Protocol>,
|
||||
public gin_helper::Constructible<Protocol> {
|
||||
public:
|
||||
static gin::Handle<Protocol> Create(v8::Isolate* isolate,
|
||||
ElectronBrowserContext* browser_context);
|
||||
|
||||
static gin::Handle<Protocol> New(gin_helper::ErrorThrower thrower);
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override;
|
||||
static v8::Local<v8::ObjectTemplate> FillObjectTemplate(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> tmpl);
|
||||
const char* GetTypeName() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/net/asar/asar_url_loader_factory.h"
|
||||
#include "shell/browser/net/proxying_url_loader_factory.h"
|
||||
#include "shell/browser/protocol_registry.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
@@ -488,7 +489,10 @@ SimpleURLLoaderWrapper::GetURLLoaderFactoryForURL(const GURL& url) {
|
||||
// Explicitly handle intercepted protocols here, even though
|
||||
// ProxyingURLLoaderFactory would handle them later on, so that we can
|
||||
// correctly intercept file:// scheme URLs.
|
||||
if (protocol_registry->IsProtocolIntercepted(url.scheme())) {
|
||||
bool bypass_custom_protocol_handlers =
|
||||
request_options_ & kBypassCustomProtocolHandlers;
|
||||
if (!bypass_custom_protocol_handlers &&
|
||||
protocol_registry->IsProtocolIntercepted(url.scheme())) {
|
||||
auto& protocol_handler =
|
||||
protocol_registry->intercept_handlers().at(url.scheme());
|
||||
mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote =
|
||||
@@ -497,7 +501,8 @@ SimpleURLLoaderWrapper::GetURLLoaderFactoryForURL(const GURL& url) {
|
||||
url_loader_factory = network::SharedURLLoaderFactory::Create(
|
||||
std::make_unique<network::WrapperPendingSharedURLLoaderFactory>(
|
||||
std::move(pending_remote)));
|
||||
} else if (protocol_registry->IsProtocolRegistered(url.scheme())) {
|
||||
} else if (!bypass_custom_protocol_handlers &&
|
||||
protocol_registry->IsProtocolRegistered(url.scheme())) {
|
||||
auto& protocol_handler = protocol_registry->handlers().at(url.scheme());
|
||||
mojo::PendingRemote<network::mojom::URLLoaderFactory> pending_remote =
|
||||
ElectronURLLoaderFactory::Create(protocol_handler.first,
|
||||
@@ -528,6 +533,10 @@ gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
|
||||
auto request = std::make_unique<network::ResourceRequest>();
|
||||
opts.Get("method", &request->method);
|
||||
opts.Get("url", &request->url);
|
||||
if (!request->url.is_valid()) {
|
||||
args->ThrowTypeError("Invalid URL");
|
||||
return gin::Handle<SimpleURLLoaderWrapper>();
|
||||
}
|
||||
request->site_for_cookies = net::SiteForCookies::FromUrl(request->url);
|
||||
opts.Get("referrer", &request->referrer);
|
||||
request->referrer_policy =
|
||||
@@ -648,7 +657,7 @@ gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
|
||||
|
||||
bool use_session_cookies = false;
|
||||
opts.Get("useSessionCookies", &use_session_cookies);
|
||||
int options = 0;
|
||||
int options = network::mojom::kURLLoadOptionSniffMimeType;
|
||||
if (!credentials_specified && !use_session_cookies) {
|
||||
// This is the default case, as well as the case when credentials is not
|
||||
// specified and useSessionCookies is false. credentials_mode will be
|
||||
@@ -657,6 +666,11 @@ gin::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
|
||||
options |= network::mojom::kURLLoadOptionBlockAllCookies;
|
||||
}
|
||||
|
||||
bool bypass_custom_protocol_handlers = false;
|
||||
opts.Get("bypassCustomProtocolHandlers", &bypass_custom_protocol_handlers);
|
||||
if (bypass_custom_protocol_handlers)
|
||||
options |= kBypassCustomProtocolHandlers;
|
||||
|
||||
v8::Local<v8::Value> body;
|
||||
v8::Local<v8::Value> chunk_pipe_getter;
|
||||
if (opts.Get("body", &body)) {
|
||||
@@ -738,6 +752,7 @@ void SimpleURLLoaderWrapper::OnResponseStarted(
|
||||
dict.Set("httpVersion", response_head.headers->GetHttpVersion());
|
||||
dict.Set("headers", response_head.headers.get());
|
||||
dict.Set("rawHeaders", response_head.raw_response_headers);
|
||||
dict.Set("mimeType", response_head.mime_type);
|
||||
Emit("response-started", final_url, dict);
|
||||
}
|
||||
|
||||
|
||||
@@ -623,6 +623,23 @@ void SetBackgroundColor(content::RenderWidgetHostView* rwhv, SkColor color) {
|
||||
->SetContentBackgroundColor(color);
|
||||
}
|
||||
|
||||
content::RenderFrameHost* GetRenderFrameHost(
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
|
||||
content::FrameTreeNode* frame_tree_node =
|
||||
content::FrameTreeNode::GloballyFindByID(frame_tree_node_id);
|
||||
content::RenderFrameHostManager* render_manager =
|
||||
frame_tree_node->render_manager();
|
||||
content::RenderFrameHost* frame_host = nullptr;
|
||||
if (render_manager) {
|
||||
frame_host = render_manager->speculative_frame_host();
|
||||
if (!frame_host)
|
||||
frame_host = render_manager->current_frame_host();
|
||||
}
|
||||
|
||||
return frame_host;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
@@ -1768,18 +1785,8 @@ bool WebContents::EmitNavigationEvent(
|
||||
const std::string& event_name,
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
bool is_main_frame = navigation_handle->IsInMainFrame();
|
||||
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
|
||||
content::FrameTreeNode* frame_tree_node =
|
||||
content::FrameTreeNode::GloballyFindByID(frame_tree_node_id);
|
||||
content::RenderFrameHostManager* render_manager =
|
||||
frame_tree_node->render_manager();
|
||||
content::RenderFrameHost* frame_host = nullptr;
|
||||
if (render_manager) {
|
||||
frame_host = render_manager->speculative_frame_host();
|
||||
if (!frame_host)
|
||||
frame_host = render_manager->current_frame_host();
|
||||
}
|
||||
int frame_process_id = -1, frame_routing_id = -1;
|
||||
content::RenderFrameHost* frame_host = GetRenderFrameHost(navigation_handle);
|
||||
if (frame_host) {
|
||||
frame_process_id = frame_host->GetProcess()->GetID();
|
||||
frame_routing_id = frame_host->GetRoutingID();
|
||||
@@ -1988,6 +1995,9 @@ void WebContents::MessageHost(const std::string& channel,
|
||||
|
||||
void WebContents::UpdateDraggableRegions(
|
||||
std::vector<mojom::DraggableRegionPtr> regions) {
|
||||
if (owner_window() && owner_window()->has_frame())
|
||||
return;
|
||||
|
||||
draggable_region_ = DraggableRegionsToSkRegion(regions);
|
||||
}
|
||||
|
||||
|
||||
@@ -26,6 +26,25 @@
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsValidWrappable(const v8::Local<v8::Value>& obj) {
|
||||
v8::Local<v8::Object> port = v8::Local<v8::Object>::Cast(obj);
|
||||
|
||||
if (!port->IsObject())
|
||||
return false;
|
||||
|
||||
if (port->InternalFieldCount() != gin::kNumberOfInternalFields)
|
||||
return false;
|
||||
|
||||
const auto* info = static_cast<gin::WrapperInfo*>(
|
||||
port->GetAlignedPointerFromInternalField(gin::kWrapperInfoIndex));
|
||||
|
||||
return info && info->embedder == gin::kEmbedderNativeGin;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
gin::WrapperInfo MessagePort::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
MessagePort::MessagePort() = default;
|
||||
@@ -48,10 +67,11 @@ void MessagePort::PostMessage(gin::Arguments* args) {
|
||||
DCHECK(!IsNeutered());
|
||||
|
||||
blink::TransferableMessage transferable_message;
|
||||
gin_helper::ErrorThrower thrower(args->isolate());
|
||||
|
||||
v8::Local<v8::Value> message_value;
|
||||
if (!args->GetNext(&message_value)) {
|
||||
args->ThrowTypeError("Expected at least one argument to postMessage");
|
||||
thrower.ThrowTypeError("Expected at least one argument to postMessage");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -61,8 +81,23 @@ void MessagePort::PostMessage(gin::Arguments* args) {
|
||||
v8::Local<v8::Value> transferables;
|
||||
std::vector<gin::Handle<MessagePort>> wrapped_ports;
|
||||
if (args->GetNext(&transferables)) {
|
||||
std::vector<v8::Local<v8::Value>> wrapped_port_values;
|
||||
if (!gin::ConvertFromV8(args->isolate(), transferables,
|
||||
&wrapped_port_values)) {
|
||||
thrower.ThrowTypeError("transferables must be an array of MessagePorts");
|
||||
return;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < wrapped_port_values.size(); ++i) {
|
||||
if (!IsValidWrappable(wrapped_port_values[i])) {
|
||||
thrower.ThrowTypeError("Port at index " + base::NumberToString(i) +
|
||||
" is not a valid port");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!gin::ConvertFromV8(args->isolate(), transferables, &wrapped_ports)) {
|
||||
args->ThrowError();
|
||||
thrower.ThrowTypeError("Passed an invalid MessagePort");
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -70,9 +105,8 @@ void MessagePort::PostMessage(gin::Arguments* args) {
|
||||
// Make sure we aren't connected to any of the passed-in ports.
|
||||
for (unsigned i = 0; i < wrapped_ports.size(); ++i) {
|
||||
if (wrapped_ports[i].get() == this) {
|
||||
gin_helper::ErrorThrower(args->isolate())
|
||||
.ThrowError("Port at index " + base::NumberToString(i) +
|
||||
" contains the source port.");
|
||||
thrower.ThrowError("Port at index " + base::NumberToString(i) +
|
||||
" contains the source port.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,6 +37,10 @@ ElectronNavigationThrottle::WillStartRequest() {
|
||||
return PROCEED;
|
||||
}
|
||||
|
||||
if (handle->IsRendererInitiated() &&
|
||||
api_contents->EmitNavigationEvent("will-frame-navigate", handle)) {
|
||||
return CANCEL;
|
||||
}
|
||||
if (handle->IsRendererInitiated() && handle->IsInMainFrame() &&
|
||||
api_contents->EmitNavigationEvent("will-navigate", handle)) {
|
||||
return CANCEL;
|
||||
|
||||
@@ -27,8 +27,6 @@ namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
const int kMaxScanRetries = 5;
|
||||
|
||||
void OnDeviceChosen(const content::BluetoothChooser::EventHandler& handler,
|
||||
const std::string& device_id) {
|
||||
if (device_id.empty()) {
|
||||
@@ -66,29 +64,15 @@ void BluetoothChooser::SetAdapterPresence(AdapterPresence presence) {
|
||||
}
|
||||
|
||||
void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
|
||||
bool idle_state = false;
|
||||
switch (state) {
|
||||
case DiscoveryState::FAILED_TO_START:
|
||||
refreshing_ = false;
|
||||
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
|
||||
break;
|
||||
return;
|
||||
case DiscoveryState::IDLE:
|
||||
refreshing_ = false;
|
||||
if (device_map_.empty()) {
|
||||
auto event = ++num_retries_ > kMaxScanRetries
|
||||
? content::BluetoothChooserEvent::CANCELLED
|
||||
: content::BluetoothChooserEvent::RESCAN;
|
||||
event_handler_.Run(event, "");
|
||||
} else {
|
||||
bool prevent_default = api_web_contents_->Emit(
|
||||
"select-bluetooth-device", GetDeviceList(),
|
||||
base::BindOnce(&OnDeviceChosen, event_handler_));
|
||||
if (!prevent_default) {
|
||||
auto it = device_map_.begin();
|
||||
auto device_id = it->first;
|
||||
event_handler_.Run(content::BluetoothChooserEvent::SELECTED,
|
||||
device_id);
|
||||
}
|
||||
}
|
||||
idle_state = true;
|
||||
break;
|
||||
case DiscoveryState::DISCOVERING:
|
||||
// The first time this state fires is due to a rescan triggering so set a
|
||||
@@ -101,6 +85,18 @@ void BluetoothChooser::ShowDiscoveryState(DiscoveryState state) {
|
||||
}
|
||||
break;
|
||||
}
|
||||
bool prevent_default =
|
||||
api_web_contents_->Emit("select-bluetooth-device", GetDeviceList(),
|
||||
base::BindOnce(&OnDeviceChosen, event_handler_));
|
||||
if (!prevent_default && idle_state) {
|
||||
if (device_map_.empty()) {
|
||||
event_handler_.Run(content::BluetoothChooserEvent::CANCELLED, "");
|
||||
} else {
|
||||
auto it = device_map_.begin();
|
||||
auto device_id = it->first;
|
||||
event_handler_.Run(content::BluetoothChooserEvent::SELECTED, device_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BluetoothChooser::AddOrUpdateDevice(const std::string& device_id,
|
||||
|
||||
@@ -44,7 +44,6 @@ class BluetoothChooser : public content::BluetoothChooser {
|
||||
std::map<std::string, std::u16string> device_map_;
|
||||
api::WebContents* api_web_contents_;
|
||||
EventHandler event_handler_;
|
||||
int num_retries_ = 0;
|
||||
bool refreshing_ = false;
|
||||
bool rescan_ = false;
|
||||
};
|
||||
|
||||
@@ -85,7 +85,10 @@ void NodeStreamLoader::NotifyComplete(int result) {
|
||||
return;
|
||||
}
|
||||
|
||||
client_->OnComplete(network::URLLoaderCompletionStatus(result));
|
||||
network::URLLoaderCompletionStatus status(result);
|
||||
status.completion_time = base::TimeTicks::Now();
|
||||
status.decoded_body_length = bytes_written_;
|
||||
client_->OnComplete(status);
|
||||
delete this;
|
||||
}
|
||||
|
||||
@@ -126,6 +129,8 @@ void NodeStreamLoader::ReadMore() {
|
||||
// Hold the buffer until the write is done.
|
||||
buffer_.Reset(isolate_, buffer);
|
||||
|
||||
bytes_written_ += node::Buffer::Length(buffer);
|
||||
|
||||
// Write buffer to mojo pipe asynchronously.
|
||||
is_reading_ = false;
|
||||
is_writing_ = true;
|
||||
|
||||
@@ -81,6 +81,8 @@ class NodeStreamLoader : public network::mojom::URLLoader {
|
||||
// Whether we are in the middle of a stream.read().
|
||||
bool is_reading_ = false;
|
||||
|
||||
size_t bytes_written_ = 0;
|
||||
|
||||
// When NotifyComplete is called while writing, we will save the result and
|
||||
// quit with it after the write is done.
|
||||
bool ended_ = false;
|
||||
|
||||
@@ -806,18 +806,23 @@ void ProxyingURLLoaderFactory::CreateLoaderAndStart(
|
||||
}
|
||||
|
||||
// Check if user has intercepted this scheme.
|
||||
auto it = intercepted_handlers_.find(request.url.scheme());
|
||||
if (it != intercepted_handlers_.end()) {
|
||||
mojo::PendingRemote<network::mojom::URLLoaderFactory> loader_remote;
|
||||
this->Clone(loader_remote.InitWithNewPipeAndPassReceiver());
|
||||
bool bypass_custom_protocol_handlers =
|
||||
options & kBypassCustomProtocolHandlers;
|
||||
if (!bypass_custom_protocol_handlers) {
|
||||
auto it = intercepted_handlers_.find(request.url.scheme());
|
||||
if (it != intercepted_handlers_.end()) {
|
||||
mojo::PendingRemote<network::mojom::URLLoaderFactory> loader_remote;
|
||||
this->Clone(loader_remote.InitWithNewPipeAndPassReceiver());
|
||||
|
||||
// <scheme, <type, handler>>
|
||||
it->second.second.Run(
|
||||
request, base::BindOnce(&ElectronURLLoaderFactory::StartLoading,
|
||||
std::move(loader), request_id, options, request,
|
||||
std::move(client), traffic_annotation,
|
||||
std::move(loader_remote), it->second.first));
|
||||
return;
|
||||
// <scheme, <type, handler>>
|
||||
it->second.second.Run(
|
||||
request,
|
||||
base::BindOnce(&ElectronURLLoaderFactory::StartLoading,
|
||||
std::move(loader), request_id, options, request,
|
||||
std::move(client), traffic_annotation,
|
||||
std::move(loader_remote), it->second.first));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// The loader of ServiceWorker forbids loading scripts from file:// URLs, and
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
|
||||
namespace electron {
|
||||
|
||||
const uint32_t kBypassCustomProtocolHandlers = 1 << 30;
|
||||
|
||||
// This class is responsible for following tasks when NetworkService is enabled:
|
||||
// 1. handling intercepted protocols;
|
||||
// 2. implementing webRequest module;
|
||||
|
||||
@@ -15,16 +15,21 @@
|
||||
#include "base/values.h"
|
||||
#include "gin/converter.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "net/cert/x509_certificate.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "net/http/http_version.h"
|
||||
#include "net/url_request/redirect_info.h"
|
||||
#include "services/network/public/cpp/data_element.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "services/network/public/cpp/resource_request_body.h"
|
||||
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
|
||||
#include "shell/browser/api/electron_api_data_pipe_holder.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/std_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
namespace gin {
|
||||
@@ -246,6 +251,246 @@ bool Converter<net::HttpRequestHeaders>::FromV8(v8::Isolate* isolate,
|
||||
return true;
|
||||
}
|
||||
|
||||
class ChunkedDataPipeReadableStream
|
||||
: public gin::Wrappable<ChunkedDataPipeReadableStream> {
|
||||
public:
|
||||
static gin::Handle<ChunkedDataPipeReadableStream> Create(
|
||||
v8::Isolate* isolate,
|
||||
network::ResourceRequestBody* request,
|
||||
network::DataElementChunkedDataPipe* data_element) {
|
||||
return gin::CreateHandle(isolate, new ChunkedDataPipeReadableStream(
|
||||
isolate, request, data_element));
|
||||
}
|
||||
|
||||
// gin::Wrappable
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override {
|
||||
return gin::Wrappable<
|
||||
ChunkedDataPipeReadableStream>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("read", &ChunkedDataPipeReadableStream::Read);
|
||||
}
|
||||
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
|
||||
private:
|
||||
ChunkedDataPipeReadableStream(
|
||||
v8::Isolate* isolate,
|
||||
network::ResourceRequestBody* request,
|
||||
network::DataElementChunkedDataPipe* data_element)
|
||||
: isolate_(isolate),
|
||||
resource_request_body_(request),
|
||||
data_element_(data_element),
|
||||
handle_watcher_(FROM_HERE,
|
||||
mojo::SimpleWatcher::ArmingPolicy::MANUAL,
|
||||
base::SequencedTaskRunner::GetCurrentDefault()) {}
|
||||
|
||||
~ChunkedDataPipeReadableStream() override = default;
|
||||
|
||||
int Init() {
|
||||
chunked_data_pipe_getter_.Bind(
|
||||
data_element_->ReleaseChunkedDataPipeGetter());
|
||||
for (auto& element : *resource_request_body_->elements_mutable()) {
|
||||
if (element.type() ==
|
||||
network::mojom::DataElement::Tag::kChunkedDataPipe &&
|
||||
data_element_ == &element.As<network::DataElementChunkedDataPipe>()) {
|
||||
element = network::DataElement(
|
||||
network::DataElementBytes(std::vector<uint8_t>()));
|
||||
break;
|
||||
}
|
||||
}
|
||||
chunked_data_pipe_getter_.set_disconnect_handler(
|
||||
base::BindOnce(&ChunkedDataPipeReadableStream::OnDataPipeGetterClosed,
|
||||
base::Unretained(this)));
|
||||
chunked_data_pipe_getter_->GetSize(
|
||||
base::BindOnce(&ChunkedDataPipeReadableStream::OnSizeReceived,
|
||||
base::Unretained(this)));
|
||||
mojo::ScopedDataPipeProducerHandle data_pipe_producer;
|
||||
mojo::ScopedDataPipeConsumerHandle data_pipe_consumer;
|
||||
MojoResult result =
|
||||
mojo::CreateDataPipe(nullptr, data_pipe_producer, data_pipe_consumer);
|
||||
if (result != MOJO_RESULT_OK)
|
||||
return net::ERR_INSUFFICIENT_RESOURCES;
|
||||
chunked_data_pipe_getter_->StartReading(std::move(data_pipe_producer));
|
||||
data_pipe_ = std::move(data_pipe_consumer);
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> Read(v8::Local<v8::ArrayBufferView> buf) {
|
||||
gin_helper::Promise<int> promise(isolate_);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
int status = ReadInternal(buf);
|
||||
|
||||
if (status == net::ERR_IO_PENDING) {
|
||||
promise_ = std::move(promise);
|
||||
} else {
|
||||
if (status < 0)
|
||||
std::move(promise).RejectWithErrorMessage(net::ErrorToString(status));
|
||||
else
|
||||
std::move(promise).Resolve(status);
|
||||
}
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
int ReadInternal(v8::Local<v8::ArrayBufferView> buf) {
|
||||
if (!data_pipe_)
|
||||
status_ = Init();
|
||||
// If there was an error either passed to the ReadCallback or as a result of
|
||||
// closing the DataPipeGetter pipe, fail the read.
|
||||
if (status_ != net::OK)
|
||||
return status_;
|
||||
|
||||
// Nothing else to do, if the entire body was read.
|
||||
if (size_ && bytes_read_ == *size_) {
|
||||
// This shouldn't be called if the stream was already completed.
|
||||
DCHECK(!is_eof_);
|
||||
|
||||
is_eof_ = true;
|
||||
return net::OK;
|
||||
}
|
||||
|
||||
if (!handle_watcher_.IsWatching()) {
|
||||
handle_watcher_.Watch(
|
||||
data_pipe_.get(),
|
||||
MOJO_HANDLE_SIGNAL_READABLE | MOJO_HANDLE_SIGNAL_PEER_CLOSED,
|
||||
base::BindRepeating(&ChunkedDataPipeReadableStream::OnHandleReadable,
|
||||
base::Unretained(this)));
|
||||
}
|
||||
|
||||
uint32_t num_bytes = buf->ByteLength();
|
||||
if (size_ && num_bytes > *size_ - bytes_read_)
|
||||
num_bytes = *size_ - bytes_read_;
|
||||
MojoResult rv = data_pipe_->ReadData(
|
||||
static_cast<void*>(static_cast<char*>(buf->Buffer()->Data()) +
|
||||
buf->ByteOffset()),
|
||||
&num_bytes, MOJO_READ_DATA_FLAG_NONE);
|
||||
if (rv == MOJO_RESULT_OK) {
|
||||
bytes_read_ += num_bytes;
|
||||
// Not needed for correctness, but this allows the consumer to send the
|
||||
// final chunk and the end of stream message together, for protocols that
|
||||
// allow it.
|
||||
if (size_ && *size_ == bytes_read_)
|
||||
is_eof_ = true;
|
||||
return num_bytes;
|
||||
}
|
||||
|
||||
if (rv == MOJO_RESULT_SHOULD_WAIT) {
|
||||
handle_watcher_.ArmOrNotify();
|
||||
buf_.Reset(isolate_, buf);
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
// The pipe was closed. If the size isn't known yet, could be a success or a
|
||||
// failure.
|
||||
if (!size_) {
|
||||
// Need to keep the buffer around because its presence is used to indicate
|
||||
// that there's a pending UploadDataStream read.
|
||||
buf_.Reset(isolate_, buf);
|
||||
|
||||
handle_watcher_.Cancel();
|
||||
data_pipe_.reset();
|
||||
return net::ERR_IO_PENDING;
|
||||
}
|
||||
|
||||
// |size_| was checked earlier, so if this point is reached, the pipe was
|
||||
// closed before receiving all bytes.
|
||||
DCHECK_LT(bytes_read_, *size_);
|
||||
|
||||
return net::ERR_FAILED;
|
||||
}
|
||||
|
||||
void OnSizeReceived(int32_t status, uint64_t size) {
|
||||
DCHECK(!size_);
|
||||
DCHECK_EQ(net::OK, status_);
|
||||
|
||||
status_ = status;
|
||||
if (status == net::OK) {
|
||||
size_ = size;
|
||||
if (size == bytes_read_) {
|
||||
// Only set this as a final chunk if there's a read in progress. Setting
|
||||
// it asynchronously could result in confusing consumers.
|
||||
if (!buf_.IsEmpty())
|
||||
is_eof_ = true;
|
||||
} else if (size < bytes_read_ ||
|
||||
(!buf_.IsEmpty() && !data_pipe_.is_valid())) {
|
||||
// If more data was received than was expected, or there's a pending
|
||||
// read and data pipe was closed without passing in as many bytes as
|
||||
// expected, the upload can't continue. If there's no pending read but
|
||||
// the pipe was closed, the closure and size difference will be noticed
|
||||
// on the next read attempt.
|
||||
status_ = net::ERR_FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// If this is done, and there's a pending read, complete the pending read.
|
||||
// If there's not a pending read, either |status_| will be reported on the
|
||||
// next read, the file will be marked as done, so ReadInternal() won't be
|
||||
// called again.
|
||||
if (!buf_.IsEmpty() && (is_eof_ || status_ != net::OK)) {
|
||||
// |data_pipe_| isn't needed any more, and if it's still open, a close
|
||||
// pipe message would cause issues, since this class normally only watches
|
||||
// the pipe when there's a pending read.
|
||||
handle_watcher_.Cancel();
|
||||
data_pipe_.reset();
|
||||
// Clear |buf_| as well, so it's only non-null while there's a pending
|
||||
// read.
|
||||
buf_.Reset();
|
||||
chunked_data_pipe_getter_.reset();
|
||||
|
||||
OnReadCompleted(status_);
|
||||
|
||||
// |this| may have been deleted at this point.
|
||||
}
|
||||
}
|
||||
|
||||
void OnHandleReadable(MojoResult result) {
|
||||
DCHECK(!buf_.IsEmpty());
|
||||
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
|
||||
v8::Local<v8::ArrayBufferView> buf = buf_.Get(isolate_);
|
||||
buf_.Reset();
|
||||
|
||||
int rv = ReadInternal(buf);
|
||||
|
||||
if (rv != net::ERR_IO_PENDING)
|
||||
OnReadCompleted(rv);
|
||||
|
||||
// |this| may have been deleted at this point.
|
||||
}
|
||||
|
||||
void OnReadCompleted(int result) {
|
||||
if (result < 0)
|
||||
std::move(promise_).RejectWithErrorMessage(net::ErrorToString(result));
|
||||
else
|
||||
std::move(promise_).Resolve(result);
|
||||
}
|
||||
|
||||
void OnDataPipeGetterClosed() {
|
||||
// If the size hasn't been received yet, treat this as receiving an error.
|
||||
// Otherwise, this will only be a problem if/when InitInternal() tries to
|
||||
// start reading again, so do nothing.
|
||||
if (status_ == net::OK && !size_)
|
||||
OnSizeReceived(net::ERR_FAILED, 0);
|
||||
}
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
int status_ = net::OK;
|
||||
scoped_refptr<network::ResourceRequestBody> resource_request_body_;
|
||||
network::DataElementChunkedDataPipe* data_element_;
|
||||
mojo::Remote<network::mojom::ChunkedDataPipeGetter> chunked_data_pipe_getter_;
|
||||
mojo::ScopedDataPipeConsumerHandle data_pipe_;
|
||||
mojo::SimpleWatcher handle_watcher_;
|
||||
absl::optional<uint64_t> size_;
|
||||
uint64_t bytes_read_ = 0;
|
||||
bool is_eof_ = false;
|
||||
v8::Global<v8::ArrayBufferView> buf_;
|
||||
gin_helper::Promise<int> promise_;
|
||||
};
|
||||
gin::WrapperInfo ChunkedDataPipeReadableStream::kWrapperInfo = {
|
||||
gin::kEmbedderNativeGin};
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> Converter<network::ResourceRequestBody>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
@@ -288,6 +533,21 @@ v8::Local<v8::Value> Converter<network::ResourceRequestBody>::ToV8(
|
||||
upload_data.Set("dataPipe", holder);
|
||||
break;
|
||||
}
|
||||
case network::mojom::DataElement::Tag::kChunkedDataPipe: {
|
||||
upload_data.Set("type", "stream");
|
||||
// ReleaseChunkedDataPipeGetter mutates the element, but unfortunately
|
||||
// gin converters are only allowed const references, so we need to cast
|
||||
// off the const here.
|
||||
auto& mutable_element =
|
||||
const_cast<network::DataElementChunkedDataPipe&>(
|
||||
element.As<network::DataElementChunkedDataPipe>());
|
||||
upload_data.Set(
|
||||
"body",
|
||||
ChunkedDataPipeReadableStream::Create(
|
||||
isolate, const_cast<network::ResourceRequestBody*>(&val),
|
||||
&mutable_element));
|
||||
break;
|
||||
}
|
||||
default:
|
||||
NOTREACHED() << "Found unsupported data element";
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@ PromiseBase::PromiseBase(v8::Isolate* isolate,
|
||||
context_(isolate, isolate->GetCurrentContext()),
|
||||
resolver_(isolate, handle) {}
|
||||
|
||||
PromiseBase::PromiseBase() : isolate_(nullptr) {}
|
||||
|
||||
PromiseBase::PromiseBase(PromiseBase&&) = default;
|
||||
|
||||
PromiseBase::~PromiseBase() = default;
|
||||
|
||||
@@ -30,6 +30,7 @@ class PromiseBase {
|
||||
public:
|
||||
explicit PromiseBase(v8::Isolate* isolate);
|
||||
PromiseBase(v8::Isolate* isolate, v8::Local<v8::Promise::Resolver> handle);
|
||||
PromiseBase();
|
||||
~PromiseBase();
|
||||
|
||||
// disable copy
|
||||
|
||||
@@ -96,9 +96,9 @@ bool IsPlainObject(const v8::Local<v8::Value>& object) {
|
||||
object->IsWeakMap() || object->IsWeakSet() ||
|
||||
object->IsArrayBuffer() || object->IsArrayBufferView() ||
|
||||
object->IsArray() || object->IsDataView() ||
|
||||
object->IsSharedArrayBuffer() || object->IsProxy() ||
|
||||
object->IsSharedArrayBuffer() || object->IsGeneratorObject() ||
|
||||
object->IsWasmModuleObject() || object->IsWasmMemoryObject() ||
|
||||
object->IsModuleNamespaceObject());
|
||||
object->IsModuleNamespaceObject() || object->IsProxy());
|
||||
}
|
||||
|
||||
bool IsPlainArray(const v8::Local<v8::Value>& arr) {
|
||||
|
||||
@@ -6,7 +6,7 @@ import * as qs from 'querystring';
|
||||
import * as http from 'http';
|
||||
import * as os from 'os';
|
||||
import { AddressInfo } from 'net';
|
||||
import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents, WebFrameMain } from 'electron/main';
|
||||
import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, webFrameMain, session, WebContents, WebFrameMain } from 'electron/main';
|
||||
|
||||
import { emittedUntil, emittedNTimes } from './lib/events-helpers';
|
||||
import { ifit, ifdescribe, defer, listen } from './lib/spec-helpers';
|
||||
@@ -568,6 +568,205 @@ describe('BrowserWindow module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('will-frame-navigate event', () => {
|
||||
let server = null as unknown as http.Server;
|
||||
let url = null as unknown as string;
|
||||
before((done) => {
|
||||
server = http.createServer((req, res) => {
|
||||
if (req.url === '/navigate-top') {
|
||||
res.end('<a target=_top href="/">navigate _top</a>');
|
||||
} else if (req.url === '/navigate-iframe') {
|
||||
res.end('<a href="/test">navigate iframe</a>');
|
||||
} else if (req.url === '/navigate-iframe?navigated') {
|
||||
res.end('Successfully navigated');
|
||||
} else if (req.url === '/navigate-iframe-immediately') {
|
||||
res.end(`
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
location.href += '?navigated'
|
||||
</script>
|
||||
`);
|
||||
} else if (req.url === '/navigate-iframe-immediately?navigated') {
|
||||
res.end('Successfully navigated');
|
||||
} else {
|
||||
res.end('');
|
||||
}
|
||||
});
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
url = `http://127.0.0.1:${(server.address() as AddressInfo).port}/`;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
after(() => {
|
||||
server.close();
|
||||
});
|
||||
|
||||
it('allows the window to be closed from the event listener', (done) => {
|
||||
w.webContents.once('will-frame-navigate', () => {
|
||||
w.close();
|
||||
done();
|
||||
});
|
||||
w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html'));
|
||||
});
|
||||
|
||||
it('can be prevented', (done) => {
|
||||
let willNavigate = false;
|
||||
w.webContents.once('will-frame-navigate', (e) => {
|
||||
willNavigate = true;
|
||||
e.preventDefault();
|
||||
});
|
||||
w.webContents.on('did-stop-loading', () => {
|
||||
if (willNavigate) {
|
||||
// i.e. it shouldn't have had '?navigated' appended to it.
|
||||
try {
|
||||
expect(w.webContents.getURL().endsWith('will-navigate.html')).to.be.true();
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
w.loadFile(path.join(fixtures, 'pages', 'will-navigate.html'));
|
||||
});
|
||||
|
||||
it('can be prevented when navigating subframe', (done) => {
|
||||
let willNavigate = false;
|
||||
w.webContents.on('did-frame-navigate', (_event, _url, _httpResponseCode, _httpStatusText, isMainFrame, frameProcessId, frameRoutingId) => {
|
||||
if (isMainFrame) return;
|
||||
|
||||
w.webContents.once('will-frame-navigate', (e) => {
|
||||
willNavigate = true;
|
||||
e.preventDefault();
|
||||
});
|
||||
|
||||
w.webContents.on('did-stop-loading', () => {
|
||||
const frame = webFrameMain.fromId(frameProcessId, frameRoutingId);
|
||||
expect(frame).to.not.be.undefined();
|
||||
if (willNavigate) {
|
||||
// i.e. it shouldn't have had '?navigated' appended to it.
|
||||
try {
|
||||
expect(frame!.url.endsWith('/navigate-iframe-immediately')).to.be.true();
|
||||
done();
|
||||
} catch (e) {
|
||||
done(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
w.loadURL(`data:text/html,<iframe src="http://127.0.0.1:${(server.address() as AddressInfo).port}/navigate-iframe-immediately"></iframe>`);
|
||||
});
|
||||
|
||||
it('is triggered when navigating from file: to http:', async () => {
|
||||
await w.loadFile(path.join(fixtures, 'api', 'blank.html'));
|
||||
w.webContents.executeJavaScript(`location.href = ${JSON.stringify(url)}`);
|
||||
const navigatedTo = await new Promise(resolve => {
|
||||
w.webContents.once('will-frame-navigate', (e) => {
|
||||
e.preventDefault();
|
||||
resolve(e.url);
|
||||
});
|
||||
});
|
||||
expect(navigatedTo).to.equal(url);
|
||||
expect(w.webContents.getURL()).to.match(/^file:/);
|
||||
});
|
||||
|
||||
it('is triggered when navigating from about:blank to http:', async () => {
|
||||
await w.loadURL('about:blank');
|
||||
w.webContents.executeJavaScript(`location.href = ${JSON.stringify(url)}`);
|
||||
const navigatedTo = await new Promise(resolve => {
|
||||
w.webContents.once('will-frame-navigate', (e) => {
|
||||
e.preventDefault();
|
||||
resolve(e.url);
|
||||
});
|
||||
});
|
||||
expect(navigatedTo).to.equal(url);
|
||||
expect(w.webContents.getURL()).to.equal('about:blank');
|
||||
});
|
||||
|
||||
it('is triggered when a cross-origin iframe navigates _top', async () => {
|
||||
await w.loadURL(`data:text/html,<iframe src="http://127.0.0.1:${(server.address() as AddressInfo).port}/navigate-top"></iframe>`);
|
||||
await setTimeout(1000);
|
||||
|
||||
let willFrameNavigateEmitted = false;
|
||||
let isMainFrameValue;
|
||||
w.webContents.on('will-frame-navigate', (event) => {
|
||||
willFrameNavigateEmitted = true;
|
||||
isMainFrameValue = event.isMainFrame;
|
||||
});
|
||||
const didNavigatePromise = once(w.webContents, 'did-navigate');
|
||||
|
||||
w.webContents.debugger.attach('1.1');
|
||||
const targets = await w.webContents.debugger.sendCommand('Target.getTargets');
|
||||
const iframeTarget = targets.targetInfos.find((t: any) => t.type === 'iframe');
|
||||
const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', {
|
||||
targetId: iframeTarget.targetId,
|
||||
flatten: true
|
||||
});
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
|
||||
await didNavigatePromise;
|
||||
|
||||
expect(willFrameNavigateEmitted).to.be.true();
|
||||
expect(isMainFrameValue).to.be.true();
|
||||
});
|
||||
|
||||
it('is triggered when a cross-origin iframe navigates itself', async () => {
|
||||
await w.loadURL(`data:text/html,<iframe src="http://127.0.0.1:${(server.address() as AddressInfo).port}/navigate-iframe"></iframe>`);
|
||||
await setTimeout(1000);
|
||||
|
||||
let willNavigateEmitted = false;
|
||||
let isMainFrameValue;
|
||||
w.webContents.on('will-frame-navigate', (event) => {
|
||||
willNavigateEmitted = true;
|
||||
isMainFrameValue = event.isMainFrame;
|
||||
});
|
||||
const didNavigatePromise = once(w.webContents, 'did-frame-navigate');
|
||||
|
||||
w.webContents.debugger.attach('1.1');
|
||||
const targets = await w.webContents.debugger.sendCommand('Target.getTargets');
|
||||
const iframeTarget = targets.targetInfos.find((t: any) => t.type === 'iframe');
|
||||
const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', {
|
||||
targetId: iframeTarget.targetId,
|
||||
flatten: true
|
||||
});
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
|
||||
await didNavigatePromise;
|
||||
|
||||
expect(willNavigateEmitted).to.be.true();
|
||||
expect(isMainFrameValue).to.be.false();
|
||||
});
|
||||
|
||||
it('can cancel when a cross-origin iframe navigates itself', async () => {
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('will-redirect event', () => {
|
||||
let server: http.Server;
|
||||
let url: string;
|
||||
@@ -652,6 +851,179 @@ describe('BrowserWindow module', () => {
|
||||
w.loadURL(`${url}/navigate-302`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('ordering', () => {
|
||||
let server = null as unknown as http.Server;
|
||||
let url = null as unknown as string;
|
||||
const navigationEvents = [
|
||||
'did-start-navigation',
|
||||
'did-navigate-in-page',
|
||||
'will-frame-navigate',
|
||||
'will-navigate',
|
||||
'will-redirect',
|
||||
'did-redirect-navigation',
|
||||
'did-frame-navigate',
|
||||
'did-navigate'
|
||||
];
|
||||
before((done) => {
|
||||
server = http.createServer((req, res) => {
|
||||
if (req.url === '/navigate') {
|
||||
res.end('<a href="/">navigate</a>');
|
||||
} else if (req.url === '/redirect') {
|
||||
res.end('<a href="/redirect2">redirect</a>');
|
||||
} else if (req.url === '/redirect2') {
|
||||
res.statusCode = 302;
|
||||
res.setHeader('location', url);
|
||||
res.end();
|
||||
} else if (req.url === '/in-page') {
|
||||
res.end('<a href="#in-page">redirect</a><div id="in-page"></div>');
|
||||
} else {
|
||||
res.end('');
|
||||
}
|
||||
});
|
||||
server.listen(0, '127.0.0.1', () => {
|
||||
url = `http://127.0.0.1:${(server.address() as AddressInfo).port}/`;
|
||||
done();
|
||||
});
|
||||
});
|
||||
it('for initial navigation, event order is consistent', async () => {
|
||||
const firedEvents: string[] = [];
|
||||
const expectedEventOrder = [
|
||||
'did-start-navigation',
|
||||
'did-frame-navigate',
|
||||
'did-navigate'
|
||||
];
|
||||
const allEvents = Promise.all(navigationEvents.map(event =>
|
||||
once(w.webContents, event).then(() => firedEvents.push(event))
|
||||
));
|
||||
const timeout = setTimeout(1000);
|
||||
w.loadURL(url);
|
||||
await Promise.race([allEvents, timeout]);
|
||||
expect(firedEvents).to.deep.equal(expectedEventOrder);
|
||||
});
|
||||
|
||||
it('for second navigation, event order is consistent', async () => {
|
||||
const firedEvents: string[] = [];
|
||||
const expectedEventOrder = [
|
||||
'did-start-navigation',
|
||||
'will-frame-navigate',
|
||||
'will-navigate',
|
||||
'did-frame-navigate',
|
||||
'did-navigate'
|
||||
];
|
||||
w.loadURL(`${url}navigate`);
|
||||
await once(w.webContents, 'did-navigate');
|
||||
await setTimeout(1000);
|
||||
navigationEvents.forEach(event =>
|
||||
once(w.webContents, event).then(() => firedEvents.push(event))
|
||||
);
|
||||
const navigationFinished = once(w.webContents, 'did-navigate');
|
||||
w.webContents.debugger.attach('1.1');
|
||||
const targets = await w.webContents.debugger.sendCommand('Target.getTargets');
|
||||
const pageTarget = targets.targetInfos.find((t: any) => t.type === 'page');
|
||||
const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', {
|
||||
targetId: pageTarget.targetId,
|
||||
flatten: true
|
||||
});
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await navigationFinished;
|
||||
expect(firedEvents).to.deep.equal(expectedEventOrder);
|
||||
});
|
||||
|
||||
it('when navigating with redirection, event order is consistent', async () => {
|
||||
const firedEvents: string[] = [];
|
||||
const expectedEventOrder = [
|
||||
'did-start-navigation',
|
||||
'will-frame-navigate',
|
||||
'will-navigate',
|
||||
'will-redirect',
|
||||
'did-redirect-navigation',
|
||||
'did-frame-navigate',
|
||||
'did-navigate'
|
||||
];
|
||||
w.loadURL(`${url}redirect`);
|
||||
await once(w.webContents, 'did-navigate');
|
||||
await setTimeout(1000);
|
||||
navigationEvents.forEach(event =>
|
||||
once(w.webContents, event).then(() => firedEvents.push(event))
|
||||
);
|
||||
const navigationFinished = once(w.webContents, 'did-navigate');
|
||||
w.webContents.debugger.attach('1.1');
|
||||
const targets = await w.webContents.debugger.sendCommand('Target.getTargets');
|
||||
const pageTarget = targets.targetInfos.find((t: any) => t.type === 'page');
|
||||
const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', {
|
||||
targetId: pageTarget.targetId,
|
||||
flatten: true
|
||||
});
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await navigationFinished;
|
||||
expect(firedEvents).to.deep.equal(expectedEventOrder);
|
||||
});
|
||||
|
||||
it('when navigating in-page, event order is consistent', async () => {
|
||||
const firedEvents: string[] = [];
|
||||
const expectedEventOrder = [
|
||||
'did-start-navigation',
|
||||
'did-navigate-in-page'
|
||||
];
|
||||
w.loadURL(`${url}in-page`);
|
||||
await once(w.webContents, 'did-navigate');
|
||||
await setTimeout(1000);
|
||||
navigationEvents.forEach(event =>
|
||||
once(w.webContents, event).then(() => firedEvents.push(event))
|
||||
);
|
||||
const navigationFinished = once(w.webContents, 'did-navigate-in-page');
|
||||
w.webContents.debugger.attach('1.1');
|
||||
const targets = await w.webContents.debugger.sendCommand('Target.getTargets');
|
||||
const pageTarget = targets.targetInfos.find((t: any) => t.type === 'page');
|
||||
const { sessionId } = await w.webContents.debugger.sendCommand('Target.attachToTarget', {
|
||||
targetId: pageTarget.targetId,
|
||||
flatten: true
|
||||
});
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mousePressed',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await w.webContents.debugger.sendCommand('Input.dispatchMouseEvent', {
|
||||
type: 'mouseReleased',
|
||||
x: 10,
|
||||
y: 10,
|
||||
clickCount: 1,
|
||||
button: 'left'
|
||||
}, sessionId);
|
||||
await navigationFinished;
|
||||
expect(firedEvents).to.deep.equal(expectedEventOrder);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -355,6 +355,31 @@ describe('ipc module', () => {
|
||||
expect(port2).not.to.be.null();
|
||||
});
|
||||
|
||||
it('throws an error when an invalid parameter is sent to postMessage', () => {
|
||||
const { port1 } = new MessageChannelMain();
|
||||
|
||||
expect(() => {
|
||||
const buffer = new ArrayBuffer(10) as any;
|
||||
port1.postMessage(null, [buffer]);
|
||||
}).to.throw(/Port at index 0 is not a valid port/);
|
||||
|
||||
expect(() => {
|
||||
port1.postMessage(null, ['1' as any]);
|
||||
}).to.throw(/Port at index 0 is not a valid port/);
|
||||
|
||||
expect(() => {
|
||||
port1.postMessage(null, [new Date() as any]);
|
||||
}).to.throw(/Port at index 0 is not a valid port/);
|
||||
});
|
||||
|
||||
it('throws when postMessage transferables contains the source port', () => {
|
||||
const { port1 } = new MessageChannelMain();
|
||||
|
||||
expect(() => {
|
||||
port1.postMessage(null, [port1]);
|
||||
}).to.throw(/Port at index 0 contains the source port./);
|
||||
});
|
||||
|
||||
it('can send messages within the process', async () => {
|
||||
const { port1, port2 } = new MessageChannelMain();
|
||||
port2.postMessage('hello');
|
||||
@@ -423,7 +448,7 @@ describe('ipc module', () => {
|
||||
const { port1 } = new MessageChannelMain();
|
||||
expect(() => {
|
||||
port1.postMessage(null, [null] as any);
|
||||
}).to.throw(/conversion failure/);
|
||||
}).to.throw(/Port at index 0 is not a valid port/);
|
||||
});
|
||||
|
||||
it('throws when passing duplicate ports', () => {
|
||||
|
||||
@@ -1438,6 +1438,18 @@ describe('net module', () => {
|
||||
expect(requestIsRedirected).to.be.true('The server should receive a request to the forward URL');
|
||||
expect(requestIsIntercepted).to.be.true('The request should be intercepted by the webRequest module');
|
||||
});
|
||||
|
||||
it('triggers webRequest handlers when bypassCustomProtocolHandlers', async () => {
|
||||
let webRequestDetails: Electron.OnBeforeRequestListenerDetails | null = null;
|
||||
const serverUrl = await respondOnce.toSingleURL((req, res) => res.end('hi'));
|
||||
session.defaultSession.webRequest.onBeforeRequest((details, cb) => {
|
||||
webRequestDetails = details;
|
||||
cb({});
|
||||
});
|
||||
const body = await net.fetch(serverUrl, { bypassCustomProtocolHandlers: true }).then(r => r.text());
|
||||
expect(body).to.equal('hi');
|
||||
expect(webRequestDetails).to.have.property('url', serverUrl);
|
||||
});
|
||||
});
|
||||
|
||||
it('should throw when calling getHeader without a name', () => {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { expect } from 'chai';
|
||||
import { v4 } from 'uuid';
|
||||
import { protocol, webContents, WebContents, session, BrowserWindow, ipcMain } from 'electron/main';
|
||||
import { protocol, webContents, WebContents, session, BrowserWindow, ipcMain, net } from 'electron/main';
|
||||
import * as ChildProcess from 'child_process';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import * as http from 'http';
|
||||
import * as fs from 'fs';
|
||||
import * as qs from 'querystring';
|
||||
@@ -10,7 +11,7 @@ import * as stream from 'stream';
|
||||
import { EventEmitter, once } from 'events';
|
||||
import { closeAllWindows, closeWindow } from './lib/window-helpers';
|
||||
import { WebmGenerator } from './lib/video-helpers';
|
||||
import { listen } from './lib/spec-helpers';
|
||||
import { listen, defer, ifit } from './lib/spec-helpers';
|
||||
import { setTimeout } from 'timers/promises';
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
@@ -34,7 +35,9 @@ const postData = {
|
||||
};
|
||||
|
||||
function getStream (chunkSize = text.length, data: Buffer | string = text) {
|
||||
const body = new stream.PassThrough();
|
||||
// allowHalfOpen required, otherwise Readable.toWeb gets confused and thinks
|
||||
// the stream isn't done when the readable half ends.
|
||||
const body = new stream.PassThrough({ allowHalfOpen: false });
|
||||
|
||||
async function sendChunks () {
|
||||
await setTimeout(0); // the stream protocol API breaks if you send data immediately.
|
||||
@@ -54,9 +57,12 @@ function getStream (chunkSize = text.length, data: Buffer | string = text) {
|
||||
sendChunks();
|
||||
return body;
|
||||
}
|
||||
function getWebStream (chunkSize = text.length, data: Buffer | string = text): ReadableStream<ArrayBufferView> {
|
||||
return stream.Readable.toWeb(getStream(chunkSize, data)) as ReadableStream<ArrayBufferView>;
|
||||
}
|
||||
|
||||
// A promise that can be resolved externally.
|
||||
function defer (): Promise<any> & {resolve: Function, reject: Function} {
|
||||
function deferPromise (): Promise<any> & {resolve: Function, reject: Function} {
|
||||
let promiseResolve: Function = null as unknown as Function;
|
||||
let promiseReject: Function = null as unknown as Function;
|
||||
const promise: any = new Promise((resolve, reject) => {
|
||||
@@ -860,7 +866,7 @@ describe('protocol module', () => {
|
||||
});
|
||||
|
||||
it('can have fetch working in it', async () => {
|
||||
const requestReceived = defer();
|
||||
const requestReceived = deferPromise();
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end();
|
||||
server.close();
|
||||
@@ -1093,4 +1099,422 @@ describe('protocol module', () => {
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
describe('handle', () => {
|
||||
afterEach(closeAllWindows);
|
||||
|
||||
it('receives requests to a custom scheme', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response('hello ' + req.url));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const resp = await net.fetch('test-scheme://foo');
|
||||
expect(resp.status).to.equal(200);
|
||||
});
|
||||
|
||||
it('can be unhandled', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response('hello ' + req.url));
|
||||
defer(() => {
|
||||
try {
|
||||
// In case of failure, make sure we unhandle. But we should succeed
|
||||
// :)
|
||||
protocol.unhandle('test-scheme');
|
||||
} catch (_ignored) { /* ignore */ }
|
||||
});
|
||||
const resp1 = await net.fetch('test-scheme://foo');
|
||||
expect(resp1.status).to.equal(200);
|
||||
protocol.unhandle('test-scheme');
|
||||
await expect(net.fetch('test-scheme://foo')).to.eventually.be.rejectedWith(/ERR_UNKNOWN_URL_SCHEME/);
|
||||
});
|
||||
|
||||
it('receives requests to an existing scheme', async () => {
|
||||
protocol.handle('https', (req) => new Response('hello ' + req.url));
|
||||
defer(() => { protocol.unhandle('https'); });
|
||||
const body = await net.fetch('https://foo').then(r => r.text());
|
||||
expect(body).to.equal('hello https://foo/');
|
||||
});
|
||||
|
||||
it('receives requests to an existing scheme when navigating', async () => {
|
||||
protocol.handle('https', (req) => new Response('hello ' + req.url));
|
||||
defer(() => { protocol.unhandle('https'); });
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('https://localhost');
|
||||
expect(await w.webContents.executeJavaScript('document.body.textContent')).to.equal('hello https://localhost/');
|
||||
});
|
||||
|
||||
it('can send buffer body', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(Buffer.from('hello ' + req.url)));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const body = await net.fetch('test-scheme://foo').then(r => r.text());
|
||||
expect(body).to.equal('hello test-scheme://foo');
|
||||
});
|
||||
|
||||
it('can send stream body', async () => {
|
||||
protocol.handle('test-scheme', () => new Response(getWebStream()));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const body = await net.fetch('test-scheme://foo').then(r => r.text());
|
||||
expect(body).to.equal(text);
|
||||
});
|
||||
|
||||
it('accepts urls with no hostname in non-standard schemes', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(req.url));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
{
|
||||
const body = await net.fetch('test-scheme://foo').then(r => r.text());
|
||||
expect(body).to.equal('test-scheme://foo');
|
||||
}
|
||||
{
|
||||
const body = await net.fetch('test-scheme:///foo').then(r => r.text());
|
||||
expect(body).to.equal('test-scheme:///foo');
|
||||
}
|
||||
{
|
||||
const body = await net.fetch('test-scheme://').then(r => r.text());
|
||||
expect(body).to.equal('test-scheme://');
|
||||
}
|
||||
});
|
||||
|
||||
it('accepts urls with a port-like component in non-standard schemes', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(req.url));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
{
|
||||
const body = await net.fetch('test-scheme://foo:30').then(r => r.text());
|
||||
expect(body).to.equal('test-scheme://foo:30');
|
||||
}
|
||||
});
|
||||
|
||||
it('normalizes urls in standard schemes', async () => {
|
||||
// NB. 'app' is registered as a standard scheme in test setup.
|
||||
protocol.handle('app', (req) => new Response(req.url));
|
||||
defer(() => { protocol.unhandle('app'); });
|
||||
{
|
||||
const body = await net.fetch('app://foo').then(r => r.text());
|
||||
expect(body).to.equal('app://foo/');
|
||||
}
|
||||
{
|
||||
const body = await net.fetch('app:///foo').then(r => r.text());
|
||||
expect(body).to.equal('app://foo/');
|
||||
}
|
||||
// NB. 'app' is registered with the default scheme type of 'host'.
|
||||
{
|
||||
const body = await net.fetch('app://foo:1234').then(r => r.text());
|
||||
expect(body).to.equal('app://foo/');
|
||||
}
|
||||
await expect(net.fetch('app://')).to.be.rejectedWith('Invalid URL');
|
||||
});
|
||||
|
||||
it('fails on URLs with a username', async () => {
|
||||
// NB. 'app' is registered as a standard scheme in test setup.
|
||||
protocol.handle('http', (req) => new Response(req.url));
|
||||
defer(() => { protocol.unhandle('http'); });
|
||||
await expect(contents.loadURL('http://x@foo:1234')).to.be.rejectedWith(/ERR_UNEXPECTED/);
|
||||
});
|
||||
|
||||
it('normalizes http urls', async () => {
|
||||
protocol.handle('http', (req) => new Response(req.url));
|
||||
defer(() => { protocol.unhandle('http'); });
|
||||
{
|
||||
const body = await net.fetch('http://foo').then(r => r.text());
|
||||
expect(body).to.equal('http://foo/');
|
||||
}
|
||||
});
|
||||
|
||||
it('can send errors', async () => {
|
||||
protocol.handle('test-scheme', () => Response.error());
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
await expect(net.fetch('test-scheme://foo')).to.eventually.be.rejectedWith('net::ERR_FAILED');
|
||||
});
|
||||
|
||||
it('handles a synchronous error in the handler', async () => {
|
||||
protocol.handle('test-scheme', () => { throw new Error('test'); });
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
await expect(net.fetch('test-scheme://foo')).to.be.rejectedWith('net::ERR_UNEXPECTED');
|
||||
});
|
||||
|
||||
it('handles an asynchronous error in the handler', async () => {
|
||||
protocol.handle('test-scheme', () => Promise.reject(new Error('rejected promise')));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
await expect(net.fetch('test-scheme://foo')).to.be.rejectedWith('net::ERR_UNEXPECTED');
|
||||
});
|
||||
|
||||
it('correctly sets statusCode', async () => {
|
||||
protocol.handle('test-scheme', () => new Response(null, { status: 201 }));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const resp = await net.fetch('test-scheme://foo');
|
||||
expect(resp.status).to.equal(201);
|
||||
});
|
||||
|
||||
it('correctly sets content-type and charset', async () => {
|
||||
protocol.handle('test-scheme', () => new Response(null, { headers: { 'content-type': 'text/html; charset=testcharset' } }));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const resp = await net.fetch('test-scheme://foo');
|
||||
expect(resp.headers.get('content-type')).to.equal('text/html; charset=testcharset');
|
||||
});
|
||||
|
||||
it('can forward to http', async () => {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end(text);
|
||||
});
|
||||
defer(() => { server.close(); });
|
||||
const { url } = await listen(server);
|
||||
|
||||
protocol.handle('test-scheme', () => net.fetch(url));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const body = await net.fetch('test-scheme://foo').then(r => r.text());
|
||||
expect(body).to.equal(text);
|
||||
});
|
||||
|
||||
it('can forward an http request with headers', async () => {
|
||||
const server = http.createServer((req, res) => {
|
||||
res.setHeader('foo', 'bar');
|
||||
res.end(text);
|
||||
});
|
||||
defer(() => { server.close(); });
|
||||
const { url } = await listen(server);
|
||||
|
||||
protocol.handle('test-scheme', (req) => net.fetch(url, { headers: req.headers }));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
|
||||
const resp = await net.fetch('test-scheme://foo');
|
||||
expect(resp.headers.get('foo')).to.equal('bar');
|
||||
});
|
||||
|
||||
it('can forward to file', async () => {
|
||||
protocol.handle('test-scheme', () => net.fetch(url.pathToFileURL(path.join(__dirname, 'fixtures', 'hello.txt')).toString()));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
|
||||
const body = await net.fetch('test-scheme://foo').then(r => r.text());
|
||||
expect(body.trimEnd()).to.equal('hello world');
|
||||
});
|
||||
|
||||
it('can receive simple request body', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(req.body));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const body = await net.fetch('test-scheme://foo', {
|
||||
method: 'POST',
|
||||
body: 'foobar'
|
||||
}).then(r => r.text());
|
||||
expect(body).to.equal('foobar');
|
||||
});
|
||||
|
||||
it('can receive stream request body', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(req.body));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
const body = await net.fetch('test-scheme://foo', {
|
||||
method: 'POST',
|
||||
body: getWebStream(),
|
||||
duplex: 'half' // https://github.com/microsoft/TypeScript/issues/53157
|
||||
} as any).then(r => r.text());
|
||||
expect(body).to.equal(text);
|
||||
});
|
||||
|
||||
it('can receive multi-part postData from loadURL', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(req.body));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
await contents.loadURL('test-scheme://foo', { postData: [{ type: 'rawData', bytes: Buffer.from('a') }, { type: 'rawData', bytes: Buffer.from('b') }] });
|
||||
expect(await contents.executeJavaScript('document.documentElement.textContent')).to.equal('ab');
|
||||
});
|
||||
|
||||
it('can receive file postData from loadURL', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(req.body));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
await contents.loadURL('test-scheme://foo', { postData: [{ type: 'file', filePath: path.join(fixturesPath, 'hello.txt'), length: 'hello world\n'.length, offset: 0, modificationTime: 0 }] });
|
||||
expect(await contents.executeJavaScript('document.documentElement.textContent')).to.equal('hello world\n');
|
||||
});
|
||||
|
||||
it('can receive file postData from a form', async () => {
|
||||
protocol.handle('test-scheme', (req) => new Response(req.body));
|
||||
defer(() => { protocol.unhandle('test-scheme'); });
|
||||
await contents.loadURL('data:text/html,<form action="test-scheme://foo" method=POST enctype="multipart/form-data"><input name=foo type=file>');
|
||||
const { debugger: dbg } = contents;
|
||||
dbg.attach();
|
||||
const { root } = await dbg.sendCommand('DOM.getDocument');
|
||||
const { nodeId: fileInputNodeId } = await dbg.sendCommand('DOM.querySelector', { nodeId: root.nodeId, selector: 'input' });
|
||||
await dbg.sendCommand('DOM.setFileInputFiles', {
|
||||
nodeId: fileInputNodeId,
|
||||
files: [
|
||||
path.join(fixturesPath, 'hello.txt')
|
||||
]
|
||||
});
|
||||
const navigated = once(contents, 'did-finish-load');
|
||||
await contents.executeJavaScript('document.querySelector("form").submit()');
|
||||
await navigated;
|
||||
expect(await contents.executeJavaScript('document.documentElement.textContent')).to.match(/------WebKitFormBoundary.*\nContent-Disposition: form-data; name="foo"; filename="hello.txt"\nContent-Type: text\/plain\n\nhello world\n\n------WebKitFormBoundary.*--\n/);
|
||||
});
|
||||
|
||||
it('can receive streaming fetch upload', async () => {
|
||||
protocol.handle('no-cors', (req) => new Response(req.body));
|
||||
defer(() => { protocol.unhandle('no-cors'); });
|
||||
await contents.loadURL('no-cors://foo');
|
||||
const fetchBodyResult = await contents.executeJavaScript(`
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue('hello world');
|
||||
controller.close();
|
||||
},
|
||||
}).pipeThrough(new TextEncoderStream());
|
||||
fetch(location.href, {method: 'POST', body: stream, duplex: 'half'}).then(x => x.text())
|
||||
`);
|
||||
expect(fetchBodyResult).to.equal('hello world');
|
||||
});
|
||||
|
||||
it('can receive streaming fetch upload when a webRequest handler is present', async () => {
|
||||
session.defaultSession.webRequest.onBeforeRequest((details, cb) => {
|
||||
console.log('webRequest', details.url, details.method);
|
||||
cb({});
|
||||
});
|
||||
defer(() => {
|
||||
session.defaultSession.webRequest.onBeforeRequest(null);
|
||||
});
|
||||
|
||||
protocol.handle('no-cors', (req) => {
|
||||
console.log('handle', req.url, req.method);
|
||||
return new Response(req.body);
|
||||
});
|
||||
defer(() => { protocol.unhandle('no-cors'); });
|
||||
await contents.loadURL('no-cors://foo');
|
||||
const fetchBodyResult = await contents.executeJavaScript(`
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue('hello world');
|
||||
controller.close();
|
||||
},
|
||||
}).pipeThrough(new TextEncoderStream());
|
||||
fetch(location.href, {method: 'POST', body: stream, duplex: 'half'}).then(x => x.text())
|
||||
`);
|
||||
expect(fetchBodyResult).to.equal('hello world');
|
||||
});
|
||||
|
||||
it('can receive an error from streaming fetch upload', async () => {
|
||||
protocol.handle('no-cors', (req) => new Response(req.body));
|
||||
defer(() => { protocol.unhandle('no-cors'); });
|
||||
await contents.loadURL('no-cors://foo');
|
||||
const fetchBodyResult = await contents.executeJavaScript(`
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.error('test')
|
||||
},
|
||||
});
|
||||
fetch(location.href, {method: 'POST', body: stream, duplex: 'half'}).then(x => x.text()).catch(err => err)
|
||||
`);
|
||||
expect(fetchBodyResult).to.be.an.instanceOf(Error);
|
||||
});
|
||||
|
||||
it('gets an error from streaming fetch upload when the renderer dies', async () => {
|
||||
let gotRequest: Function;
|
||||
const receivedRequest = new Promise<Request>(resolve => { gotRequest = resolve; });
|
||||
protocol.handle('no-cors', (req) => {
|
||||
if (/fetch/.test(req.url)) gotRequest(req);
|
||||
return new Response();
|
||||
});
|
||||
defer(() => { protocol.unhandle('no-cors'); });
|
||||
await contents.loadURL('no-cors://foo');
|
||||
contents.executeJavaScript(`
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
window.controller = controller // no GC
|
||||
},
|
||||
});
|
||||
fetch(location.href + '/fetch', {method: 'POST', body: stream, duplex: 'half'}).then(x => x.text()).catch(err => err)
|
||||
`);
|
||||
const req = await receivedRequest;
|
||||
contents.destroy();
|
||||
// Undo .destroy() for the next test
|
||||
contents = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
|
||||
await expect(req.body!.getReader().read()).to.eventually.be.rejectedWith('net::ERR_FAILED');
|
||||
});
|
||||
|
||||
it('can bypass intercepeted protocol handlers', async () => {
|
||||
protocol.handle('http', () => new Response('custom'));
|
||||
defer(() => { protocol.unhandle('http'); });
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end('default');
|
||||
});
|
||||
defer(() => server.close());
|
||||
const { url } = await listen(server);
|
||||
expect(await net.fetch(url, { bypassCustomProtocolHandlers: true }).then(r => r.text())).to.equal('default');
|
||||
});
|
||||
|
||||
it('bypassing custom protocol handlers also bypasses new protocols', async () => {
|
||||
protocol.handle('app', () => new Response('custom'));
|
||||
defer(() => { protocol.unhandle('app'); });
|
||||
await expect(net.fetch('app://foo', { bypassCustomProtocolHandlers: true })).to.be.rejectedWith('net::ERR_UNKNOWN_URL_SCHEME');
|
||||
});
|
||||
|
||||
it('can forward to the original handler', async () => {
|
||||
protocol.handle('http', (req) => net.fetch(req, { bypassCustomProtocolHandlers: true }));
|
||||
defer(() => { protocol.unhandle('http'); });
|
||||
const server = http.createServer((req, res) => {
|
||||
res.end('hello');
|
||||
server.close();
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
await contents.loadURL(url);
|
||||
expect(await contents.executeJavaScript('document.documentElement.textContent')).to.equal('hello');
|
||||
});
|
||||
|
||||
it('supports sniffing mime type', async () => {
|
||||
protocol.handle('http', async (req) => {
|
||||
return net.fetch(req, { bypassCustomProtocolHandlers: true });
|
||||
});
|
||||
defer(() => { protocol.unhandle('http'); });
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
if (/html/.test(req.url ?? '')) { res.end('<!doctype html><body>hi'); } else { res.end('hi'); }
|
||||
});
|
||||
const { url } = await listen(server);
|
||||
defer(() => server.close());
|
||||
|
||||
{
|
||||
await contents.loadURL(url);
|
||||
const doc = await contents.executeJavaScript('document.documentElement.outerHTML');
|
||||
expect(doc).to.match(/white-space: pre-wrap/);
|
||||
}
|
||||
{
|
||||
await contents.loadURL(url + '?html');
|
||||
const doc = await contents.executeJavaScript('document.documentElement.outerHTML');
|
||||
expect(doc).to.equal('<html><head></head><body>hi</body></html>');
|
||||
}
|
||||
});
|
||||
|
||||
// TODO(nornagon): this test doesn't pass on Linux currently, investigate.
|
||||
ifit(process.platform !== 'linux')('is fast', async () => {
|
||||
// 128 MB of spaces.
|
||||
const chunk = new Uint8Array(128 * 1024 * 1024);
|
||||
chunk.fill(' '.charCodeAt(0));
|
||||
|
||||
const server = http.createServer((req, res) => {
|
||||
// The sniffed mime type for the space-filled chunk will be
|
||||
// text/plain, which chews up all its performance in the renderer
|
||||
// trying to wrap lines. Setting content-type to text/html measures
|
||||
// something closer to just the raw cost of getting the bytes over
|
||||
// the wire.
|
||||
res.setHeader('content-type', 'text/html');
|
||||
res.end(chunk);
|
||||
});
|
||||
defer(() => server.close());
|
||||
const { url } = await listen(server);
|
||||
|
||||
const rawTime = await (async () => {
|
||||
await contents.loadURL(url); // warm
|
||||
const begin = Date.now();
|
||||
await contents.loadURL(url);
|
||||
const end = Date.now();
|
||||
return end - begin;
|
||||
})();
|
||||
|
||||
// Fetching through an intercepted handler should not be too much slower
|
||||
// than it would be if the protocol hadn't been intercepted.
|
||||
|
||||
protocol.handle('http', async (req) => {
|
||||
return net.fetch(req, { bypassCustomProtocolHandlers: true });
|
||||
});
|
||||
defer(() => { protocol.unhandle('http'); });
|
||||
|
||||
const interceptedTime = await (async () => {
|
||||
const begin = Date.now();
|
||||
await contents.loadURL(url);
|
||||
const end = Date.now();
|
||||
return end - begin;
|
||||
})();
|
||||
expect(interceptedTime).to.be.lessThan(rawTime * 1.5);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,13 +1,16 @@
|
||||
import { expect } from 'chai';
|
||||
import * as http from 'http';
|
||||
import * as http2 from 'http2';
|
||||
import * as qs from 'querystring';
|
||||
import * as path from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as url from 'url';
|
||||
import * as WebSocket from 'ws';
|
||||
import { ipcMain, protocol, session, WebContents, webContents } from 'electron/main';
|
||||
import { Socket } from 'net';
|
||||
import { listen } from './lib/spec-helpers';
|
||||
import { AddressInfo, Socket } from 'net';
|
||||
import { listen, defer } from './lib/spec-helpers';
|
||||
import { once } from 'events';
|
||||
import { ReadableStream } from 'stream/web';
|
||||
|
||||
const fixturesPath = path.resolve(__dirname, 'fixtures');
|
||||
|
||||
@@ -35,14 +38,35 @@ describe('webRequest module', () => {
|
||||
}
|
||||
});
|
||||
let defaultURL: string;
|
||||
let http2URL: string;
|
||||
|
||||
const certPath = path.join(fixturesPath, 'certificates');
|
||||
const h2server = http2.createSecureServer({
|
||||
key: fs.readFileSync(path.join(certPath, 'server.key')),
|
||||
cert: fs.readFileSync(path.join(certPath, 'server.pem'))
|
||||
}, async (req, res) => {
|
||||
if (req.method === 'POST') {
|
||||
const chunks = [];
|
||||
for await (const chunk of req) chunks.push(chunk);
|
||||
res.end(Buffer.concat(chunks).toString('utf8'));
|
||||
} else {
|
||||
res.end('<html></html>');
|
||||
}
|
||||
});
|
||||
|
||||
before(async () => {
|
||||
protocol.registerStringProtocol('cors', (req, cb) => cb(''));
|
||||
defaultURL = (await listen(server)).url + '/';
|
||||
await new Promise<void>((resolve) => {
|
||||
h2server.listen(0, '127.0.0.1', () => resolve());
|
||||
});
|
||||
http2URL = `https://127.0.0.1:${(h2server.address() as AddressInfo).port}/`;
|
||||
console.log(http2URL);
|
||||
});
|
||||
|
||||
after(() => {
|
||||
server.close();
|
||||
h2server.close();
|
||||
protocol.unregisterProtocol('cors');
|
||||
});
|
||||
|
||||
@@ -50,6 +74,8 @@ describe('webRequest module', () => {
|
||||
// NB. sandbox: true is used because it makes navigations much (~8x) faster.
|
||||
before(async () => {
|
||||
contents = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
|
||||
// const w = new BrowserWindow({webPreferences: {sandbox: true}})
|
||||
// contents = w.webContents
|
||||
await contents.loadFile(path.join(fixturesPath, 'pages', 'fetch.html'));
|
||||
});
|
||||
after(() => contents.destroy());
|
||||
@@ -161,6 +187,92 @@ describe('webRequest module', () => {
|
||||
});
|
||||
await expect(ajax(fileURL)).to.eventually.be.rejected();
|
||||
});
|
||||
|
||||
it('can handle a streaming upload', async () => {
|
||||
// Streaming fetch uploads are only supported on HTTP/2, which is only
|
||||
// supported over TLS, so...
|
||||
session.defaultSession.setCertificateVerifyProc((req, cb) => cb(0));
|
||||
defer(() => {
|
||||
session.defaultSession.setCertificateVerifyProc(null);
|
||||
});
|
||||
const contents = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
|
||||
defer(() => contents.close());
|
||||
await contents.loadURL(http2URL);
|
||||
|
||||
ses.webRequest.onBeforeRequest((details, callback) => {
|
||||
callback({});
|
||||
});
|
||||
|
||||
const result = await contents.executeJavaScript(`
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue('hello world');
|
||||
controller.close();
|
||||
},
|
||||
}).pipeThrough(new TextEncoderStream());
|
||||
fetch("${http2URL}", {
|
||||
method: 'POST',
|
||||
body: stream,
|
||||
duplex: 'half',
|
||||
}).then(r => r.text())
|
||||
`);
|
||||
expect(result).to.equal('hello world');
|
||||
});
|
||||
|
||||
it('can handle a streaming upload if the uploadData is read', async () => {
|
||||
// Streaming fetch uploads are only supported on HTTP/2, which is only
|
||||
// supported over TLS, so...
|
||||
session.defaultSession.setCertificateVerifyProc((req, cb) => cb(0));
|
||||
defer(() => {
|
||||
session.defaultSession.setCertificateVerifyProc(null);
|
||||
});
|
||||
const contents = (webContents as typeof ElectronInternal.WebContents).create({ sandbox: true });
|
||||
defer(() => contents.close());
|
||||
await contents.loadURL(http2URL);
|
||||
|
||||
function makeStreamFromPipe (pipe: any): ReadableStream {
|
||||
const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
|
||||
return new ReadableStream({
|
||||
async pull (controller) {
|
||||
try {
|
||||
const rv = await pipe.read(buf);
|
||||
if (rv > 0) {
|
||||
controller.enqueue(buf.subarray(0, rv));
|
||||
} else {
|
||||
controller.close();
|
||||
}
|
||||
} catch (e) {
|
||||
controller.error(e);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
ses.webRequest.onBeforeRequest(async (details, callback) => {
|
||||
const chunks = [];
|
||||
for await (const chunk of makeStreamFromPipe((details.uploadData[0] as any).body)) { chunks.push(chunk); }
|
||||
callback({});
|
||||
});
|
||||
|
||||
const result = await contents.executeJavaScript(`
|
||||
const stream = new ReadableStream({
|
||||
async start(controller) {
|
||||
controller.enqueue('hello world');
|
||||
controller.close();
|
||||
},
|
||||
}).pipeThrough(new TextEncoderStream());
|
||||
fetch("${http2URL}", {
|
||||
method: 'POST',
|
||||
body: stream,
|
||||
duplex: 'half',
|
||||
}).then(r => r.text())
|
||||
`);
|
||||
|
||||
// NOTE: since the upload stream was consumed by the onBeforeRequest
|
||||
// handler, it can't be used again to upload to the actual server.
|
||||
// This is a limitation of the WebRequest API.
|
||||
expect(result).to.equal('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('webRequest.onBeforeSendHeaders', () => {
|
||||
|
||||
22
spec/fixtures/api/frame-subscriber.html
vendored
22
spec/fixtures/api/frame-subscriber.html
vendored
@@ -1,11 +1,11 @@
|
||||
<html>
|
||||
<body>
|
||||
<div style="width: 10px; height: 10px;" id="dirty"></div>
|
||||
</body>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
setInterval(function(){
|
||||
document.getElementById('dirty').style.backgroundColor =
|
||||
'#'+(Math.random()*0xFFFFFF<<0).toString(16)
|
||||
}, 100)
|
||||
</script>
|
||||
</html>
|
||||
<html>
|
||||
<body>
|
||||
<div style="width: 10px; height: 10px;" id="dirty"></div>
|
||||
</body>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
setInterval(function(){
|
||||
document.getElementById('dirty').style.backgroundColor =
|
||||
'#'+(Math.random()*0xFFFFFF<<0).toString(16)
|
||||
}, 100)
|
||||
</script>
|
||||
</html>
|
||||
|
||||
22
spec/fixtures/api/offscreen-rendering.html
vendored
22
spec/fixtures/api/offscreen-rendering.html
vendored
@@ -1,11 +1,11 @@
|
||||
<html>
|
||||
<body>
|
||||
<div style="width: 10px; height: 10px;" id="dirty"></div>
|
||||
</body>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
setInterval(function(){
|
||||
document.getElementById('dirty').style.backgroundColor =
|
||||
'#'+(Math.random()*0xFFFFFF<<0).toString(16)
|
||||
}, 10)
|
||||
</script>
|
||||
</html>
|
||||
<html>
|
||||
<body>
|
||||
<div style="width: 10px; height: 10px;" id="dirty"></div>
|
||||
</body>
|
||||
<script type="text/javascript" charset="utf-8">
|
||||
setInterval(function(){
|
||||
document.getElementById('dirty').style.backgroundColor =
|
||||
'#'+(Math.random()*0xFFFFFF<<0).toString(16)
|
||||
}, 10)
|
||||
</script>
|
||||
</html>
|
||||
|
||||
18
spec/fixtures/api/service-workers/index.html
vendored
18
spec/fixtures/api/service-workers/index.html
vendored
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<script>
|
||||
navigator.serviceWorker.register('sw.js', {
|
||||
scope: location.pathname.split('/').slice(0, 2).join('/') + '/'
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<script>
|
||||
navigator.serviceWorker.register('sw.js', {
|
||||
scope: location.pathname.split('/').slice(0, 2).join('/') + '/'
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
18
spec/fixtures/api/service-workers/logs.html
vendored
18
spec/fixtures/api/service-workers/logs.html
vendored
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<script>
|
||||
navigator.serviceWorker.register('sw-logs.js', {
|
||||
scope: location.pathname.split('/').slice(0, 2).join('/') + '/'
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<body>
|
||||
<script>
|
||||
navigator.serviceWorker.register('sw-logs.js', {
|
||||
scope: location.pathname.split('/').slice(0, 2).join('/') + '/'
|
||||
})
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
12
spec/fixtures/pages/webview-will-navigate-in-frame.html
vendored
Normal file
12
spec/fixtures/pages/webview-will-navigate-in-frame.html
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
<html>
|
||||
<body>
|
||||
<script>
|
||||
function loadSubframe() {
|
||||
const frame = document.createElement("iframe");
|
||||
frame.id = "frame";
|
||||
frame.setAttribute("src", "./webview-will-navigate.html");
|
||||
document.body.appendChild(frame);
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1480,7 +1480,7 @@ describe('<webview> tag', function () {
|
||||
});
|
||||
|
||||
describe('will-navigate event', () => {
|
||||
it('emits when a url that leads to outside of the page is clicked', async () => {
|
||||
it('emits when a url that leads to outside of the page is loaded', async () => {
|
||||
const { url } = await loadWebViewAndWaitForEvent(w, {
|
||||
src: `file://${fixtures}/pages/webview-will-navigate.html`
|
||||
}, 'will-navigate');
|
||||
@@ -1489,6 +1489,47 @@ describe('<webview> tag', function () {
|
||||
});
|
||||
});
|
||||
|
||||
describe('will-frame-navigate event', () => {
|
||||
it('emits when a link that leads to outside of the page is loaded', async () => {
|
||||
const { url, isMainFrame } = await loadWebViewAndWaitForEvent(w, {
|
||||
src: `file://${fixtures}/pages/webview-will-navigate.html`
|
||||
}, 'will-frame-navigate');
|
||||
expect(url).to.equal('http://host/');
|
||||
expect(isMainFrame).to.be.true();
|
||||
});
|
||||
|
||||
it('emits when a link within an iframe, which leads to outside of the page, is loaded', async () => {
|
||||
await loadWebView(w, {
|
||||
src: `file://${fixtures}/pages/webview-will-navigate-in-frame.html`,
|
||||
nodeIntegration: ''
|
||||
});
|
||||
|
||||
const { url, frameProcessId, frameRoutingId } = await w.executeJavaScript(`
|
||||
new Promise((resolve, reject) => {
|
||||
let hasFrameNavigatedOnce = false;
|
||||
const webview = document.getElementById('webview');
|
||||
webview.addEventListener('will-frame-navigate', ({url, isMainFrame, frameProcessId, frameRoutingId}) => {
|
||||
if (isMainFrame) return;
|
||||
if (hasFrameNavigatedOnce) resolve({
|
||||
url,
|
||||
isMainFrame,
|
||||
frameProcessId,
|
||||
frameRoutingId,
|
||||
});
|
||||
|
||||
// First navigation is the initial iframe load within the <webview>
|
||||
hasFrameNavigatedOnce = true;
|
||||
});
|
||||
webview.executeJavaScript('loadSubframe()');
|
||||
});
|
||||
`);
|
||||
|
||||
expect(url).to.equal('http://host/');
|
||||
expect(frameProcessId).to.be.a('number');
|
||||
expect(frameRoutingId).to.be.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
describe('did-navigate event', () => {
|
||||
it('emits when a url that leads to outside of the page is clicked', async () => {
|
||||
const pageUrl = url.pathToFileURL(path.join(fixtures, 'pages', 'webview-will-navigate.html')).toString();
|
||||
|
||||
1
typings/internal-ambient.d.ts
vendored
1
typings/internal-ambient.d.ts
vendored
@@ -141,6 +141,7 @@ declare namespace NodeJS {
|
||||
hasUserActivation?: boolean;
|
||||
mode?: string;
|
||||
destination?: string;
|
||||
bypassCustomProtocolHandlers?: boolean;
|
||||
};
|
||||
type ResponseHead = {
|
||||
statusCode: number;
|
||||
|
||||
5
typings/internal-electron.d.ts
vendored
5
typings/internal-electron.d.ts
vendored
@@ -182,6 +182,11 @@ declare namespace Electron {
|
||||
setBackgroundThrottling(allowed: boolean): void;
|
||||
}
|
||||
|
||||
interface Protocol {
|
||||
registerProtocol(scheme: string, handler: any): boolean;
|
||||
interceptProtocol(scheme: string, handler: any): boolean;
|
||||
}
|
||||
|
||||
namespace Main {
|
||||
class BaseWindow extends Electron.BaseWindow {}
|
||||
class View extends Electron.View {}
|
||||
|
||||
Reference in New Issue
Block a user