Compare commits

..

32 Commits

Author SHA1 Message Date
Electron Bot
9faf23509d Bump v12.0.0-beta.20 2021-02-01 07:01:03 -08:00
trop[bot]
b4ae35a63d fix: pass button callback in constructor (#27555)
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2021-01-31 09:30:10 +09:00
trop[bot]
025abfc1ba docs: update Xcode / macOS SDK version in build-instructions-macos.md (#27536)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2021-01-29 12:49:12 -08:00
trop[bot]
563bb8559f docs: Update Readme, don't mention Electron < 2 (#27541)
* chore: Update Readme, don't mention Electron < 2

* chore: Add back versioning info

Co-authored-by: Felix Rieseberg <felix@felixrieseberg.com>
2021-01-29 12:44:08 -08:00
trop[bot]
30be5bfa4b fix: replace default frameName title with null check (#27552)
* refactor: replace default frameName title with null check

* add isNativeWindowOpen check in makeBrowserWindowOptions

* modify snapshot test files

* replace title with frame-name again for proxy - not native open

* modify proxy snapshot title key-value to come after height key-value

Co-authored-by: mlaurencin <mlaurencin@electronjs.org>
2021-01-29 12:42:34 -08:00
trop[bot]
28fa60fbcb feat: enable world safe JS by default (#27502)
* feat: enable world safe JS by default

* refactor: use the ctx bridge to send executeJavaScript results in a world safe way

* docs: add more info about the breaking change

* include default in IsEnabled check

* test: fix failing http spec

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Samuel Attard <sattard@slack-corp.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-01-29 12:39:56 -08:00
Electron Bot
37c68d56d6 Bump v12.0.0-beta.19 2021-01-28 07:01:38 -08:00
trop[bot]
da58ded8f9 docs: update verb tenses for structured clone notes (#27501)
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2021-01-27 17:06:17 +09:00
trop[bot]
80a3f10b6b docs: add missing contextBridge API to README (#27511)
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2021-01-27 17:03:10 +09:00
Milan Burda
889abd0c8e docs: update OSR max FPS number (#26805) (#27508)
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2021-01-27 16:34:45 +09:00
trop[bot]
0305f08888 refactor: cleanup WebFrameMain + improve tests (#27500)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2021-01-26 19:58:07 -08:00
Milan Burda
0604f8727c fix: CSP with unsafe-eval detection with Trusted Types (#27446) (#27471) 2021-01-26 21:42:16 -06:00
trop[bot]
551896c4ce build: fix build with enable_printing=false (#27474)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2021-01-26 14:12:05 -08:00
trop[bot]
c11a5dcf29 fix: hiddenInset missing maximize button (#27462)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-01-25 11:29:09 -08:00
trop[bot]
b86eb74fbf fix: don't throw on bad icons in BrowserWindow constructor (#27463)
* fix: do not throw if NativeImage conversion fails.

Throwing is an unannounced semver/major breaking change, so revert that
behavior but keep the rest of the #26546 refactor.
2021-01-25 11:32:32 -06:00
trop[bot]
e49a88ba53 fix: <webview> not working with Trusted Types (#27467)
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2021-01-25 08:42:02 -08:00
Electron Bot
4ab817768d Bump v12.0.0-beta.18 2021-01-25 07:01:21 -08:00
trop[bot]
f730284113 fix: add eyedropper tool functionality to browser view (#27442)
Co-authored-by: mlaurencin <mlaurencin@electronjs.org>
2021-01-21 16:19:07 -08:00
trop[bot]
6122f4bece fix: actually clear pending requests in DevToolsAgentHost (#27440)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-01-21 13:58:16 -08:00
Milan Burda
017628f84d fix: apply tzdata2020f to ICU (#27370)
Co-authored-by: Milan Burda <miburda@microsoft.com>
2021-01-21 13:01:01 -08:00
Electron Bot
eb132d8b3e Bump v12.0.0-beta.17 2021-01-21 11:21:36 -08:00
trop[bot]
f26025301a fix: enable navigator.setAppBadge/clearAppBadge (#27431)
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2021-01-21 12:16:21 -05:00
John Kleinschmidt
a79750b871 Revert "Bump v12.0.0-beta.17"
This reverts commit 870d8c0307.
2021-01-21 10:29:05 -05:00
Electron Bot
870d8c0307 Bump v12.0.0-beta.17 2021-01-21 07:02:20 -08:00
trop[bot]
1f22b2bfdc feat: add frame and webContents to webRequest details (#27334)
* feat: add frame and webContents to webRequest details

* chore: use frame_converter.h

Co-authored-by: Jeremy Rose <nornagon@nornagon.net>
Co-authored-by: Cheng Zhao <zcbenz@gmail.com>
2021-01-21 15:55:30 +09:00
Milan Burda
e25de07657 feat: add webFrameMain.send() / webFrameMain.postMessage() (#26807) (#27366) 2021-01-21 15:49:02 +09:00
trop[bot]
6bfccca157 fix: Shutdown crash in DownloadItem callback (#27418)
The call stack for one of our top crashes looks like this:

```
node::Abort (node_errors.cc:241)
node::Assert (node_errors.cc:256)
node::MakeCallback (callback.cc:226)
gin_helper::internal::CallMethodWithArgs (event_emitter_caller.cc:23)
gin_helper::EmitEvent<T> (event_emitter_caller.h:51)
gin_helper::EventEmitterMixin<T>::Emit<T> (event_emitter_mixin.h:81)
electron::api::DownloadItem::OnDownloadUpdated (electron_api_download_item.cc:115)
download::DownloadItemImpl::UpdateObservers (download_item_impl.cc:482)
content::DownloadManagerImpl::Shutdown (download_manager_impl.cc:508)
content::BrowserContext::~BrowserContext (browser_context.cc:476)
```

Full stack here: https://sentry.io/share/issue/9b030a0601b547188181b543c16ecda2/

During browser shutdown, the `DownloadManager` was being cleaned up
*after* the Node environment had already been destroyed. This caused the
`DownloadItem::OnDownloadUpdated` callback to crash when trying to emit
the JS `done` event.

To prevent this, we now manually shut down the `DownloadManager`
earlier. This is also mentioned in the comment on
`DownloadManager::Shutdown`:

```
// Shutdown the download manager. Content calls this when BrowserContext is
// being destructed. If the embedder needs this to be called earlier, it can
// call it. In that case, the delegate's Shutdown() method will only be called
// once.
```

Co-authored-by: Biru Mohanathas <birunthan@mohanathas.com>
2021-01-21 15:31:05 +09:00
Shelley Vohr
58c1ce50d4 fix: incorrect case in content::PermissionType mapping (#27422) 2021-01-20 15:59:05 -08:00
trop[bot]
c46ed96421 fix: increase stack size on windows (#27384)
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2021-01-20 11:48:05 -05:00
trop[bot]
9278459c46 fix: prevent crash when keyboard event immediately precedes calling BrowserWindow.close() (#27359)
* fix: prevent crash when destroyed widget receives keyboard event

Activating a key to close a window will cause a silent crash. Handling the keyboard
event will lead to a nullptr dereferenced in Chromium code if the window widget has
already been destroyed.

* test: ensure BrowserWindow doesn't crash from keyboard events during close

Co-authored-by: samuelmaddock <samuel.maddock@gmail.com>
2021-01-19 15:51:09 +09:00
Milan Burda
a6af3bd8df chore: remove unused sendToAll + related APIs (#26771) (#27354) 2021-01-19 15:49:54 +09:00
trop[bot]
c74780117e feat: exposeInMainWorld allow to expose non-object APIs (#26834)
* feat: ExposeAPIInMainWorld allow to process non-object APIs cpp part

* fix: add IsTypedArray check before DeepFreeze of the api

* feat: ExposeAPIInMainWorld allow to process non-object APIs js part - ts types change

* test: add new context bridge tests

* docs: ctx bridge exposeInMainWorld change documentation for any API

Co-authored-by: nikitakot <nikitakot@microsoft.com>
2021-01-19 15:46:01 +09:00
90 changed files with 3524 additions and 515 deletions

View File

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

View File

@@ -1 +1 @@
12.0.0-beta.16
12.0.0-beta.20

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -0,0 +1 @@
fix_apply_tzdata2020f_to_icu.patch

File diff suppressed because it is too large Load Diff

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View 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

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

View 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

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

12
shell/browser/browser.h Normal file → Executable file
View 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);
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

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

View File

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