mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
35 Commits
v13.0.0-ni
...
v13.0.0-ni
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
6d0a0319e1 | ||
|
|
19ff18ac40 | ||
|
|
c41b8d536b | ||
|
|
7677576da8 | ||
|
|
1abd36f6c8 | ||
|
|
788e51127f | ||
|
|
ee0550efca | ||
|
|
b788ceb7bd | ||
|
|
e87061398b | ||
|
|
c9b813a1f9 | ||
|
|
3bc220db29 | ||
|
|
6001f03e46 | ||
|
|
771e34a53a | ||
|
|
3db4e612f4 | ||
|
|
03b43e4d8c | ||
|
|
e89b3ca1d1 | ||
|
|
d3b1566181 | ||
|
|
5a5d964720 | ||
|
|
228a184b48 | ||
|
|
7672aa9525 | ||
|
|
b111bba387 | ||
|
|
b133b6fd45 | ||
|
|
45eee46864 | ||
|
|
5521f8acca | ||
|
|
b37982987a | ||
|
|
8eee9d1290 | ||
|
|
6fc5ff77c1 | ||
|
|
cffb51e141 | ||
|
|
e96fa95b94 | ||
|
|
efca7007b6 | ||
|
|
c2909a3b8d | ||
|
|
430189fa84 | ||
|
|
94381cda49 | ||
|
|
528b0f0e74 | ||
|
|
cdcee04bbe |
1
.github/config.yml
vendored
1
.github/config.yml
vendored
@@ -36,5 +36,6 @@ authorizedUsers:
|
||||
- loc
|
||||
- MarshallOfSound
|
||||
- miniak
|
||||
- mlaurencin
|
||||
- nornagon
|
||||
- zcbenz
|
||||
|
||||
3
.gitmodules
vendored
3
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "vendor/requests"]
|
||||
path = vendor/requests
|
||||
url = https://github.com/kennethreitz/requests
|
||||
[submodule "vendor/boto"]
|
||||
path = vendor/boto
|
||||
url = https://github.com/boto/boto.git
|
||||
|
||||
4
DEPS
4
DEPS
@@ -159,7 +159,7 @@ hooks = [
|
||||
'action': [
|
||||
'python3',
|
||||
'-c',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["python", "script/lib/npx.py", "yarn@' + (Var("yarn_version")) + '", "install", "--frozen-lockfile"]);',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["python3", "script/lib/npx.py", "yarn@' + (Var("yarn_version")) + '", "install", "--frozen-lockfile"]);',
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -169,7 +169,7 @@ hooks = [
|
||||
'action': [
|
||||
'python3',
|
||||
'-c',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron", "vendor", "requests")); subprocess.check_call(["python", "setup.py", "build"]);',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron", "vendor", "requests")); subprocess.check_call(["python3", "setup.py", "build"]);',
|
||||
],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
13.0.0-nightly.20201201
|
||||
13.0.0-nightly.20201209
|
||||
@@ -91,11 +91,6 @@ These individual tutorials expand on topics discussed in the guide above.
|
||||
* Electron Releases & Developer Feedback
|
||||
* [Versioning Policy](tutorial/electron-versioning.md)
|
||||
* [Release Timelines](tutorial/electron-timelines.md)
|
||||
* [Packaging App Source Code with asar](tutorial/application-packaging.md)
|
||||
* [Generating asar Archives](tutorial/application-packaging.md#generating-asar-archives)
|
||||
* [Using asar Archives](tutorial/application-packaging.md#using-asar-archives)
|
||||
* [Limitations](tutorial/application-packaging.md#limitations-of-the-node-api)
|
||||
* [Adding Unpacked Files to asar Archives](tutorial/application-packaging.md#adding-unpacked-files-to-asar-archives)
|
||||
* [Testing Widevine CDM](tutorial/testing-widevine-cdm.md)
|
||||
|
||||
---
|
||||
|
||||
@@ -16,7 +16,7 @@ const { app, contentTracing } = require('electron')
|
||||
app.whenReady().then(() => {
|
||||
(async () => {
|
||||
await contentTracing.startRecording({
|
||||
include_categories: ['*']
|
||||
included_categories: ['*']
|
||||
})
|
||||
console.log('Tracing started')
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
|
||||
@@ -44,19 +44,19 @@ The `contextBridge` module has the following methods:
|
||||
### `contextBridge.exposeInMainWorld(apiKey, api)` _Experimental_
|
||||
|
||||
* `apiKey` String - The key to inject the API onto `window` with. The API will be accessible on `window[apiKey]`.
|
||||
* `api` Record<String, any> - Your API object, more information on what this API can be and how it works is available below.
|
||||
* `api` any - Your API, more information on what this API can be and how it works is available below.
|
||||
|
||||
## Usage
|
||||
|
||||
### API Objects
|
||||
### API
|
||||
|
||||
The `api` object provided to [`exposeInMainWorld`](#contextbridgeexposeinmainworldapikey-api-experimental) must be an object
|
||||
The `api` provided to [`exposeInMainWorld`](#contextbridgeexposeinmainworldapikey-api-experimental) must be a `Function`, `String`, `Number`, `Array`, `Boolean`, or an object
|
||||
whose keys are strings and values are a `Function`, `String`, `Number`, `Array`, `Boolean`, or another nested object that meets the same conditions.
|
||||
|
||||
`Function` values are proxied to the other context and all other values are **copied** and **frozen**. Any data / primitives sent in
|
||||
the API object become immutable and updates on either side of the bridge do not result in an update on the other side.
|
||||
the API become immutable and updates on either side of the bridge do not result in an update on the other side.
|
||||
|
||||
An example of a complex API object is shown below:
|
||||
An example of a complex API is shown below:
|
||||
|
||||
```javascript
|
||||
const { contextBridge } = require('electron')
|
||||
|
||||
@@ -120,6 +120,24 @@ debugging purposes.
|
||||
|
||||
Prints Chrome's internal logging to the console.
|
||||
|
||||
### `ELECTRON_DEBUG_DRAG_REGIONS`
|
||||
|
||||
Adds coloration to draggable regions on [`BrowserView`](./browser-view.md)s on macOS - draggable regions will be colored
|
||||
green and non-draggable regions will be colored red to aid debugging.
|
||||
|
||||
### `ELECTRON_DEBUG_NOTIFICATIONS`
|
||||
|
||||
Adds extra logs to [`Notification`](./notification.md) lifecycles on macOS to aid in debugging. Extra logging will be displayed when new Notifications are created or activated. They will also be displayed when common actions are taken: a notification is shown, dismissed, its button is clicked, or it is replied to.
|
||||
|
||||
Sample output:
|
||||
|
||||
```sh
|
||||
Notification created (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76-F88EC9D73D44)
|
||||
Notification displayed (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76-F88EC9D73D44)
|
||||
Notification activated (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76-F88EC9D73D44)
|
||||
Notification replied to (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76-F88EC9D73D44)
|
||||
```
|
||||
|
||||
### `ELECTRON_LOG_ASAR_READS`
|
||||
|
||||
When Electron reads from an ASAR file, log the read offset and file path to
|
||||
|
||||
@@ -500,6 +500,7 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
||||
* `pointerLock` - Request to directly interpret mouse movements as an input method. Click [here](https://developer.mozilla.org/en-US/docs/Web/API/Pointer_Lock_API) to know more.
|
||||
* `fullscreen` - Request for the app to enter fullscreen mode.
|
||||
* `openExternal` - Request to open links in external applications.
|
||||
* `unknown` - An unrecognized permission request
|
||||
* `callback` Function
|
||||
* `permissionGranted` Boolean - Allow or deny the permission.
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
@@ -511,7 +512,9 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
||||
|
||||
Sets the handler which can be used to respond to permission requests for the `session`.
|
||||
Calling `callback(true)` will allow the permission and `callback(false)` will reject it.
|
||||
To clear the handler, call `setPermissionRequestHandler(null)`.
|
||||
To clear the handler, call `setPermissionRequestHandler(null)`. Please note that
|
||||
you must also implement `setPermissionCheckHandler` to get complete permission handling.
|
||||
Most web APIs do a permission check and then make a permission request if the check is denied.
|
||||
|
||||
```javascript
|
||||
const { session } = require('electron')
|
||||
@@ -527,28 +530,32 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
||||
#### `ses.setPermissionCheckHandler(handler)`
|
||||
|
||||
* `handler` Function\<Boolean> | null
|
||||
* `webContents` [WebContents](web-contents.md) - WebContents checking the permission. Please note that if the request comes from a subframe you should use `requestingUrl` to check the request origin.
|
||||
* `webContents` ([WebContents](web-contents.md) | null) - WebContents checking the permission. Please note that if the request comes from a subframe you should use `requestingUrl` to check the request origin. Cross origin sub frames making permission checks will pass a `null` webContents to this handler. You should use `embeddingOrigin` and `requestingOrigin` to determine what origin the owning frame and the requesting frame are on respectively.
|
||||
* `permission` String - Type of permission check. Valid values are `midiSysex`, `notifications`, `geolocation`, `media`,`mediaKeySystem`,`midi`, `pointerLock`, `fullscreen`, `openExternal`, or `serial`.
|
||||
* `requestingOrigin` String - The origin URL of the permission check
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
* `securityOrigin` String - The security origin of the `media` check.
|
||||
* `mediaType` String - The type of media access being requested, can be `video`,
|
||||
* `embeddingOrigin` String (optional) - The origin of the frame embedding the frame that made the permission check. Only set for cross-origin sub frames making permission checks.
|
||||
* `securityOrigin` String (optional) - The security origin of the `media` check.
|
||||
* `mediaType` String (optional) - The type of media access being requested, can be `video`,
|
||||
`audio` or `unknown`
|
||||
* `requestingUrl` String - The last URL the requesting frame loaded
|
||||
* `requestingUrl` String (optional) - The last URL the requesting frame loaded. This is not provided for cross-origin sub frames making permission checks.
|
||||
* `isMainFrame` Boolean - Whether the frame making the request is the main frame
|
||||
|
||||
Sets the handler which can be used to respond to permission checks for the `session`.
|
||||
Returning `true` will allow the permission and `false` will reject it.
|
||||
Returning `true` will allow the permission and `false` will reject it. Please note that
|
||||
you must also implement `setPermissionRequestHandler` to get complete permission handling.
|
||||
Most web APIs do a permission check and then make a permission request if the check is denied.
|
||||
To clear the handler, call `setPermissionCheckHandler(null)`.
|
||||
|
||||
```javascript
|
||||
const { session } = require('electron')
|
||||
session.fromPartition('some-partition').setPermissionCheckHandler((webContents, permission) => {
|
||||
if (webContents.getURL() === 'some-host' && permission === 'notifications') {
|
||||
return false // denied
|
||||
const url = require('url')
|
||||
session.fromPartition('some-partition').setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
|
||||
if (new URL(requestingOrigin).hostname === 'some-host' && permission === 'notifications') {
|
||||
return true // granted
|
||||
}
|
||||
|
||||
return true
|
||||
return false // denied
|
||||
})
|
||||
```
|
||||
|
||||
|
||||
@@ -1871,7 +1871,7 @@ Returns `Boolean` - If *offscreen rendering* is enabled returns whether it is cu
|
||||
* `fps` Integer
|
||||
|
||||
If *offscreen rendering* is enabled sets the frame rate to the specified number.
|
||||
Only values between 1 and 60 are accepted.
|
||||
Only values between 1 and 240 are accepted.
|
||||
|
||||
#### `contents.getFrameRate()`
|
||||
|
||||
@@ -1969,7 +1969,7 @@ The zoom factor is the zoom percent divided by 100, so 300% = 3.0.
|
||||
#### `contents.frameRate`
|
||||
|
||||
An `Integer` property that sets the frame rate of the web contents to the specified number.
|
||||
Only values between 1 and 60 are accepted.
|
||||
Only values between 1 and 240 are accepted.
|
||||
|
||||
Only applicable if *offscreen rendering* is enabled.
|
||||
|
||||
|
||||
@@ -14,6 +14,28 @@ This document uses the following convention to categorize breaking changes:
|
||||
|
||||
## Planned Breaking API Changes (13.0)
|
||||
|
||||
### API Changed: `session.setPermissionCheckHandler(handler)`
|
||||
|
||||
The `handler` methods first parameter was previously always a `webContents`, it can now sometimes be `null`. You should use the `requestingOrigin`, `embeddingOrigin` and `securityOrigin` properties to respond to the permission check correctly. As the `webContents` can be `null` it can no longer be relied on.
|
||||
|
||||
```js
|
||||
// Old code
|
||||
session.setPermissionCheckHandler((webContents, permission) => {
|
||||
if (webContents.getURL().startsWith('https://google.com/') && permission === 'notification') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Replace with
|
||||
session.setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
|
||||
if (new URL(requestingOrigin).hostname === 'google.com' && permission === 'notification') {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
```
|
||||
|
||||
### Removed: `shell.moveItemToTrash()`
|
||||
|
||||
The deprecated synchronous `shell.moveItemToTrash()` API has been removed. Use
|
||||
@@ -29,6 +51,7 @@ shell.trashItem(path).then(/* ... */)
|
||||
### Removed: `BrowserWindow` extension APIs
|
||||
|
||||
The deprecated extension APIs have been removed:
|
||||
|
||||
* `BrowserWindow.addExtension(path)`
|
||||
* `BrowserWindow.addDevToolsExtension(path)`
|
||||
* `BrowserWindow.removeExtension(name)`
|
||||
@@ -37,6 +60,7 @@ The deprecated extension APIs have been removed:
|
||||
* `BrowserWindow.getDevToolsExtensions()`
|
||||
|
||||
Use the session APIs instead:
|
||||
|
||||
* `ses.loadExtension(path)`
|
||||
* `ses.removeExtension(extension_id)`
|
||||
* `ses.getAllExtensions()`
|
||||
@@ -313,6 +337,47 @@ you should plan to update your native modules to be context aware.
|
||||
|
||||
For more detailed information see [#18397](https://github.com/electron/electron/issues/18397).
|
||||
|
||||
### Deprecated: `BrowserWindow` extension APIs
|
||||
|
||||
The following extension APIs have been deprecated:
|
||||
|
||||
* `BrowserWindow.addExtension(path)`
|
||||
* `BrowserWindow.addDevToolsExtension(path)`
|
||||
* `BrowserWindow.removeExtension(name)`
|
||||
* `BrowserWindow.removeDevToolsExtension(name)`
|
||||
* `BrowserWindow.getExtensions()`
|
||||
* `BrowserWindow.getDevToolsExtensions()`
|
||||
|
||||
Use the session APIs instead:
|
||||
|
||||
* `ses.loadExtension(path)`
|
||||
* `ses.removeExtension(extension_id)`
|
||||
* `ses.getAllExtensions()`
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 9
|
||||
BrowserWindow.addExtension(path)
|
||||
BrowserWindow.addDevToolsExtension(path)
|
||||
// Replace with
|
||||
session.defaultSession.loadExtension(path)
|
||||
```
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 9
|
||||
BrowserWindow.removeExtension(name)
|
||||
BrowserWindow.removeDevToolsExtension(name)
|
||||
// Replace with
|
||||
session.defaultSession.removeExtension(extension_id)
|
||||
```
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 9
|
||||
BrowserWindow.getExtensions()
|
||||
BrowserWindow.getDevToolsExtensions()
|
||||
// Replace with
|
||||
session.defaultSession.getAllExtensions()
|
||||
```
|
||||
|
||||
### Removed: `<webview>.getWebContents()`
|
||||
|
||||
This API, which was deprecated in Electron 8.0, is now removed.
|
||||
|
||||
@@ -1,25 +1,38 @@
|
||||
# Application Distribution
|
||||
|
||||
To distribute your app with Electron, you need to package and rebrand it. The easiest way to do this is to use one of the following third party packaging tools:
|
||||
## Overview
|
||||
|
||||
To distribute your app with Electron, you need to package and rebrand it.
|
||||
To do this, you can either use specialized tooling or manual approaches.
|
||||
|
||||
## With tooling
|
||||
|
||||
You can use the following tools to distribute your application:
|
||||
|
||||
* [electron-forge](https://github.com/electron-userland/electron-forge)
|
||||
* [electron-builder](https://github.com/electron-userland/electron-builder)
|
||||
* [electron-packager](https://github.com/electron/electron-packager)
|
||||
|
||||
These tools will take care of all the steps you need to take to end up with a distributable Electron applications, such as packaging your application, rebranding the executable, setting the right icons and optionally creating installers.
|
||||
These tools will take care of all the steps you need to take to end up with a
|
||||
distributable Electron application, such as bundling your application,
|
||||
rebranding the executable, and setting the right icons.
|
||||
|
||||
You can check the example of how to package your app with `electron-forge` in
|
||||
our [Quick Start Guide](quick-start.md#package-and-distribute-the-application).
|
||||
|
||||
## Manual distribution
|
||||
|
||||
You can also choose to manually get your app ready for distribution. The steps needed to do this are outlined below.
|
||||
### With prebuilt binaries
|
||||
|
||||
To distribute your app with Electron, you need to download Electron's [prebuilt
|
||||
To distribute your app manually, you need to download Electron's [prebuilt
|
||||
binaries](https://github.com/electron/electron/releases). Next, the folder
|
||||
containing your app should be named `app` and placed in Electron's resources
|
||||
directory as shown in the following examples. Note that the location of
|
||||
Electron's prebuilt binaries is indicated with `electron/` in the examples
|
||||
below.
|
||||
directory as shown in the following examples.
|
||||
|
||||
On macOS:
|
||||
> *NOTE:* the location of Electron's prebuilt binaries is indicated
|
||||
with `electron/` in the examples below.
|
||||
|
||||
*On macOS:*
|
||||
|
||||
```plaintext
|
||||
electron/Electron.app/Contents/Resources/app/
|
||||
@@ -28,7 +41,7 @@ electron/Electron.app/Contents/Resources/app/
|
||||
└── index.html
|
||||
```
|
||||
|
||||
On Windows and Linux:
|
||||
*On Windows and Linux:*
|
||||
|
||||
```plaintext
|
||||
electron/resources/app
|
||||
@@ -37,47 +50,44 @@ electron/resources/app
|
||||
└── index.html
|
||||
```
|
||||
|
||||
Then execute `Electron.app` (or `electron` on Linux, `electron.exe` on Windows),
|
||||
and Electron will start as your app. The `electron` directory will then be
|
||||
your distribution to deliver to final users.
|
||||
Then execute `Electron.app` on macOS, `electron` on Linux, or `electron.exe`
|
||||
on Windows, and Electron will start as your app. The `electron` directory
|
||||
will then be your distribution to deliver to users.
|
||||
|
||||
## Packaging Your App into a File
|
||||
### With an app source code archive
|
||||
|
||||
Apart from shipping your app by copying all of its source files, you can also
|
||||
package your app into an [asar](https://github.com/electron/asar) archive to avoid
|
||||
exposing your app's source code to users.
|
||||
Instead of from shipping your app by copying all of its source files, you can
|
||||
package your app into an [asar] archive to improve the performance of reading
|
||||
files on platforms like Windows, if you are not already using a bundler such
|
||||
as Parcel or Webpack.
|
||||
|
||||
To use an `asar` archive to replace the `app` folder, you need to rename the
|
||||
archive to `app.asar`, and put it under Electron's resources directory like
|
||||
below, and Electron will then try to read the archive and start from it.
|
||||
|
||||
On macOS:
|
||||
*On macOS:*
|
||||
|
||||
```plaintext
|
||||
electron/Electron.app/Contents/Resources/
|
||||
└── app.asar
|
||||
```
|
||||
|
||||
On Windows and Linux:
|
||||
*On Windows and Linux:*
|
||||
|
||||
```plaintext
|
||||
electron/resources/
|
||||
└── app.asar
|
||||
```
|
||||
|
||||
More details can be found in [Application packaging](application-packaging.md).
|
||||
You can find more details on how to use `asar` in the
|
||||
[`electron/asar` repository][asar].
|
||||
|
||||
## Rebranding with Downloaded Binaries
|
||||
### Rebranding with downloaded binaries
|
||||
|
||||
After bundling your app into Electron, you will want to rebrand Electron
|
||||
before distributing it to users.
|
||||
|
||||
### Windows
|
||||
|
||||
You can rename `electron.exe` to any name you like, and edit its icon and other
|
||||
information with tools like [rcedit](https://github.com/electron/rcedit).
|
||||
|
||||
### macOS
|
||||
#### macOS
|
||||
|
||||
You can rename `Electron.app` to any name you want, and you also have to rename
|
||||
the `CFBundleDisplayName`, `CFBundleIdentifier` and `CFBundleName` fields in the
|
||||
@@ -104,60 +114,20 @@ MyApp.app/Contents
|
||||
└── MyApp Helper
|
||||
```
|
||||
|
||||
### Linux
|
||||
#### Windows
|
||||
|
||||
You can rename `electron.exe` to any name you like, and edit its icon and other
|
||||
information with tools like [rcedit](https://github.com/electron/rcedit).
|
||||
|
||||
#### Linux
|
||||
|
||||
You can rename the `electron` executable to any name you like.
|
||||
|
||||
## Rebranding by Rebuilding Electron from Source
|
||||
### Rebranding by rebuilding Electron from source
|
||||
|
||||
It is also possible to rebrand Electron by changing the product name and
|
||||
building it from source. To do this you need to set the build argument
|
||||
corresponding to the product name (`electron_product_name = "YourProductName"`)
|
||||
in the `args.gn` file and rebuild.
|
||||
|
||||
### Creating a Custom Electron Fork
|
||||
|
||||
Creating a custom fork of Electron is almost certainly not something you will
|
||||
need to do in order to build your app, even for "Production Level" applications.
|
||||
Using a tool such as `electron-packager` or `electron-forge` will allow you to
|
||||
"Rebrand" Electron without having to do these steps.
|
||||
|
||||
You need to fork Electron when you have custom C++ code that you have patched
|
||||
directly into Electron, that either cannot be upstreamed, or has been rejected
|
||||
from the official version. As maintainers of Electron, we very much would like
|
||||
to make your scenario work, so please try as hard as you can to get your changes
|
||||
into the official version of Electron, it will be much much easier on you, and
|
||||
we appreciate your help.
|
||||
|
||||
#### Creating a Custom Release with surf-build
|
||||
|
||||
1. Install [Surf](https://github.com/surf-build/surf), via npm:
|
||||
`npm install -g surf-build@latest`
|
||||
|
||||
2. Create a new S3 bucket and create the following empty directory structure:
|
||||
|
||||
```sh
|
||||
- electron/
|
||||
- symbols/
|
||||
- dist/
|
||||
```
|
||||
|
||||
3. Set the following Environment Variables:
|
||||
|
||||
* `ELECTRON_GITHUB_TOKEN` - a token that can create releases on GitHub
|
||||
* `ELECTRON_S3_ACCESS_KEY`, `ELECTRON_S3_BUCKET`, `ELECTRON_S3_SECRET_KEY` -
|
||||
the place where you'll upload Node.js headers as well as symbols
|
||||
* `ELECTRON_RELEASE` - Set to `true` and the upload part will run, leave unset
|
||||
and `surf-build` will do CI-type checks, appropriate to run for every
|
||||
pull request.
|
||||
* `CI` - Set to `true` or else it will fail
|
||||
* `GITHUB_TOKEN` - set it to the same as `ELECTRON_GITHUB_TOKEN`
|
||||
* `SURF_TEMP` - set to `C:\Temp` on Windows to prevent path too long issues
|
||||
* `TARGET_ARCH` - set to `ia32` or `x64`
|
||||
|
||||
4. In `script/upload.py`, you _must_ set `ELECTRON_REPO` to your fork (`MYORG/electron`),
|
||||
especially if you are a contributor to Electron proper.
|
||||
|
||||
5. `surf-build -r https://github.com/MYORG/electron -s YOUR_COMMIT -n 'surf-PLATFORM-ARCH'`
|
||||
|
||||
6. Wait a very, very long time for the build to complete.
|
||||
[asar]: https://github.com/electron/asar
|
||||
|
||||
@@ -1,194 +0,0 @@
|
||||
# Application Packaging
|
||||
|
||||
To mitigate [issues](https://github.com/joyent/node/issues/6960) around long
|
||||
path names on Windows, slightly speed up `require` and conceal your source code
|
||||
from cursory inspection, you can choose to package your app into an [asar][asar]
|
||||
archive with little changes to your source code.
|
||||
|
||||
Most users will get this feature for free, since it's supported out of the box
|
||||
by [`electron-packager`][electron-packager], [`electron-forge`][electron-forge],
|
||||
and [`electron-builder`][electron-builder]. If you are not using any of these
|
||||
tools, read on.
|
||||
|
||||
## Generating `asar` Archives
|
||||
|
||||
An [asar][asar] archive is a simple tar-like format that concatenates files
|
||||
into a single file. Electron can read arbitrary files from it without unpacking
|
||||
the whole file.
|
||||
|
||||
Steps to package your app into an `asar` archive:
|
||||
|
||||
### 1. Install the asar Utility
|
||||
|
||||
```sh
|
||||
$ npm install -g asar
|
||||
```
|
||||
|
||||
### 2. Package with `asar pack`
|
||||
|
||||
```sh
|
||||
$ asar pack your-app app.asar
|
||||
```
|
||||
|
||||
## Using `asar` Archives
|
||||
|
||||
In Electron there are two sets of APIs: Node APIs provided by Node.js and Web
|
||||
APIs provided by Chromium. Both APIs support reading files from `asar` archives.
|
||||
|
||||
### Node API
|
||||
|
||||
With special patches in Electron, Node APIs like `fs.readFile` and `require`
|
||||
treat `asar` archives as virtual directories, and the files in it as normal
|
||||
files in the filesystem.
|
||||
|
||||
For example, suppose we have an `example.asar` archive under `/path/to`:
|
||||
|
||||
```sh
|
||||
$ asar list /path/to/example.asar
|
||||
/app.js
|
||||
/file.txt
|
||||
/dir/module.js
|
||||
/static/index.html
|
||||
/static/main.css
|
||||
/static/jquery.min.js
|
||||
```
|
||||
|
||||
Read a file in the `asar` archive:
|
||||
|
||||
```javascript
|
||||
const fs = require('fs')
|
||||
fs.readFileSync('/path/to/example.asar/file.txt')
|
||||
```
|
||||
|
||||
List all files under the root of the archive:
|
||||
|
||||
```javascript
|
||||
const fs = require('fs')
|
||||
fs.readdirSync('/path/to/example.asar')
|
||||
```
|
||||
|
||||
Use a module from the archive:
|
||||
|
||||
```javascript
|
||||
require('./path/to/example.asar/dir/module.js')
|
||||
```
|
||||
|
||||
You can also display a web page in an `asar` archive with `BrowserWindow`:
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.loadURL('file:///path/to/example.asar/static/index.html')
|
||||
```
|
||||
|
||||
### Web API
|
||||
|
||||
In a web page, files in an archive can be requested with the `file:` protocol.
|
||||
Like the Node API, `asar` archives are treated as directories.
|
||||
|
||||
For example, to get a file with `$.get`:
|
||||
|
||||
```html
|
||||
<script>
|
||||
let $ = require('./jquery.min.js')
|
||||
$.get('file:///path/to/example.asar/file.txt', (data) => {
|
||||
console.log(data)
|
||||
})
|
||||
</script>
|
||||
```
|
||||
|
||||
### Treating an `asar` Archive as a Normal File
|
||||
|
||||
For some cases like verifying the `asar` archive's checksum, we need to read the
|
||||
content of an `asar` archive as a file. For this purpose you can use the built-in
|
||||
`original-fs` module which provides original `fs` APIs without `asar` support:
|
||||
|
||||
```javascript
|
||||
const originalFs = require('original-fs')
|
||||
originalFs.readFileSync('/path/to/example.asar')
|
||||
```
|
||||
|
||||
You can also set `process.noAsar` to `true` to disable the support for `asar` in
|
||||
the `fs` module:
|
||||
|
||||
```javascript
|
||||
const fs = require('fs')
|
||||
process.noAsar = true
|
||||
fs.readFileSync('/path/to/example.asar')
|
||||
```
|
||||
|
||||
## Limitations of the Node API
|
||||
|
||||
Even though we tried hard to make `asar` archives in the Node API work like
|
||||
directories as much as possible, there are still limitations due to the
|
||||
low-level nature of the Node API.
|
||||
|
||||
### Archives Are Read-only
|
||||
|
||||
The archives can not be modified so all Node APIs that can modify files will not
|
||||
work with `asar` archives.
|
||||
|
||||
### Working Directory Can Not Be Set to Directories in Archive
|
||||
|
||||
Though `asar` archives are treated as directories, there are no actual
|
||||
directories in the filesystem, so you can never set the working directory to
|
||||
directories in `asar` archives. Passing them as the `cwd` option of some APIs
|
||||
will also cause errors.
|
||||
|
||||
### Extra Unpacking on Some APIs
|
||||
|
||||
Most `fs` APIs can read a file or get a file's information from `asar` archives
|
||||
without unpacking, but for some APIs that rely on passing the real file path to
|
||||
underlying system calls, Electron will extract the needed file into a
|
||||
temporary file and pass the path of the temporary file to the APIs to make them
|
||||
work. This adds a little overhead for those APIs.
|
||||
|
||||
APIs that requires extra unpacking are:
|
||||
|
||||
* `child_process.execFile`
|
||||
* `child_process.execFileSync`
|
||||
* `fs.open`
|
||||
* `fs.openSync`
|
||||
* `process.dlopen` - Used by `require` on native modules
|
||||
|
||||
### Fake Stat Information of `fs.stat`
|
||||
|
||||
The `Stats` object returned by `fs.stat` and its friends on files in `asar`
|
||||
archives is generated by guessing, because those files do not exist on the
|
||||
filesystem. So you should not trust the `Stats` object except for getting file
|
||||
size and checking file type.
|
||||
|
||||
### Executing Binaries Inside `asar` Archive
|
||||
|
||||
There are Node APIs that can execute binaries like `child_process.exec`,
|
||||
`child_process.spawn` and `child_process.execFile`, but only `execFile` is
|
||||
supported to execute binaries inside `asar` archive.
|
||||
|
||||
This is because `exec` and `spawn` accept `command` instead of `file` as input,
|
||||
and `command`s are executed under shell. There is no reliable way to determine
|
||||
whether a command uses a file in asar archive, and even if we do, we can not be
|
||||
sure whether we can replace the path in command without side effects.
|
||||
|
||||
## Adding Unpacked Files to `asar` Archives
|
||||
|
||||
As stated above, some Node APIs will unpack the file to the filesystem when
|
||||
called. Apart from the performance issues, various anti-virus scanners might
|
||||
be triggered by this behavior.
|
||||
|
||||
As a workaround, you can leave various files unpacked using the `--unpack` option.
|
||||
In the following example, shared libraries of native Node.js modules will not be
|
||||
packed:
|
||||
|
||||
```sh
|
||||
$ asar pack app app.asar --unpack *.node
|
||||
```
|
||||
|
||||
After running the command, you will notice that a folder named `app.asar.unpacked`
|
||||
was created together with the `app.asar` file. It contains the unpacked files
|
||||
and should be shipped together with the `app.asar` archive.
|
||||
|
||||
[asar]: https://github.com/electron/asar
|
||||
[electron-packager]: https://github.com/electron/electron-packager
|
||||
[electron-forge]: https://github.com/electron-userland/electron-forge
|
||||
[electron-builder]: https://github.com/electron-userland/electron-builder
|
||||
@@ -13,7 +13,7 @@ project.
|
||||
* There are two rendering modes that can be used (see the section below) and only
|
||||
the dirty area is passed to the `paint` event to be more efficient.
|
||||
* You can stop/continue the rendering as well as set the frame rate.
|
||||
* The maximum frame rate is 60 because greater values bring only performance
|
||||
* The maximum frame rate is 240 because greater values bring only performance
|
||||
losses with no benefits.
|
||||
* When nothing is happening on a webpage, no frames are generated.
|
||||
* An offscreen window is always created as a
|
||||
|
||||
@@ -124,6 +124,7 @@ Your Electron application uses the `package.json` file as the main entry point (
|
||||
{
|
||||
"name": "my-electron-app",
|
||||
"version": "0.1.0",
|
||||
"description": "My Electron app",
|
||||
"main": "main.js"
|
||||
}
|
||||
```
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
# Using Native Node Modules
|
||||
|
||||
Native Node modules are supported by Electron, but since Electron is very
|
||||
likely to use a different V8 version from the Node binary installed on your
|
||||
system, the modules you use will need to be recompiled for Electron. Otherwise,
|
||||
Native Node.js modules are supported by Electron, but since Electron has a different
|
||||
[application binary interface (ABI)][abi] from a given Node.js binary (due to
|
||||
differences such as using Chromium's BoringSSL instead of OpenSSL), the native
|
||||
modules you use will need to be recompiled for Electron. Otherwise,
|
||||
you will get the following class of error when you try to run your app:
|
||||
|
||||
```sh
|
||||
@@ -23,9 +24,11 @@ You can install modules like other Node projects, and then rebuild the modules
|
||||
for Electron with the [`electron-rebuild`][electron-rebuild] package. This
|
||||
module can automatically determine the version of Electron and handle the
|
||||
manual steps of downloading headers and rebuilding native modules for your app.
|
||||
If you are using [Electron Forge][electron-forge], this tool is used automatically
|
||||
in both development mode and when making distributables.
|
||||
|
||||
For example, to install `electron-rebuild` and then rebuild modules with it
|
||||
via the command line:
|
||||
For example, to install the standalone `electron-rebuild` tool and then rebuild
|
||||
modules with it via the command line:
|
||||
|
||||
```sh
|
||||
npm install --save-dev electron-rebuild
|
||||
@@ -33,12 +36,12 @@ npm install --save-dev electron-rebuild
|
||||
# Every time you run "npm install", run this:
|
||||
./node_modules/.bin/electron-rebuild
|
||||
|
||||
# On Windows if you have trouble, try:
|
||||
# If you have trouble on Windows, try:
|
||||
.\node_modules\.bin\electron-rebuild.cmd
|
||||
```
|
||||
|
||||
For more information on usage and integration with other tools, consult the
|
||||
project's README.
|
||||
For more information on usage and integration with other tools such as [Electron
|
||||
Packager][electron-packager], consult the project's README.
|
||||
|
||||
### Using `npm`
|
||||
|
||||
@@ -147,23 +150,25 @@ for an example delay-load hook if you're implementing your own.
|
||||
native Node modules with prebuilt binaries for multiple versions of Node
|
||||
and Electron.
|
||||
|
||||
If modules provide binaries for the usage in Electron, make sure to omit
|
||||
`--build-from-source` and the `npm_config_build_from_source` environment
|
||||
variable in order to take full advantage of the prebuilt binaries.
|
||||
If the `prebuild`-powered module provide binaries for the usage in Electron,
|
||||
make sure to omit `--build-from-source` and the `npm_config_build_from_source`
|
||||
environment variable in order to take full advantage of the prebuilt binaries.
|
||||
|
||||
## Modules that rely on `node-pre-gyp`
|
||||
|
||||
The [`node-pre-gyp` tool][node-pre-gyp] provides a way to deploy native Node
|
||||
modules with prebuilt binaries, and many popular modules are using it.
|
||||
|
||||
Usually those modules work fine under Electron, but sometimes when Electron uses
|
||||
a newer version of V8 than Node and/or there are ABI changes, bad things may
|
||||
happen. So in general, it is recommended to always build native modules from
|
||||
source code. `electron-rebuild` handles this for you automatically.
|
||||
Sometimes those modules work fine under Electron, but when there are no
|
||||
Electron-specific binaries available, you'll need to build from source.
|
||||
Because of this, it is recommended to use `electron-rebuild` for these modules.
|
||||
|
||||
If you are following the `npm` way of installing modules, then this is done
|
||||
by default, if not, you have to pass `--build-from-source` to `npm`, or set the
|
||||
`npm_config_build_from_source` environment variable.
|
||||
If you are following the `npm` way of installing modules, you'll need to pass
|
||||
`--build-from-source` to `npm`, or set the `npm_config_build_from_source`
|
||||
environment variable.
|
||||
|
||||
[abi]: https://en.wikipedia.org/wiki/Application_binary_interface
|
||||
[electron-rebuild]: https://github.com/electron/electron-rebuild
|
||||
[electron-forge]: https://electronforge.io/
|
||||
[electron-packager]: https://github.com/electron/electron-packager
|
||||
[node-pre-gyp]: https://github.com/mapbox/node-pre-gyp
|
||||
|
||||
@@ -689,7 +689,8 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (info.size === 0) return ['', false];
|
||||
if (info.unpacked) {
|
||||
const realPath = archive.copyFileOut(filePath);
|
||||
return fs.readFileSync(realPath, { encoding: 'utf8' });
|
||||
const str = fs.readFileSync(realPath, { encoding: 'utf8' });
|
||||
return [str, str.length > 0];
|
||||
}
|
||||
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
|
||||
@@ -1,38 +1,48 @@
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const { createScreen } = process._linkedBinding('electron_common_screen');
|
||||
|
||||
let _screen: Electron.Screen;
|
||||
|
||||
const createScreenIfNeeded = () => {
|
||||
if (_screen === undefined) {
|
||||
_screen = createScreen();
|
||||
}
|
||||
};
|
||||
|
||||
// We can't call createScreen until after app.on('ready'), but this module
|
||||
// exposes an instance created by createScreen. In order to avoid
|
||||
// side-effecting and calling createScreen upon import of this module, instead
|
||||
// we export a proxy which lazily calls createScreen on first access.
|
||||
export default new Proxy({}, {
|
||||
get: (target, prop: keyof Electron.Screen) => {
|
||||
if (_screen === undefined) {
|
||||
_screen = createScreen();
|
||||
get: (target, property: keyof Electron.Screen) => {
|
||||
createScreenIfNeeded();
|
||||
const value = _screen[property];
|
||||
if (typeof value === 'function') {
|
||||
return value.bind(_screen);
|
||||
}
|
||||
const v = _screen[prop];
|
||||
if (typeof v === 'function') {
|
||||
return v.bind(_screen);
|
||||
}
|
||||
return v;
|
||||
return value;
|
||||
},
|
||||
set: (target, property: string, value: unknown) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.set(_screen, property, value);
|
||||
},
|
||||
ownKeys: () => {
|
||||
if (_screen === undefined) {
|
||||
_screen = createScreen();
|
||||
}
|
||||
createScreenIfNeeded();
|
||||
return Reflect.ownKeys(_screen);
|
||||
},
|
||||
has: (target, prop: string) => {
|
||||
if (_screen === undefined) {
|
||||
_screen = createScreen();
|
||||
}
|
||||
return prop in _screen;
|
||||
has: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return property in _screen;
|
||||
},
|
||||
getOwnPropertyDescriptor: (target, prop: string) => {
|
||||
if (_screen === undefined) {
|
||||
_screen = createScreen();
|
||||
}
|
||||
return Reflect.getOwnPropertyDescriptor(_screen, prop);
|
||||
getOwnPropertyDescriptor: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.getOwnPropertyDescriptor(_screen, property);
|
||||
},
|
||||
getPrototypeOf: () => {
|
||||
// This is necessary as a result of weirdness with EventEmitterMixin
|
||||
// and FunctionTemplate - we need to explicitly ensure it's returned
|
||||
// in the prototype.
|
||||
return EventEmitter.prototype;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ if ('getAppLevelAppearance' in systemPreferences) {
|
||||
}
|
||||
|
||||
if ('getEffectiveAppearance' in systemPreferences) {
|
||||
const nativeEAGetter = systemPreferences.getAppLevelAppearance;
|
||||
const nativeEAGetter = systemPreferences.getEffectiveAppearance;
|
||||
Object.defineProperty(systemPreferences, 'effectiveAppearance', {
|
||||
get: () => nativeEAGetter.call(systemPreferences)
|
||||
});
|
||||
|
||||
@@ -131,10 +131,7 @@ WebContents.prototype.send = function (channel, ...args) {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = false;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
return this._send(false /* internal */, channel, args);
|
||||
};
|
||||
|
||||
WebContents.prototype.postMessage = function (...args) {
|
||||
@@ -149,20 +146,7 @@ WebContents.prototype._sendInternal = function (channel, ...args) {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = true;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
};
|
||||
WebContents.prototype._sendInternalToAll = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = true;
|
||||
const sendToAll = true;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
return this._send(true /* internal */, channel, args);
|
||||
};
|
||||
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
@@ -171,10 +155,7 @@ WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
throw new Error('Missing required frameId argument');
|
||||
}
|
||||
|
||||
const internal = false;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
|
||||
return this._sendToFrame(false /* internal */, frameId, channel, args);
|
||||
};
|
||||
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
@@ -183,10 +164,7 @@ WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args
|
||||
throw new Error('Missing required frameId argument');
|
||||
}
|
||||
|
||||
const internal = true;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
|
||||
return this._sendToFrame(true /* internal */, frameId, channel, args);
|
||||
};
|
||||
|
||||
// Following methods are mapped to webFrame.
|
||||
@@ -199,7 +177,7 @@ const webFrameMethods = [
|
||||
|
||||
for (const method of webFrameMethods) {
|
||||
WebContents.prototype[method] = function (...args: any[]): Promise<any> {
|
||||
return ipcMainUtils.invokeInWebContents(this, false, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, method, ...args);
|
||||
return ipcMainUtils.invokeInWebContents(this, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, method, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
@@ -217,11 +195,11 @@ const waitTillCanExecuteJavaScript = async (webContents: Electron.WebContents) =
|
||||
// WebContents has been loaded.
|
||||
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
|
||||
await waitTillCanExecuteJavaScript(this);
|
||||
return ipcMainUtils.invokeInWebContents(this, false, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScript', String(code), !!hasUserGesture);
|
||||
return ipcMainUtils.invokeInWebContents(this, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScript', String(code), !!hasUserGesture);
|
||||
};
|
||||
WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (worldId, code, hasUserGesture) {
|
||||
await waitTillCanExecuteJavaScript(this);
|
||||
return ipcMainUtils.invokeInWebContents(this, false, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScriptInIsolatedWorld', worldId, code, !!hasUserGesture);
|
||||
return ipcMainUtils.invokeInWebContents(this, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScriptInIsolatedWorld', worldId, code, !!hasUserGesture);
|
||||
};
|
||||
|
||||
// Translate the options of printToPDF.
|
||||
@@ -483,16 +461,6 @@ const addReplyToEvent = (event: any) => {
|
||||
};
|
||||
};
|
||||
|
||||
const addReplyInternalToEvent = (event: any) => {
|
||||
Object.defineProperty(event, '_replyInternal', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
value: (...args: any[]) => {
|
||||
event.sender._sendToFrameInternal(event.frameId, ...args);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const addReturnValueToEvent = (event: any) => {
|
||||
Object.defineProperty(event, 'returnValue', {
|
||||
set: (value) => event.sendReply(value),
|
||||
@@ -537,7 +505,6 @@ WebContents.prototype._init = function () {
|
||||
// Dispatch IPC messages to the ipc module.
|
||||
this.on('-ipc-message' as any, function (this: Electron.WebContents, event: any, internal: boolean, channel: string, args: any[]) {
|
||||
if (internal) {
|
||||
addReplyInternalToEvent(event);
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event);
|
||||
@@ -563,7 +530,6 @@ WebContents.prototype._init = function () {
|
||||
this.on('-ipc-message-sync' as any, function (this: Electron.WebContents, event: any, internal: boolean, channel: string, args: any[]) {
|
||||
addReturnValueToEvent(event);
|
||||
if (internal) {
|
||||
addReplyInternalToEvent(event);
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event);
|
||||
|
||||
@@ -14,7 +14,7 @@ export const handleSync = function <T extends IPCHandler> (channel: string, hand
|
||||
|
||||
let nextId = 0;
|
||||
|
||||
export function invokeInWebContents<T> (sender: Electron.WebContents, sendToAll: boolean, command: string, ...args: any[]) {
|
||||
export function invokeInWebContents<T> (sender: Electron.WebContents, command: string, ...args: any[]) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const requestId = ++nextId;
|
||||
const channel = `${command}_RESPONSE_${requestId}`;
|
||||
@@ -33,10 +33,6 @@ export function invokeInWebContents<T> (sender: Electron.WebContents, sendToAll:
|
||||
}
|
||||
});
|
||||
|
||||
if (sendToAll) {
|
||||
sender._sendInternalToAll(command, requestId, ...args);
|
||||
} else {
|
||||
sender._sendInternal(command, requestId, ...args);
|
||||
}
|
||||
sender._sendInternal(command, requestId, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,24 +1,5 @@
|
||||
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
|
||||
import type { WebContents, LoadURLOptions } from 'electron/main';
|
||||
import { EventEmitter } from 'events';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
|
||||
// The history operation in renderer is redirected to browser.
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_GO_BACK, function (event) {
|
||||
event.sender.goBack();
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_GO_FORWARD, function (event) {
|
||||
event.sender.goForward();
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_GO_TO_OFFSET, function (event, offset) {
|
||||
event.sender.goToOffset(offset);
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_LENGTH, function (event) {
|
||||
event.returnValue = event.sender.length();
|
||||
});
|
||||
|
||||
// JavaScript implementation of Chromium's NavigationController.
|
||||
// Instead of relying on Chromium for history control, we completely do history
|
||||
|
||||
@@ -31,7 +31,7 @@ const finalizationRegistry = new FinalizationRegistry((fi: FinalizerInfo) => {
|
||||
const ref = rendererFunctionCache.get(mapKey);
|
||||
if (ref !== undefined && ref.deref() === undefined) {
|
||||
rendererFunctionCache.delete(mapKey);
|
||||
if (!fi.webContents.isDestroyed()) { fi.webContents.sendToFrame(fi.frameId, IPC_MESSAGES.RENDERER_RELEASE_CALLBACK, fi.id[0], fi.id[1]); }
|
||||
if (!fi.webContents.isDestroyed()) { fi.webContents._sendToFrameInternal(fi.frameId, IPC_MESSAGES.RENDERER_RELEASE_CALLBACK, fi.id[0], fi.id[1]); }
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -100,6 +100,22 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_SANDBOX_LOAD, async function (event
|
||||
};
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_GO_BACK, function (event) {
|
||||
event.sender.goBack();
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_GO_FORWARD, function (event) {
|
||||
event.sender.goForward();
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_GO_TO_OFFSET, function (event, offset) {
|
||||
event.sender.goToOffset(offset);
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.NAVIGATION_CONTROLLER_LENGTH, function (event) {
|
||||
event.returnValue = event.sender.length();
|
||||
});
|
||||
|
||||
ipcMainInternal.on(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, function (event, preloadPath: string, error: Error) {
|
||||
event.sender.emit('preload-error', event, preloadPath, error);
|
||||
});
|
||||
|
||||
@@ -8,7 +8,7 @@ const checkContextIsolationEnabled = () => {
|
||||
};
|
||||
|
||||
const contextBridge: Electron.ContextBridge = {
|
||||
exposeInMainWorld: (key: string, api: Record<string, any>) => {
|
||||
exposeInMainWorld: (key: string, api: any) => {
|
||||
checkContextIsolationEnabled();
|
||||
return binding.exposeAPIInMainWorld(key, api);
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ ipcRenderer.sendToHost = function (channel, ...args) {
|
||||
};
|
||||
|
||||
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, false, webContentsId, channel, args);
|
||||
return ipc.sendTo(internal, webContentsId, channel, args);
|
||||
};
|
||||
|
||||
ipcRenderer.invoke = async function (channel, ...args) {
|
||||
|
||||
@@ -14,11 +14,7 @@ ipcRendererInternal.sendSync = function (channel, ...args) {
|
||||
};
|
||||
|
||||
ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, false, webContentsId, channel, args);
|
||||
};
|
||||
|
||||
ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, true, webContentsId, channel, args);
|
||||
return ipc.sendTo(internal, webContentsId, channel, args);
|
||||
};
|
||||
|
||||
ipcRendererInternal.invoke = async function<T> (channel: string, ...args: any[]) {
|
||||
|
||||
@@ -180,7 +180,7 @@ const warnAboutInsecureCSP = function () {
|
||||
|
||||
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
|
||||
'font-weight: bold;', warning);
|
||||
});
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
11
npm/index.js
11
npm/index.js
@@ -4,11 +4,14 @@ const path = require('path');
|
||||
const pathFile = path.join(__dirname, 'path.txt');
|
||||
|
||||
function getElectronPath () {
|
||||
let executablePath;
|
||||
if (fs.existsSync(pathFile)) {
|
||||
const executablePath = fs.readFileSync(pathFile, 'utf-8');
|
||||
if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
|
||||
return path.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath);
|
||||
}
|
||||
executablePath = fs.readFileSync(pathFile, 'utf-8');
|
||||
}
|
||||
if (process.env.ELECTRON_OVERRIDE_DIST_PATH) {
|
||||
return path.join(process.env.ELECTRON_OVERRIDE_DIST_PATH, executablePath || 'electron');
|
||||
}
|
||||
if (executablePath) {
|
||||
return path.join(__dirname, 'dist', executablePath);
|
||||
} else {
|
||||
throw new Error('Electron failed to install correctly, please delete node_modules/electron and try installing again');
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "13.0.0-nightly.20201201",
|
||||
"version": "13.0.0-nightly.20201209",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -1321,12 +1321,8 @@ std::vector<gin_helper::Dictionary> App::GetAppMetrics(v8::Isolate* isolate) {
|
||||
gin_helper::Dictionary pid_dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
gin_helper::Dictionary cpu_dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
|
||||
// TODO(zcbenz): Just call SetHidden when this file is converted to gin.
|
||||
gin_helper::Dictionary(isolate, pid_dict.GetHandle())
|
||||
.SetHidden("simple", true);
|
||||
gin_helper::Dictionary(isolate, cpu_dict.GetHandle())
|
||||
.SetHidden("simple", true);
|
||||
|
||||
pid_dict.SetHidden("simple", true);
|
||||
cpu_dict.SetHidden("simple", true);
|
||||
cpu_dict.Set(
|
||||
"percentCPUUsage",
|
||||
process_metric.second->metrics->GetPlatformIndependentCPUUsage() /
|
||||
@@ -1361,9 +1357,7 @@ std::vector<gin_helper::Dictionary> App::GetAppMetrics(v8::Isolate* isolate) {
|
||||
auto memory_info = process_metric.second->GetMemoryInfo();
|
||||
|
||||
gin_helper::Dictionary memory_dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
// TODO(zcbenz): Just call SetHidden when this file is converted to gin.
|
||||
gin_helper::Dictionary(isolate, memory_dict.GetHandle())
|
||||
.SetHidden("simple", true);
|
||||
memory_dict.SetHidden("simple", true);
|
||||
memory_dict.Set("workingSetSize",
|
||||
static_cast<double>(memory_info.working_set_size >> 10));
|
||||
memory_dict.Set(
|
||||
|
||||
@@ -33,15 +33,6 @@ gin::WrapperInfo Screen::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
namespace {
|
||||
|
||||
// Find an item in container according to its ID.
|
||||
template <class T>
|
||||
typename T::iterator FindById(T* container, int id) {
|
||||
auto predicate = [id](const typename T::value_type& item) -> bool {
|
||||
return item.id() == id;
|
||||
};
|
||||
return std::find_if(container->begin(), container->end(), predicate);
|
||||
}
|
||||
|
||||
// Convert the changed_metrics bitmask to string array.
|
||||
std::vector<std::string> MetricsToArray(uint32_t metrics) {
|
||||
std::vector<std::string> array;
|
||||
|
||||
@@ -26,7 +26,7 @@ namespace electron {
|
||||
namespace api {
|
||||
|
||||
#if defined(OS_MAC)
|
||||
enum NotificationCenterKind {
|
||||
enum class NotificationCenterKind {
|
||||
kNSDistributedNotificationCenter = 0,
|
||||
kNSNotificationCenter,
|
||||
kNSWorkspaceNotificationCenter,
|
||||
|
||||
@@ -140,12 +140,13 @@ void SystemPreferences::PostNotification(const std::string& name,
|
||||
int SystemPreferences::SubscribeNotification(
|
||||
const std::string& name,
|
||||
const NotificationCallback& callback) {
|
||||
return DoSubscribeNotification(name, callback,
|
||||
kNSDistributedNotificationCenter);
|
||||
return DoSubscribeNotification(
|
||||
name, callback, NotificationCenterKind::kNSDistributedNotificationCenter);
|
||||
}
|
||||
|
||||
void SystemPreferences::UnsubscribeNotification(int request_id) {
|
||||
DoUnsubscribeNotification(request_id, kNSDistributedNotificationCenter);
|
||||
DoUnsubscribeNotification(
|
||||
request_id, NotificationCenterKind::kNSDistributedNotificationCenter);
|
||||
}
|
||||
|
||||
void SystemPreferences::PostLocalNotification(const std::string& name,
|
||||
@@ -159,11 +160,13 @@ void SystemPreferences::PostLocalNotification(const std::string& name,
|
||||
int SystemPreferences::SubscribeLocalNotification(
|
||||
const std::string& name,
|
||||
const NotificationCallback& callback) {
|
||||
return DoSubscribeNotification(name, callback, kNSNotificationCenter);
|
||||
return DoSubscribeNotification(name, callback,
|
||||
NotificationCenterKind::kNSNotificationCenter);
|
||||
}
|
||||
|
||||
void SystemPreferences::UnsubscribeLocalNotification(int request_id) {
|
||||
DoUnsubscribeNotification(request_id, kNSNotificationCenter);
|
||||
DoUnsubscribeNotification(request_id,
|
||||
NotificationCenterKind::kNSNotificationCenter);
|
||||
}
|
||||
|
||||
void SystemPreferences::PostWorkspaceNotification(
|
||||
@@ -179,12 +182,13 @@ void SystemPreferences::PostWorkspaceNotification(
|
||||
int SystemPreferences::SubscribeWorkspaceNotification(
|
||||
const std::string& name,
|
||||
const NotificationCallback& callback) {
|
||||
return DoSubscribeNotification(name, callback,
|
||||
kNSWorkspaceNotificationCenter);
|
||||
return DoSubscribeNotification(
|
||||
name, callback, NotificationCenterKind::kNSWorkspaceNotificationCenter);
|
||||
}
|
||||
|
||||
void SystemPreferences::UnsubscribeWorkspaceNotification(int request_id) {
|
||||
DoUnsubscribeNotification(request_id, kNSWorkspaceNotificationCenter);
|
||||
DoUnsubscribeNotification(
|
||||
request_id, NotificationCenterKind::kNSWorkspaceNotificationCenter);
|
||||
}
|
||||
|
||||
int SystemPreferences::DoSubscribeNotification(
|
||||
@@ -195,13 +199,13 @@ int SystemPreferences::DoSubscribeNotification(
|
||||
__block NotificationCallback copied_callback = callback;
|
||||
NSNotificationCenter* center;
|
||||
switch (kind) {
|
||||
case kNSDistributedNotificationCenter:
|
||||
case NotificationCenterKind::kNSDistributedNotificationCenter:
|
||||
center = [NSDistributedNotificationCenter defaultCenter];
|
||||
break;
|
||||
case kNSNotificationCenter:
|
||||
case NotificationCenterKind::kNSNotificationCenter:
|
||||
center = [NSNotificationCenter defaultCenter];
|
||||
break;
|
||||
case kNSWorkspaceNotificationCenter:
|
||||
case NotificationCenterKind::kNSWorkspaceNotificationCenter:
|
||||
center = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
break;
|
||||
default:
|
||||
@@ -239,13 +243,13 @@ void SystemPreferences::DoUnsubscribeNotification(int request_id,
|
||||
id observer = iter->second;
|
||||
NSNotificationCenter* center;
|
||||
switch (kind) {
|
||||
case kNSDistributedNotificationCenter:
|
||||
case NotificationCenterKind::kNSDistributedNotificationCenter:
|
||||
center = [NSDistributedNotificationCenter defaultCenter];
|
||||
break;
|
||||
case kNSNotificationCenter:
|
||||
case NotificationCenterKind::kNSNotificationCenter:
|
||||
center = [NSNotificationCenter defaultCenter];
|
||||
break;
|
||||
case kNSWorkspaceNotificationCenter:
|
||||
case NotificationCenterKind::kNSWorkspaceNotificationCenter:
|
||||
center = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
break;
|
||||
default:
|
||||
|
||||
@@ -1577,7 +1577,6 @@ void WebContents::MessageSync(bool internal,
|
||||
}
|
||||
|
||||
void WebContents::MessageTo(bool internal,
|
||||
bool send_to_all,
|
||||
int32_t web_contents_id,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) {
|
||||
@@ -1585,7 +1584,7 @@ void WebContents::MessageTo(bool internal,
|
||||
auto* web_contents = FromID(web_contents_id);
|
||||
|
||||
if (web_contents) {
|
||||
web_contents->SendIPCMessageWithSender(internal, send_to_all, channel,
|
||||
web_contents->SendIPCMessageWithSender(internal, channel,
|
||||
std::move(arguments), ID());
|
||||
}
|
||||
}
|
||||
@@ -2685,7 +2684,6 @@ bool WebContents::IsFocused() const {
|
||||
#endif
|
||||
|
||||
bool WebContents::SendIPCMessage(bool internal,
|
||||
bool send_to_all,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
@@ -2695,37 +2693,21 @@ bool WebContents::SendIPCMessage(bool internal,
|
||||
gin::StringToV8(isolate, "Failed to serialize arguments")));
|
||||
return false;
|
||||
}
|
||||
return SendIPCMessageWithSender(internal, send_to_all, channel,
|
||||
std::move(message));
|
||||
return SendIPCMessageWithSender(internal, channel, std::move(message));
|
||||
}
|
||||
|
||||
bool WebContents::SendIPCMessageWithSender(bool internal,
|
||||
bool send_to_all,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage args,
|
||||
int32_t sender_id) {
|
||||
std::vector<content::RenderFrameHost*> target_hosts;
|
||||
if (!send_to_all) {
|
||||
auto* frame_host = web_contents()->GetMainFrame();
|
||||
if (frame_host) {
|
||||
target_hosts.push_back(frame_host);
|
||||
}
|
||||
} else {
|
||||
target_hosts = web_contents()->GetAllFrames();
|
||||
}
|
||||
|
||||
for (auto* frame_host : target_hosts) {
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
&electron_renderer);
|
||||
electron_renderer->Message(internal, false, channel, args.ShallowClone(),
|
||||
sender_id);
|
||||
}
|
||||
auto* frame_host = web_contents()->GetMainFrame();
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||
electron_renderer->Message(internal, channel, std::move(args), sender_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebContents::SendIPCMessageToFrame(bool internal,
|
||||
bool send_to_all,
|
||||
int32_t frame_id,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args) {
|
||||
@@ -2747,7 +2729,7 @@ bool WebContents::SendIPCMessageToFrame(bool internal,
|
||||
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
(*iter)->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||
electron_renderer->Message(internal, send_to_all, channel, std::move(message),
|
||||
electron_renderer->Message(internal, channel, std::move(message),
|
||||
0 /* sender_id */);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -249,18 +249,15 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
|
||||
// Send messages to browser.
|
||||
bool SendIPCMessage(bool internal,
|
||||
bool send_to_all,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args);
|
||||
|
||||
bool SendIPCMessageWithSender(bool internal,
|
||||
bool send_to_all,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage args,
|
||||
int32_t sender_id = 0);
|
||||
|
||||
bool SendIPCMessageToFrame(bool internal,
|
||||
bool send_to_all,
|
||||
int32_t frame_id,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args);
|
||||
@@ -619,7 +616,6 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
blink::CloneableMessage arguments,
|
||||
MessageSyncCallback callback) override;
|
||||
void MessageTo(bool internal,
|
||||
bool send_to_all,
|
||||
int32_t web_contents_id,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) override;
|
||||
|
||||
@@ -257,21 +257,26 @@ WebRequest::~WebRequest() {
|
||||
gin::ObjectTemplateBuilder WebRequest::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::Wrappable<WebRequest>::GetObjectTemplateBuilder(isolate)
|
||||
.SetMethod("onBeforeRequest",
|
||||
&WebRequest::SetResponseListener<kOnBeforeRequest>)
|
||||
.SetMethod("onBeforeSendHeaders",
|
||||
&WebRequest::SetResponseListener<kOnBeforeSendHeaders>)
|
||||
.SetMethod("onHeadersReceived",
|
||||
&WebRequest::SetResponseListener<kOnHeadersReceived>)
|
||||
.SetMethod(
|
||||
"onBeforeRequest",
|
||||
&WebRequest::SetResponseListener<ResponseEvent::kOnBeforeRequest>)
|
||||
.SetMethod(
|
||||
"onBeforeSendHeaders",
|
||||
&WebRequest::SetResponseListener<ResponseEvent::kOnBeforeSendHeaders>)
|
||||
.SetMethod(
|
||||
"onHeadersReceived",
|
||||
&WebRequest::SetResponseListener<ResponseEvent::kOnHeadersReceived>)
|
||||
.SetMethod("onSendHeaders",
|
||||
&WebRequest::SetSimpleListener<kOnSendHeaders>)
|
||||
&WebRequest::SetSimpleListener<SimpleEvent::kOnSendHeaders>)
|
||||
.SetMethod("onBeforeRedirect",
|
||||
&WebRequest::SetSimpleListener<kOnBeforeRedirect>)
|
||||
.SetMethod("onResponseStarted",
|
||||
&WebRequest::SetSimpleListener<kOnResponseStarted>)
|
||||
&WebRequest::SetSimpleListener<SimpleEvent::kOnBeforeRedirect>)
|
||||
.SetMethod(
|
||||
"onResponseStarted",
|
||||
&WebRequest::SetSimpleListener<SimpleEvent::kOnResponseStarted>)
|
||||
.SetMethod("onErrorOccurred",
|
||||
&WebRequest::SetSimpleListener<kOnErrorOccurred>)
|
||||
.SetMethod("onCompleted", &WebRequest::SetSimpleListener<kOnCompleted>);
|
||||
&WebRequest::SetSimpleListener<SimpleEvent::kOnErrorOccurred>)
|
||||
.SetMethod("onCompleted",
|
||||
&WebRequest::SetSimpleListener<SimpleEvent::kOnCompleted>);
|
||||
}
|
||||
|
||||
const char* WebRequest::GetTypeName() {
|
||||
@@ -286,8 +291,8 @@ int WebRequest::OnBeforeRequest(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
net::CompletionOnceCallback callback,
|
||||
GURL* new_url) {
|
||||
return HandleResponseEvent(kOnBeforeRequest, info, std::move(callback),
|
||||
new_url, request);
|
||||
return HandleResponseEvent(ResponseEvent::kOnBeforeRequest, info,
|
||||
std::move(callback), new_url, request);
|
||||
}
|
||||
|
||||
int WebRequest::OnBeforeSendHeaders(extensions::WebRequestInfo* info,
|
||||
@@ -295,7 +300,7 @@ int WebRequest::OnBeforeSendHeaders(extensions::WebRequestInfo* info,
|
||||
BeforeSendHeadersCallback callback,
|
||||
net::HttpRequestHeaders* headers) {
|
||||
return HandleResponseEvent(
|
||||
kOnBeforeSendHeaders, info,
|
||||
ResponseEvent::kOnBeforeSendHeaders, info,
|
||||
base::BindOnce(std::move(callback), std::set<std::string>(),
|
||||
std::set<std::string>()),
|
||||
headers, request, *headers);
|
||||
@@ -312,25 +317,26 @@ int WebRequest::OnHeadersReceived(
|
||||
original_response_headers ? original_response_headers->GetStatusLine()
|
||||
: std::string();
|
||||
return HandleResponseEvent(
|
||||
kOnHeadersReceived, info, std::move(callback),
|
||||
ResponseEvent::kOnHeadersReceived, info, std::move(callback),
|
||||
std::make_pair(override_response_headers, status_line), request);
|
||||
}
|
||||
|
||||
void WebRequest::OnSendHeaders(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const net::HttpRequestHeaders& headers) {
|
||||
HandleSimpleEvent(kOnSendHeaders, info, request, headers);
|
||||
HandleSimpleEvent(SimpleEvent::kOnSendHeaders, info, request, headers);
|
||||
}
|
||||
|
||||
void WebRequest::OnBeforeRedirect(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const GURL& new_location) {
|
||||
HandleSimpleEvent(kOnBeforeRedirect, info, request, new_location);
|
||||
HandleSimpleEvent(SimpleEvent::kOnBeforeRedirect, info, request,
|
||||
new_location);
|
||||
}
|
||||
|
||||
void WebRequest::OnResponseStarted(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request) {
|
||||
HandleSimpleEvent(kOnResponseStarted, info, request);
|
||||
HandleSimpleEvent(SimpleEvent::kOnResponseStarted, info, request);
|
||||
}
|
||||
|
||||
void WebRequest::OnErrorOccurred(extensions::WebRequestInfo* info,
|
||||
@@ -338,7 +344,7 @@ void WebRequest::OnErrorOccurred(extensions::WebRequestInfo* info,
|
||||
int net_error) {
|
||||
callbacks_.erase(info->id);
|
||||
|
||||
HandleSimpleEvent(kOnErrorOccurred, info, request, net_error);
|
||||
HandleSimpleEvent(SimpleEvent::kOnErrorOccurred, info, request, net_error);
|
||||
}
|
||||
|
||||
void WebRequest::OnCompleted(extensions::WebRequestInfo* info,
|
||||
@@ -346,7 +352,7 @@ void WebRequest::OnCompleted(extensions::WebRequestInfo* info,
|
||||
int net_error) {
|
||||
callbacks_.erase(info->id);
|
||||
|
||||
HandleSimpleEvent(kOnCompleted, info, request, net_error);
|
||||
HandleSimpleEvent(SimpleEvent::kOnCompleted, info, request, net_error);
|
||||
}
|
||||
|
||||
void WebRequest::OnRequestWillBeDestroyed(extensions::WebRequestInfo* info) {
|
||||
|
||||
@@ -86,14 +86,14 @@ class WebRequest : public gin::Wrappable<WebRequest>, public WebRequestAPI {
|
||||
WebRequest(v8::Isolate* isolate, content::BrowserContext* browser_context);
|
||||
~WebRequest() override;
|
||||
|
||||
enum SimpleEvent {
|
||||
enum class SimpleEvent {
|
||||
kOnSendHeaders,
|
||||
kOnBeforeRedirect,
|
||||
kOnResponseStarted,
|
||||
kOnCompleted,
|
||||
kOnErrorOccurred,
|
||||
};
|
||||
enum ResponseEvent {
|
||||
enum class ResponseEvent {
|
||||
kOnBeforeRequest,
|
||||
kOnBeforeSendHeaders,
|
||||
kOnHeadersReceived,
|
||||
|
||||
@@ -224,7 +224,12 @@ blink::mojom::PermissionStatus ElectronPermissionManager::GetPermissionStatus(
|
||||
content::PermissionType permission,
|
||||
const GURL& requesting_origin,
|
||||
const GURL& embedding_origin) {
|
||||
return blink::mojom::PermissionStatus::GRANTED;
|
||||
base::DictionaryValue details;
|
||||
details.SetString("embeddingOrigin", embedding_origin.spec());
|
||||
bool granted = CheckPermissionWithDetails(permission, nullptr,
|
||||
requesting_origin, &details);
|
||||
return granted ? blink::mojom::PermissionStatus::GRANTED
|
||||
: blink::mojom::PermissionStatus::DENIED;
|
||||
}
|
||||
|
||||
int ElectronPermissionManager::SubscribePermissionStatusChange(
|
||||
@@ -247,13 +252,28 @@ bool ElectronPermissionManager::CheckPermissionWithDetails(
|
||||
return true;
|
||||
}
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
render_frame_host
|
||||
? content::WebContents::FromRenderFrameHost(render_frame_host)
|
||||
: nullptr;
|
||||
auto mutable_details =
|
||||
details == nullptr ? base::DictionaryValue() : details->Clone();
|
||||
mutable_details.SetStringKey("requestingUrl",
|
||||
render_frame_host->GetLastCommittedURL().spec());
|
||||
mutable_details.SetBoolKey("isMainFrame",
|
||||
render_frame_host->GetParent() == nullptr);
|
||||
if (render_frame_host) {
|
||||
mutable_details.SetStringKey(
|
||||
"requestingUrl", render_frame_host->GetLastCommittedURL().spec());
|
||||
}
|
||||
mutable_details.SetBoolKey(
|
||||
"isMainFrame",
|
||||
render_frame_host && render_frame_host->GetParent() == nullptr);
|
||||
switch (permission) {
|
||||
case content::PermissionType::AUDIO_CAPTURE:
|
||||
mutable_details.SetStringKey("mediaType", "audio");
|
||||
break;
|
||||
case content::PermissionType::VIDEO_CAPTURE:
|
||||
mutable_details.SetStringKey("mediaType", "video");
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return check_handler_.Run(web_contents, permission, requesting_origin,
|
||||
mutable_details);
|
||||
}
|
||||
@@ -263,7 +283,11 @@ ElectronPermissionManager::GetPermissionStatusForFrame(
|
||||
content::PermissionType permission,
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
const GURL& requesting_origin) {
|
||||
return blink::mojom::PermissionStatus::GRANTED;
|
||||
base::DictionaryValue details;
|
||||
bool granted = CheckPermissionWithDetails(permission, render_frame_host,
|
||||
requesting_origin, &details);
|
||||
return granted ? blink::mojom::PermissionStatus::GRANTED
|
||||
: blink::mojom::PermissionStatus::DENIED;
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -4,6 +4,11 @@
|
||||
|
||||
#include "shell/browser/native_browser_view_views.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "shell/browser/ui/drag_util.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view.h"
|
||||
#include "ui/gfx/geometry/rect.h"
|
||||
#include "ui/views/background.h"
|
||||
@@ -22,6 +27,25 @@ void NativeBrowserViewViews::SetAutoResizeFlags(uint8_t flags) {
|
||||
ResetAutoResizeProportions();
|
||||
}
|
||||
|
||||
void NativeBrowserViewViews::UpdateDraggableRegions(
|
||||
const std::vector<mojom::DraggableRegionPtr>& regions) {
|
||||
// We need to snap the regions to the bounds of the current BrowserView.
|
||||
// For example, if an attached BrowserView is draggable but its bounds are
|
||||
// { x: 200, y: 100, width: 300, height: 300 }
|
||||
// then we need to add 200 to the x-value and 100 to the
|
||||
// y-value of each of the passed regions or it will be incorrectly
|
||||
// assumed that the regions begin in the top left corner as they
|
||||
// would for the main client window.
|
||||
auto const offset = GetBounds().OffsetFromOrigin();
|
||||
auto snapped_regions = mojo::Clone(regions);
|
||||
for (auto& snapped_region : snapped_regions) {
|
||||
snapped_region->bounds.Offset(offset);
|
||||
snapped_region->draggable = true;
|
||||
}
|
||||
|
||||
draggable_region_ = DraggableRegionsToSkRegion(snapped_regions);
|
||||
}
|
||||
|
||||
void NativeBrowserViewViews::SetAutoResizeProportions(
|
||||
const gfx::Size& window_size) {
|
||||
if ((auto_resize_flags_ & AutoResizeFlags::kAutoResizeHorizontal) &&
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
#ifndef SHELL_BROWSER_NATIVE_BROWSER_VIEW_VIEWS_H_
|
||||
#define SHELL_BROWSER_NATIVE_BROWSER_VIEW_VIEWS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
||||
#include "shell/browser/native_browser_view.h"
|
||||
#include "third_party/skia/include/core/SkRegion.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -26,6 +30,10 @@ class NativeBrowserViewViews : public NativeBrowserView {
|
||||
void SetBounds(const gfx::Rect& bounds) override;
|
||||
gfx::Rect GetBounds() override;
|
||||
void SetBackgroundColor(SkColor color) override;
|
||||
void UpdateDraggableRegions(
|
||||
const std::vector<mojom::DraggableRegionPtr>& regions) override;
|
||||
|
||||
SkRegion* draggable_region() const { return draggable_region_.get(); }
|
||||
|
||||
private:
|
||||
void ResetAutoResizeProportions();
|
||||
@@ -40,6 +48,8 @@ class NativeBrowserViewViews : public NativeBrowserView {
|
||||
float auto_vertical_proportion_height_ = 0.;
|
||||
float auto_vertical_proportion_top_ = 0.;
|
||||
|
||||
std::unique_ptr<SkRegion> draggable_region_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NativeBrowserViewViews);
|
||||
};
|
||||
|
||||
|
||||
@@ -1460,6 +1460,16 @@ views::View* NativeWindowViews::GetContentsView() {
|
||||
bool NativeWindowViews::ShouldDescendIntoChildForEventHandling(
|
||||
gfx::NativeView child,
|
||||
const gfx::Point& location) {
|
||||
// App window should claim mouse events that fall within any BrowserViews'
|
||||
// draggable region.
|
||||
for (auto* view : browser_views()) {
|
||||
auto* native_view = static_cast<NativeBrowserViewViews*>(view);
|
||||
auto* view_draggable_region = native_view->draggable_region();
|
||||
if (view_draggable_region &&
|
||||
view_draggable_region->contains(location.x(), location.y()))
|
||||
return false;
|
||||
}
|
||||
|
||||
// App window should claim mouse events that fall within the draggable region.
|
||||
if (draggable_region() &&
|
||||
draggable_region()->contains(location.x(), location.y()))
|
||||
|
||||
@@ -40,5 +40,9 @@
|
||||
<string>This app needs access to the microphone</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app needs access to the camera</string>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>This app needs access to Bluetooth</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
<string>This app needs access to Bluetooth</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 13,0,0,20201201
|
||||
PRODUCTVERSION 13,0,0,20201201
|
||||
FILEVERSION 13,0,0,20201209
|
||||
PRODUCTVERSION 13,0,0,20201209
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "shell/browser/ui/views/frameless_view.h"
|
||||
|
||||
#include "shell/browser/native_browser_view_views.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "ui/aura/window.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
@@ -68,6 +69,15 @@ int FramelessView::NonClientHitTest(const gfx::Point& cursor) {
|
||||
if (frame_->IsFullscreen())
|
||||
return HTCLIENT;
|
||||
|
||||
// Check attached BrowserViews for potential draggable areas.
|
||||
for (auto* view : window_->browser_views()) {
|
||||
auto* native_view = static_cast<NativeBrowserViewViews*>(view);
|
||||
auto* view_draggable_region = native_view->draggable_region();
|
||||
if (view_draggable_region &&
|
||||
view_draggable_region->contains(cursor.x(), cursor.y()))
|
||||
return HTCAPTION;
|
||||
}
|
||||
|
||||
// Check for possible draggable region in the client area for the frameless
|
||||
// window.
|
||||
SkRegion* draggable_region = window_->draggable_region();
|
||||
|
||||
@@ -8,7 +8,6 @@ import "third_party/blink/public/mojom/messaging/transferable_message.mojom";
|
||||
interface ElectronRenderer {
|
||||
Message(
|
||||
bool internal,
|
||||
bool send_to_all,
|
||||
string channel,
|
||||
blink.mojom.CloneableMessage arguments,
|
||||
int32 sender_id);
|
||||
@@ -67,7 +66,6 @@ interface ElectronBrowser {
|
||||
// WebContents's main frame, specified by |web_contents_id|.
|
||||
MessageTo(
|
||||
bool internal,
|
||||
bool send_to_all,
|
||||
int32 web_contents_id,
|
||||
string channel,
|
||||
blink.mojom.CloneableMessage arguments);
|
||||
|
||||
@@ -176,14 +176,18 @@ void ErrorMessageListener(v8::Local<v8::Message> message,
|
||||
v8::Isolate* isolate = v8::Isolate::GetCurrent();
|
||||
node::Environment* env = node::Environment::GetCurrent(isolate);
|
||||
|
||||
// TODO(codebytere): properly emit the after() hooks now
|
||||
// that the exception has been handled.
|
||||
// See node/lib/internal/process/execution.js#L176-L180
|
||||
|
||||
// Ensure that the async id stack is properly cleared so the async
|
||||
// hook stack does not become corrupted.
|
||||
|
||||
if (env) {
|
||||
// Emit the after() hooks now that the exception has been handled.
|
||||
// Analogous to node/lib/internal/process/execution.js#L176-L180
|
||||
if (env->async_hooks()->fields()[node::AsyncHooks::kAfter]) {
|
||||
while (env->async_hooks()->fields()[node::AsyncHooks::kStackLength]) {
|
||||
node::AsyncWrap::EmitAfter(env, env->execution_async_id());
|
||||
env->async_hooks()->pop_async_context(env->execution_async_id());
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the async id stack is properly cleared so the async
|
||||
// hook stack does not become corrupted.
|
||||
env->async_hooks()->clear_async_id_stack();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -513,11 +513,13 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
|
||||
}
|
||||
}
|
||||
|
||||
void ExposeAPIInMainWorld(const std::string& key,
|
||||
v8::Local<v8::Object> api_object,
|
||||
void ExposeAPIInMainWorld(v8::Isolate* isolate,
|
||||
const std::string& key,
|
||||
v8::Local<v8::Value> api,
|
||||
gin_helper::Arguments* args) {
|
||||
TRACE_EVENT1("electron", "ContextBridge::ExposeAPIInMainWorld", "key", key);
|
||||
auto* render_frame = GetRenderFrame(api_object);
|
||||
|
||||
auto* render_frame = GetRenderFrame(isolate->GetCurrentContext()->Global());
|
||||
CHECK(render_frame);
|
||||
auto* frame = render_frame->GetWebFrame();
|
||||
CHECK(frame);
|
||||
@@ -539,12 +541,13 @@ void ExposeAPIInMainWorld(const std::string& key,
|
||||
context_bridge::ObjectCache object_cache;
|
||||
v8::Context::Scope main_context_scope(main_context);
|
||||
|
||||
v8::MaybeLocal<v8::Object> maybe_proxy = CreateProxyForAPI(
|
||||
api_object, isolated_context, main_context, &object_cache, false, 0);
|
||||
v8::MaybeLocal<v8::Value> maybe_proxy = PassValueToOtherContext(
|
||||
isolated_context, main_context, api, &object_cache, false, 0);
|
||||
if (maybe_proxy.IsEmpty())
|
||||
return;
|
||||
auto proxy = maybe_proxy.ToLocalChecked();
|
||||
if (!DeepFreeze(proxy, main_context))
|
||||
if (proxy->IsObject() && !proxy->IsTypedArray() &&
|
||||
!DeepFreeze(v8::Local<v8::Object>::Cast(proxy), main_context))
|
||||
return;
|
||||
|
||||
global.SetReadOnlyNonConfigurable(key, proxy);
|
||||
|
||||
@@ -172,7 +172,6 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer>,
|
||||
void SendTo(v8::Isolate* isolate,
|
||||
gin_helper::ErrorThrower thrower,
|
||||
bool internal,
|
||||
bool send_to_all,
|
||||
int32_t web_contents_id,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> arguments) {
|
||||
@@ -184,8 +183,8 @@ class IPCRenderer : public gin::Wrappable<IPCRenderer>,
|
||||
if (!electron::SerializeV8Value(isolate, arguments, &message)) {
|
||||
return;
|
||||
}
|
||||
electron_browser_remote_->MessageTo(internal, send_to_all, web_contents_id,
|
||||
channel, std::move(message));
|
||||
electron_browser_remote_->MessageTo(internal, web_contents_id, channel,
|
||||
std::move(message));
|
||||
}
|
||||
|
||||
void SendToHost(v8::Isolate* isolate,
|
||||
|
||||
@@ -132,7 +132,6 @@ void ElectronApiServiceImpl::OnConnectionError() {
|
||||
}
|
||||
|
||||
void ElectronApiServiceImpl::Message(bool internal,
|
||||
bool send_to_all,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
int32_t sender_id) {
|
||||
@@ -168,18 +167,6 @@ void ElectronApiServiceImpl::Message(bool internal,
|
||||
v8::Local<v8::Value> args = gin::ConvertToV8(isolate, arguments);
|
||||
|
||||
EmitIPCEvent(context, internal, channel, {}, args, sender_id);
|
||||
|
||||
// Also send the message to all sub-frames.
|
||||
// TODO(MarshallOfSound): Completely move this logic to the main process
|
||||
if (send_to_all) {
|
||||
for (blink::WebFrame* child = frame->FirstChild(); child;
|
||||
child = child->NextSibling())
|
||||
if (child->IsWebLocalFrame()) {
|
||||
v8::Local<v8::Context> child_context =
|
||||
renderer_client_->GetContext(child->ToWebLocalFrame(), isolate);
|
||||
EmitIPCEvent(child_context, internal, channel, {}, args, sender_id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ElectronApiServiceImpl::ReceivePostMessage(
|
||||
|
||||
@@ -29,7 +29,6 @@ class ElectronApiServiceImpl : public mojom::ElectronRenderer,
|
||||
mojo::PendingAssociatedReceiver<mojom::ElectronRenderer> receiver);
|
||||
|
||||
void Message(bool internal,
|
||||
bool send_to_all,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments,
|
||||
int32_t sender_id) override;
|
||||
|
||||
@@ -104,6 +104,27 @@ describe('contextBridge', () => {
|
||||
};
|
||||
|
||||
it('should proxy numbers', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', 123);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return root.example;
|
||||
});
|
||||
expect(result).to.equal(123);
|
||||
});
|
||||
|
||||
it('should make global properties read-only', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', 123);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
root.example = 456;
|
||||
return root.example;
|
||||
});
|
||||
expect(result).to.equal(123);
|
||||
});
|
||||
|
||||
it('should proxy nested numbers', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
myNumber: 123
|
||||
@@ -129,6 +150,16 @@ describe('contextBridge', () => {
|
||||
});
|
||||
|
||||
it('should proxy strings', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', 'my-words');
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return root.example;
|
||||
});
|
||||
expect(result).to.equal('my-words');
|
||||
});
|
||||
|
||||
it('should proxy nested strings', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
myString: 'my-words'
|
||||
@@ -141,6 +172,16 @@ describe('contextBridge', () => {
|
||||
});
|
||||
|
||||
it('should proxy arrays', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', [123, 'my-words']);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return [root.example, Array.isArray(root.example)];
|
||||
});
|
||||
expect(result).to.deep.equal([[123, 'my-words'], true]);
|
||||
});
|
||||
|
||||
it('should proxy nested arrays', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
myArr: [123, 'my-words']
|
||||
@@ -153,6 +194,21 @@ describe('contextBridge', () => {
|
||||
});
|
||||
|
||||
it('should make arrays immutable', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', [123, 'my-words']);
|
||||
});
|
||||
const immutable = await callWithBindings((root: any) => {
|
||||
try {
|
||||
root.example.push(456);
|
||||
return false;
|
||||
} catch {
|
||||
return true;
|
||||
}
|
||||
});
|
||||
expect(immutable).to.equal(true);
|
||||
});
|
||||
|
||||
it('should make nested arrays immutable', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
myArr: [123, 'my-words']
|
||||
@@ -170,6 +226,16 @@ describe('contextBridge', () => {
|
||||
});
|
||||
|
||||
it('should proxy booleans', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', true);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return root.example;
|
||||
});
|
||||
expect(result).to.equal(true);
|
||||
});
|
||||
|
||||
it('should proxy nested booleans', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
myBool: true
|
||||
@@ -182,6 +248,18 @@ describe('contextBridge', () => {
|
||||
});
|
||||
|
||||
it('should proxy promises and resolve with the correct value', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example',
|
||||
Promise.resolve('i-resolved')
|
||||
);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return root.example;
|
||||
});
|
||||
expect(result).to.equal('i-resolved');
|
||||
});
|
||||
|
||||
it('should proxy nested promises and resolve with the correct value', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
myPromise: Promise.resolve('i-resolved')
|
||||
@@ -194,6 +272,21 @@ describe('contextBridge', () => {
|
||||
});
|
||||
|
||||
it('should proxy promises and reject with the correct value', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', Promise.reject(new Error('i-rejected')));
|
||||
});
|
||||
const result = await callWithBindings(async (root: any) => {
|
||||
try {
|
||||
await root.example;
|
||||
return null;
|
||||
} catch (err) {
|
||||
return err;
|
||||
}
|
||||
});
|
||||
expect(result).to.be.an.instanceOf(Error).with.property('message', 'Uncaught Error: i-rejected');
|
||||
});
|
||||
|
||||
it('should proxy nested promises and reject with the correct value', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
myPromise: Promise.reject(new Error('i-rejected'))
|
||||
@@ -249,6 +342,16 @@ describe('contextBridge', () => {
|
||||
expect(result).to.deep.equal([123, 'help', false, 'promise']);
|
||||
});
|
||||
|
||||
it('should proxy functions', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', () => 'return-value');
|
||||
});
|
||||
const result = await callWithBindings(async (root: any) => {
|
||||
return root.example();
|
||||
});
|
||||
expect(result).equal('return-value');
|
||||
});
|
||||
|
||||
it('should proxy methods that are callable multiple times', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
@@ -299,7 +402,31 @@ describe('contextBridge', () => {
|
||||
expect(result).to.deep.equal([123, 456, 789, false]);
|
||||
});
|
||||
|
||||
it('it should proxy null and undefined correctly', async () => {
|
||||
it('it should proxy null', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', null);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
// Convert to strings as although the context bridge keeps the right value
|
||||
// IPC does not
|
||||
return `${root.example}`;
|
||||
});
|
||||
expect(result).to.deep.equal('null');
|
||||
});
|
||||
|
||||
it('it should proxy undefined', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', undefined);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
// Convert to strings as although the context bridge keeps the right value
|
||||
// IPC does not
|
||||
return `${root.example}`;
|
||||
});
|
||||
expect(result).to.deep.equal('undefined');
|
||||
});
|
||||
|
||||
it('it should proxy nested null and undefined correctly', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
values: [null, undefined]
|
||||
@@ -313,6 +440,19 @@ describe('contextBridge', () => {
|
||||
expect(result).to.deep.equal(['null', 'undefined']);
|
||||
});
|
||||
|
||||
it('should proxy symbols', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
const mySymbol = Symbol('unique');
|
||||
const isSymbol = (s: Symbol) => s === mySymbol;
|
||||
contextBridge.exposeInMainWorld('symbol', mySymbol);
|
||||
contextBridge.exposeInMainWorld('isSymbol', isSymbol);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return root.isSymbol(root.symbol);
|
||||
});
|
||||
expect(result).to.equal(true, 'symbols should be equal across contexts');
|
||||
});
|
||||
|
||||
it('should proxy symbols such that symbol equality works', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
const mySymbol = Symbol('unique');
|
||||
@@ -341,6 +481,26 @@ describe('contextBridge', () => {
|
||||
expect(result).to.equal(123, 'symbols key lookup should work across contexts');
|
||||
});
|
||||
|
||||
it('should proxy typed arrays', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', new Uint8Array(100));
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return Object.getPrototypeOf(root.example) === Uint8Array.prototype;
|
||||
});
|
||||
expect(result).equal(true);
|
||||
});
|
||||
|
||||
it('should proxy regexps', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', /a/g);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return Object.getPrototypeOf(root.example) === RegExp.prototype;
|
||||
});
|
||||
expect(result).equal(true);
|
||||
});
|
||||
|
||||
it('should proxy typed arrays and regexps through the serializer', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
@@ -466,6 +626,22 @@ describe('contextBridge', () => {
|
||||
expect(result).to.equal('final value');
|
||||
});
|
||||
|
||||
it('should work with complex nested methods and promises attached directly to the global', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example',
|
||||
(second: Function) => second((fourth: Function) => {
|
||||
return fourth();
|
||||
})
|
||||
);
|
||||
});
|
||||
const result = await callWithBindings((root: any) => {
|
||||
return root.example((third: Function) => {
|
||||
return third(() => Promise.resolve('final value'));
|
||||
});
|
||||
});
|
||||
expect(result).to.equal('final value');
|
||||
});
|
||||
|
||||
it('should throw an error when recursion depth is exceeded', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
contextBridge.exposeInMainWorld('example', {
|
||||
@@ -641,6 +817,127 @@ describe('contextBridge', () => {
|
||||
expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true));
|
||||
});
|
||||
|
||||
it('should not leak prototypes when attaching directly to the global', async () => {
|
||||
await makeBindingWindow(() => {
|
||||
const toExpose = {
|
||||
number: 123,
|
||||
string: 'string',
|
||||
boolean: true,
|
||||
arr: [123, 'string', true, ['foo']],
|
||||
symbol: Symbol('foo'),
|
||||
bigInt: 10n,
|
||||
getObject: () => ({ thing: 123 }),
|
||||
getNumber: () => 123,
|
||||
getString: () => 'string',
|
||||
getBoolean: () => true,
|
||||
getArr: () => [123, 'string', true, ['foo']],
|
||||
getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] }),
|
||||
getFunctionFromFunction: async () => () => null,
|
||||
object: {
|
||||
number: 123,
|
||||
string: 'string',
|
||||
boolean: true,
|
||||
arr: [123, 'string', true, ['foo']],
|
||||
getPromise: async () => ({ number: 123, string: 'string', boolean: true, fn: () => 'string', arr: [123, 'string', true, ['foo']] })
|
||||
},
|
||||
receiveArguments: (fn: any) => fn({ key: 'value' }),
|
||||
symbolKeyed: {
|
||||
[Symbol('foo')]: 123
|
||||
}
|
||||
};
|
||||
const keys: string[] = [];
|
||||
Object.entries(toExpose).forEach(([key, value]) => {
|
||||
keys.push(key);
|
||||
contextBridge.exposeInMainWorld(key, value);
|
||||
});
|
||||
contextBridge.exposeInMainWorld('keys', keys);
|
||||
});
|
||||
const result = await callWithBindings(async (root: any) => {
|
||||
const { keys } = root;
|
||||
const cleanedRoot: any = {};
|
||||
for (const [key, value] of Object.entries(root)) {
|
||||
if (keys.includes(key)) {
|
||||
cleanedRoot[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
let arg: any;
|
||||
cleanedRoot.receiveArguments((o: any) => { arg = o; });
|
||||
const protoChecks = [
|
||||
...Object.keys(cleanedRoot).map(key => [key, String]),
|
||||
...Object.getOwnPropertySymbols(cleanedRoot.symbolKeyed).map(key => [key, Symbol]),
|
||||
[cleanedRoot, Object],
|
||||
[cleanedRoot.number, Number],
|
||||
[cleanedRoot.string, String],
|
||||
[cleanedRoot.boolean, Boolean],
|
||||
[cleanedRoot.arr, Array],
|
||||
[cleanedRoot.arr[0], Number],
|
||||
[cleanedRoot.arr[1], String],
|
||||
[cleanedRoot.arr[2], Boolean],
|
||||
[cleanedRoot.arr[3], Array],
|
||||
[cleanedRoot.arr[3][0], String],
|
||||
[cleanedRoot.symbol, Symbol],
|
||||
[cleanedRoot.bigInt, BigInt],
|
||||
[cleanedRoot.getNumber, Function],
|
||||
[cleanedRoot.getNumber(), Number],
|
||||
[cleanedRoot.getObject(), Object],
|
||||
[cleanedRoot.getString(), String],
|
||||
[cleanedRoot.getBoolean(), Boolean],
|
||||
[cleanedRoot.getArr(), Array],
|
||||
[cleanedRoot.getArr()[0], Number],
|
||||
[cleanedRoot.getArr()[1], String],
|
||||
[cleanedRoot.getArr()[2], Boolean],
|
||||
[cleanedRoot.getArr()[3], Array],
|
||||
[cleanedRoot.getArr()[3][0], String],
|
||||
[cleanedRoot.getFunctionFromFunction, Function],
|
||||
[cleanedRoot.getFunctionFromFunction(), Promise],
|
||||
[await cleanedRoot.getFunctionFromFunction(), Function],
|
||||
[cleanedRoot.getPromise(), Promise],
|
||||
[await cleanedRoot.getPromise(), Object],
|
||||
[(await cleanedRoot.getPromise()).number, Number],
|
||||
[(await cleanedRoot.getPromise()).string, String],
|
||||
[(await cleanedRoot.getPromise()).boolean, Boolean],
|
||||
[(await cleanedRoot.getPromise()).fn, Function],
|
||||
[(await cleanedRoot.getPromise()).fn(), String],
|
||||
[(await cleanedRoot.getPromise()).arr, Array],
|
||||
[(await cleanedRoot.getPromise()).arr[0], Number],
|
||||
[(await cleanedRoot.getPromise()).arr[1], String],
|
||||
[(await cleanedRoot.getPromise()).arr[2], Boolean],
|
||||
[(await cleanedRoot.getPromise()).arr[3], Array],
|
||||
[(await cleanedRoot.getPromise()).arr[3][0], String],
|
||||
[cleanedRoot.object, Object],
|
||||
[cleanedRoot.object.number, Number],
|
||||
[cleanedRoot.object.string, String],
|
||||
[cleanedRoot.object.boolean, Boolean],
|
||||
[cleanedRoot.object.arr, Array],
|
||||
[cleanedRoot.object.arr[0], Number],
|
||||
[cleanedRoot.object.arr[1], String],
|
||||
[cleanedRoot.object.arr[2], Boolean],
|
||||
[cleanedRoot.object.arr[3], Array],
|
||||
[cleanedRoot.object.arr[3][0], String],
|
||||
[await cleanedRoot.object.getPromise(), Object],
|
||||
[(await cleanedRoot.object.getPromise()).number, Number],
|
||||
[(await cleanedRoot.object.getPromise()).string, String],
|
||||
[(await cleanedRoot.object.getPromise()).boolean, Boolean],
|
||||
[(await cleanedRoot.object.getPromise()).fn, Function],
|
||||
[(await cleanedRoot.object.getPromise()).fn(), String],
|
||||
[(await cleanedRoot.object.getPromise()).arr, Array],
|
||||
[(await cleanedRoot.object.getPromise()).arr[0], Number],
|
||||
[(await cleanedRoot.object.getPromise()).arr[1], String],
|
||||
[(await cleanedRoot.object.getPromise()).arr[2], Boolean],
|
||||
[(await cleanedRoot.object.getPromise()).arr[3], Array],
|
||||
[(await cleanedRoot.object.getPromise()).arr[3][0], String],
|
||||
[arg, Object],
|
||||
[arg.key, String]
|
||||
];
|
||||
return {
|
||||
protoMatches: protoChecks.map(([a, Constructor]) => Object.getPrototypeOf(a) === Constructor.prototype)
|
||||
};
|
||||
});
|
||||
// Every protomatch should be true
|
||||
expect(result.protoMatches).to.deep.equal(result.protoMatches.map(() => true));
|
||||
});
|
||||
|
||||
describe('internalContextBridge', () => {
|
||||
describe('overrideGlobalValueFromIsolatedWorld', () => {
|
||||
it('should override top level properties', async () => {
|
||||
|
||||
@@ -2,6 +2,18 @@ import { expect } from 'chai';
|
||||
import { screen } from 'electron/main';
|
||||
|
||||
describe('screen module', () => {
|
||||
describe('methods reassignment', () => {
|
||||
it('works for a selected method', () => {
|
||||
const originalFunction = screen.getPrimaryDisplay;
|
||||
try {
|
||||
(screen as any).getPrimaryDisplay = () => null;
|
||||
expect(screen.getPrimaryDisplay()).to.be.null();
|
||||
} finally {
|
||||
screen.getPrimaryDisplay = originalFunction;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('screen.getCursorScreenPoint()', () => {
|
||||
it('returns a point object', () => {
|
||||
const point = screen.getCursorScreenPoint();
|
||||
|
||||
@@ -1444,7 +1444,8 @@ describe('asar package', function () {
|
||||
|
||||
it('reads a normal file with unpacked files', function () {
|
||||
const p = path.join(asarDir, 'unpack.asar', 'a.txt');
|
||||
expect(internalModuleReadJSON(p).toString().trim()).to.equal('a');
|
||||
const [s, c] = internalModuleReadJSON(p);
|
||||
expect([s.toString().trim(), c]).to.eql(['a', true]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
2
typings/internal-ambient.d.ts
vendored
2
typings/internal-ambient.d.ts
vendored
@@ -32,7 +32,7 @@ declare namespace NodeJS {
|
||||
send(internal: boolean, channel: string, args: any[]): void;
|
||||
sendSync(internal: boolean, channel: string, args: any[]): any;
|
||||
sendToHost(channel: string, args: any[]): void;
|
||||
sendTo(internal: boolean, sendToAll: boolean, webContentsId: number, channel: string, args: any[]): void;
|
||||
sendTo(internal: boolean, webContentsId: number, channel: string, args: any[]): void;
|
||||
invoke<T>(internal: boolean, channel: string, args: any[]): Promise<{ error: string, result: T }>;
|
||||
postMessage(channel: string, message: any, transferables: MessagePort[]): void;
|
||||
}
|
||||
|
||||
7
typings/internal-electron.d.ts
vendored
7
typings/internal-electron.d.ts
vendored
@@ -67,12 +67,11 @@ declare namespace Electron {
|
||||
_windowOpenHandler: ((opts: {url: string, frameName: string, features: string}) => any) | null;
|
||||
_callWindowOpenHandler(event: any, url: string, frameName: string, rawFeatures: string): Electron.BrowserWindowConstructorOptions | null;
|
||||
_setNextChildWebPreferences(prefs: Partial<Electron.BrowserWindowConstructorOptions['webPreferences']> & Pick<Electron.BrowserWindowConstructorOptions, 'backgroundColor'>): void;
|
||||
_send(internal: boolean, sendToAll: boolean, channel: string, args: any): boolean;
|
||||
_sendToFrame(internal: boolean, sendToAll: boolean, frameId: number, channel: string, args: any): boolean;
|
||||
_send(internal: boolean, channel: string, args: any): boolean;
|
||||
_sendToFrame(internal: boolean, frameId: number, channel: string, args: any): boolean;
|
||||
_sendToFrameInternal(frameId: number, channel: string, ...args: any[]): boolean;
|
||||
_postMessage(channel: string, message: any, transfer?: any[]): void;
|
||||
_sendInternal(channel: string, ...args: any[]): void;
|
||||
_sendInternalToAll(channel: string, ...args: any[]): void;
|
||||
_printToPDF(options: any): Promise<Buffer>;
|
||||
_print(options: any, callback?: (success: boolean, failureReason: string) => void): void;
|
||||
_getPrinters(): Electron.PrinterInfo[];
|
||||
@@ -232,14 +231,12 @@ declare namespace ElectronInternal {
|
||||
|
||||
interface IpcRendererInternal extends Electron.IpcRenderer {
|
||||
invoke<T>(channel: string, ...args: any[]): Promise<T>;
|
||||
sendToAll(webContentsId: number, channel: string, ...args: any[]): void;
|
||||
onMessageFromMain(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void): this;
|
||||
onceMessageFromMain(channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void): this;
|
||||
}
|
||||
|
||||
// Internal IPC has _replyInternal and NO reply method
|
||||
interface IpcMainInternalEvent extends Omit<Electron.IpcMainEvent, 'reply'> {
|
||||
_replyInternal(...args: any[]): void;
|
||||
}
|
||||
|
||||
interface IpcMainInternal extends NodeJS.EventEmitter {
|
||||
|
||||
1
vendor/boto
vendored
1
vendor/boto
vendored
Submodule vendor/boto deleted from f7574aa6cc
Reference in New Issue
Block a user