mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
32 Commits
v12.0.0-be
...
v12.0.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9faf23509d | ||
|
|
b4ae35a63d | ||
|
|
025abfc1ba | ||
|
|
563bb8559f | ||
|
|
30be5bfa4b | ||
|
|
28fa60fbcb | ||
|
|
37c68d56d6 | ||
|
|
da58ded8f9 | ||
|
|
80a3f10b6b | ||
|
|
889abd0c8e | ||
|
|
0305f08888 | ||
|
|
0604f8727c | ||
|
|
551896c4ce | ||
|
|
c11a5dcf29 | ||
|
|
b86eb74fbf | ||
|
|
e49a88ba53 | ||
|
|
4ab817768d | ||
|
|
f730284113 | ||
|
|
6122f4bece | ||
|
|
017628f84d | ||
|
|
eb132d8b3e | ||
|
|
f26025301a | ||
|
|
a79750b871 | ||
|
|
870d8c0307 | ||
|
|
1f22b2bfdc | ||
|
|
e25de07657 | ||
|
|
6bfccca157 | ||
|
|
58c1ce50d4 | ||
|
|
c46ed96421 | ||
|
|
9278459c46 | ||
|
|
a6af3bd8df | ||
|
|
c74780117e |
13
BUILD.gn
13
BUILD.gn
@@ -1155,6 +1155,19 @@ if (is_mac) {
|
||||
ldflags += [ "/guard:cf,nolongjmp" ]
|
||||
}
|
||||
|
||||
if (current_cpu == "x86") {
|
||||
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
|
||||
# Chrome's main thread. This saves significant memory on threads (like
|
||||
# those in the Windows thread pool, and others) whose stack size we can
|
||||
# only control through this setting. Because Chrome's main thread needs
|
||||
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
|
||||
# fibers to switch to a 1.5 MiB stack before running any other code.
|
||||
ldflags += [ "/STACK:0x80000" ]
|
||||
} else {
|
||||
# Increase the initial stack size. The default is 1MB, this is 8MB.
|
||||
ldflags += [ "/STACK:0x800000" ]
|
||||
}
|
||||
|
||||
# This is to support renaming of electron.exe. node-gyp has hard-coded
|
||||
# executable names which it will recognise as node. This module definition
|
||||
# file claims that the electron executable is in fact named "node.exe",
|
||||
|
||||
@@ -1 +1 @@
|
||||
12.0.0-beta.16
|
||||
12.0.0-beta.20
|
||||
@@ -29,15 +29,12 @@ The preferred method is to install Electron as a development dependency in your
|
||||
app:
|
||||
|
||||
```sh
|
||||
npm install electron --save-dev [--save-exact]
|
||||
npm install electron --save-dev
|
||||
```
|
||||
|
||||
The `--save-exact` flag is recommended for Electron prior to version 2, as it does not follow semantic
|
||||
versioning. As of version 2.0.0, Electron follows semver, so you don't need `--save-exact` flag. For info on how to manage Electron versions in your apps, see
|
||||
[Electron versioning](docs/tutorial/electron-versioning.md).
|
||||
|
||||
For more installation options and troubleshooting tips, see
|
||||
[installation](docs/tutorial/installation.md).
|
||||
[installation](docs/tutorial/installation.md). For info on how to manage Electron versions in your apps, see
|
||||
[Electron versioning](docs/tutorial/electron-versioning.md).
|
||||
|
||||
## Quick start & Electron Fiddle
|
||||
|
||||
|
||||
@@ -143,10 +143,17 @@ static_library("chrome") {
|
||||
"//chrome/browser/platform_util.h",
|
||||
"//chrome/browser/ui/browser_dialogs.h",
|
||||
"//chrome/browser/ui/color_chooser.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper.cc",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view.cc",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view.h",
|
||||
]
|
||||
|
||||
if (use_aura) {
|
||||
sources += [ "//chrome/browser/platform_util_aura.cc" ]
|
||||
sources += [
|
||||
"//chrome/browser/platform_util_aura.cc",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc",
|
||||
]
|
||||
|
||||
if (!is_win) {
|
||||
sources += [
|
||||
@@ -163,6 +170,8 @@ static_library("chrome") {
|
||||
"//chrome/browser/media/webrtc/window_icon_util_mac.mm",
|
||||
"//chrome/browser/ui/cocoa/color_chooser_mac.h",
|
||||
"//chrome/browser/ui/cocoa/color_chooser_mac.mm",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.mm",
|
||||
]
|
||||
deps += [
|
||||
"//components/remote_cocoa/app_shim",
|
||||
|
||||
@@ -148,6 +148,7 @@ These individual tutorials expand on topics discussed in the guide above.
|
||||
|
||||
### Modules for the Renderer Process (Web Page):
|
||||
|
||||
* [contextBridge](api/context-bridge.md)
|
||||
* [desktopCapturer](api/desktop-capturer.md)
|
||||
* [ipcRenderer](api/ipc-renderer.md)
|
||||
* [remote](api/remote.md)
|
||||
|
||||
4
docs/api/app.md
Normal file → Executable file
4
docs/api/app.md
Normal file → Executable file
@@ -1174,9 +1174,9 @@ For `infoType` equal to `basic`:
|
||||
|
||||
Using `basic` should be preferred if only basic information like `vendorId` or `driverId` is needed.
|
||||
|
||||
### `app.setBadgeCount(count)` _Linux_ _macOS_
|
||||
### `app.setBadgeCount([count])` _Linux_ _macOS_
|
||||
|
||||
* `count` Integer
|
||||
* `count` Integer (optional) - If a value is provided, set the badge to the provided value otherwise, on macOS, display a plain white dot (e.g. unknown number of notifications). On Linux, if a value is not provided the badge will not display.
|
||||
|
||||
Returns `Boolean` - Whether the call succeeded.
|
||||
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -62,10 +62,9 @@ included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
|
||||
> **NOTE:** Since the main process does not have support for DOM objects such as
|
||||
> special Electron objects will throw an exception.
|
||||
>
|
||||
> Since the main process does not have support for DOM objects such as
|
||||
> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over
|
||||
> Electron's IPC to the main process, as the main process would have no way to decode
|
||||
> them. Attempting to send such objects over IPC will result in an error.
|
||||
@@ -90,11 +89,10 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no
|
||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
|
||||
> **NOTE:** Since the main process does not have support for DOM objects such as
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects will throw an exception.
|
||||
>
|
||||
> Since the main process does not have support for DOM objects such as
|
||||
> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over
|
||||
> Electron's IPC to the main process, as the main process would have no way to decode
|
||||
> them. Attempting to send such objects over IPC will result in an error.
|
||||
@@ -134,11 +132,10 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no
|
||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
|
||||
> **NOTE:** Since the main process does not have support for DOM objects such as
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects will throw an exception.
|
||||
>
|
||||
> Since the main process does not have support for DOM objects such as
|
||||
> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over
|
||||
> Electron's IPC to the main process, as the main process would have no way to decode
|
||||
> them. Attempting to send such objects over IPC will result in an error.
|
||||
|
||||
@@ -1658,8 +1658,7 @@ included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
> special Electron objects will throw an exception.
|
||||
|
||||
The renderer process can handle the message by listening to `channel` with the
|
||||
[`ipcRenderer`](ipc-renderer.md) module.
|
||||
@@ -1707,9 +1706,8 @@ Send an asynchronous message to a specific frame in a renderer process via
|
||||
chains will not be included. Sending Functions, Promises, Symbols, WeakMaps, or
|
||||
WeakSets will throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects will throw an exception.
|
||||
|
||||
The renderer process can handle the message by listening to `channel` with the
|
||||
[`ipcRenderer`](ipc-renderer.md) module.
|
||||
@@ -1872,7 +1870,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()`
|
||||
|
||||
@@ -1970,7 +1968,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.
|
||||
|
||||
|
||||
@@ -101,6 +101,47 @@ Works like `executeJavaScript` but evaluates `scripts` in an isolated context.
|
||||
|
||||
Returns `boolean` - Whether the reload was initiated successfully. Only results in `false` when the frame has no history.
|
||||
|
||||
#### `frame.send(channel, ...args)`
|
||||
|
||||
* `channel` String
|
||||
* `...args` any[]
|
||||
|
||||
Send an asynchronous message to the renderer process via `channel`, along with
|
||||
arguments. Arguments will be serialized with the [Structured Clone
|
||||
Algorithm][SCA], just like [`postMessage`][], so prototype chains will not be
|
||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
The renderer process can handle the message by listening to `channel` with the
|
||||
[`ipcRenderer`](ipc-renderer.md) module.
|
||||
|
||||
#### `frame.postMessage(channel, message, [transfer])`
|
||||
|
||||
* `channel` String
|
||||
* `message` any
|
||||
* `transfer` MessagePortMain[] (optional)
|
||||
|
||||
Send a message to the renderer process, optionally transferring ownership of
|
||||
zero or more [`MessagePortMain`][] objects.
|
||||
|
||||
The transferred `MessagePortMain` objects will be available in the renderer
|
||||
process by accessing the `ports` property of the emitted event. When they
|
||||
arrive in the renderer, they will be native DOM `MessagePort` objects.
|
||||
|
||||
For example:
|
||||
|
||||
```js
|
||||
// Main process
|
||||
const { port1, port2 } = new MessageChannelMain()
|
||||
webContents.mainFrame.postMessage('port', { message: 'hello' }, [port1])
|
||||
|
||||
// Renderer process
|
||||
ipcRenderer.on('port', (e, msg) => {
|
||||
const [port] = e.ports
|
||||
// ...
|
||||
})
|
||||
```
|
||||
|
||||
### Instance Properties
|
||||
|
||||
#### `frame.url` _Readonly_
|
||||
|
||||
@@ -51,6 +51,8 @@ The following methods are available on instances of `WebRequest`:
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
@@ -94,6 +96,8 @@ Some examples of valid `urls`:
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
@@ -121,6 +125,8 @@ The `callback` has to be called with a `response` object.
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
@@ -141,6 +147,8 @@ response are visible by the time this listener is fired.
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
@@ -173,6 +181,8 @@ The `callback` has to be called with a `response` object.
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
@@ -197,6 +207,8 @@ and response headers are available.
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
@@ -222,6 +234,8 @@ redirect is about to occur.
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
@@ -245,6 +259,8 @@ completed.
|
||||
* `url` String
|
||||
* `method` String
|
||||
* `webContentsId` Integer (optional)
|
||||
* `webContents` WebContents (optional)
|
||||
* `frame` WebFrameMain (optional)
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
|
||||
@@ -12,6 +12,16 @@ This document uses the following convention to categorize breaking changes:
|
||||
- **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release.
|
||||
- **Removed:** An API or feature was removed, and is no longer supported by Electron.
|
||||
|
||||
## Planned Breaking API Changes (14.0)
|
||||
|
||||
### Removed: `worldSafeExecuteJavaScript`
|
||||
|
||||
In Electron 14, `worldSafeExecuteJavaScript` will be removed. There is no alternative, please
|
||||
ensure your code works with this property enabled. It has been enabled by default since Electron
|
||||
12.
|
||||
|
||||
You will be affected by this change if you use either `webFrame.executeJavaScript` or `webFrame.executeJavaScriptInIsolatedWorld`. You will need to ensure that values returned by either of those methods are supported by the [Context Bridge API](api/context-bridge.md#parameter--error--return-type-support) as these methods use the same value passing semantics.
|
||||
|
||||
## Planned Breaking API Changes (13.0)
|
||||
|
||||
### Removed: `shell.moveItemToTrash()`
|
||||
@@ -34,6 +44,15 @@ Chromium has removed support for Flash, and so we must follow suit. See
|
||||
Chromium's [Flash Roadmap](https://www.chromium.org/flash-roadmap) for more
|
||||
details.
|
||||
|
||||
### Default Changed: `worldSafeExecuteJavaScript` defaults to `true`
|
||||
|
||||
In Electron 12, `worldSafeExecuteJavaScript` will be enabled by default. To restore
|
||||
the previous behavior, `worldSafeExecuteJavaScript: false` must be specified in WebPreferences.
|
||||
Please note that setting this option to `false` is **insecure**.
|
||||
|
||||
This option will be removed in Electron 14 so please migrate your code to support the default
|
||||
value.
|
||||
|
||||
### Default Changed: `contextIsolation` defaults to `true`
|
||||
|
||||
In Electron 12, `contextIsolation` will be enabled by default. To restore
|
||||
|
||||
@@ -42,7 +42,7 @@ $ pip install pyobjc
|
||||
If you're developing Electron and don't plan to redistribute your
|
||||
custom Electron build, you may skip this section.
|
||||
|
||||
Official Electron builds are built with [Xcode 9.4.1](http://adcdownload.apple.com/Developer_Tools/Xcode_9.4.1/Xcode_9.4.1.xip), and the macOS 10.13 SDK. Building with a newer SDK works too, but the releases currently use the 10.13 SDK.
|
||||
Official Electron builds are built with [Xcode 12.2](https://download.developer.apple.com/Developer_Tools/Xcode_12.2/Xcode_12.2.xip), and the macOS 11.0 SDK. Building with a newer SDK works too, but the releases currently use the 11.0 SDK.
|
||||
|
||||
## Building Electron
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ Two modes of rendering can be used and only the dirty area is passed in the
|
||||
`'paint'` event to be more efficient. The rendering can be stopped, continued
|
||||
and the frame rate can be set. The specified frame rate is a top limit value,
|
||||
when there is nothing happening on a webpage, no frames are generated. The
|
||||
maximum frame rate is 60, because above that there is no benefit, only
|
||||
maximum frame rate is 240, because above that there is no benefit, only
|
||||
performance loss.
|
||||
|
||||
**Note:** An offscreen window is always created as a [Frameless Window](../api/frameless-window.md).
|
||||
|
||||
@@ -83,5 +83,18 @@
|
||||
<message name="IDS_DOWNLOAD_MORE_ACTIONS"
|
||||
desc="Tooltip of a button on the downloads page that shows a menu with actions like 'Open downloads folder' or 'Clear all'">
|
||||
More actions
|
||||
</message>
|
||||
</message>
|
||||
<!-- Badging -->
|
||||
<message name="IDS_SATURATED_BADGE_CONTENT" desc="The content to display when the application's badge is too large to display to indicate that the badge is more than a given maximum. This string should be as short as possible, preferably only one character beyond the content">
|
||||
<ph name="MAXIMUM_VALUE">$1<ex>99</ex></ph>+
|
||||
</message>
|
||||
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS_SATURATED" desc="The accessibility text which will be read by a screen reader when the notification count is too large to display (e.g. greater than 99).">
|
||||
{MAX_UNREAD_NOTIFICATIONS, plural, =1 {More than 1 unread notification} other {More than # unread notifications}}
|
||||
</message>
|
||||
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS_UNSPECIFIED" desc="The accessibility text which will be read by a screen reader when there are some unspecified number of notifications, or user attention is required">
|
||||
Unread Notifications
|
||||
</message>
|
||||
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS" desc="The accessibility text which will be read by a screen reader when there are notifcatications">
|
||||
{UNREAD_NOTIFICATIONS, plural, =1 {1 Unread Notification} other {# Unread Notifications}}
|
||||
</message>
|
||||
</grit-part>
|
||||
|
||||
@@ -328,6 +328,10 @@ filenames = {
|
||||
"shell/browser/api/ui_event.h",
|
||||
"shell/browser/auto_updater.cc",
|
||||
"shell/browser/auto_updater.h",
|
||||
"shell/browser/badging/badge_manager.cc",
|
||||
"shell/browser/badging/badge_manager.h",
|
||||
"shell/browser/badging/badge_manager_factory.cc",
|
||||
"shell/browser/badging/badge_manager_factory.h",
|
||||
"shell/browser/browser.cc",
|
||||
"shell/browser/browser.h",
|
||||
"shell/browser/browser_observer.h",
|
||||
|
||||
@@ -126,22 +126,16 @@ const binding = process._linkedBinding('electron_browser_web_contents');
|
||||
const printing = process._linkedBinding('electron_browser_printing');
|
||||
const { WebContents } = binding as { WebContents: { prototype: Electron.WebContents } };
|
||||
|
||||
WebContents.prototype.postMessage = function (...args) {
|
||||
return this.mainFrame.postMessage(...args);
|
||||
};
|
||||
|
||||
WebContents.prototype.send = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = false;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
};
|
||||
|
||||
WebContents.prototype.postMessage = function (...args) {
|
||||
if (Array.isArray(args[2])) {
|
||||
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
|
||||
}
|
||||
this._postMessage(...args);
|
||||
return this._send(false /* internal */, channel, args);
|
||||
};
|
||||
|
||||
WebContents.prototype._sendInternal = function (channel, ...args) {
|
||||
@@ -149,44 +143,31 @@ 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);
|
||||
return this._send(true /* internal */, channel, args);
|
||||
};
|
||||
WebContents.prototype._sendInternalToAll = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
|
||||
function getWebFrame (contents: Electron.WebContents, frame: number | [number, number]) {
|
||||
if (typeof frame === 'number') {
|
||||
return webFrameMain.fromId(contents.mainFrame.processId, frame);
|
||||
} else if (Array.isArray(frame) && frame.length === 2 && frame.every(value => typeof value === 'number')) {
|
||||
return webFrameMain.fromId(frame[0], frame[1]);
|
||||
} else {
|
||||
throw new Error('Missing required frame argument (must be number or [processId, frameId])');
|
||||
}
|
||||
}
|
||||
|
||||
const internal = true;
|
||||
const sendToAll = true;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
const frame = getWebFrame(this, frameId);
|
||||
if (!frame) return false;
|
||||
frame.send(channel, ...args);
|
||||
return true;
|
||||
};
|
||||
WebContents.prototype.sendToFrame = function (frame, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
|
||||
throw new Error('Missing required frame argument (must be number or array)');
|
||||
}
|
||||
|
||||
const internal = false;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frame, channel, args);
|
||||
};
|
||||
WebContents.prototype._sendToFrameInternal = function (frame, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
|
||||
throw new Error('Missing required frame argument (must be number or array)');
|
||||
}
|
||||
|
||||
const internal = true;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frame, channel, args);
|
||||
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
|
||||
const frame = getWebFrame(this, frameId);
|
||||
if (!frame) return false;
|
||||
frame._sendInternal(channel, ...args);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Following methods are mapped to webFrame.
|
||||
@@ -199,7 +180,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 +198,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.
|
||||
|
||||
@@ -1,4 +1,29 @@
|
||||
const { fromId } = process._linkedBinding('electron_browser_web_frame_main');
|
||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||
|
||||
const { WebFrameMain, fromId } = process._linkedBinding('electron_browser_web_frame_main');
|
||||
|
||||
WebFrameMain.prototype.send = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
return this._send(false /* internal */, channel, args);
|
||||
};
|
||||
|
||||
WebFrameMain.prototype._sendInternal = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
return this._send(true /* internal */, channel, args);
|
||||
};
|
||||
|
||||
WebFrameMain.prototype.postMessage = function (...args) {
|
||||
if (Array.isArray(args[2])) {
|
||||
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
|
||||
}
|
||||
this._postMessage(...args);
|
||||
};
|
||||
|
||||
export default {
|
||||
fromId
|
||||
|
||||
@@ -47,6 +47,7 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
|
||||
embedder,
|
||||
features,
|
||||
frameName,
|
||||
isNativeWindowOpen,
|
||||
overrideOptions: overrideBrowserWindowOptions
|
||||
});
|
||||
|
||||
@@ -199,10 +200,11 @@ const securityWebPreferences: { [key: string]: boolean } = {
|
||||
enableWebSQL: false
|
||||
};
|
||||
|
||||
function makeBrowserWindowOptions ({ embedder, features, frameName, overrideOptions, useDeprecatedBehaviorForBareValues = true, useDeprecatedBehaviorForOptionInheritance = true }: {
|
||||
function makeBrowserWindowOptions ({ embedder, features, frameName, isNativeWindowOpen, overrideOptions, useDeprecatedBehaviorForBareValues = true, useDeprecatedBehaviorForOptionInheritance = true }: {
|
||||
embedder: WebContents,
|
||||
features: string,
|
||||
frameName: string,
|
||||
isNativeWindowOpen: boolean,
|
||||
overrideOptions?: BrowserWindowConstructorOptions,
|
||||
useDeprecatedBehaviorForBareValues?: boolean
|
||||
useDeprecatedBehaviorForOptionInheritance?: boolean
|
||||
@@ -216,9 +218,9 @@ function makeBrowserWindowOptions ({ embedder, features, frameName, overrideOpti
|
||||
options: {
|
||||
...(useDeprecatedBehaviorForOptionInheritance && deprecatedInheritedOptions),
|
||||
show: true,
|
||||
title: frameName,
|
||||
width: 800,
|
||||
height: 600,
|
||||
...(!isNativeWindowOpen && { title: frameName }),
|
||||
...parsedOptions,
|
||||
...overrideOptions,
|
||||
webPreferences: makeWebPreferences({ embedder, insecureParsedWebPreferences: parsedWebPreferences, secureOverrideWebPreferences: overrideOptions && overrideOptions.webPreferences, useDeprecatedBehaviorForOptionInheritance: true })
|
||||
|
||||
@@ -145,6 +145,9 @@ require('@electron/internal/browser/api/protocol');
|
||||
// Load web-contents module to ensure it is populated on app ready
|
||||
require('@electron/internal/browser/api/web-contents');
|
||||
|
||||
// Load web-frame-main module to ensure it is populated on app ready
|
||||
require('@electron/internal/browser/api/web-frame-main');
|
||||
|
||||
// Set main startup script of the app.
|
||||
const mainStartupScript = packageJson.main || 'index.js';
|
||||
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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[]) {
|
||||
|
||||
@@ -81,7 +81,7 @@ const isUnsafeEvalEnabled: () => Promise<boolean> = function () {
|
||||
// Call _executeJavaScript to bypass the world-safe deprecation warning
|
||||
return (webFrame as any)._executeJavaScript(`(${(() => {
|
||||
try {
|
||||
new Function(''); // eslint-disable-line no-new,no-new-func
|
||||
eval(window.trustedTypes.emptyScript); // eslint-disable-line no-eval
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -44,7 +44,9 @@ export class WebViewImpl {
|
||||
// Create internal iframe element.
|
||||
this.internalElement = this.createInternalElement();
|
||||
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' });
|
||||
shadowRoot.innerHTML = '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>';
|
||||
const style = shadowRoot.ownerDocument.createElement('style');
|
||||
style.textContent = ':host { display: flex; }';
|
||||
shadowRoot.appendChild(style);
|
||||
this.setupWebViewAttributes();
|
||||
this.viewInstanceId = getNextId();
|
||||
shadowRoot.appendChild(this.internalElement);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "12.0.0-beta.16",
|
||||
"version": "12.0.0-beta.20",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -15,5 +15,7 @@
|
||||
|
||||
"src/electron/patches/depot_tools": "src/third_party/depot_tools",
|
||||
|
||||
"src/electron/patches/icu": "src/third_party/icu",
|
||||
|
||||
"src/electron/patches/nan": "src/third_party/nan"
|
||||
}
|
||||
|
||||
1
patches/icu/.patches
Normal file
1
patches/icu/.patches
Normal file
@@ -0,0 +1 @@
|
||||
fix_apply_tzdata2020f_to_icu.patch
|
||||
1937
patches/icu/fix_apply_tzdata2020f_to_icu.patch
Normal file
1937
patches/icu/fix_apply_tzdata2020f_to_icu.patch
Normal file
File diff suppressed because it is too large
Load Diff
@@ -101,7 +101,7 @@ BaseWindow::BaseWindow(v8::Isolate* isolate,
|
||||
#if defined(TOOLKIT_VIEWS)
|
||||
v8::Local<v8::Value> icon;
|
||||
if (options.Get(options::kIcon, &icon)) {
|
||||
SetIcon(isolate, icon);
|
||||
SetIconImpl(isolate, icon, NativeImage::OnConvertError::kWarn);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@@ -1003,8 +1003,15 @@ bool BaseWindow::SetThumbarButtons(gin_helper::Arguments* args) {
|
||||
|
||||
#if defined(TOOLKIT_VIEWS)
|
||||
void BaseWindow::SetIcon(v8::Isolate* isolate, v8::Local<v8::Value> icon) {
|
||||
SetIconImpl(isolate, icon, NativeImage::OnConvertError::kThrow);
|
||||
}
|
||||
|
||||
void BaseWindow::SetIconImpl(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> icon,
|
||||
NativeImage::OnConvertError on_error) {
|
||||
NativeImage* native_image = nullptr;
|
||||
if (!NativeImage::TryConvertNativeImage(isolate, icon, &native_image))
|
||||
if (!NativeImage::TryConvertNativeImage(isolate, icon, &native_image,
|
||||
on_error))
|
||||
return;
|
||||
|
||||
#if defined(OS_WIN)
|
||||
|
||||
@@ -223,6 +223,9 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
|
||||
bool SetThumbarButtons(gin_helper::Arguments* args);
|
||||
#if defined(TOOLKIT_VIEWS)
|
||||
void SetIcon(v8::Isolate* isolate, v8::Local<v8::Value> icon);
|
||||
void SetIconImpl(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> icon,
|
||||
NativeImage::OnConvertError on_error);
|
||||
#endif
|
||||
#if defined(OS_WIN)
|
||||
typedef base::RepeatingCallback<void(v8::Local<v8::Value>,
|
||||
|
||||
@@ -185,6 +185,7 @@ v8::Local<v8::Promise> Debugger::SendCommand(gin::Arguments* args) {
|
||||
void Debugger::ClearPendingRequests() {
|
||||
for (auto& it : pending_requests_)
|
||||
it.second.RejectWithErrorMessage("target closed while handling command");
|
||||
pending_requests_.clear();
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "base/values.h"
|
||||
#include "chrome/browser/browser_process.h"
|
||||
#include "chrome/browser/ssl/security_state_tab_helper.h"
|
||||
#include "chrome/browser/ui/views/eye_dropper/eye_dropper.h"
|
||||
#include "chrome/common/pref_names.h"
|
||||
#include "components/prefs/pref_service.h"
|
||||
#include "components/prefs/scoped_user_pref_update.h"
|
||||
@@ -616,7 +617,9 @@ WebContents::WebContents(v8::Isolate* isolate,
|
||||
devtools_file_system_indexer_(new DevToolsFileSystemIndexer),
|
||||
file_task_runner_(
|
||||
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
print_task_runner_(CreatePrinterHandlerTaskRunner()),
|
||||
#endif
|
||||
weak_factory_(this) {
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
// WebContents created by extension host will have valid ViewType set.
|
||||
@@ -650,7 +653,9 @@ WebContents::WebContents(v8::Isolate* isolate,
|
||||
devtools_file_system_indexer_(new DevToolsFileSystemIndexer),
|
||||
file_task_runner_(
|
||||
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
print_task_runner_(CreatePrinterHandlerTaskRunner()),
|
||||
#endif
|
||||
weak_factory_(this) {
|
||||
DCHECK(type != Type::kRemote)
|
||||
<< "Can't take ownership of a remote WebContents";
|
||||
@@ -666,7 +671,9 @@ WebContents::WebContents(v8::Isolate* isolate,
|
||||
devtools_file_system_indexer_(new DevToolsFileSystemIndexer),
|
||||
file_task_runner_(
|
||||
base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()})),
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
print_task_runner_(CreatePrinterHandlerTaskRunner()),
|
||||
#endif
|
||||
weak_factory_(this) {
|
||||
// Read options.
|
||||
options.Get("backgroundThrottling", &background_throttling_);
|
||||
@@ -1547,39 +1554,6 @@ void WebContents::ReceivePostMessage(
|
||||
channel, message_value, std::move(wrapped_ports));
|
||||
}
|
||||
|
||||
void WebContents::PostMessage(const std::string& channel,
|
||||
v8::Local<v8::Value> message_value,
|
||||
base::Optional<v8::Local<v8::Value>> transfer) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
blink::TransferableMessage transferable_message;
|
||||
if (!electron::SerializeV8Value(isolate, message_value,
|
||||
&transferable_message)) {
|
||||
// SerializeV8Value sets an exception.
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<gin::Handle<MessagePort>> wrapped_ports;
|
||||
if (transfer) {
|
||||
if (!gin::ConvertFromV8(isolate, *transfer, &wrapped_ports)) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
gin::StringToV8(isolate, "Invalid value for transfer")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool threw_exception = false;
|
||||
transferable_message.ports =
|
||||
MessagePort::DisentanglePorts(isolate, wrapped_ports, &threw_exception);
|
||||
if (threw_exception)
|
||||
return;
|
||||
|
||||
content::RenderFrameHost* frame_host = web_contents()->GetMainFrame();
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||
electron_renderer->ReceivePostMessage(channel,
|
||||
std::move(transferable_message));
|
||||
}
|
||||
|
||||
void WebContents::MessageSync(
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
@@ -1594,7 +1568,6 @@ void WebContents::MessageSync(
|
||||
}
|
||||
|
||||
void WebContents::MessageTo(bool internal,
|
||||
bool send_to_all,
|
||||
int32_t web_contents_id,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) {
|
||||
@@ -1602,7 +1575,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());
|
||||
}
|
||||
}
|
||||
@@ -2691,7 +2664,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();
|
||||
@@ -2701,73 +2673,17 @@ 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);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WebContents::SendIPCMessageToFrame(bool internal,
|
||||
bool send_to_all,
|
||||
v8::Local<v8::Value> frame,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args) {
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, args, &message)) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
gin::StringToV8(isolate, "Failed to serialize arguments")));
|
||||
return false;
|
||||
}
|
||||
int32_t frame_id;
|
||||
int32_t process_id;
|
||||
if (gin::ConvertFromV8(isolate, frame, &frame_id)) {
|
||||
process_id = web_contents()->GetMainFrame()->GetProcess()->GetID();
|
||||
} else {
|
||||
std::vector<int32_t> id_pair;
|
||||
if (gin::ConvertFromV8(isolate, frame, &id_pair) && id_pair.size() == 2) {
|
||||
process_id = id_pair[0];
|
||||
frame_id = id_pair[1];
|
||||
} else {
|
||||
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
|
||||
isolate,
|
||||
"frameId must be a number or a pair of [processId, frameId]")));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
auto* rfh = content::RenderFrameHost::FromID(process_id, frame_id);
|
||||
if (!rfh || !rfh->IsRenderFrameLive() ||
|
||||
content::WebContents::FromRenderFrameHost(rfh) != web_contents())
|
||||
return false;
|
||||
|
||||
auto* frame_host = web_contents()->GetMainFrame();
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
rfh->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||
electron_renderer->Message(internal, send_to_all, channel, std::move(message),
|
||||
0 /* sender_id */);
|
||||
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(&electron_renderer);
|
||||
electron_renderer->Message(internal, channel, std::move(args), sender_id);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3255,6 +3171,12 @@ content::ColorChooser* WebContents::OpenColorChooser(
|
||||
#endif
|
||||
}
|
||||
|
||||
std::unique_ptr<content::EyeDropper> WebContents::OpenEyeDropper(
|
||||
content::RenderFrameHost* frame,
|
||||
content::EyeDropperListener* listener) {
|
||||
return ShowEyeDropper(frame, listener);
|
||||
}
|
||||
|
||||
void WebContents::RunFileChooser(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
@@ -3674,8 +3596,6 @@ v8::Local<v8::ObjectTemplate> WebContents::FillObjectTemplate(
|
||||
.SetMethod("focus", &WebContents::Focus)
|
||||
.SetMethod("isFocused", &WebContents::IsFocused)
|
||||
.SetMethod("_send", &WebContents::SendIPCMessage)
|
||||
.SetMethod("_postMessage", &WebContents::PostMessage)
|
||||
.SetMethod("_sendToFrame", &WebContents::SendIPCMessageToFrame)
|
||||
.SetMethod("sendInputEvent", &WebContents::SendInputEvent)
|
||||
.SetMethod("beginFrameSubscription", &WebContents::BeginFrameSubscription)
|
||||
.SetMethod("endFrameSubscription", &WebContents::EndFrameSubscription)
|
||||
|
||||
@@ -252,26 +252,14 @@ 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,
|
||||
v8::Local<v8::Value> frame,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args);
|
||||
|
||||
void PostMessage(const std::string& channel,
|
||||
v8::Local<v8::Value> message,
|
||||
base::Optional<v8::Local<v8::Value>> transfer);
|
||||
|
||||
// Send WebInputEvent to the page.
|
||||
void SendInputEvent(v8::Isolate* isolate, v8::Local<v8::Value> input_event);
|
||||
|
||||
@@ -439,7 +427,6 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
electron::mojom::ElectronBrowser::MessageSyncCallback callback,
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
void MessageTo(bool internal,
|
||||
bool send_to_all,
|
||||
int32_t web_contents_id,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments);
|
||||
@@ -649,6 +636,9 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
SkColor color,
|
||||
const std::vector<blink::mojom::ColorSuggestionPtr>& suggestions)
|
||||
override;
|
||||
std::unique_ptr<content::EyeDropper> OpenEyeDropper(
|
||||
content::RenderFrameHost* frame,
|
||||
content::EyeDropperListener* listener) override;
|
||||
void RunFileChooser(content::RenderFrameHost* render_frame_host,
|
||||
scoped_refptr<content::FileSelectListener> listener,
|
||||
const blink::mojom::FileChooserParams& params) override;
|
||||
@@ -787,7 +777,10 @@ class WebContents : public gin::Wrappable<WebContents>,
|
||||
DevToolsIndexingJobsMap devtools_indexing_jobs_;
|
||||
|
||||
scoped_refptr<base::SequencedTaskRunner> file_task_runner_;
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
scoped_refptr<base::TaskRunner> print_task_runner_;
|
||||
#endif
|
||||
|
||||
// Stores the frame thats currently in fullscreen, nullptr if there is none.
|
||||
content::RenderFrameHost* fullscreen_frame_ = nullptr;
|
||||
|
||||
@@ -13,9 +13,12 @@
|
||||
#include "base/logging.h"
|
||||
#include "content/browser/renderer_host/frame_tree_node.h" // nogncheck
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "electron/shell/common/api/api.mojom.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "shell/browser/api/message_port.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/blink_converter.h"
|
||||
#include "shell/common/gin_converters/frame_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
@@ -24,6 +27,8 @@
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/v8_value_serializer.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -151,25 +156,82 @@ v8::Local<v8::Promise> WebFrameMain::ExecuteJavaScriptInIsolatedWorld(
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool WebFrameMain::Reload(v8::Isolate* isolate) {
|
||||
bool WebFrameMain::Reload() {
|
||||
if (!CheckRenderFrame())
|
||||
return false;
|
||||
return render_frame_->Reload();
|
||||
}
|
||||
|
||||
int WebFrameMain::FrameTreeNodeID(v8::Isolate* isolate) const {
|
||||
void WebFrameMain::Send(v8::Isolate* isolate,
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args) {
|
||||
blink::CloneableMessage message;
|
||||
if (!gin::ConvertFromV8(isolate, args, &message)) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
gin::StringToV8(isolate, "Failed to serialize arguments")));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CheckRenderFrame())
|
||||
return;
|
||||
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
render_frame_->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
&electron_renderer);
|
||||
electron_renderer->Message(internal, channel, std::move(message),
|
||||
0 /* sender_id */);
|
||||
}
|
||||
|
||||
void WebFrameMain::PostMessage(v8::Isolate* isolate,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> message_value,
|
||||
base::Optional<v8::Local<v8::Value>> transfer) {
|
||||
blink::TransferableMessage transferable_message;
|
||||
if (!electron::SerializeV8Value(isolate, message_value,
|
||||
&transferable_message)) {
|
||||
// SerializeV8Value sets an exception.
|
||||
return;
|
||||
}
|
||||
|
||||
std::vector<gin::Handle<MessagePort>> wrapped_ports;
|
||||
if (transfer) {
|
||||
if (!gin::ConvertFromV8(isolate, *transfer, &wrapped_ports)) {
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
gin::StringToV8(isolate, "Invalid value for transfer")));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
bool threw_exception = false;
|
||||
transferable_message.ports =
|
||||
MessagePort::DisentanglePorts(isolate, wrapped_ports, &threw_exception);
|
||||
if (threw_exception)
|
||||
return;
|
||||
|
||||
if (!CheckRenderFrame())
|
||||
return;
|
||||
|
||||
mojo::AssociatedRemote<mojom::ElectronRenderer> electron_renderer;
|
||||
render_frame_->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
&electron_renderer);
|
||||
electron_renderer->ReceivePostMessage(channel,
|
||||
std::move(transferable_message));
|
||||
}
|
||||
|
||||
int WebFrameMain::FrameTreeNodeID() const {
|
||||
if (!CheckRenderFrame())
|
||||
return -1;
|
||||
return render_frame_->GetFrameTreeNodeId();
|
||||
}
|
||||
|
||||
std::string WebFrameMain::Name(v8::Isolate* isolate) const {
|
||||
std::string WebFrameMain::Name() const {
|
||||
if (!CheckRenderFrame())
|
||||
return std::string();
|
||||
return render_frame_->GetFrameName();
|
||||
}
|
||||
|
||||
base::ProcessId WebFrameMain::OSProcessID(v8::Isolate* isolate) const {
|
||||
base::ProcessId WebFrameMain::OSProcessID() const {
|
||||
if (!CheckRenderFrame())
|
||||
return -1;
|
||||
base::ProcessHandle process_handle =
|
||||
@@ -177,38 +239,37 @@ base::ProcessId WebFrameMain::OSProcessID(v8::Isolate* isolate) const {
|
||||
return base::GetProcId(process_handle);
|
||||
}
|
||||
|
||||
int WebFrameMain::ProcessID(v8::Isolate* isolate) const {
|
||||
int WebFrameMain::ProcessID() const {
|
||||
if (!CheckRenderFrame())
|
||||
return -1;
|
||||
return render_frame_->GetProcess()->GetID();
|
||||
}
|
||||
|
||||
int WebFrameMain::RoutingID(v8::Isolate* isolate) const {
|
||||
int WebFrameMain::RoutingID() const {
|
||||
if (!CheckRenderFrame())
|
||||
return -1;
|
||||
return render_frame_->GetRoutingID();
|
||||
}
|
||||
|
||||
GURL WebFrameMain::URL(v8::Isolate* isolate) const {
|
||||
GURL WebFrameMain::URL() const {
|
||||
if (!CheckRenderFrame())
|
||||
return GURL::EmptyGURL();
|
||||
return render_frame_->GetLastCommittedURL();
|
||||
}
|
||||
|
||||
content::RenderFrameHost* WebFrameMain::Top(v8::Isolate* isolate) const {
|
||||
content::RenderFrameHost* WebFrameMain::Top() const {
|
||||
if (!CheckRenderFrame())
|
||||
return nullptr;
|
||||
return render_frame_->GetMainFrame();
|
||||
}
|
||||
|
||||
content::RenderFrameHost* WebFrameMain::Parent(v8::Isolate* isolate) const {
|
||||
content::RenderFrameHost* WebFrameMain::Parent() const {
|
||||
if (!CheckRenderFrame())
|
||||
return nullptr;
|
||||
return render_frame_->GetParent();
|
||||
}
|
||||
|
||||
std::vector<content::RenderFrameHost*> WebFrameMain::Frames(
|
||||
v8::Isolate* isolate) const {
|
||||
std::vector<content::RenderFrameHost*> WebFrameMain::Frames() const {
|
||||
std::vector<content::RenderFrameHost*> frame_hosts;
|
||||
if (!CheckRenderFrame())
|
||||
return frame_hosts;
|
||||
@@ -221,8 +282,7 @@ std::vector<content::RenderFrameHost*> WebFrameMain::Frames(
|
||||
return frame_hosts;
|
||||
}
|
||||
|
||||
std::vector<content::RenderFrameHost*> WebFrameMain::FramesInSubtree(
|
||||
v8::Isolate* isolate) const {
|
||||
std::vector<content::RenderFrameHost*> WebFrameMain::FramesInSubtree() const {
|
||||
std::vector<content::RenderFrameHost*> frame_hosts;
|
||||
if (!CheckRenderFrame())
|
||||
return frame_hosts;
|
||||
@@ -234,6 +294,11 @@ std::vector<content::RenderFrameHost*> WebFrameMain::FramesInSubtree(
|
||||
return frame_hosts;
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<WebFrameMain> WebFrameMain::New(v8::Isolate* isolate) {
|
||||
return gin::Handle<WebFrameMain>();
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<WebFrameMain> WebFrameMain::From(v8::Isolate* isolate,
|
||||
content::RenderFrameHost* rfh) {
|
||||
@@ -261,13 +326,17 @@ void WebFrameMain::RenderFrameDeleted(content::RenderFrameHost* rfh) {
|
||||
web_frame->MarkRenderFrameDisposed();
|
||||
}
|
||||
|
||||
gin::ObjectTemplateBuilder WebFrameMain::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::Wrappable<WebFrameMain>::GetObjectTemplateBuilder(isolate)
|
||||
// static
|
||||
v8::Local<v8::ObjectTemplate> WebFrameMain::FillObjectTemplate(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::ObjectTemplate> templ) {
|
||||
return gin_helper::ObjectTemplateBuilder(isolate, templ)
|
||||
.SetMethod("executeJavaScript", &WebFrameMain::ExecuteJavaScript)
|
||||
.SetMethod("executeJavaScriptInIsolatedWorld",
|
||||
&WebFrameMain::ExecuteJavaScriptInIsolatedWorld)
|
||||
.SetMethod("reload", &WebFrameMain::Reload)
|
||||
.SetMethod("_send", &WebFrameMain::Send)
|
||||
.SetMethod("_postMessage", &WebFrameMain::PostMessage)
|
||||
.SetProperty("frameTreeNodeId", &WebFrameMain::FrameTreeNodeID)
|
||||
.SetProperty("name", &WebFrameMain::Name)
|
||||
.SetProperty("osProcessId", &WebFrameMain::OSProcessID)
|
||||
@@ -277,7 +346,8 @@ gin::ObjectTemplateBuilder WebFrameMain::GetObjectTemplateBuilder(
|
||||
.SetProperty("top", &WebFrameMain::Top)
|
||||
.SetProperty("parent", &WebFrameMain::Parent)
|
||||
.SetProperty("frames", &WebFrameMain::Frames)
|
||||
.SetProperty("framesInSubtree", &WebFrameMain::FramesInSubtree);
|
||||
.SetProperty("framesInSubtree", &WebFrameMain::FramesInSubtree)
|
||||
.Build();
|
||||
}
|
||||
|
||||
const char* WebFrameMain::GetTypeName() {
|
||||
@@ -311,6 +381,7 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
void* priv) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("WebFrameMain", WebFrameMain::GetConstructor(context));
|
||||
dict.SetMethod("fromId", &FromID);
|
||||
}
|
||||
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "base/process/process.h"
|
||||
#include "gin/handle.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "shell/common/gin_helper/constructible.h"
|
||||
|
||||
class GURL;
|
||||
|
||||
@@ -32,8 +33,12 @@ namespace electron {
|
||||
namespace api {
|
||||
|
||||
// Bindings for accessing frames from the main process.
|
||||
class WebFrameMain : public gin::Wrappable<WebFrameMain> {
|
||||
class WebFrameMain : public gin::Wrappable<WebFrameMain>,
|
||||
public gin_helper::Constructible<WebFrameMain> {
|
||||
public:
|
||||
// Create a new WebFrameMain and return the V8 wrapper of it.
|
||||
static gin::Handle<WebFrameMain> New(v8::Isolate* isolate);
|
||||
|
||||
static gin::Handle<WebFrameMain> FromID(v8::Isolate* isolate,
|
||||
int render_process_id,
|
||||
int render_frame_id);
|
||||
@@ -51,8 +56,9 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain> {
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override;
|
||||
static v8::Local<v8::ObjectTemplate> FillObjectTemplate(
|
||||
v8::Isolate*,
|
||||
v8::Local<v8::ObjectTemplate>);
|
||||
const char* GetTypeName() override;
|
||||
|
||||
protected:
|
||||
@@ -70,20 +76,27 @@ class WebFrameMain : public gin::Wrappable<WebFrameMain> {
|
||||
gin::Arguments* args,
|
||||
int world_id,
|
||||
const base::string16& code);
|
||||
bool Reload(v8::Isolate* isolate);
|
||||
bool Reload();
|
||||
void Send(v8::Isolate* isolate,
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> args);
|
||||
void PostMessage(v8::Isolate* isolate,
|
||||
const std::string& channel,
|
||||
v8::Local<v8::Value> message_value,
|
||||
base::Optional<v8::Local<v8::Value>> transfer);
|
||||
|
||||
int FrameTreeNodeID(v8::Isolate* isolate) const;
|
||||
std::string Name(v8::Isolate* isolate) const;
|
||||
base::ProcessId OSProcessID(v8::Isolate* isolate) const;
|
||||
int ProcessID(v8::Isolate* isolate) const;
|
||||
int RoutingID(v8::Isolate* isolate) const;
|
||||
GURL URL(v8::Isolate* isolate) const;
|
||||
int FrameTreeNodeID() const;
|
||||
std::string Name() const;
|
||||
base::ProcessId OSProcessID() const;
|
||||
int ProcessID() const;
|
||||
int RoutingID() const;
|
||||
GURL URL() const;
|
||||
|
||||
content::RenderFrameHost* Top(v8::Isolate* isolate) const;
|
||||
content::RenderFrameHost* Parent(v8::Isolate* isolate) const;
|
||||
std::vector<content::RenderFrameHost*> Frames(v8::Isolate* isolate) const;
|
||||
std::vector<content::RenderFrameHost*> FramesInSubtree(
|
||||
v8::Isolate* isolate) const;
|
||||
content::RenderFrameHost* Top() const;
|
||||
content::RenderFrameHost* Parent() const;
|
||||
std::vector<content::RenderFrameHost*> Frames() const;
|
||||
std::vector<content::RenderFrameHost*> FramesInSubtree() const;
|
||||
|
||||
content::RenderFrameHost* render_frame_ = nullptr;
|
||||
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
#include "net/http/http_content_disposition.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/api/electron_api_web_frame_main.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/frame_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/net_converter.h"
|
||||
#include "shell/common/gin_converters/std_converter.h"
|
||||
@@ -156,12 +158,18 @@ void ToDictionary(gin::Dictionary* details, extensions::WebRequestInfo* info) {
|
||||
HttpResponseHeadersToV8(info->response_headers.get()));
|
||||
}
|
||||
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(
|
||||
content::RenderFrameHost::FromID(info->render_process_id,
|
||||
info->frame_id));
|
||||
auto* api_web_contents = WebContents::From(web_contents);
|
||||
if (api_web_contents)
|
||||
details->Set("webContentsId", api_web_contents->ID());
|
||||
auto* render_frame_host =
|
||||
content::RenderFrameHost::FromID(info->render_process_id, info->frame_id);
|
||||
if (render_frame_host) {
|
||||
details->Set("frame", render_frame_host);
|
||||
auto* web_contents =
|
||||
content::WebContents::FromRenderFrameHost(render_frame_host);
|
||||
auto* api_web_contents = WebContents::From(web_contents);
|
||||
if (api_web_contents) {
|
||||
details->Set("webContents", api_web_contents);
|
||||
details->Set("webContentsId", api_web_contents->ID());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ToDictionary(gin::Dictionary* details,
|
||||
|
||||
81
shell/browser/badging/badge_manager.cc
Executable file
81
shell/browser/badging/badge_manager.cc
Executable file
@@ -0,0 +1,81 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/badging/badge_manager.h"
|
||||
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
|
||||
#include "base/i18n/number_formatting.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "build/build_config.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/render_process_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "shell/browser/badging/badge_manager_factory.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/strings/grit/ui_strings.h"
|
||||
|
||||
namespace badging {
|
||||
|
||||
BadgeManager::BadgeManager() = default;
|
||||
BadgeManager::~BadgeManager() = default;
|
||||
|
||||
// static
|
||||
void BadgeManager::BindFrameReceiver(
|
||||
content::RenderFrameHost* frame,
|
||||
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
auto* browser_context =
|
||||
content::WebContents::FromRenderFrameHost(frame)->GetBrowserContext();
|
||||
|
||||
auto* badge_manager =
|
||||
badging::BadgeManagerFactory::GetInstance()->GetForBrowserContext(
|
||||
browser_context);
|
||||
if (!badge_manager)
|
||||
return;
|
||||
|
||||
auto context = std::make_unique<FrameBindingContext>(
|
||||
frame->GetProcess()->GetID(), frame->GetRoutingID());
|
||||
|
||||
badge_manager->receivers_.Add(badge_manager, std::move(receiver),
|
||||
std::move(context));
|
||||
}
|
||||
|
||||
std::string BadgeManager::GetBadgeString(base::Optional<int> badge_content) {
|
||||
if (!badge_content)
|
||||
return "•";
|
||||
|
||||
if (badge_content > kMaxBadgeContent) {
|
||||
return base::UTF16ToUTF8(l10n_util::GetStringFUTF16(
|
||||
IDS_SATURATED_BADGE_CONTENT, base::FormatNumber(kMaxBadgeContent)));
|
||||
}
|
||||
|
||||
return base::UTF16ToUTF8(base::FormatNumber(badge_content.value()));
|
||||
}
|
||||
|
||||
void BadgeManager::SetBadge(blink::mojom::BadgeValuePtr mojo_value) {
|
||||
if (mojo_value->is_number() && mojo_value->get_number() == 0) {
|
||||
mojo::ReportBadMessage(
|
||||
"|value| should not be zero when it is |number| (ClearBadge should be "
|
||||
"called instead)!");
|
||||
return;
|
||||
}
|
||||
|
||||
base::Optional<int> value =
|
||||
mojo_value->is_flag() ? base::nullopt
|
||||
: base::make_optional(mojo_value->get_number());
|
||||
|
||||
electron::Browser::Get()->SetBadgeCount(value);
|
||||
}
|
||||
|
||||
void BadgeManager::ClearBadge() {
|
||||
electron::Browser::Get()->SetBadgeCount(0);
|
||||
}
|
||||
|
||||
} // namespace badging
|
||||
90
shell/browser/badging/badge_manager.h
Normal file
90
shell/browser/badging/badge_manager.h
Normal file
@@ -0,0 +1,90 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_BADGING_BADGE_MANAGER_H_
|
||||
#define SHELL_BROWSER_BADGING_BADGE_MANAGER_H_
|
||||
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "base/optional.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
#include "mojo/public/cpp/bindings/receiver_set.h"
|
||||
#include "third_party/blink/public/mojom/badging/badging.mojom.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
namespace content {
|
||||
class RenderFrameHost;
|
||||
class RenderProcessHost;
|
||||
} // namespace content
|
||||
|
||||
namespace badging {
|
||||
|
||||
// The maximum value of badge contents before saturation occurs.
|
||||
constexpr int kMaxBadgeContent = 99;
|
||||
|
||||
// Maintains a record of badge contents and dispatches badge changes to a
|
||||
// delegate.
|
||||
class BadgeManager : public KeyedService, public blink::mojom::BadgeService {
|
||||
public:
|
||||
BadgeManager();
|
||||
~BadgeManager() override;
|
||||
|
||||
static void BindFrameReceiver(
|
||||
content::RenderFrameHost* frame,
|
||||
mojo::PendingReceiver<blink::mojom::BadgeService> receiver);
|
||||
|
||||
// Determines the text to put on the badge based on some badge_content.
|
||||
static std::string GetBadgeString(base::Optional<int> badge_content);
|
||||
|
||||
private:
|
||||
// The BindingContext of a mojo request. Allows mojo calls to be tied back
|
||||
// to the execution context they belong to without trusting the renderer for
|
||||
// that information. This is an abstract base class that different types of
|
||||
// execution contexts derive.
|
||||
class BindingContext {
|
||||
public:
|
||||
virtual ~BindingContext() = default;
|
||||
};
|
||||
|
||||
// The BindingContext for Window execution contexts.
|
||||
class FrameBindingContext final : public BindingContext {
|
||||
public:
|
||||
FrameBindingContext(int process_id, int frame_id)
|
||||
: process_id_(process_id), frame_id_(frame_id) {}
|
||||
~FrameBindingContext() override = default;
|
||||
|
||||
int GetProcessId() { return process_id_; }
|
||||
int GetFrameId() { return frame_id_; }
|
||||
|
||||
private:
|
||||
int process_id_;
|
||||
int frame_id_;
|
||||
};
|
||||
|
||||
// blink::mojom::BadgeService:
|
||||
// Note: These are private to stop them being called outside of mojo as they
|
||||
// require a mojo binding context.
|
||||
void SetBadge(blink::mojom::BadgeValuePtr value) override;
|
||||
void ClearBadge() override;
|
||||
|
||||
// All the mojo receivers for the BadgeManager. Keeps track of the
|
||||
// render_frame the binding is associated with, so as to not have to rely
|
||||
// on the renderer passing it in.
|
||||
mojo::ReceiverSet<blink::mojom::BadgeService, std::unique_ptr<BindingContext>>
|
||||
receivers_;
|
||||
|
||||
// Delegate which handles actual setting and clearing of the badge.
|
||||
// Note: This is currently only set on Windows and MacOS.
|
||||
// std::unique_ptr<BadgeManagerDelegate> delegate_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BadgeManager);
|
||||
};
|
||||
|
||||
} // namespace badging
|
||||
|
||||
#endif // SHELL_BROWSER_BADGING_BADGE_MANAGER_H_
|
||||
41
shell/browser/badging/badge_manager_factory.cc
Normal file
41
shell/browser/badging/badge_manager_factory.cc
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/badging/badge_manager_factory.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
#include "base/memory/singleton.h"
|
||||
#include "components/keyed_service/content/browser_context_dependency_manager.h"
|
||||
#include "shell/browser/badging/badge_manager.h"
|
||||
|
||||
namespace badging {
|
||||
|
||||
// static
|
||||
BadgeManager* BadgeManagerFactory::GetForBrowserContext(
|
||||
content::BrowserContext* context) {
|
||||
return static_cast<badging::BadgeManager*>(
|
||||
GetInstance()->GetServiceForBrowserContext(context, true));
|
||||
}
|
||||
|
||||
// static
|
||||
BadgeManagerFactory* BadgeManagerFactory::GetInstance() {
|
||||
return base::Singleton<BadgeManagerFactory>::get();
|
||||
}
|
||||
|
||||
BadgeManagerFactory::BadgeManagerFactory()
|
||||
: BrowserContextKeyedServiceFactory(
|
||||
"BadgeManager",
|
||||
BrowserContextDependencyManager::GetInstance()) {}
|
||||
|
||||
BadgeManagerFactory::~BadgeManagerFactory() {}
|
||||
|
||||
KeyedService* BadgeManagerFactory::BuildServiceInstanceFor(
|
||||
content::BrowserContext* context) const {
|
||||
return new BadgeManager();
|
||||
}
|
||||
|
||||
} // namespace badging
|
||||
44
shell/browser/badging/badge_manager_factory.h
Normal file
44
shell/browser/badging/badge_manager_factory.h
Normal file
@@ -0,0 +1,44 @@
|
||||
// Copyright 2018 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_BADGING_BADGE_MANAGER_FACTORY_H_
|
||||
#define SHELL_BROWSER_BADGING_BADGE_MANAGER_FACTORY_H_
|
||||
|
||||
#include "base/macros.h"
|
||||
#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
|
||||
|
||||
namespace base {
|
||||
template <typename T>
|
||||
struct DefaultSingletonTraits;
|
||||
}
|
||||
|
||||
namespace badging {
|
||||
|
||||
class BadgeManager;
|
||||
|
||||
// Singleton that provides access to context specific BadgeManagers.
|
||||
class BadgeManagerFactory : public BrowserContextKeyedServiceFactory {
|
||||
public:
|
||||
// Gets the BadgeManager for the specified context
|
||||
static BadgeManager* GetForBrowserContext(content::BrowserContext* context);
|
||||
|
||||
// Returns the BadgeManagerFactory singleton.
|
||||
static BadgeManagerFactory* GetInstance();
|
||||
|
||||
private:
|
||||
friend struct base::DefaultSingletonTraits<BadgeManagerFactory>;
|
||||
|
||||
BadgeManagerFactory();
|
||||
~BadgeManagerFactory() override;
|
||||
|
||||
// BrowserContextKeyedServiceFactory
|
||||
KeyedService* BuildServiceInstanceFor(
|
||||
content::BrowserContext* context) const override;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(BadgeManagerFactory);
|
||||
};
|
||||
|
||||
} // namespace badging
|
||||
|
||||
#endif // SHELL_BROWSER_BADGING_BADGE_MANAGER_FACTORY_H_
|
||||
0
shell/browser/browser.cc
Normal file → Executable file
0
shell/browser/browser.cc
Normal file → Executable file
12
shell/browser/browser.h
Normal file → Executable file
12
shell/browser/browser.h
Normal file → Executable file
@@ -23,6 +23,7 @@
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#include "base/files/file_path.h"
|
||||
#include "shell/browser/ui/win/taskbar_host.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_MAC)
|
||||
@@ -107,7 +108,7 @@ class Browser : public WindowListObserver {
|
||||
#endif
|
||||
|
||||
// Set/Get the badge count.
|
||||
bool SetBadgeCount(int count);
|
||||
bool SetBadgeCount(base::Optional<int> count);
|
||||
int GetBadgeCount();
|
||||
|
||||
#if defined(OS_WIN)
|
||||
@@ -364,6 +365,15 @@ class Browser : public WindowListObserver {
|
||||
base::DictionaryValue about_panel_options_;
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
void UpdateBadgeContents(HWND hwnd,
|
||||
const base::Optional<std::string>& badge_content,
|
||||
const std::string& badge_alt_string);
|
||||
|
||||
// In charge of running taskbar related APIs.
|
||||
TaskbarHost taskbar_host_;
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(Browser);
|
||||
};
|
||||
|
||||
|
||||
@@ -140,10 +140,10 @@ base::string16 Browser::GetApplicationNameForProtocol(const GURL& url) {
|
||||
return base::ASCIIToUTF16(GetXdgAppOutput(argv).value_or(std::string()));
|
||||
}
|
||||
|
||||
bool Browser::SetBadgeCount(int count) {
|
||||
if (IsUnityRunning()) {
|
||||
unity::SetDownloadCount(count);
|
||||
badge_count_ = count;
|
||||
bool Browser::SetBadgeCount(base::Optional<int> count) {
|
||||
if (IsUnityRunning() && count.has_value()) {
|
||||
unity::SetDownloadCount(count.value());
|
||||
badge_count_ = count.value();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "net/base/mac/url_conversions.h"
|
||||
#include "shell/browser/badging/badge_manager.h"
|
||||
#include "shell/browser/mac/dict_util.h"
|
||||
#include "shell/browser/mac/electron_application.h"
|
||||
#include "shell/browser/mac/electron_application_delegate.h"
|
||||
@@ -219,9 +220,15 @@ base::string16 Browser::GetApplicationNameForProtocol(const GURL& url) {
|
||||
|
||||
void Browser::SetAppUserModelID(const base::string16& name) {}
|
||||
|
||||
bool Browser::SetBadgeCount(int count) {
|
||||
DockSetBadgeText(count != 0 ? base::NumberToString(count) : "");
|
||||
badge_count_ = count;
|
||||
bool Browser::SetBadgeCount(base::Optional<int> count) {
|
||||
DockSetBadgeText(!count.has_value() || count.value() != 0
|
||||
? badging::BadgeManager::GetBadgeString(count)
|
||||
: "");
|
||||
if (count.has_value()) {
|
||||
badge_count_ = count.value();
|
||||
} else {
|
||||
badge_count_ = 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
101
shell/browser/browser_win.cc
Normal file → Executable file
101
shell/browser/browser_win.cc
Normal file → Executable file
@@ -28,16 +28,21 @@
|
||||
#include "chrome/browser/icon_manager.h"
|
||||
#include "electron/electron_version.h"
|
||||
#include "shell/browser/api/electron_api_app.h"
|
||||
#include "shell/browser/badging/badge_manager.h"
|
||||
#include "shell/browser/electron_browser_main_parts.h"
|
||||
#include "shell/browser/ui/message_box.h"
|
||||
#include "shell/browser/ui/win/jump_list.h"
|
||||
#include "shell/browser/window_list.h"
|
||||
#include "shell/common/application_info.h"
|
||||
#include "shell/common/gin_converters/file_path_converter.h"
|
||||
#include "shell/common/gin_converters/image_converter.h"
|
||||
#include "shell/common/gin_helper/arguments.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/skia_util.h"
|
||||
#include "skia/ext/legacy_display_globals.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/events/keycodes/keyboard_code_conversion_win.h"
|
||||
#include "ui/strings/grit/ui_strings.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -603,8 +608,100 @@ v8::Local<v8::Promise> Browser::GetApplicationInfoForProtocol(
|
||||
return handle;
|
||||
}
|
||||
|
||||
bool Browser::SetBadgeCount(int count) {
|
||||
return false;
|
||||
bool Browser::SetBadgeCount(base::Optional<int> count) {
|
||||
base::Optional<std::string> badge_content;
|
||||
if (count.has_value() && count.value() == 0) {
|
||||
badge_content = base::nullopt;
|
||||
} else {
|
||||
badge_content = badging::BadgeManager::GetBadgeString(count);
|
||||
}
|
||||
|
||||
// There are 3 different cases when the badge has a value:
|
||||
// 1. |contents| is between 1 and 99 inclusive => Set the accessibility text
|
||||
// to a pluralized notification count (e.g. 4 Unread Notifications).
|
||||
// 2. |contents| is greater than 99 => Set the accessibility text to
|
||||
// More than |kMaxBadgeContent| unread notifications, so the
|
||||
// accessibility text matches what is displayed on the badge (e.g. More
|
||||
// than 99 notifications).
|
||||
// 3. The badge is set to 'flag' => Set the accessibility text to something
|
||||
// less specific (e.g. Unread Notifications).
|
||||
std::string badge_alt_string;
|
||||
if (count.has_value()) {
|
||||
badge_count_ = count.value();
|
||||
badge_alt_string = (uint64_t)badge_count_ <= badging::kMaxBadgeContent
|
||||
// Case 1.
|
||||
? l10n_util::GetPluralStringFUTF8(
|
||||
IDS_BADGE_UNREAD_NOTIFICATIONS, badge_count_)
|
||||
// Case 2.
|
||||
: l10n_util::GetPluralStringFUTF8(
|
||||
IDS_BADGE_UNREAD_NOTIFICATIONS_SATURATED,
|
||||
badging::kMaxBadgeContent);
|
||||
} else {
|
||||
// Case 3.
|
||||
badge_alt_string =
|
||||
l10n_util::GetStringUTF8(IDS_BADGE_UNREAD_NOTIFICATIONS_UNSPECIFIED);
|
||||
badge_count_ = 0;
|
||||
}
|
||||
for (auto* window : WindowList::GetWindows()) {
|
||||
// On Windows set the badge on the first window found.
|
||||
UpdateBadgeContents(window->GetAcceleratedWidget(), badge_content,
|
||||
badge_alt_string);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Browser::UpdateBadgeContents(
|
||||
HWND hwnd,
|
||||
const base::Optional<std::string>& badge_content,
|
||||
const std::string& badge_alt_string) {
|
||||
SkBitmap badge;
|
||||
if (badge_content) {
|
||||
std::string content = badge_content.value();
|
||||
constexpr int kOverlayIconSize = 16;
|
||||
// This is the color used by the Windows 10 Badge API, for platform
|
||||
// consistency.
|
||||
constexpr int kBackgroundColor = SkColorSetRGB(0x26, 0x25, 0x2D);
|
||||
constexpr int kForegroundColor = SK_ColorWHITE;
|
||||
constexpr int kRadius = kOverlayIconSize / 2;
|
||||
// The minimum gap to have between our content and the edge of the badge.
|
||||
constexpr int kMinMargin = 3;
|
||||
// The amount of space we have to render the icon.
|
||||
constexpr int kMaxBounds = kOverlayIconSize - 2 * kMinMargin;
|
||||
constexpr int kMaxTextSize = 24; // Max size for our text.
|
||||
constexpr int kMinTextSize = 7; // Min size for our text.
|
||||
|
||||
badge.allocN32Pixels(kOverlayIconSize, kOverlayIconSize);
|
||||
SkCanvas canvas(badge, skia::LegacyDisplayGlobals::GetSkSurfaceProps());
|
||||
|
||||
SkPaint paint;
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(kBackgroundColor);
|
||||
|
||||
canvas.clear(SK_ColorTRANSPARENT);
|
||||
canvas.drawCircle(kRadius, kRadius, kRadius, paint);
|
||||
|
||||
paint.reset();
|
||||
paint.setColor(kForegroundColor);
|
||||
|
||||
SkFont font;
|
||||
|
||||
SkRect bounds;
|
||||
int text_size = kMaxTextSize;
|
||||
// Find the largest |text_size| larger than |kMinTextSize| in which
|
||||
// |content| fits into our 16x16px icon, with margins.
|
||||
do {
|
||||
font.setSize(text_size--);
|
||||
font.measureText(content.c_str(), content.size(), SkTextEncoding::kUTF8,
|
||||
&bounds);
|
||||
} while (text_size >= kMinTextSize &&
|
||||
(bounds.width() > kMaxBounds || bounds.height() > kMaxBounds));
|
||||
|
||||
canvas.drawSimpleText(
|
||||
content.c_str(), content.size(), SkTextEncoding::kUTF8,
|
||||
kRadius - bounds.width() / 2 - bounds.x(),
|
||||
kRadius - bounds.height() / 2 - bounds.y(), font, paint);
|
||||
}
|
||||
taskbar_host_.SetOverlayIcon(hwnd, badge, badge_alt_string);
|
||||
}
|
||||
|
||||
void Browser::SetLoginItemSettings(LoginItemSettings settings) {
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/api/electron_api_web_request.h"
|
||||
#include "shell/browser/badging/badge_manager.h"
|
||||
#include "shell/browser/child_web_contents_tracker.h"
|
||||
#include "shell/browser/electron_autofill_driver_factory.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
@@ -1639,12 +1640,6 @@ void ElectronBrowserClient::BindHostReceiverForRenderer(
|
||||
#endif
|
||||
}
|
||||
|
||||
void BindBadgeManagerFrameReceiver(
|
||||
content::RenderFrameHost* frame,
|
||||
mojo::PendingReceiver<blink::mojom::BadgeService> receiver) {
|
||||
LOG(WARNING) << "The Chromium Badging API is not available in Electron";
|
||||
}
|
||||
|
||||
void BindElectronBrowser(
|
||||
content::RenderFrameHost* frame_host,
|
||||
mojo::PendingReceiver<electron::mojom::ElectronBrowser> receiver) {
|
||||
@@ -1688,7 +1683,7 @@ void ElectronBrowserClient::RegisterBrowserInterfaceBindersForFrame(
|
||||
map->Add<network_hints::mojom::NetworkHintsHandler>(
|
||||
base::BindRepeating(&BindNetworkHintsHandler));
|
||||
map->Add<blink::mojom::BadgeService>(
|
||||
base::BindRepeating(&BindBadgeManagerFrameReceiver));
|
||||
base::BindRepeating(&badging::BadgeManager::BindFrameReceiver));
|
||||
map->Add<electron::mojom::ElectronBrowser>(
|
||||
base::BindRepeating(&BindElectronBrowser));
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
|
||||
@@ -88,13 +88,12 @@ void ElectronBrowserHandlerImpl::MessageSync(bool internal,
|
||||
}
|
||||
|
||||
void ElectronBrowserHandlerImpl::MessageTo(bool internal,
|
||||
bool send_to_all,
|
||||
int32_t web_contents_id,
|
||||
const std::string& channel,
|
||||
blink::CloneableMessage arguments) {
|
||||
api::WebContents* api_web_contents = api::WebContents::From(web_contents());
|
||||
if (api_web_contents) {
|
||||
api_web_contents->MessageTo(internal, send_to_all, web_contents_id, channel,
|
||||
api_web_contents->MessageTo(internal, web_contents_id, channel,
|
||||
std::move(arguments));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -45,7 +45,6 @@ class ElectronBrowserHandlerImpl : public mojom::ElectronBrowser,
|
||||
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;
|
||||
|
||||
@@ -517,6 +517,16 @@ void ElectronBrowserMainParts::PostMainMessageLoopRun() {
|
||||
FreeAppDelegate();
|
||||
#endif
|
||||
|
||||
// Shutdown the DownloadManager before destroying Node to prevent
|
||||
// DownloadItem callbacks from crashing.
|
||||
for (auto& iter : ElectronBrowserContext::browser_context_map()) {
|
||||
auto* download_manager =
|
||||
content::BrowserContext::GetDownloadManager(iter.second.get());
|
||||
if (download_manager) {
|
||||
download_manager->Shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure destruction callbacks are called before message loop is
|
||||
// destroyed, otherwise some objects that need to be deleted on IO thread
|
||||
// won't be freed.
|
||||
|
||||
@@ -1712,12 +1712,12 @@ void NativeWindowMac::AddContentViewLayers(bool minimizable, bool closable) {
|
||||
[[window_ standardWindowButton:NSWindowZoomButton] setHidden:YES];
|
||||
[[window_ standardWindowButton:NSWindowMiniaturizeButton] setHidden:YES];
|
||||
[[window_ standardWindowButton:NSWindowCloseButton] setHidden:YES];
|
||||
}
|
||||
|
||||
// Some third-party macOS utilities check the zoom button's enabled state to
|
||||
// determine whether to show custom UI on hover, so we disable it here to
|
||||
// prevent them from doing so in a frameless app window.
|
||||
SetMaximizable(false);
|
||||
// Some third-party macOS utilities check the zoom button's enabled state
|
||||
// to determine whether to show custom UI on hover, so we disable it here
|
||||
// to prevent them from doing so in a frameless app window.
|
||||
SetMaximizable(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1207,7 +1207,9 @@ void NativeWindowViews::SetProgressBar(double progress,
|
||||
void NativeWindowViews::SetOverlayIcon(const gfx::Image& overlay,
|
||||
const std::string& description) {
|
||||
#if defined(OS_WIN)
|
||||
taskbar_host_.SetOverlayIcon(GetAcceleratedWidget(), overlay, description);
|
||||
SkBitmap overlay_bitmap = overlay.AsBitmap();
|
||||
taskbar_host_.SetOverlayIcon(GetAcceleratedWidget(), overlay_bitmap,
|
||||
description);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -1415,6 +1417,10 @@ void NativeWindowViews::OnWidgetDestroying(views::Widget* widget) {
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeWindowViews::OnWidgetDestroyed(views::Widget* changed_widget) {
|
||||
widget_destroyed_ = true;
|
||||
}
|
||||
|
||||
void NativeWindowViews::DeleteDelegate() {
|
||||
if (is_modal() && this->parent()) {
|
||||
auto* parent = this->parent();
|
||||
@@ -1511,6 +1517,9 @@ void NativeWindowViews::OnWidgetMove() {
|
||||
void NativeWindowViews::HandleKeyboardEvent(
|
||||
content::WebContents*,
|
||||
const content::NativeWebKeyboardEvent& event) {
|
||||
if (widget_destroyed_)
|
||||
return;
|
||||
|
||||
#if defined(OS_LINUX)
|
||||
if (event.windows_key_code == ui::VKEY_BROWSER_BACK)
|
||||
NotifyWindowExecuteAppCommand(kBrowserBackward);
|
||||
|
||||
@@ -174,6 +174,7 @@ class NativeWindowViews : public NativeWindow,
|
||||
void OnWidgetBoundsChanged(views::Widget* widget,
|
||||
const gfx::Rect& bounds) override;
|
||||
void OnWidgetDestroying(views::Widget* widget) override;
|
||||
void OnWidgetDestroyed(views::Widget* widget) override;
|
||||
|
||||
// views::WidgetDelegate:
|
||||
void DeleteDelegate() override;
|
||||
@@ -313,6 +314,7 @@ class NativeWindowViews : public NativeWindow,
|
||||
std::string title_;
|
||||
gfx::Size widget_size_;
|
||||
double opacity_ = 1.0;
|
||||
bool widget_destroyed_ = false;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(NativeWindowViews);
|
||||
};
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 12,0,0,16
|
||||
PRODUCTVERSION 12,0,0,16
|
||||
FILEVERSION 12,0,0,20
|
||||
PRODUCTVERSION 12,0,0,20
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -167,7 +167,7 @@ bool MenuBar::AcceleratorPressed(const ui::Accelerator& accelerator) {
|
||||
|
||||
if (keycode == accelerator.key_code()) {
|
||||
auto event = accelerator.ToKeyEvent();
|
||||
ButtonPressed(button, event);
|
||||
ButtonPressed(button->tag(), event);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -254,7 +254,7 @@ const char* MenuBar::GetClassName() const {
|
||||
return kViewClassName;
|
||||
}
|
||||
|
||||
void MenuBar::ButtonPressed(views::Button* source, const ui::Event& event) {
|
||||
void MenuBar::ButtonPressed(int id, const ui::Event& event) {
|
||||
// Hide the accelerator when a submenu is activated.
|
||||
SetAcceleratorVisibility(false);
|
||||
|
||||
@@ -264,13 +264,22 @@ void MenuBar::ButtonPressed(views::Button* source, const ui::Event& event) {
|
||||
if (!window_->HasFocus())
|
||||
window_->RequestFocus();
|
||||
|
||||
int id = source->tag();
|
||||
ElectronMenuModel::ItemType type = menu_model_->GetTypeAt(id);
|
||||
if (type != ElectronMenuModel::TYPE_SUBMENU) {
|
||||
menu_model_->ActivatedAt(id, 0);
|
||||
return;
|
||||
}
|
||||
|
||||
SubmenuButton* source = nullptr;
|
||||
for (auto* child : children()) {
|
||||
auto* button = static_cast<SubmenuButton*>(child);
|
||||
if (button->tag() == id) {
|
||||
source = button;
|
||||
break;
|
||||
}
|
||||
}
|
||||
DCHECK(source);
|
||||
|
||||
// Deleted in MenuDelegate::OnMenuClosed
|
||||
auto* menu_delegate = new MenuDelegate(this);
|
||||
menu_delegate->RunMenu(
|
||||
@@ -304,12 +313,10 @@ void MenuBar::OnThemeChanged() {
|
||||
void MenuBar::RebuildChildren() {
|
||||
RemoveAllChildViews(true);
|
||||
for (int i = 0, n = GetItemCount(); i < n; ++i) {
|
||||
auto* button =
|
||||
new SubmenuButton(menu_model_->GetLabelAt(i), background_color_);
|
||||
auto* button = new SubmenuButton(
|
||||
base::BindRepeating(&MenuBar::ButtonPressed, base::Unretained(this), i),
|
||||
menu_model_->GetLabelAt(i), background_color_);
|
||||
button->set_tag(i);
|
||||
button->SetCallback(base::BindRepeating(&MenuBar::ButtonPressed,
|
||||
base::Unretained(this),
|
||||
base::Unretained(button)));
|
||||
AddChildView(button);
|
||||
}
|
||||
UpdateViewColors();
|
||||
|
||||
@@ -80,7 +80,7 @@ class MenuBar : public views::AccessiblePaneView,
|
||||
// views::View:
|
||||
const char* GetClassName() const override;
|
||||
|
||||
void ButtonPressed(views::Button* source, const ui::Event& event);
|
||||
void ButtonPressed(int id, const ui::Event& event);
|
||||
|
||||
void RebuildChildren();
|
||||
void UpdateViewColors();
|
||||
|
||||
@@ -20,9 +20,10 @@
|
||||
|
||||
namespace electron {
|
||||
|
||||
SubmenuButton::SubmenuButton(const base::string16& title,
|
||||
SubmenuButton::SubmenuButton(PressedCallback callback,
|
||||
const base::string16& title,
|
||||
const SkColor& background_color)
|
||||
: views::MenuButton(PressedCallback(), gfx::RemoveAccelerator(title)),
|
||||
: views::MenuButton(callback, gfx::RemoveAccelerator(title)),
|
||||
background_color_(background_color) {
|
||||
#if defined(OS_LINUX)
|
||||
// Dont' use native style border.
|
||||
|
||||
@@ -16,7 +16,9 @@ namespace electron {
|
||||
// Special button that used by menu bar to show submenus.
|
||||
class SubmenuButton : public views::MenuButton {
|
||||
public:
|
||||
SubmenuButton(const base::string16& title, const SkColor& background_color);
|
||||
SubmenuButton(PressedCallback callback,
|
||||
const base::string16& title,
|
||||
const SkColor& background_color);
|
||||
~SubmenuButton() override;
|
||||
|
||||
void SetAcceleratorVisibility(bool visible);
|
||||
|
||||
@@ -166,13 +166,12 @@ bool TaskbarHost::SetProgressBar(HWND window,
|
||||
}
|
||||
|
||||
bool TaskbarHost::SetOverlayIcon(HWND window,
|
||||
const gfx::Image& overlay,
|
||||
const SkBitmap& overlay,
|
||||
const std::string& text) {
|
||||
if (!InitializeTaskbar())
|
||||
return false;
|
||||
|
||||
base::win::ScopedHICON icon(
|
||||
IconUtil::CreateHICONFromSkBitmap(overlay.AsBitmap()));
|
||||
base::win::ScopedHICON icon(IconUtil::CreateHICONFromSkBitmap(overlay));
|
||||
return SUCCEEDED(taskbar_->SetOverlayIcon(window, icon.get(),
|
||||
base::UTF8ToUTF16(text).c_str()));
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class TaskbarHost {
|
||||
|
||||
// Set the overlay icon in taskbar.
|
||||
bool SetOverlayIcon(HWND window,
|
||||
const gfx::Image& overlay,
|
||||
const SkBitmap& overlay,
|
||||
const std::string& text);
|
||||
|
||||
// Set the region of the window to show as a thumbnail in taskbar.
|
||||
|
||||
@@ -137,7 +137,7 @@ WebContentsPreferences::WebContentsPreferences(
|
||||
"electron");
|
||||
}
|
||||
SetDefaultBoolIfUndefined(options::kContextIsolation, false);
|
||||
SetDefaultBoolIfUndefined(options::kWorldSafeExecuteJavaScript, false);
|
||||
SetDefaultBoolIfUndefined(options::kWorldSafeExecuteJavaScript, true);
|
||||
SetDefaultBoolIfUndefined(options::kJavaScript, true);
|
||||
SetDefaultBoolIfUndefined(options::kImages, true);
|
||||
SetDefaultBoolIfUndefined(options::kTextAreasAreResizable, true);
|
||||
@@ -437,7 +437,7 @@ void WebContentsPreferences::OverrideWebkitPrefs(
|
||||
#endif
|
||||
|
||||
prefs->world_safe_execute_javascript =
|
||||
IsEnabled(options::kWorldSafeExecuteJavaScript);
|
||||
IsEnabled(options::kWorldSafeExecuteJavaScript, true);
|
||||
|
||||
int guest_instance_id = 0;
|
||||
if (GetAsInteger(&preference_, options::kGuestInstanceID, &guest_instance_id))
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/pattern.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
@@ -138,7 +139,10 @@ NativeImage::~NativeImage() {
|
||||
// static
|
||||
bool NativeImage::TryConvertNativeImage(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> image,
|
||||
NativeImage** native_image) {
|
||||
NativeImage** native_image,
|
||||
OnConvertError on_error) {
|
||||
std::string error_message;
|
||||
|
||||
base::FilePath icon_path;
|
||||
if (gin::ConvertFromV8(isolate, image, &icon_path)) {
|
||||
*native_image = NativeImage::CreateFromPath(isolate, icon_path).get();
|
||||
@@ -148,17 +152,27 @@ bool NativeImage::TryConvertNativeImage(v8::Isolate* isolate,
|
||||
#else
|
||||
const auto img_path = icon_path.value();
|
||||
#endif
|
||||
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
|
||||
isolate, "Failed to load image from path '" + img_path + "'")));
|
||||
return false;
|
||||
error_message = "Failed to load image from path '" + img_path + "'";
|
||||
}
|
||||
} else {
|
||||
if (!gin::ConvertFromV8(isolate, image, native_image)) {
|
||||
isolate->ThrowException(v8::Exception::Error(gin::StringToV8(
|
||||
isolate, "Argument must be a file path or a NativeImage")));
|
||||
return false;
|
||||
error_message = "Argument must be a file path or a NativeImage";
|
||||
}
|
||||
}
|
||||
|
||||
if (!error_message.empty()) {
|
||||
switch (on_error) {
|
||||
case OnConvertError::kThrow:
|
||||
isolate->ThrowException(
|
||||
v8::Exception::Error(gin::StringToV8(isolate, error_message)));
|
||||
break;
|
||||
case OnConvertError::kWarn:
|
||||
LOG(WARNING) << error_message;
|
||||
break;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -75,11 +75,13 @@ class NativeImage : public gin::Wrappable<NativeImage> {
|
||||
const gfx::Size& size);
|
||||
#endif
|
||||
|
||||
static v8::Local<v8::FunctionTemplate> GetConstructor(v8::Isolate* isolate);
|
||||
enum class OnConvertError { kThrow, kWarn };
|
||||
|
||||
static bool TryConvertNativeImage(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> image,
|
||||
NativeImage** native_image);
|
||||
static bool TryConvertNativeImage(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> image,
|
||||
NativeImage** native_image,
|
||||
OnConvertError on_error = OnConvertError::kThrow);
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
|
||||
@@ -190,7 +190,6 @@ v8::Local<v8::Value> Converter<content::PermissionType>::ToV8(
|
||||
return StringToV8(isolate, "clipboard-read");
|
||||
case content::PermissionType::CLIPBOARD_SANITIZED_WRITE:
|
||||
return StringToV8(isolate, "clipboard-sanitized-write");
|
||||
case content::PermissionType::CAMERA_PAN_TILT_ZOOM:
|
||||
case content::PermissionType::FONT_ACCESS:
|
||||
return StringToV8(isolate, "font-access");
|
||||
case content::PermissionType::IDLE_DETECTION:
|
||||
@@ -209,6 +208,7 @@ v8::Local<v8::Value> Converter<content::PermissionType>::ToV8(
|
||||
return StringToV8(isolate, "persistent-storage");
|
||||
case content::PermissionType::GEOLOCATION:
|
||||
return StringToV8(isolate, "geolocation");
|
||||
case content::PermissionType::CAMERA_PAN_TILT_ZOOM:
|
||||
case content::PermissionType::AUDIO_CAPTURE:
|
||||
case content::PermissionType::VIDEO_CAPTURE:
|
||||
return StringToV8(isolate, "media");
|
||||
|
||||
@@ -127,22 +127,6 @@ v8::MaybeLocal<v8::Value> GetPrivate(v8::Local<v8::Context> context,
|
||||
gin::StringToV8(context->GetIsolate(), key)));
|
||||
}
|
||||
|
||||
// Where the context bridge should create the exception it is about to throw
|
||||
enum class BridgeErrorTarget {
|
||||
// The source / calling context. This is default and correct 99% of the time,
|
||||
// the caller / context asking for the conversion will receive the error and
|
||||
// therefore the error should be made in that context
|
||||
kSource,
|
||||
// The destination / target context. This should only be used when the source
|
||||
// won't catch the error that results from the value it is passing over the
|
||||
// bridge. This can **only** occur when returning a value from a function as
|
||||
// we convert the return value after the method has terminated and execution
|
||||
// has been returned to the caller. In this scenario the error will the be
|
||||
// catchable in the "destination" context and therefore we create the error
|
||||
// there.
|
||||
kDestination
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
v8::MaybeLocal<v8::Value> PassValueToOtherContext(
|
||||
@@ -152,7 +136,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
|
||||
context_bridge::ObjectCache* object_cache,
|
||||
bool support_dynamic_properties,
|
||||
int recursion_depth,
|
||||
BridgeErrorTarget error_target = BridgeErrorTarget::kSource) {
|
||||
BridgeErrorTarget error_target) {
|
||||
TRACE_EVENT0("electron", "ContextBridge::PassValueToOtherContext");
|
||||
if (recursion_depth >= kMaxRecursion) {
|
||||
v8::Context::Scope error_scope(error_target == BridgeErrorTarget::kSource
|
||||
@@ -288,6 +272,18 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
|
||||
// re-construct in the destination context
|
||||
if (value->IsNativeError()) {
|
||||
v8::Context::Scope destination_context_scope(destination_context);
|
||||
// We should try to pull "message" straight off of the error as a
|
||||
// v8::Message includes some pretext that can get duplicated each time it
|
||||
// crosses the bridge we fallback to the v8::Message approach if we can't
|
||||
// pull "message" for some reason
|
||||
v8::MaybeLocal<v8::Value> maybe_message = value.As<v8::Object>()->Get(
|
||||
source_context,
|
||||
gin::ConvertToV8(source_context->GetIsolate(), "message"));
|
||||
v8::Local<v8::Value> message;
|
||||
if (maybe_message.ToLocal(&message) && message->IsString()) {
|
||||
return v8::MaybeLocal<v8::Value>(
|
||||
v8::Exception::Error(message.As<v8::String>()));
|
||||
}
|
||||
return v8::MaybeLocal<v8::Value>(v8::Exception::Error(
|
||||
v8::Exception::CreateMessage(destination_context->GetIsolate(), value)
|
||||
->Get()));
|
||||
@@ -513,11 +509,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 +537,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);
|
||||
|
||||
@@ -18,6 +18,31 @@ namespace api {
|
||||
|
||||
void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info);
|
||||
|
||||
// Where the context bridge should create the exception it is about to throw
|
||||
enum class BridgeErrorTarget {
|
||||
// The source / calling context. This is default and correct 99% of the time,
|
||||
// the caller / context asking for the conversion will receive the error and
|
||||
// therefore the error should be made in that context
|
||||
kSource,
|
||||
// The destination / target context. This should only be used when the source
|
||||
// won't catch the error that results from the value it is passing over the
|
||||
// bridge. This can **only** occur when returning a value from a function as
|
||||
// we convert the return value after the method has terminated and execution
|
||||
// has been returned to the caller. In this scenario the error will the be
|
||||
// catchable in the "destination" context and therefore we create the error
|
||||
// there.
|
||||
kDestination
|
||||
};
|
||||
|
||||
v8::MaybeLocal<v8::Value> PassValueToOtherContext(
|
||||
v8::Local<v8::Context> source_context,
|
||||
v8::Local<v8::Context> destination_context,
|
||||
v8::Local<v8::Value> value,
|
||||
context_bridge::ObjectCache* object_cache,
|
||||
bool support_dynamic_properties,
|
||||
int recursion_depth,
|
||||
BridgeErrorTarget error_target = BridgeErrorTarget::kSource);
|
||||
|
||||
v8::MaybeLocal<v8::Object> CreateProxyForAPI(
|
||||
const v8::Local<v8::Object>& api_object,
|
||||
const v8::Local<v8::Context>& source_context,
|
||||
|
||||
@@ -173,7 +173,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) {
|
||||
@@ -185,8 +184,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,
|
||||
|
||||
@@ -26,6 +26,8 @@
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/options_switches.h"
|
||||
#include "shell/renderer/api/context_bridge/object_cache.h"
|
||||
#include "shell/renderer/api/electron_api_context_bridge.h"
|
||||
#include "shell/renderer/api/electron_api_spell_check_client.h"
|
||||
#include "shell/renderer/electron_renderer_client.h"
|
||||
#include "third_party/blink/public/common/browser_interface_broker_proxy.h"
|
||||
@@ -160,21 +162,25 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
|
||||
|
||||
void CopyResultToCallingContextAndFinalize(
|
||||
v8::Isolate* isolate,
|
||||
const v8::Local<v8::Value>& result) {
|
||||
blink::CloneableMessage ret;
|
||||
bool success;
|
||||
std::string error_message;
|
||||
const v8::Local<v8::Object>& result) {
|
||||
v8::MaybeLocal<v8::Value> maybe_result;
|
||||
bool success = true;
|
||||
std::string error_message =
|
||||
"An unknown exception occurred while getting the result of the script";
|
||||
{
|
||||
v8::TryCatch try_catch(isolate);
|
||||
success = gin::ConvertFromV8(isolate, result, &ret);
|
||||
context_bridge::ObjectCache object_cache;
|
||||
maybe_result = PassValueToOtherContext(result->CreationContext(),
|
||||
promise_.GetContext(), result,
|
||||
&object_cache, false, 0);
|
||||
if (maybe_result.IsEmpty() || try_catch.HasCaught()) {
|
||||
success = false;
|
||||
}
|
||||
if (try_catch.HasCaught()) {
|
||||
auto message = try_catch.Message();
|
||||
|
||||
if (message.IsEmpty() ||
|
||||
!gin::ConvertFromV8(isolate, message->Get(), &error_message)) {
|
||||
error_message =
|
||||
"An unknown exception occurred while getting the result of "
|
||||
"the script";
|
||||
if (!message.IsEmpty()) {
|
||||
gin::ConvertFromV8(isolate, message->Get(), &error_message);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -190,7 +196,7 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
|
||||
} else {
|
||||
v8::Local<v8::Context> context = promise_.GetContext();
|
||||
v8::Context::Scope context_scope(context);
|
||||
v8::Local<v8::Value> cloned_value = gin::ConvertToV8(isolate, ret);
|
||||
v8::Local<v8::Value> cloned_value = maybe_result.ToLocalChecked();
|
||||
if (callback_)
|
||||
std::move(callback_).Run(cloned_value, v8::Undefined(isolate));
|
||||
promise_.Resolve(cloned_value);
|
||||
@@ -213,7 +219,8 @@ class ScriptExecutionCallback : public blink::WebScriptExecutionCallback {
|
||||
value.As<v8::Object>()->CreationContext()) &&
|
||||
value->IsObject();
|
||||
if (should_clone_value) {
|
||||
CopyResultToCallingContextAndFinalize(isolate, value);
|
||||
CopyResultToCallingContextAndFinalize(isolate,
|
||||
value.As<v8::Object>());
|
||||
} else {
|
||||
// Right now only single results per frame is supported.
|
||||
if (callback_)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -551,46 +551,42 @@ describe('app module', () => {
|
||||
const platformIsNotSupported =
|
||||
(process.platform === 'win32') ||
|
||||
(process.platform === 'linux' && !app.isUnityRunning());
|
||||
const platformIsSupported = !platformIsNotSupported;
|
||||
|
||||
const expectedBadgeCount = 42;
|
||||
|
||||
after(() => { app.badgeCount = 0; });
|
||||
|
||||
describe('on supported platform', () => {
|
||||
it('with properties', () => {
|
||||
ifdescribe(!platformIsNotSupported)('on supported platform', () => {
|
||||
describe('with properties', () => {
|
||||
it('sets a badge count', function () {
|
||||
if (platformIsNotSupported) return this.skip();
|
||||
|
||||
app.badgeCount = expectedBadgeCount;
|
||||
expect(app.badgeCount).to.equal(expectedBadgeCount);
|
||||
});
|
||||
});
|
||||
|
||||
it('with functions', () => {
|
||||
it('sets a badge count', function () {
|
||||
if (platformIsNotSupported) return this.skip();
|
||||
|
||||
describe('with functions', () => {
|
||||
it('sets a numerical badge count', function () {
|
||||
app.setBadgeCount(expectedBadgeCount);
|
||||
expect(app.getBadgeCount()).to.equal(expectedBadgeCount);
|
||||
});
|
||||
it('sets an non numeric (dot) badge count', function () {
|
||||
app.setBadgeCount();
|
||||
// Badge count should be zero when non numeric (dot) is requested
|
||||
expect(app.getBadgeCount()).to.equal(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('on unsupported platform', () => {
|
||||
it('with properties', () => {
|
||||
ifdescribe(process.platform !== 'win32' && platformIsNotSupported)('on unsupported platform', () => {
|
||||
describe('with properties', () => {
|
||||
it('does not set a badge count', function () {
|
||||
if (platformIsSupported) return this.skip();
|
||||
|
||||
app.badgeCount = 9999;
|
||||
expect(app.badgeCount).to.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
it('with functions', () => {
|
||||
describe('with functions', () => {
|
||||
it('does not set a badge count)', function () {
|
||||
if (platformIsSupported) return this.skip();
|
||||
|
||||
app.setBadgeCount(9999);
|
||||
expect(app.getBadgeCount()).to.equal(0);
|
||||
});
|
||||
|
||||
@@ -62,6 +62,15 @@ describe('BrowserWindow module', () => {
|
||||
const appProcess = childProcess.spawn(process.execPath, [appPath]);
|
||||
await new Promise((resolve) => { appProcess.once('exit', resolve); });
|
||||
});
|
||||
|
||||
it('does not crash or throw when passed an invalid icon', async () => {
|
||||
expect(() => {
|
||||
const w = new BrowserWindow({
|
||||
icon: undefined
|
||||
} as any);
|
||||
w.destroy();
|
||||
}).not.to.throw();
|
||||
});
|
||||
});
|
||||
|
||||
describe('garbage collection', () => {
|
||||
@@ -110,6 +119,14 @@ describe('BrowserWindow module', () => {
|
||||
await emittedOnce(w.webContents, 'before-unload-fired');
|
||||
});
|
||||
|
||||
it('should not crash when keyboard event is sent before closing', async () => {
|
||||
await w.loadURL('data:text/html,pls no crash');
|
||||
const closed = emittedOnce(w, 'closed');
|
||||
w.webContents.sendInputEvent({ type: 'keyDown', keyCode: 'Escape' });
|
||||
w.close();
|
||||
await closed;
|
||||
});
|
||||
|
||||
describe('when invoked synchronously inside navigation observer', () => {
|
||||
let server: http.Server = null as unknown as http.Server;
|
||||
let url: string = null as unknown as string;
|
||||
|
||||
@@ -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', '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'))
|
||||
@@ -207,7 +300,7 @@ describe('contextBridge', () => {
|
||||
return err;
|
||||
}
|
||||
});
|
||||
expect(result).to.be.an.instanceOf(Error).with.property('message', 'Uncaught Error: i-rejected');
|
||||
expect(result).to.be.an.instanceOf(Error).with.property('message', 'i-rejected');
|
||||
});
|
||||
|
||||
it('should proxy promises and resolve with the correct value if it resolves later', async () => {
|
||||
@@ -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,137 @@ 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,
|
||||
getError: () => new Error('foo'),
|
||||
getWeirdError: () => {
|
||||
const e = new Error('foo');
|
||||
e.message = { garbage: true } as any;
|
||||
return e;
|
||||
},
|
||||
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.getError(), Error],
|
||||
[cleanedRoot.getError().message, String],
|
||||
[cleanedRoot.getWeirdError(), Error],
|
||||
[cleanedRoot.getWeirdError().message, String],
|
||||
[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 () => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { EventEmitter } from 'events';
|
||||
import { expect } from 'chai';
|
||||
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain } from 'electron/main';
|
||||
import { BrowserWindow, ipcMain, IpcMainInvokeEvent, MessageChannelMain, WebContents } from 'electron/main';
|
||||
import { closeAllWindows } from './window-helpers';
|
||||
import { emittedOnce } from './events-helpers';
|
||||
|
||||
@@ -449,97 +449,102 @@ describe('ipc module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebContents.postMessage', () => {
|
||||
it('sends a message', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
|
||||
w.loadURL('about:blank');
|
||||
await w.webContents.executeJavaScript(`(${function () {
|
||||
const { ipcRenderer } = require('electron');
|
||||
ipcRenderer.on('foo', (_e, msg) => {
|
||||
ipcRenderer.send('bar', msg);
|
||||
const generateTests = (title: string, postMessage: (contents: WebContents) => typeof WebContents.prototype.postMessage) => {
|
||||
describe(title, () => {
|
||||
it('sends a message', async () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } });
|
||||
w.loadURL('about:blank');
|
||||
await w.webContents.executeJavaScript(`(${function () {
|
||||
const { ipcRenderer } = require('electron');
|
||||
ipcRenderer.on('foo', (_e, msg) => {
|
||||
ipcRenderer.send('bar', msg);
|
||||
});
|
||||
}})()`);
|
||||
postMessage(w.webContents)('foo', { some: 'message' });
|
||||
const [, msg] = await emittedOnce(ipcMain, 'bar');
|
||||
expect(msg).to.deep.equal({ some: 'message' });
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('throws on missing channel', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
(postMessage(w.webContents) as any)();
|
||||
}).to.throw(/Insufficient number of arguments/);
|
||||
});
|
||||
}})()`);
|
||||
w.webContents.postMessage('foo', { some: 'message' });
|
||||
const [, msg] = await emittedOnce(ipcMain, 'bar');
|
||||
expect(msg).to.deep.equal({ some: 'message' });
|
||||
});
|
||||
|
||||
describe('error handling', () => {
|
||||
it('throws on missing channel', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
(w.webContents.postMessage as any)();
|
||||
}).to.throw(/Insufficient number of arguments/);
|
||||
});
|
||||
it('throws on invalid channel', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
postMessage(w.webContents)(null as any, '', []);
|
||||
}).to.throw(/Error processing argument at index 0/);
|
||||
});
|
||||
|
||||
it('throws on invalid channel', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
w.webContents.postMessage(null as any, '', []);
|
||||
}).to.throw(/Error processing argument at index 0/);
|
||||
});
|
||||
it('throws on missing message', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
(postMessage(w.webContents) as any)('channel');
|
||||
}).to.throw(/Insufficient number of arguments/);
|
||||
});
|
||||
|
||||
it('throws on missing message', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
(w.webContents.postMessage as any)('channel');
|
||||
}).to.throw(/Insufficient number of arguments/);
|
||||
});
|
||||
it('throws on non-serializable message', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
postMessage(w.webContents)('channel', w);
|
||||
}).to.throw(/An object could not be cloned/);
|
||||
});
|
||||
|
||||
it('throws on non-serializable message', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
w.webContents.postMessage('channel', w);
|
||||
}).to.throw(/An object could not be cloned/);
|
||||
});
|
||||
it('throws on invalid transferable list', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
postMessage(w.webContents)('', '', null as any);
|
||||
}).to.throw(/Invalid value for transfer/);
|
||||
});
|
||||
|
||||
it('throws on invalid transferable list', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
w.webContents.postMessage('', '', null as any);
|
||||
}).to.throw(/Invalid value for transfer/);
|
||||
});
|
||||
it('throws on transferring non-transferable', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
(postMessage(w.webContents) as any)('channel', '', [123]);
|
||||
}).to.throw(/Invalid value for transfer/);
|
||||
});
|
||||
|
||||
it('throws on transferring non-transferable', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
(w.webContents.postMessage as any)('channel', '', [123]);
|
||||
}).to.throw(/Invalid value for transfer/);
|
||||
});
|
||||
it('throws when passing null ports', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
postMessage(w.webContents)('foo', null, [null] as any);
|
||||
}).to.throw(/Invalid value for transfer/);
|
||||
});
|
||||
|
||||
it('throws when passing null ports', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
expect(() => {
|
||||
w.webContents.postMessage('foo', null, [null] as any);
|
||||
}).to.throw(/Invalid value for transfer/);
|
||||
});
|
||||
it('throws when passing duplicate ports', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
const { port1 } = new MessageChannelMain();
|
||||
expect(() => {
|
||||
postMessage(w.webContents)('foo', null, [port1, port1]);
|
||||
}).to.throw(/duplicate/);
|
||||
});
|
||||
|
||||
it('throws when passing duplicate ports', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
const { port1 } = new MessageChannelMain();
|
||||
expect(() => {
|
||||
w.webContents.postMessage('foo', null, [port1, port1]);
|
||||
}).to.throw(/duplicate/);
|
||||
});
|
||||
|
||||
it('throws when passing ports that have already been neutered', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
const { port1 } = new MessageChannelMain();
|
||||
w.webContents.postMessage('foo', null, [port1]);
|
||||
expect(() => {
|
||||
w.webContents.postMessage('foo', null, [port1]);
|
||||
}).to.throw(/already neutered/);
|
||||
it('throws when passing ports that have already been neutered', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
await w.loadURL('about:blank');
|
||||
const { port1 } = new MessageChannelMain();
|
||||
postMessage(w.webContents)('foo', null, [port1]);
|
||||
expect(() => {
|
||||
postMessage(w.webContents)('foo', null, [port1]);
|
||||
}).to.throw(/already neutered/);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
generateTests('WebContents.postMessage', contents => contents.postMessage.bind(contents));
|
||||
generateTests('WebFrameMain.postMessage', contents => contents.mainFrame.postMessage.bind(contents.mainFrame));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -60,9 +60,18 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
const [event1] = await detailsPromise;
|
||||
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
|
||||
event1[0].reply('preload-ping');
|
||||
const details = await pongPromise;
|
||||
expect(details[1]).to.equal(event1[0].frameId);
|
||||
expect(details[1]).to.equal(event1[0].senderFrame.routingId);
|
||||
const [, frameId] = await pongPromise;
|
||||
expect(frameId).to.equal(event1[0].frameId);
|
||||
});
|
||||
|
||||
it('should correctly reply to the main frame with using event.senderFrame.send', async () => {
|
||||
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
|
||||
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
|
||||
const [event1] = await detailsPromise;
|
||||
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
|
||||
event1[0].senderFrame.send('preload-ping');
|
||||
const [, frameId] = await pongPromise;
|
||||
expect(frameId).to.equal(event1[0].frameId);
|
||||
});
|
||||
|
||||
it('should correctly reply to the sub-frames with using event.reply', async () => {
|
||||
@@ -71,9 +80,18 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
const [, event2] = await detailsPromise;
|
||||
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
|
||||
event2[0].reply('preload-ping');
|
||||
const details = await pongPromise;
|
||||
expect(details[1]).to.equal(event2[0].frameId);
|
||||
expect(details[1]).to.equal(event2[0].senderFrame.routingId);
|
||||
const [, frameId] = await pongPromise;
|
||||
expect(frameId).to.equal(event2[0].frameId);
|
||||
});
|
||||
|
||||
it('should correctly reply to the sub-frames with using event.senderFrame.send', async () => {
|
||||
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 2);
|
||||
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-container${fixtureSuffix}.html`));
|
||||
const [, event2] = await detailsPromise;
|
||||
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
|
||||
event2[0].senderFrame.send('preload-ping');
|
||||
const [, frameId] = await pongPromise;
|
||||
expect(frameId).to.equal(event2[0].frameId);
|
||||
});
|
||||
|
||||
it('should correctly reply to the nested sub-frames with using event.reply', async () => {
|
||||
@@ -82,9 +100,18 @@ describe('renderer nodeIntegrationInSubFrames', () => {
|
||||
const [, , event3] = await detailsPromise;
|
||||
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
|
||||
event3[0].reply('preload-ping');
|
||||
const details = await pongPromise;
|
||||
expect(details[1]).to.equal(event3[0].frameId);
|
||||
expect(details[1]).to.equal(event3[0].senderFrame.routingId);
|
||||
const [, frameId] = await pongPromise;
|
||||
expect(frameId).to.equal(event3[0].frameId);
|
||||
});
|
||||
|
||||
it('should correctly reply to the nested sub-frames with using event.senderFrame.send', async () => {
|
||||
const detailsPromise = emittedNTimes(ipcMain, 'preload-ran', 3);
|
||||
w.loadFile(path.resolve(__dirname, `fixtures/sub-frames/frame-with-frame-container${fixtureSuffix}.html`));
|
||||
const [, , event3] = await detailsPromise;
|
||||
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
|
||||
event3[0].senderFrame.send('preload-ping');
|
||||
const [, frameId] = await pongPromise;
|
||||
expect(frameId).to.equal(event3[0].frameId);
|
||||
});
|
||||
|
||||
it('should not expose globals in main world', async () => {
|
||||
|
||||
@@ -2,7 +2,7 @@ import { expect } from 'chai';
|
||||
import * as http from 'http';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
import { BrowserWindow, WebFrameMain, webFrameMain } from 'electron/main';
|
||||
import { BrowserWindow, WebFrameMain, webFrameMain, ipcMain } from 'electron/main';
|
||||
import { closeAllWindows } from './window-helpers';
|
||||
import { emittedOnce, emittedNTimes } from './events-helpers';
|
||||
import { AddressInfo } from 'net';
|
||||
@@ -126,11 +126,12 @@ describe('webFrameMain module', () => {
|
||||
const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } });
|
||||
await w.loadFile(path.join(subframesPath, 'frame.html'));
|
||||
const webFrame = w.webContents.mainFrame;
|
||||
expect(webFrame).to.haveOwnProperty('frameTreeNodeId');
|
||||
expect(webFrame).to.haveOwnProperty('name');
|
||||
expect(webFrame).to.haveOwnProperty('osProcessId');
|
||||
expect(webFrame).to.haveOwnProperty('processId');
|
||||
expect(webFrame).to.haveOwnProperty('routingId');
|
||||
expect(webFrame).to.have.ownProperty('url').that.is.a('string');
|
||||
expect(webFrame).to.have.ownProperty('frameTreeNodeId').that.is.a('number');
|
||||
expect(webFrame).to.have.ownProperty('name').that.is.a('string');
|
||||
expect(webFrame).to.have.ownProperty('osProcessId').that.is.a('number');
|
||||
expect(webFrame).to.have.ownProperty('processId').that.is.a('number');
|
||||
expect(webFrame).to.have.ownProperty('routingId').that.is.a('number');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -173,6 +174,24 @@ describe('webFrameMain module', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('WebFrame.send', () => {
|
||||
it('works', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
preload: path.join(subframesPath, 'preload.js'),
|
||||
nodeIntegrationInSubFrames: true
|
||||
}
|
||||
});
|
||||
await w.loadURL('about:blank');
|
||||
const webFrame = w.webContents.mainFrame;
|
||||
const pongPromise = emittedOnce(ipcMain, 'preload-pong');
|
||||
webFrame.send('preload-ping');
|
||||
const [, routingId] = await pongPromise;
|
||||
expect(routingId).to.equal(webFrame.routingId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('disposed WebFrames', () => {
|
||||
let w: BrowserWindow;
|
||||
let webFrame: WebFrameMain;
|
||||
|
||||
@@ -90,6 +90,9 @@ describe('webRequest module', () => {
|
||||
expect(details.id).to.be.a('number');
|
||||
expect(details.timestamp).to.be.a('number');
|
||||
expect(details.webContentsId).to.be.a('number');
|
||||
expect(details.webContents).to.be.an('object');
|
||||
expect(details.webContents!.id).to.equal(details.webContentsId);
|
||||
expect(details.frame).to.be.an('object');
|
||||
expect(details.url).to.be.a('string').that.is.equal(defaultURL);
|
||||
expect(details.method).to.be.a('string').that.is.equal('GET');
|
||||
expect(details.resourceType).to.be.a('string').that.is.equal('xhr');
|
||||
|
||||
@@ -1550,3 +1550,57 @@ describe('navigator.clipboard', () => {
|
||||
expect(clipboard).to.not.equal('Read permission denied.');
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe((process.platform !== 'linux' || app.isUnityRunning()))('navigator.setAppBadge/clearAppBadge', () => {
|
||||
let w: BrowserWindow;
|
||||
before(async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false
|
||||
});
|
||||
await w.loadFile(path.join(fixturesPath, 'pages', 'blank.html'));
|
||||
});
|
||||
|
||||
const expectedBadgeCount = 42;
|
||||
|
||||
const fireAppBadgeAction: any = (action: string, value: any) => {
|
||||
return w.webContents.executeJavaScript(`
|
||||
navigator.${action}AppBadge(${value}).then(() => 'success').catch(err => err.message)`);
|
||||
};
|
||||
|
||||
// For some reason on macOS changing the badge count doesn't happen right away, so wait
|
||||
// until it changes.
|
||||
async function waitForBadgeCount (value: number) {
|
||||
let badgeCount = app.getBadgeCount();
|
||||
while (badgeCount !== value) {
|
||||
await new Promise(resolve => setTimeout(resolve, 10));
|
||||
badgeCount = app.getBadgeCount();
|
||||
}
|
||||
return badgeCount;
|
||||
}
|
||||
|
||||
after(() => {
|
||||
app.badgeCount = 0;
|
||||
closeAllWindows();
|
||||
});
|
||||
|
||||
it('setAppBadge can set a numerical value', async () => {
|
||||
const result = await fireAppBadgeAction('set', expectedBadgeCount);
|
||||
expect(result).to.equal('success');
|
||||
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
|
||||
});
|
||||
|
||||
it('setAppBadge can set an empty(dot) value', async () => {
|
||||
const result = await fireAppBadgeAction('set');
|
||||
expect(result).to.equal('success');
|
||||
expect(waitForBadgeCount(0)).to.eventually.equal(0);
|
||||
});
|
||||
|
||||
it('clearAppBadge can clear a value', async () => {
|
||||
let result = await fireAppBadgeAction('set', expectedBadgeCount);
|
||||
expect(result).to.equal('success');
|
||||
expect(waitForBadgeCount(expectedBadgeCount)).to.eventually.equal(expectedBadgeCount);
|
||||
result = await fireAppBadgeAction('clear');
|
||||
expect(result).to.equal('success');
|
||||
expect(waitForBadgeCount(0)).to.eventually.equal(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -277,7 +277,7 @@ describe('chrome extensions', () => {
|
||||
it('can cancel http requests', async () => {
|
||||
await w.loadURL(url);
|
||||
await customSession.loadExtension(path.join(fixtures, 'extensions', 'chrome-webRequest'));
|
||||
await expect(fetch(w.webContents, url)).to.eventually.be.rejectedWith(TypeError);
|
||||
await expect(fetch(w.webContents, url)).to.eventually.be.rejectedWith('Failed to fetch');
|
||||
});
|
||||
|
||||
it('does not cancel http requests when no extension loaded', async () => {
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
"sender": "[WebContents]"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"width": 800,
|
||||
"title": "frame name",
|
||||
"title": "cool",
|
||||
"backgroundColor": "blue",
|
||||
"focusable": false,
|
||||
"webPreferences": {
|
||||
@@ -43,11 +43,11 @@
|
||||
"sender": "[WebContents]"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"width": 800,
|
||||
"title": "frame name",
|
||||
"title": "cool",
|
||||
"backgroundColor": "blue",
|
||||
"focusable": false,
|
||||
"webPreferences": {
|
||||
@@ -80,11 +80,11 @@
|
||||
"sender": "[WebContents]"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"width": 800,
|
||||
"title": "frame name",
|
||||
"title": "cool",
|
||||
"backgroundColor": "gray",
|
||||
"focusable": false,
|
||||
"webPreferences": {
|
||||
@@ -115,7 +115,7 @@
|
||||
"sender": "[WebContents]"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"width": 800,
|
||||
@@ -150,11 +150,11 @@
|
||||
"sender": "[WebContents]"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"width": 800,
|
||||
"title": "frame name",
|
||||
"title": "cool",
|
||||
"backgroundColor": "blue",
|
||||
"focusable": false,
|
||||
"webPreferences": {
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
"processId": "placeholder-process-id"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"show": true,
|
||||
"title": "frame name",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "frame-name",
|
||||
"top": 5,
|
||||
"left": 10,
|
||||
"resizable": false,
|
||||
@@ -42,13 +42,13 @@
|
||||
"processId": "placeholder-process-id"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"show": true,
|
||||
"title": "frame name",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "frame-name",
|
||||
"resizable": false,
|
||||
"x": 0,
|
||||
"y": 10,
|
||||
@@ -76,13 +76,13 @@
|
||||
"processId": "placeholder-process-id"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"show": true,
|
||||
"title": "frame name",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "frame-name",
|
||||
"backgroundColor": "gray",
|
||||
"webPreferences": {
|
||||
"nodeIntegration": false,
|
||||
@@ -110,13 +110,13 @@
|
||||
"processId": "placeholder-process-id"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"show": true,
|
||||
"title": "sup",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "sup",
|
||||
"x": 50,
|
||||
"y": 20,
|
||||
"webPreferences": {
|
||||
@@ -142,13 +142,13 @@
|
||||
"processId": "placeholder-process-id"
|
||||
},
|
||||
"about:blank",
|
||||
"frame name",
|
||||
"frame-name",
|
||||
"new-window",
|
||||
{
|
||||
"show": false,
|
||||
"title": "frame name",
|
||||
"width": 800,
|
||||
"height": 600,
|
||||
"title": "frame-name",
|
||||
"top": 1,
|
||||
"left": 1,
|
||||
"x": 1,
|
||||
|
||||
@@ -10,7 +10,7 @@ function genSnapshot (browserWindow: BrowserWindow, features: string) {
|
||||
browserWindow.webContents.on('new-window', (...args: any[]) => {
|
||||
resolve([features, ...args]);
|
||||
});
|
||||
browserWindow.webContents.executeJavaScript(`window.open('about:blank', 'frame name', '${features}') && true`);
|
||||
browserWindow.webContents.executeJavaScript(`window.open('about:blank', 'frame-name', '${features}') && true`);
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@ describe('security warnings', () => {
|
||||
let server: http.Server;
|
||||
let w: BrowserWindow;
|
||||
let useCsp = true;
|
||||
let useTrustedTypes = false;
|
||||
let serverUrl: string;
|
||||
|
||||
before((done) => {
|
||||
@@ -48,8 +49,11 @@ describe('security warnings', () => {
|
||||
return;
|
||||
}
|
||||
|
||||
const cspHeaders = { 'Content-Security-Policy': 'script-src \'self\' \'unsafe-inline\'' };
|
||||
response.writeHead(200, useCsp ? cspHeaders : undefined);
|
||||
const cspHeaders = [
|
||||
...(useCsp ? ['script-src \'self\' \'unsafe-inline\''] : []),
|
||||
...(useTrustedTypes ? ['require-trusted-types-for \'script\'; trusted-types *'] : [])
|
||||
];
|
||||
response.writeHead(200, { 'Content-Security-Policy': cspHeaders });
|
||||
response.write(file, 'binary');
|
||||
response.end();
|
||||
});
|
||||
@@ -68,6 +72,7 @@ describe('security warnings', () => {
|
||||
|
||||
afterEach(async () => {
|
||||
useCsp = true;
|
||||
useTrustedTypes = false;
|
||||
await closeWindow(w);
|
||||
w = null as unknown as any;
|
||||
});
|
||||
@@ -129,6 +134,22 @@ describe('security warnings', () => {
|
||||
expect(message).to.include('Insecure Content-Security-Policy');
|
||||
});
|
||||
|
||||
it('should warn about insecure Content-Security-Policy (Trusted Types)', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
enableRemoteModule: false,
|
||||
...webPreferences
|
||||
}
|
||||
});
|
||||
|
||||
useCsp = false;
|
||||
useTrustedTypes = true;
|
||||
w.loadURL(`${serverUrl}/base-page-security.html`);
|
||||
const [,, message] = await emittedUntil(w.webContents, 'console-message', messageContainsSecurityWarning);
|
||||
expect(message).to.include('Insecure Content-Security-Policy');
|
||||
});
|
||||
|
||||
it('should warn about allowRunningInsecureContent', async () => {
|
||||
w = new BrowserWindow({
|
||||
show: false,
|
||||
|
||||
@@ -63,7 +63,6 @@ describe('<webview> tag', function () {
|
||||
show: false,
|
||||
webPreferences: {
|
||||
webviewTag: true,
|
||||
nodeIntegration: true,
|
||||
sandbox: true
|
||||
}
|
||||
});
|
||||
@@ -76,7 +75,6 @@ describe('<webview> tag', function () {
|
||||
show: false,
|
||||
webPreferences: {
|
||||
webviewTag: true,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: true
|
||||
}
|
||||
});
|
||||
@@ -89,7 +87,6 @@ describe('<webview> tag', function () {
|
||||
show: false,
|
||||
webPreferences: {
|
||||
webviewTag: true,
|
||||
nodeIntegration: true,
|
||||
contextIsolation: true,
|
||||
sandbox: true
|
||||
}
|
||||
@@ -98,6 +95,17 @@ describe('<webview> tag', function () {
|
||||
await emittedOnce(ipcMain, 'pong');
|
||||
});
|
||||
|
||||
it('works with Trusted Types', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
webPreferences: {
|
||||
webviewTag: true
|
||||
}
|
||||
});
|
||||
w.loadFile(path.join(fixtures, 'pages', 'webview-trusted-types.html'));
|
||||
await emittedOnce(ipcMain, 'pong');
|
||||
});
|
||||
|
||||
it('is disabled by default', async () => {
|
||||
const w = new BrowserWindow({
|
||||
show: false,
|
||||
|
||||
@@ -22,6 +22,13 @@ describe('chromium feature', () => {
|
||||
expect(() => {
|
||||
navigator.setAppBadge(42);
|
||||
}).to.not.throw();
|
||||
expect(() => {
|
||||
// setAppBadge with no argument should show dot
|
||||
navigator.setAppBadge();
|
||||
}).to.not.throw();
|
||||
expect(() => {
|
||||
navigator.clearAppBadge();
|
||||
}).to.not.throw();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
8
spec/fixtures/pages/webview-trusted-types.html
vendored
Normal file
8
spec/fixtures/pages/webview-trusted-types.html
vendored
Normal file
@@ -0,0 +1,8 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Content-Security-Policy" content="require-trusted-types-for 'script'; trusted-types *">
|
||||
</head>
|
||||
<body>
|
||||
<webview preload="../module/isolated-ping.js" src="about:blank"/>
|
||||
</body>
|
||||
</html>
|
||||
45
typings/internal-ambient.d.ts
vendored
45
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;
|
||||
}
|
||||
@@ -215,6 +215,10 @@ declare namespace NodeJS {
|
||||
_linkedBinding(name: 'electron_browser_view'): { View: Electron.View };
|
||||
_linkedBinding(name: 'electron_browser_web_contents_view'): { WebContentsView: typeof Electron.WebContentsView };
|
||||
_linkedBinding(name: 'electron_browser_web_view_manager'): WebViewManagerBinding;
|
||||
_linkedBinding(name: 'electron_browser_web_frame_main'): {
|
||||
WebFrameMain: typeof Electron.WebFrameMain;
|
||||
fromId(processId: number, routingId: number): Electron.WebFrameMain;
|
||||
}
|
||||
_linkedBinding(name: 'electron_renderer_crash_reporter'): Electron.CrashReporter;
|
||||
_linkedBinding(name: 'electron_renderer_ipc'): { ipc: IpcRendererBinding };
|
||||
log: NodeJS.WriteStream['write'];
|
||||
@@ -269,6 +273,7 @@ declare interface Window {
|
||||
}
|
||||
};
|
||||
ResizeObserver: ResizeObserver;
|
||||
trustedTypes: TrustedTypePolicyFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -321,6 +326,44 @@ interface ResizeObserverEntry {
|
||||
readonly contentRect: DOMRectReadOnly;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#trusted-types
|
||||
|
||||
type TrustedHTML = string;
|
||||
type TrustedScript = string;
|
||||
type TrustedScriptURL = string;
|
||||
type TrustedType = TrustedHTML | TrustedScript | TrustedScriptURL;
|
||||
type StringContext = 'TrustedHTML' | 'TrustedScript' | 'TrustedScriptURL';
|
||||
|
||||
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#typedef-trustedtypepolicy
|
||||
|
||||
interface TrustedTypePolicy {
|
||||
createHTML(input: string, ...arguments: any[]): TrustedHTML;
|
||||
createScript(input: string, ...arguments: any[]): TrustedScript;
|
||||
createScriptURL(input: string, ...arguments: any[]): TrustedScriptURL;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#typedef-trustedtypepolicyoptions
|
||||
|
||||
interface TrustedTypePolicyOptions {
|
||||
createHTML?: (input: string, ...arguments: any[]) => TrustedHTML;
|
||||
createScript?: (input: string, ...arguments: any[]) => TrustedScript;
|
||||
createScriptURL?: (input: string, ...arguments: any[]) => TrustedScriptURL;
|
||||
}
|
||||
|
||||
// https://w3c.github.io/webappsec-trusted-types/dist/spec/#typedef-trustedtypepolicyfactory
|
||||
|
||||
interface TrustedTypePolicyFactory {
|
||||
createPolicy(policyName: string, policyOptions: TrustedTypePolicyOptions): TrustedTypePolicy
|
||||
isHTML(value: any): boolean;
|
||||
isScript(value: any): boolean;
|
||||
isScriptURL(value: any): boolean;
|
||||
readonly emptyHTML: TrustedHTML;
|
||||
readonly emptyScript: TrustedScript;
|
||||
getAttributeType(tagName: string, attribute: string, elementNs?: string, attrNs?: string): StringContext | null;
|
||||
getPropertyType(tagName: string, property: string, elementNs?: string): StringContext | null;
|
||||
readonly defaultPolicy: TrustedTypePolicy | null;
|
||||
}
|
||||
|
||||
// https://github.com/microsoft/TypeScript/pull/38232
|
||||
|
||||
interface WeakRef<T extends object> {
|
||||
|
||||
12
typings/internal-electron.d.ts
vendored
12
typings/internal-electron.d.ts
vendored
@@ -67,12 +67,9 @@ 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 | [number, number], channel: string, args: any): boolean;
|
||||
_send(internal: boolean, channel: string, args: any): boolean;
|
||||
_sendToFrameInternal(frameId: number | [number, 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[];
|
||||
@@ -94,6 +91,12 @@ declare namespace Electron {
|
||||
allowGuestViewElementDefinition(window: Window, context: any): void;
|
||||
}
|
||||
|
||||
interface WebFrameMain {
|
||||
_send(internal: boolean, channel: string, args: any): void;
|
||||
_sendInternal(channel: string, ...args: any[]): void;
|
||||
_postMessage(channel: string, message: any, transfer?: any[]): void;
|
||||
}
|
||||
|
||||
interface WebPreferences {
|
||||
guestInstanceId?: number;
|
||||
openerId?: number;
|
||||
@@ -232,7 +235,6 @@ 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user