Compare commits

...

78 Commits

Author SHA1 Message Date
Keeley Hammond
a8278fc6d5 Merge branch 'main' into replace-browserview-merge-check 2023-09-12 07:22:01 -07:00
Keeley Hammond
551c56e95e chore: fix browser-view lint 2023-08-31 12:38:09 -07:00
Keeley Hammond
0256ee3b3a Merge branch 'main' into replace-browserview 2023-08-30 18:07:18 -07:00
Jeremy Rose
30dac6851b Update docs/api/base-window.md
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2023-04-24 16:41:55 -07:00
Jeremy Rose
ffb2bd1ccc docs fixes 2023-04-24 16:34:57 -07:00
Jeremy Rose
e058817206 fix color regexes 2023-04-24 15:01:52 -07:00
Jeremy Rose
a7fec0b561 Update docs/api/web-contents-view.md
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
2023-04-24 14:59:16 -07:00
Jeremy Rose
64178a46cf add note to breaking-changes 2023-04-19 15:44:29 -07:00
Jeremy Rose
30ae5cc3c8 update docs 2023-04-19 12:50:37 -07:00
Jeremy Rose
cfddfa7406 clean up enable_views_api buildflag 2023-04-18 15:23:15 -07:00
Jeremy Rose
f3ee9e8416 Merge branch 'main' into replace-browserview 2023-04-10 14:56:28 -07:00
Jeremy Rose
5bb4eb12e5 lint 2023-04-05 16:16:13 -07:00
Jeremy Rose
240780b6eb fix test on windows 2023-04-05 16:01:59 -07:00
Jeremy Rose
949eaae00e Merge remote-tracking branch 'origin/main' into replace-browserview 2023-04-05 11:04:09 -07:00
Jeremy Rose
cf183f083a fix types 2023-03-29 11:33:48 -07:00
Jeremy Rose
d3c53124f6 Merge remote-tracking branch 'origin/main' into replace-browserview 2023-03-27 10:12:27 -07:00
Jeremy Rose
f78fc0518d Merge remote-tracking branch 'origin/main' into replace-browserview 2023-03-02 14:16:22 -08:00
Jeremy Rose
3862dd4d29 fix test 2023-02-27 14:25:41 -08:00
Jeremy Rose
de516cfc09 Merge remote-tracking branch 'origin/main' into replace-browserview 2023-02-27 13:46:49 -08:00
Jeremy Rose
3f7184c2f5 Merge remote-tracking branch 'origin/main' into replace-browserview 2023-02-14 10:28:16 -08:00
Jeremy Rose
14006c209f fix initial visibility and add tests 2023-02-09 16:10:13 -08:00
Jeremy Rose
adc9286c82 Merge remote-tracking branch 'origin/main' into replace-browserview 2023-02-09 14:45:00 -08:00
Jeremy Rose
d3490fca78 rough example 2023-02-07 13:07:17 -08:00
Jeremy Rose
673b4e8e54 address review 2023-02-07 12:54:24 -08:00
Jeremy Rose
5f480f383f Update docs/tutorial/web-embeds.md
Co-authored-by: Samuel Maddock <smaddock@salesforce.com>
2023-02-07 12:42:52 -08:00
Jeremy Rose
186399d93b Merge remote-tracking branch 'origin/main' into replace-browserview 2023-02-07 12:40:58 -08:00
Jeremy Rose
01e0ddf3cd fix lint 2023-01-31 15:35:40 -08:00
Jeremy Rose
a19f58551b Merge branch 'main' into replace-browserview 2023-01-31 15:16:33 -08:00
Jeremy Rose
663126de7e Merge branch 'main' into replace-browserview 2023-01-31 11:03:14 -08:00
Jeremy Rose
bfa17d177b pull in test from #36414 2022-11-21 14:31:35 -08:00
Jeremy Rose
0d77e774a8 Merge remote-tracking branch 'origin/main' into replace-browserview 2022-11-21 14:05:06 -08:00
Jeremy Rose
88d0b7fb89 restore browserView webcontents type 2022-11-10 16:25:38 -08:00
Jeremy Rose
b3814f4139 remove ENABLE_VIEWS_API build flag 2022-11-10 16:05:43 -08:00
Jeremy Rose
c5bf1ef902 fix merge 2022-11-07 11:20:12 -08:00
Jeremy Rose
46906e5eb4 Merge remote-tracking branch 'origin/main' into replace-browserview 2022-11-07 11:17:41 -08:00
Jeremy Rose
cbdfbad726 more fix 2022-11-02 12:21:35 -07:00
Jeremy Rose
0a70344212 fix build on views 2022-11-02 10:32:37 -07:00
Jeremy Rose
5a171f66d4 Merge branch 'main' into replace-browserview 2022-11-01 17:13:33 -07:00
Jeremy Rose
85f9cb5e32 make draggable regions work 2022-11-01 17:07:44 -07:00
Jeremy Rose
7569f2cf5d Merge branch 'refactor-draggable-regions-again' into replace-browserview 2022-11-01 16:40:20 -07:00
Jeremy Rose
4fae1af7ad refactor: move draggable regions to WebContents 2022-11-01 16:29:18 -07:00
Jeremy Rose
c75570627d lint 2022-10-27 17:22:01 -07:00
Jeremy Rose
252c402443 Merge remote-tracking branch 'origin/main' into replace-browserview 2022-10-27 17:06:43 -07:00
Jeremy Rose
a8e0c15ffc add ,index param to addChildView 2022-10-12 19:25:29 -07:00
Jeremy Rose
674f782b38 update docs 2022-10-12 15:23:37 -07:00
Jeremy Rose
42d23e5a40 lint 2022-10-12 14:49:58 -07:00
Jeremy Rose
bf77393286 fix some tests 2022-10-12 13:14:51 -07:00
Jeremy Rose
fea43a4727 suppress fade animation when adding and removing WCVs 2022-10-12 13:00:51 -07:00
Jeremy Rose
8bcbe7b703 Merge remote-tracking branch 'origin/main' into replace-browserview 2022-10-12 11:24:13 -07:00
Jeremy Rose
c28eef26aa lint 2022-10-11 15:17:41 -07:00
Jeremy Rose
1a4df6eb0c observe view_ deletion and CHECK in relevant places 2022-10-11 15:00:41 -07:00
Jeremy Rose
34f0476549 don't double destroy WebContents 2022-10-11 14:53:31 -07:00
Jeremy Rose
40ce8c371c lint 2022-10-11 14:46:18 -07:00
Jeremy Rose
afc9babec2 Merge branch 'main' into replace-browserview 2022-10-05 17:15:23 -07:00
Jeremy Rose
7590abe62c cpplint 2022-10-05 17:13:34 -07:00
Jeremy Rose
997d46f090 trigger beforeunload; fix crash when getting webContents after destroy 2022-10-05 17:05:29 -07:00
Jeremy Rose
389fefac8a update setBackgroundColor invalid arg test 2022-10-05 16:35:10 -07:00
Jeremy Rose
1e94bf0c42 another test 2022-10-05 16:20:33 -07:00
Jeremy Rose
04899edbc2 fix bv tests 2022-10-05 16:17:26 -07:00
Jeremy Rose
d3a714709b Merge remote-tracking branch 'origin/main' into replace-browserview 2022-10-05 15:28:29 -07:00
Jeremy Rose
5ffe8672a3 wip autoresize 2022-09-26 11:01:54 -07:00
Jeremy Rose
c26f8701b4 View events 2022-09-21 20:10:06 -05:00
Jeremy Rose
e50312f7f7 fix some bv tests 2022-09-21 15:24:17 -07:00
Jeremy Rose
3d6a5c6e09 Merge branch 'browserview-tests' into replace-browserview 2022-09-15 14:43:43 -07:00
Jeremy Rose
c545482250 test: add more tests for BrowserView.setAutoResize 2022-09-15 14:40:55 -07:00
Jeremy Rose
82551ba2cf do setOwnerWindow 2022-09-15 13:11:36 -07:00
Jeremy Rose
ade06e0915 lint 2022-09-15 12:41:19 -07:00
Jeremy Rose
b1c7457434 most of the BrowserView shim 2022-09-15 11:45:46 -07:00
Jeremy Rose
2de4fc7129 fix close button 2022-09-15 10:40:45 -07:00
Jeremy Rose
fdc2151a63 background color 2022-09-15 10:28:02 -07:00
Jeremy Rose
2d4f131fe5 add flexlayout 2022-09-14 20:27:39 -07:00
Jeremy Rose
46cbe4feee drop log 2022-09-14 15:11:44 -07:00
Jeremy Rose
ac4c0b2f0f restore references to browserview in docs, but replaced with references to webcontentsview 2022-09-14 15:05:01 -07:00
Jeremy Rose
f0ceb6ed6d drop beforeunload 2022-09-14 09:52:36 -07:00
Jeremy Rose
30bad4a24f remove unrelated unresponsive event changes 2022-09-13 11:28:43 -07:00
Jeremy Rose
77cf368826 contentView, children 2022-09-13 11:06:14 -07:00
Jeremy Rose
59a5ecbb5e add View.setBounds 2022-09-13 11:06:14 -07:00
Jeremy Rose
fad08947ea wip 2022-09-13 11:06:12 -07:00
64 changed files with 2887 additions and 868 deletions

View File

@@ -671,13 +671,6 @@ source_set("electron_lib") {
]
}
if (enable_views_api) {
sources += [
"shell/browser/api/views/electron_api_image_view.cc",
"shell/browser/api/views/electron_api_image_view.h",
]
}
if (enable_printing) {
sources += [
"shell/browser/printing/print_view_manager_electron.cc",

View File

@@ -53,12 +53,6 @@ module.exports = ({
const ignoredModules = [];
if (defines.ENABLE_VIEWS_API === 'false') {
ignoredModules.push(
'@electron/internal/browser/api/views/image-view.js'
);
}
const plugins = [];
if (onlyPrintingGraph) {

View File

@@ -9,7 +9,6 @@ buildflag_header("buildflags") {
header = "buildflags.h"
flags = [
"ENABLE_VIEWS_API=$enable_views_api",
"ENABLE_PDF_VIEWER=$enable_pdf_viewer",
"ENABLE_ELECTRON_EXTENSIONS=$enable_electron_extensions",
"ENABLE_BUILTIN_SPELLCHECKER=$enable_builtin_spellchecker",

View File

@@ -3,8 +3,6 @@
# found in the LICENSE file.
declare_args() {
enable_views_api = true
enable_pdf_viewer = true
# Provide a fake location provider for mocking

View File

@@ -106,7 +106,7 @@ These individual tutorials expand on topics discussed in the guide above.
* [app](api/app.md)
* [autoUpdater](api/auto-updater.md)
* [BrowserView](api/browser-view.md)
* [BaseWindow](api/base-window.md)
* [BrowserWindow](api/browser-window.md)
* [contentTracing](api/content-tracing.md)
* [desktopCapturer](api/desktop-capturer.md)
@@ -134,8 +134,10 @@ These individual tutorials expand on topics discussed in the guide above.
* [TouchBar](api/touch-bar.md)
* [Tray](api/tray.md)
* [utilityProcess](api/utility-process.md)
* [View](api/view.md)
* [webContents](api/web-contents.md)
* [webFrameMain](api/web-frame-main.md)
* [WebContentsView](api/web-contents-view.md)
### Modules for the Renderer Process (Web Page):

1523
docs/api/base-window.md Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,5 +1,9 @@
# BrowserView
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
A `BrowserView` can be used to embed additional web content into a
[`BrowserWindow`](browser-window.md). It is like a child window, except that it is positioned
relative to its owning window. It is meant to be an alternative to the
@@ -9,6 +13,10 @@ relative to its owning window. It is meant to be an alternative to the
> Create and control views.
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
Process: [Main](../glossary.md#main-process)
This module cannot be used until the `ready` event of the `app`
@@ -30,7 +38,7 @@ app.whenReady().then(() => {
})
```
### `new BrowserView([options])` _Experimental_
### `new BrowserView([options])` _Experimental_ _Deprecated_
* `options` Object (optional)
* `webPreferences` [WebPreferences](structures/web-preferences.md?inline) (optional) - Settings of web page's features.
@@ -39,7 +47,7 @@ app.whenReady().then(() => {
Objects created with `new BrowserView` have the following properties:
#### `view.webContents` _Experimental_
#### `view.webContents` _Experimental_ _Deprecated_
A [`WebContents`](web-contents.md) object owned by this view.
@@ -47,7 +55,7 @@ A [`WebContents`](web-contents.md) object owned by this view.
Objects created with `new BrowserView` have the following instance methods:
#### `view.setAutoResize(options)` _Experimental_
#### `view.setAutoResize(options)` _Experimental_ _Deprecated_
* `options` Object
* `width` boolean (optional) - If `true`, the view's width will grow and shrink together
@@ -59,19 +67,19 @@ Objects created with `new BrowserView` have the following instance methods:
* `vertical` boolean (optional) - If `true`, the view's y position and height will grow
and shrink proportionally with the window. `false` by default.
#### `view.setBounds(bounds)` _Experimental_
#### `view.setBounds(bounds)` _Experimental_ _Deprecated_
* `bounds` [Rectangle](structures/rectangle.md)
Resizes and moves the view to the supplied bounds relative to the window.
#### `view.getBounds()` _Experimental_
#### `view.getBounds()` _Experimental_ _Deprecated_
Returns [`Rectangle`](structures/rectangle.md)
The `bounds` of this BrowserView instance as `Object`.
#### `view.setBackgroundColor(color)` _Experimental_
#### `view.setBackgroundColor(color)` _Experimental_ _Deprecated_
* `color` string - Color in Hex, RGB, ARGB, HSL, HSLA or named CSS color format. The alpha channel is
optional for the hex type.
@@ -79,25 +87,25 @@ The `bounds` of this BrowserView instance as `Object`.
Examples of valid `color` values:
* Hex
* #fff (RGB)
* #ffff (ARGB)
* #ffffff (RRGGBB)
* #ffffffff (AARRGGBB)
* `#fff` (RGB)
* `#ffff` (ARGB)
* `#ffffff` (RRGGBB)
* `#ffffffff` (AARRGGBB)
* RGB
* rgb\((\[\d]+),\s*(\[\d]+),\s*(\[\d]+)\)
* e.g. rgb(255, 255, 255)
* `rgb\(([\d]+),\s*([\d]+),\s*([\d]+)\)`
* e.g. `rgb(255, 255, 255)`
* RGBA
* rgba\((\[\d]+),\s*(\[\d]+),\s*(\[\d]+),\s*(\[\d.]+)\)
* e.g. rgba(255, 255, 255, 1.0)
* `rgba\(([\d]+),\s*([\d]+),\s*([\d]+),\s*([\d.]+)\)`
* e.g. `rgba(255, 255, 255, 1.0)`
* HSL
* hsl\((-?\[\d.]+),\s*(\[\d.]+)%,\s*(\[\d.]+)%\)
* e.g. hsl(200, 20%, 50%)
* `hsl\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%\)`
* e.g. `hsl(200, 20%, 50%)`
* HSLA
* hsla\((-?\[\d.]+),\s*(\[\d.]+)%,\s*(\[\d.]+)%,\s*(\[\d.]+)\)
* e.g. hsla(200, 20%, 50%, 0.5)
* `hsla\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)`
* e.g. `hsla(200, 20%, 50%, 0.5)`
* Color name
* Options are listed in [SkParseColor.cpp](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/utils/SkParseColor.cpp;l=11-152;drc=eea4bf52cb0d55e2a39c828b017c80a5ee054148)
* Similar to CSS Color Module Level 3 keywords, but case-sensitive.
* e.g. `blueviolet` or `red`
**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBA` or `RGA`.
**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBAA` or `RGB`.

View File

@@ -140,7 +140,7 @@ state is `hidden` in order to minimize power consumption.
* On Linux the type of modal windows will be changed to `dialog`.
* On Linux many desktop environments do not support hiding a modal window.
## Class: BrowserWindow
## Class: BrowserWindow extends `BaseWindow`
> Create and control browser windows.
@@ -470,10 +470,14 @@ Returns `BrowserWindow | null` - The window that is focused in this application,
Returns `BrowserWindow | null` - The window that owns the given `webContents`
or `null` if the contents are not owned by a window.
#### `BrowserWindow.fromBrowserView(browserView)`
#### `BrowserWindow.fromBrowserView(browserView)` _Deprecated_
* `browserView` [BrowserView](browser-view.md)
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
Returns `BrowserWindow | null` - The window that owns the given `browserView`. If the given view is not attached to any window, returns `null`.
#### `BrowserWindow.fromId(id)`
@@ -1606,41 +1610,62 @@ machine has a touch bar.
**Note:** The TouchBar API is currently experimental and may change or be
removed in future Electron releases.
#### `win.setBrowserView(browserView)` _Experimental_
#### `win.setBrowserView(browserView)` _Experimental_ _Deprecated_
* `browserView` [BrowserView](browser-view.md) | null - Attach `browserView` to `win`.
If there are other `BrowserView`s attached, they will be removed from
this window.
#### `win.getBrowserView()` _Experimental_
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
#### `win.getBrowserView()` _Experimental_ _Deprecated_
Returns `BrowserView | null` - The `BrowserView` attached to `win`. Returns `null`
if one is not attached. Throws an error if multiple `BrowserView`s are attached.
#### `win.addBrowserView(browserView)` _Experimental_
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
#### `win.addBrowserView(browserView)` _Experimental_ _Deprecated_
* `browserView` [BrowserView](browser-view.md)
Replacement API for setBrowserView supporting work with multi browser views.
#### `win.removeBrowserView(browserView)` _Experimental_
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
#### `win.removeBrowserView(browserView)` _Experimental_ _Deprecated_
* `browserView` [BrowserView](browser-view.md)
#### `win.setTopBrowserView(browserView)` _Experimental_
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
#### `win.setTopBrowserView(browserView)` _Experimental_ _Deprecated_
* `browserView` [BrowserView](browser-view.md)
Raises `browserView` above other `BrowserView`s attached to `win`.
Throws an error if `browserView` is not attached to `win`.
#### `win.getBrowserViews()` _Experimental_
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
#### `win.getBrowserViews()` _Experimental_ _Deprecated_
Returns `BrowserView[]` - a sorted by z-index array of all BrowserViews that have been attached
with `addBrowserView` or `setBrowserView`. The top-most BrowserView is the last element of the array.
**Note:** The BrowserView API is currently experimental and may change or be
removed in future Electron releases.
> **Note**
> The `BrowserView` class is deprecated, and replaced by the new
> [`WebContentsView`](web-contents-view.md) class.
#### `win.setTitleBarOverlay(options)` _Windows_

View File

@@ -132,11 +132,6 @@ Setting this variable is the same as passing `--log-file`
on the command line. For more info, see `--log-file` in [command-line
switches](./command-line-switches.md#--log-filepath).
### `ELECTRON_DEBUG_DRAG_REGIONS`
Adds coloration to draggable regions on [`BrowserView`](./browser-view.md)s on macOS - draggable regions will be colored
green and non-draggable regions will be colored red to aid debugging.
### `ELECTRON_DEBUG_NOTIFICATIONS`
Adds extra logs to [`Notification`](./notification.md) lifecycles on macOS to aid in debugging. Extra logging will be displayed when new Notifications are created or activated. They will also be displayed when common actions are taken: a notification is shown, dismissed, its button is clicked, or it is replied to.

106
docs/api/view.md Normal file
View File

@@ -0,0 +1,106 @@
# View
> Create and layout native views.
Process: [Main](../glossary.md#main-process)
This module cannot be used until the `ready` event of the `app`
module is emitted.
```javascript
const { BaseWindow, View } = require('electron')
const win = new BaseWindow()
const view = new View()
view.setBackgroundColor('red')
view.setBounds({ x: 0, y: 0, width: 100, height: 100 })
win.contentView.addChildView(view)
```
## Class: View
> A basic native view.
Process: [Main](../glossary.md#main-process)
`View` is an [EventEmitter][event-emitter].
### `new View()`
Creates a new `View`.
### Instance Events
Objects created with `new View` emit the following events:
#### Event: 'bounds-changed'
Emitted when the view's bounds have changed in response to being laid out. The
new bounds can be retrieved with [`view.getBounds()`](#viewgetbounds).
### Instance Methods
Objects created with `new View` have the following instance methods:
#### `view.addChildView(view[, index])`
* `view` View - Child view to add.
* `index` Integer (optional) - Index at which to insert the child view.
Defaults to adding the child at the end of the child list.
#### `view.removeChildView(view)`
* `view` View - Child view to remove.
#### `view.setBounds(bounds)`
* `bounds` [Rectangle](structures/rectangle.md) - New bounds of the View.
#### `view.getBounds()`
Returns [`Rectangle`](structures/rectangle.md) - The bounds of this View, relative to its parent.
#### `view.setBackgroundColor(color)`
* `color` string - Color in Hex, RGB, ARGB, HSL, HSLA or named CSS color format. The alpha channel is
optional for the hex type.
Examples of valid `color` values:
* Hex
* `#fff` (RGB)
* `#ffff` (ARGB)
* `#ffffff` (RRGGBB)
* `#ffffffff` (AARRGGBB)
* RGB
* `rgb\(([\d]+),\s*([\d]+),\s*([\d]+)\)`
* e.g. `rgb(255, 255, 255)`
* RGBA
* `rgba\(([\d]+),\s*([\d]+),\s*([\d]+),\s*([\d.]+)\)`
* e.g. `rgba(255, 255, 255, 1.0)`
* HSL
* `hsl\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%\)`
* e.g. `hsl(200, 20%, 50%)`
* HSLA
* `hsla\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)`
* e.g. `hsla(200, 20%, 50%, 0.5)`
* Color name
* Options are listed in [SkParseColor.cpp](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/utils/SkParseColor.cpp;l=11-152;drc=eea4bf52cb0d55e2a39c828b017c80a5ee054148)
* Similar to CSS Color Module Level 3 keywords, but case-sensitive.
* e.g. `blueviolet` or `red`
**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBAA` or `RGB`.
#### `view.setVisible(visible)`
* `visible` boolean - If false, the view will be hidden from display.
### Instance Properties
Objects created with `new View` have the following properties:
#### `view.children` _Readonly_
A `View[]` property representing the child views of this view.
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter

View File

@@ -0,0 +1,126 @@
# WebContentsView
> A View that displays a WebContents.
Process: [Main](../glossary.md#main-process)
This module cannot be used until the `ready` event of the `app`
module is emitted.
```javascript
const { BaseWindow, WebContentsView } = require('electron')
const win = new BaseWindow({ width: 800, height: 400 })
const view1 = new WebContentsView()
win.contentView.addChildView(view1)
view1.webContents.loadURL('https://electronjs.org')
view1.setBounds({ x: 0, y: 0, width: 400, height: 400 })
const view2 = new WebContentsView()
win.contentView.addChildView(view2)
view2.webContents.loadURL('https://github.com/electron/electron')
view2.setBounds({ x: 400, y: 0, width: 400, height: 400 })
```
## Class: WebContentsView extends `View`
> A View that displays a WebContents.
Process: [Main](../glossary.md#main-process)
`WebContentsView` is an [EventEmitter][event-emitter].
### `new WebContentsView([options])`
* `options` WebPreferences (optional) - See the [`webPreferences` option in
BrowserWindow](./browser-window.md#new-browserwindowoptions).
Creates an empty WebContentsView.
### Instance Events
Objects created with `new View` emit the following events:
#### Event: 'bounds-changed'
Emitted when the view's bounds have changed in response to being laid out. The
new bounds can be retrieved with [`view.getBounds()`](#viewgetbounds).
### Instance Methods
Objects created with `new WebContentsView` have the following instance methods:
#### `view.addChildView(view[, index])`
* `view` View - Child view to add.
* `index` Integer (optional) - Index at which to insert the child view.
Defaults to adding the child at the end of the child list.
#### `view.removeChildView(view)`
* `view` View - Child view to remove.
#### `view.setBounds(bounds)`
* `bounds` [Rectangle](structures/rectangle.md) - New bounds of the View.
#### `view.getBounds()`
Returns [`Rectangle`](structures/rectangle.md) - The bounds of this View, relative to its parent.
#### `view.setBackgroundColor(color)`
* `color` string - Color in Hex, RGB, ARGB, HSL, HSLA or named CSS color format. The alpha channel is
optional for the hex type.
Examples of valid `color` values:
* Hex
* `#fff` (RGB)
* `#ffff` (ARGB)
* `#ffffff` (RRGGBB)
* `#ffffffff` (AARRGGBB)
* RGB
* `rgb\(([\d]+),\s*([\d]+),\s*([\d]+)\)`
* e.g. `rgb(255, 255, 255)`
* RGBA
* `rgba\(([\d]+),\s*([\d]+),\s*([\d]+),\s*([\d.]+)\)`
* e.g. `rgba(255, 255, 255, 1.0)`
* HSL
* `hsl\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%\)`
* e.g. `hsl(200, 20%, 50%)`
* HSLA
* `hsla\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)`
* e.g. `hsla(200, 20%, 50%, 0.5)`
* Color name
* Options are listed in [SkParseColor.cpp](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/utils/SkParseColor.cpp;l=11-152;drc=eea4bf52cb0d55e2a39c828b017c80a5ee054148)
* Similar to CSS Color Module Level 3 keywords, but case-sensitive.
* e.g. `blueviolet` or `red`
**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBAA` or `RGB`.
#### `view.setVisible(visible)`
* `visible` boolean - If false, the view will be hidden from display.
### Instance Properties
Objects created with `new WebContentsView` have the following properties:
#### `view.webContents` _Readonly_
A `WebContents` property containing a reference to the displayed `WebContents`.
Use this to interact with the `WebContents`, for instance to load a URL.
```javascript
const { WebContentsView } = require('electron')
const view = new WebContentsView()
view.webContents.loadURL('https://electronjs.org/')
```
#### `view.children` _Readonly_
A `View[]` property representing the child views of this view.
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter

View File

@@ -4,9 +4,10 @@
Electron's `webview` tag is based on [Chromium's `webview`][chrome-webview], which
is undergoing dramatic architectural changes. This impacts the stability of `webviews`,
including rendering, navigation, and event routing. We currently recommend to not
use the `webview` tag and to consider alternatives, like `iframe`, [Electron's `BrowserView`](browser-view.md),
or an architecture that avoids embedded content altogether.
including rendering, navigation, and event routing. We currently recommend to
not use the `webview` tag and to consider alternatives, like `iframe`, a
[`WebContentsView`](web-contents-view.md), or an architecture that avoids
embedded content altogether.
## Enabling

View File

@@ -52,6 +52,16 @@ if (ret === null) {
}
```
### Behavior Changed: `BrowserView.setAutoResize` behavior on macOS
Previously, the `setAutoResize` function of the `BrowserView` API was backed by [autoresizing](https://developer.apple.com/documentation/appkit/nsview/1483281-autoresizingmask?language=objc) on macOS, and by a custom algorithm on Windows and Linux.
For simple use cases such as making a BrowserView fill the entire window, the behavior of these two approaches was identical.
However, in more advanced cases, BrowserViews would be autoresized differently on macOS than they would be on other platforms, as the custom resizing algorithm for Windows and Linux did not perfectly match the behavior of macOS's autoresizing API.
In Electron 26, BrowserView is now a wrapper around the new [WebContentsView](api/web-contents-view.md) API.
As part of this work, the autoresizing behavior is now standardized across all platforms.
BrowserView autoresizing on macOS now uses the same algorithm that is used on Windows and Linux.
### Removed: `ipcRenderer.sendTo()`
The `ipcRenderer.sendTo()` API has been removed. It should be replaced by setting up a [`MessageChannel`](tutorial/message-ports.md#setting-up-a-messagechannel-between-two-renderers) between the renderers.

View File

@@ -79,8 +79,8 @@ will be able to execute native code on the user's machine.
Under no circumstances should you load and execute remote code with
Node.js integration enabled. Instead, use only local files (packaged together
with your application) to execute Node.js code. To display remote content, use
the [`<webview>`][webview-tag] tag or [`BrowserView`][browser-view], make sure
to disable the `nodeIntegration` and enable `contextIsolation`.
the [`<webview>`][webview-tag] tag or a [`WebContentsView`][web-contents-view]
and make sure to disable the `nodeIntegration` and enable `contextIsolation`.
:::
:::info Electron security warnings
@@ -166,7 +166,7 @@ This recommendation is the default behavior in Electron since 5.0.0.
:::
It is paramount that you do not enable Node.js integration in any renderer
([`BrowserWindow`][browser-window], [`BrowserView`][browser-view], or
([`BrowserWindow`][browser-window], [`WebContentsView`][web-contents-view], or
[`<webview>`][webview-tag]) that loads remote content. The goal is to limit the
powers you grant to remote content, thus making it dramatically more difficult
for an attacker to harm your users should they gain the ability to execute
@@ -306,8 +306,8 @@ This recommendation is Electron's default.
You may have already guessed that disabling the `webSecurity` property on a
renderer process ([`BrowserWindow`][browser-window],
[`BrowserView`][browser-view], or [`<webview>`][webview-tag]) disables crucial
security features.
[`WebContentsView`][web-contents-view], or [`<webview>`][webview-tag]) disables
crucial security features.
Do not disable `webSecurity` in production applications.
@@ -761,10 +761,10 @@ function validateSender (frame) {
[breaking-changes]: ../breaking-changes.md
[browser-window]: ../api/browser-window.md
[browser-view]: ../api/browser-view.md
[webview-tag]: ../api/webview-tag.md
[web-contents-view]: ../api/web-contents-view.md
[responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure
[web-contents]: ../api/web-contents.md
[window-open-handler]: ../api/web-contents.md#contentssetwindowopenhandlerhandler
[will-navigate]: ../api/web-contents.md#event-will-navigate
[open-external]: ../api/shell.md#shellopenexternalurl-options
[responsible-disclosure]: https://en.wikipedia.org/wiki/Responsible_disclosure

View File

@@ -42,16 +42,15 @@ Compared to an `<iframe>`, `<webview>` tends to be slightly slower but offers
much greater control in loading and communicating with the third-party content
and handling various events.
### BrowserViews
### WebContentsView
[BrowserViews](../api/browser-view.md) are not a part of the DOM - instead,
they are created in and controlled by your Main process. They are simply
another layer of web content on top of your existing window. This means
that they are completely separate from your own `BrowserWindow` content and
their position is not controlled by the DOM or CSS. Instead, it is controlled
by setting the bounds in the Main process.
[`WebContentsView`](../api/web-contents-view.md)s are not a part of the
DOM—instead, they are created, controlled, positioned, and sized by your
Main process. Using `WebContentsView`, you can combine and layer many pages
together in the same [`BaseWindow`](../api/base-window.md).
`BrowserViews` offer the greatest control over their contents, since they
implement the `webContents` similarly to how the `BrowserWindow` does it.
However, as `BrowserViews` are not a part of your DOM, but are rather overlaid
on top of them, you will have to manage their position manually.
`WebContentsView`s offer the greatest control over their contents, since they
implement the `webContents` similarly to how `BrowserWindow` does it. However,
as `WebContentsView`s are not elements inside the DOM, positioning them
accurately with respect to DOM content requires coordination between the
Main and Renderer processes.

View File

@@ -4,6 +4,7 @@ auto_filenames = {
"docs/api/accelerator.md",
"docs/api/app.md",
"docs/api/auto-updater.md",
"docs/api/base-window.md",
"docs/api/browser-view.md",
"docs/api/browser-window.md",
"docs/api/client-request.md",
@@ -63,6 +64,8 @@ auto_filenames = {
"docs/api/touch-bar.md",
"docs/api/tray.md",
"docs/api/utility-process.md",
"docs/api/view.md",
"docs/api/web-contents-view.md",
"docs/api/web-contents.md",
"docs/api/web-frame-main.md",
"docs/api/web-frame.md",

View File

@@ -118,6 +118,8 @@ filenames = {
lib_sources_mac = [
"shell/app/electron_main_delegate_mac.h",
"shell/app/electron_main_delegate_mac.mm",
"shell/browser/animation_util.h",
"shell/browser/animation_util_mac.mm",
"shell/browser/api/electron_api_app_mac.mm",
"shell/browser/api/electron_api_menu_mac.h",
"shell/browser/api/electron_api_menu_mac.mm",
@@ -141,8 +143,6 @@ filenames = {
"shell/browser/mac/in_app_purchase_product.mm",
"shell/browser/mac/in_app_purchase.h",
"shell/browser/mac/in_app_purchase.mm",
"shell/browser/native_browser_view_mac.h",
"shell/browser/native_browser_view_mac.mm",
"shell/browser/native_window_mac.h",
"shell/browser/native_window_mac.mm",
"shell/browser/notifications/mac/cocoa_notification.h",
@@ -207,8 +207,6 @@ filenames = {
lib_sources_views = [
"shell/browser/api/electron_api_menu_views.cc",
"shell/browser/api/electron_api_menu_views.h",
"shell/browser/native_browser_view_views.cc",
"shell/browser/native_browser_view_views.h",
"shell/browser/native_window_views.cc",
"shell/browser/native_window_views.h",
"shell/browser/ui/drag_util_views.cc",
@@ -253,8 +251,6 @@ filenames = {
"shell/browser/api/electron_api_auto_updater.h",
"shell/browser/api/electron_api_base_window.cc",
"shell/browser/api/electron_api_base_window.h",
"shell/browser/api/electron_api_browser_view.cc",
"shell/browser/api/electron_api_browser_view.h",
"shell/browser/api/electron_api_browser_window.cc",
"shell/browser/api/electron_api_browser_window.h",
"shell/browser/api/electron_api_content_tracing.cc",
@@ -337,6 +333,8 @@ filenames = {
"shell/browser/api/save_page_handler.h",
"shell/browser/api/ui_event.cc",
"shell/browser/api/ui_event.h",
"shell/browser/api/views/electron_api_image_view.cc",
"shell/browser/api/views/electron_api_image_view.h",
"shell/browser/auto_updater.cc",
"shell/browser/auto_updater.h",
"shell/browser/badging/badge_manager.cc",
@@ -412,8 +410,6 @@ filenames = {
"shell/browser/media/media_device_id_salt.h",
"shell/browser/microtasks_runner.cc",
"shell/browser/microtasks_runner.h",
"shell/browser/native_browser_view.cc",
"shell/browser/native_browser_view.h",
"shell/browser/native_window.cc",
"shell/browser/native_window.h",
"shell/browser/native_window_features.cc",

View File

@@ -4,7 +4,7 @@ const { BaseWindow } = process._linkedBinding('electron_browser_base_window') as
Object.setPrototypeOf(BaseWindow.prototype, EventEmitter.prototype);
BaseWindow.prototype._init = function () {
BaseWindow.prototype._init = function (this: TLWT) {
// Avoid recursive require.
const { app } = require('electron');
@@ -103,7 +103,7 @@ Object.defineProperty(BaseWindow.prototype, 'movable', {
});
BaseWindow.getFocusedWindow = () => {
return BaseWindow.getAllWindows().find((win) => win.isFocused());
return BaseWindow.getAllWindows().find((win) => win.isFocused()) ?? null;
};
module.exports = BaseWindow;

View File

@@ -1,3 +1,119 @@
const { BrowserView } = process._linkedBinding('electron_browser_browser_view');
import { BrowserWindow, AutoResizeOptions, Rectangle, WebContentsView, WebPreferences, WebContents } from 'electron/main';
export default BrowserView;
const v8Util = process._linkedBinding('electron_common_v8_util');
export default class BrowserView {
#webContentsView: WebContentsView;
// AutoResize state
#resizeListener: ((...args: any[]) => void) | null = null;
#lastWindowSize: {width: number, height: number} = { width: 0, height: 0 };
#autoResizeFlags: AutoResizeOptions = {};
constructor (options: {webPreferences: WebPreferences, webContents?: WebContents} = { webPreferences: {} }) {
const { webPreferences = {}, webContents } = options;
if (webContents) {
v8Util.setHiddenValue(webPreferences, 'webContents', webContents);
}
webPreferences.type = 'browserView';
this.#webContentsView = new WebContentsView(webPreferences);
}
get webContents () {
return this.#webContentsView.webContents;
}
setBounds (bounds: Rectangle) {
this.#webContentsView.setBounds(bounds);
this.#autoHorizontalProportion = null;
this.#autoVerticalProportion = null;
}
getBounds () {
return this.#webContentsView.getBounds();
}
setAutoResize (options: AutoResizeOptions) {
if (options == null || typeof options !== 'object') { throw new Error('Invalid auto resize options'); }
this.#autoResizeFlags = {
width: !!options.width,
height: !!options.height,
horizontal: !!options.horizontal,
vertical: !!options.vertical
};
this.#autoHorizontalProportion = null;
this.#autoVerticalProportion = null;
}
setBackgroundColor (color: string) {
this.#webContentsView.setBackgroundColor(color);
}
// Internal methods
get ownerWindow (): BrowserWindow | null {
return this.webContents.getOwnerBrowserWindow();
}
set ownerWindow (w: BrowserWindow | null) {
const oldWindow = this.webContents.getOwnerBrowserWindow();
if (oldWindow && this.#resizeListener) {
oldWindow.off('resize', this.#resizeListener);
this.#resizeListener = null;
}
this.webContents._setOwnerWindow(w);
if (w) {
this.#lastWindowSize = w.getBounds();
w.on('resize', this.#resizeListener = this.#autoResize.bind(this));
}
}
#autoHorizontalProportion: {width: number, left: number} | null = null;
#autoVerticalProportion: {height: number, top: number} | null = null;
#autoResize () {
if (!this.ownerWindow) throw new Error('Electron bug: #autoResize called without owner window');
if (this.#autoResizeFlags.horizontal && this.#autoHorizontalProportion == null) {
const viewBounds = this.#webContentsView.getBounds();
this.#autoHorizontalProportion = {
width: this.#lastWindowSize.width / viewBounds.width,
left: this.#lastWindowSize.width / viewBounds.x
};
}
if (this.#autoResizeFlags.vertical && this.#autoVerticalProportion == null) {
const viewBounds = this.#webContentsView.getBounds();
this.#autoVerticalProportion = {
height: this.#lastWindowSize.height / viewBounds.height,
top: this.#lastWindowSize.height / viewBounds.y
};
}
const newBounds = this.ownerWindow.getBounds();
let widthDelta = newBounds.width - this.#lastWindowSize.width;
let heightDelta = newBounds.height - this.#lastWindowSize.height;
if (!this.#autoResizeFlags.width) widthDelta = 0;
if (!this.#autoResizeFlags.height) heightDelta = 0;
const newViewBounds = this.#webContentsView.getBounds();
if (widthDelta || heightDelta) {
this.#webContentsView.setBounds({
...newViewBounds,
width: newViewBounds.width + widthDelta,
height: newViewBounds.height + heightDelta
});
}
if (this.#autoHorizontalProportion) {
newViewBounds.width = newBounds.width / this.#autoHorizontalProportion.width;
newViewBounds.x = newBounds.width / this.#autoHorizontalProportion.left;
}
if (this.#autoVerticalProportion) {
newViewBounds.height = newBounds.height / this.#autoVerticalProportion.height;
newViewBounds.y = newBounds.y / this.#autoVerticalProportion.top;
}
if (this.#autoHorizontalProportion || this.#autoVerticalProportion) {
this.#webContentsView.setBounds(newViewBounds);
}
}
get webContentsView () {
return this.#webContentsView;
}
}

View File

@@ -1,4 +1,4 @@
import { BaseWindow, WebContents, BrowserView, TouchBar } from 'electron/main';
import { BaseWindow, WebContents, TouchBar, BrowserView } from 'electron/main';
import type { BrowserWindow as BWT } from 'electron/main';
import * as deprecate from '@electron/internal/common/deprecate';
const { BrowserWindow } = process._linkedBinding('electron_browser_window') as { BrowserWindow: typeof BWT };
@@ -7,7 +7,7 @@ Object.setPrototypeOf(BrowserWindow.prototype, BaseWindow.prototype);
BrowserWindow.prototype._init = function (this: BWT) {
// Call parent class's _init.
BaseWindow.prototype._init.call(this);
(BaseWindow.prototype as any)._init.call(this);
// Avoid recursive require.
const { app } = require('electron');
@@ -53,6 +53,12 @@ BrowserWindow.prototype._init = function (this: BWT) {
this.on(event as any, visibilityChanged);
}
this._browserViews = [];
this.on('close', () => {
this._browserViews.forEach(b => b.webContents?.close({ waitForBeforeUnload: true }));
});
const warn = deprecate.warnOnceMessage('\'scroll-touch-{begin,end,edge}\' are deprecated and will be removed. Please use the WebContents \'input-event\' event instead.');
this.webContents.on('input-event', (_, e) => {
if (e.type === 'gestureScrollBegin') {
@@ -191,4 +197,42 @@ BrowserWindow.prototype.setBackgroundThrottling = function (allowed: boolean) {
return this.webContents.setBackgroundThrottling(allowed);
};
BrowserWindow.prototype.addBrowserView = function (browserView: BrowserView) {
if (browserView.ownerWindow) { browserView.ownerWindow.removeBrowserView(browserView); }
this.contentView.addChildView(browserView.webContentsView);
browserView.ownerWindow = this;
browserView.webContents._setOwnerWindow(this);
this._browserViews.push(browserView);
};
BrowserWindow.prototype.setBrowserView = function (browserView: BrowserView) {
this._browserViews.forEach(bv => {
this.removeBrowserView(bv);
});
if (browserView) { this.addBrowserView(browserView); }
};
BrowserWindow.prototype.removeBrowserView = function (browserView: BrowserView) {
const idx = this._browserViews.indexOf(browserView);
if (idx >= 0) {
this.contentView.removeChildView(browserView.webContentsView);
browserView.ownerWindow = null;
this._browserViews.splice(idx, 1);
}
};
BrowserWindow.prototype.getBrowserView = function () {
if (this._browserViews.length > 1) { throw new Error('This BrowserWindow has multiple BrowserViews, use getBrowserViews() instead'); }
return this._browserViews[0] ?? null;
};
BrowserWindow.prototype.getBrowserViews = function () {
return [...this._browserViews];
};
BrowserWindow.prototype.setTopBrowserView = function (browserView: BrowserView) {
if (browserView.ownerWindow !== this) { throw new Error('Given BrowserView is not attached to the window'); }
this.addBrowserView(browserView);
};
module.exports = BrowserWindow;

View File

@@ -14,6 +14,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'dialog', loader: () => require('./dialog') },
{ name: 'globalShortcut', loader: () => require('./global-shortcut') },
{ name: 'ipcMain', loader: () => require('./ipc-main') },
{ name: 'ImageView', loader: () => require('./views/image-view') },
{ name: 'inAppPurchase', loader: () => require('./in-app-purchase') },
{ name: 'Menu', loader: () => require('./menu') },
{ name: 'MenuItem', loader: () => require('./menu-item') },
@@ -39,9 +40,3 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
{ name: 'WebContentsView', loader: () => require('./web-contents-view') },
{ name: 'webFrameMain', loader: () => require('./web-frame-main') }
];
if (BUILDFLAG(ENABLE_VIEWS_API)) {
browserModuleList.push(
{ name: 'ImageView', loader: () => require('./views/image-view') }
);
}

View File

@@ -1,3 +1,6 @@
import { EventEmitter } from 'events';
const { View } = process._linkedBinding('electron_browser_view');
Object.setPrototypeOf((View as any).prototype, EventEmitter.prototype);
export default View;

View File

@@ -152,6 +152,10 @@ 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');
// Required because `new BrowserWindow` calls some WebContentsView stuff, so
// the inheritance needs to be set up before that happens.
require('@electron/internal/browser/api/web-contents-view');
// Set main startup script of the app.
const mainStartupScript = packageJson.main || 'index.js';

View File

@@ -0,0 +1,18 @@
// Copyright (c) 2022 Salesforce, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_ANIMATION_UTIL_H_
#define ELECTRON_SHELL_BROWSER_ANIMATION_UTIL_H_
#include "build/build_config.h"
#if BUILDFLAG(IS_MAC)
class ScopedCAActionDisabler {
public:
ScopedCAActionDisabler();
~ScopedCAActionDisabler();
};
#endif
#endif // ELECTRON_SHELL_BROWSER_ANIMATION_UTIL_H_

View File

@@ -0,0 +1,17 @@
// Copyright (c) 2022 Salesforce, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/animation_util.h"
#import <QuartzCore/QuartzCore.h>
// Disables actions within a scope.
ScopedCAActionDisabler::ScopedCAActionDisabler() {
[CATransaction begin];
[CATransaction setDisableActions:YES];
}
ScopedCAActionDisabler::~ScopedCAActionDisabler() {
[CATransaction commit];
}

View File

@@ -13,7 +13,6 @@
#include "base/task/single_thread_task_runner.h"
#include "electron/buildflags/buildflags.h"
#include "gin/dictionary.h"
#include "shell/browser/api/electron_api_browser_view.h"
#include "shell/browser/api/electron_api_menu.h"
#include "shell/browser/api/electron_api_view.h"
#include "shell/browser/api/electron_api_web_contents.h"
@@ -97,6 +96,8 @@ BaseWindow::BaseWindow(v8::Isolate* isolate,
options, parent.IsEmpty() ? nullptr : parent->window_.get()));
window_->AddObserver(this);
SetContentView(View::Create(isolate));
#if defined(TOOLKIT_VIEWS)
v8::Local<v8::Value> icon;
if (options.Get(options::kIcon, &icon)) {
@@ -164,7 +165,6 @@ void BaseWindow::OnWindowClosed() {
Emit("closed");
RemoveFromParentChildWindows();
BaseWindow::ResetBrowserViews();
// Destroy the native class when window is closed.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
@@ -311,8 +311,7 @@ void BaseWindow::OnWindowMessage(UINT message, WPARAM w_param, LPARAM l_param) {
#endif
void BaseWindow::SetContentView(gin::Handle<View> view) {
ResetBrowserViews();
content_view_.Reset(isolate(), view.ToV8());
content_view_.Reset(JavascriptEnvironment::GetIsolate(), view.ToV8());
window_->SetContentView(view->view());
}
@@ -749,70 +748,6 @@ void BaseWindow::SetParentWindow(v8::Local<v8::Value> value,
}
}
void BaseWindow::SetBrowserView(
absl::optional<gin::Handle<BrowserView>> browser_view) {
ResetBrowserViews();
if (browser_view)
AddBrowserView(*browser_view);
}
void BaseWindow::AddBrowserView(gin::Handle<BrowserView> browser_view) {
if (!base::Contains(browser_views_, browser_view.ToV8())) {
// If we're reparenting a BrowserView, ensure that it's detached from
// its previous owner window.
BaseWindow* owner_window = browser_view->owner_window();
if (owner_window) {
// iter == browser_views_.end() should imply owner_window != this.
DCHECK_NE(owner_window, this);
owner_window->RemoveBrowserView(browser_view);
browser_view->SetOwnerWindow(nullptr);
}
// If the user set the BrowserView's bounds before adding it to the window,
// we need to get those initial bounds *before* adding it to the window
// so bounds isn't returned relative despite not being correctly positioned
// relative to the window.
auto bounds = browser_view->GetBounds();
window_->AddBrowserView(browser_view->view());
window_->AddDraggableRegionProvider(browser_view.get());
browser_view->SetOwnerWindow(this);
browser_views_.emplace_back().Reset(isolate(), browser_view.ToV8());
// Recalibrate bounds relative to the containing window.
if (!bounds.IsEmpty())
browser_view->SetBounds(bounds);
}
}
void BaseWindow::RemoveBrowserView(gin::Handle<BrowserView> browser_view) {
auto iter = std::find(browser_views_.begin(), browser_views_.end(),
browser_view.ToV8());
if (iter != browser_views_.end()) {
window_->RemoveDraggableRegionProvider(browser_view.get());
window_->RemoveBrowserView(browser_view->view());
browser_view->SetOwnerWindow(nullptr);
iter->Reset();
browser_views_.erase(iter);
}
}
void BaseWindow::SetTopBrowserView(gin::Handle<BrowserView> browser_view,
gin_helper::Arguments* args) {
BaseWindow* owner_window = browser_view->owner_window();
auto iter = std::find(browser_views_.begin(), browser_views_.end(),
browser_view.ToV8());
if (iter == browser_views_.end() || (owner_window && owner_window != this)) {
args->ThrowError("Given BrowserView is not attached to the window");
return;
}
browser_views_.erase(iter);
browser_views_.emplace_back().Reset(isolate(), browser_view.ToV8());
window_->SetTopBrowserView(browser_view->view());
}
std::string BaseWindow::GetMediaSourceId() const {
return window_->GetDesktopMediaID().ToString();
}
@@ -1010,31 +945,6 @@ std::vector<v8::Local<v8::Object>> BaseWindow::GetChildWindows() const {
return child_windows_.Values(isolate());
}
v8::Local<v8::Value> BaseWindow::GetBrowserView(
gin_helper::Arguments* args) const {
if (browser_views_.empty()) {
return v8::Null(isolate());
} else if (browser_views_.size() == 1) {
auto first_view = browser_views_.begin();
return v8::Local<v8::Value>::New(isolate(), *first_view);
} else {
args->ThrowError(
"BrowserWindow have multiple BrowserViews, "
"Use getBrowserViews() instead");
return v8::Null(isolate());
}
}
std::vector<v8::Local<v8::Value>> BaseWindow::GetBrowserViews() const {
std::vector<v8::Local<v8::Value>> ret;
for (auto const& browser_view : browser_views_) {
ret.push_back(v8::Local<v8::Value>::New(isolate(), browser_view));
}
return ret;
}
bool BaseWindow::IsModal() const {
return window_->is_modal();
}
@@ -1132,31 +1042,6 @@ int32_t BaseWindow::GetID() const {
return weak_map_id();
}
void BaseWindow::ResetBrowserViews() {
v8::HandleScope scope(isolate());
for (auto& item : browser_views_) {
gin::Handle<BrowserView> browser_view;
if (gin::ConvertFromV8(isolate(),
v8::Local<v8::Value>::New(isolate(), item),
&browser_view) &&
!browser_view.IsEmpty()) {
// There's a chance that the BrowserView may have been reparented - only
// reset if the owner window is *this* window.
BaseWindow* owner_window = browser_view->owner_window();
DCHECK_EQ(owner_window, this);
browser_view->SetOwnerWindow(nullptr);
window_->RemoveBrowserView(browser_view->view());
window_->RemoveDraggableRegionProvider(browser_view.get());
browser_view->SetOwnerWindow(nullptr);
}
item.Reset();
}
browser_views_.clear();
}
void BaseWindow::RemoveFromParentChildWindows() {
if (parent_window_.IsEmpty())
return;
@@ -1267,10 +1152,6 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
.SetMethod("setMenu", &BaseWindow::SetMenu)
.SetMethod("removeMenu", &BaseWindow::RemoveMenu)
.SetMethod("setParentWindow", &BaseWindow::SetParentWindow)
.SetMethod("setBrowserView", &BaseWindow::SetBrowserView)
.SetMethod("addBrowserView", &BaseWindow::AddBrowserView)
.SetMethod("removeBrowserView", &BaseWindow::RemoveBrowserView)
.SetMethod("setTopBrowserView", &BaseWindow::SetTopBrowserView)
.SetMethod("getMediaSourceId", &BaseWindow::GetMediaSourceId)
.SetMethod("getNativeWindowHandle", &BaseWindow::GetNativeWindowHandle)
.SetMethod("setProgressBar", &BaseWindow::SetProgressBar)
@@ -1325,10 +1206,10 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
.SetMethod("previewFile", &BaseWindow::PreviewFile)
.SetMethod("closeFilePreview", &BaseWindow::CloseFilePreview)
.SetMethod("getContentView", &BaseWindow::GetContentView)
.SetProperty("contentView", &BaseWindow::GetContentView,
&BaseWindow::SetContentView)
.SetMethod("getParentWindow", &BaseWindow::GetParentWindow)
.SetMethod("getChildWindows", &BaseWindow::GetChildWindows)
.SetMethod("getBrowserView", &BaseWindow::GetBrowserView)
.SetMethod("getBrowserViews", &BaseWindow::GetBrowserViews)
.SetMethod("isModal", &BaseWindow::IsModal)
.SetMethod("setThumbarButtons", &BaseWindow::SetThumbarButtons)
#if defined(TOOLKIT_VIEWS)

View File

@@ -22,7 +22,6 @@
namespace electron::api {
class View;
class BrowserView;
class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
public NativeWindowObserver {
@@ -173,14 +172,6 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
void SetMenu(v8::Isolate* isolate, v8::Local<v8::Value> menu);
void RemoveMenu();
void SetParentWindow(v8::Local<v8::Value> value, gin_helper::Arguments* args);
virtual void SetBrowserView(
absl::optional<gin::Handle<BrowserView>> browser_view);
virtual void AddBrowserView(gin::Handle<BrowserView> browser_view);
virtual void RemoveBrowserView(gin::Handle<BrowserView> browser_view);
virtual void SetTopBrowserView(gin::Handle<BrowserView> browser_view,
gin_helper::Arguments* args);
virtual std::vector<v8::Local<v8::Value>> GetBrowserViews() const;
virtual void ResetBrowserViews();
std::string GetMediaSourceId() const;
v8::Local<v8::Value> GetNativeWindowHandle();
void SetProgressBar(double progress, gin_helper::Arguments* args);
@@ -228,7 +219,6 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
v8::Local<v8::Value> GetContentView() const;
v8::Local<v8::Value> GetParentWindow() const;
std::vector<v8::Local<v8::Object>> GetChildWindows() const;
v8::Local<v8::Value> GetBrowserView(gin_helper::Arguments* args) const;
bool IsModal() const;
// Extra APIs added in JS.
@@ -255,9 +245,6 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
// Helpers.
// Remove BrowserView.
void ResetBrowserView();
// Remove this window from parent window's |child_windows_|.
void RemoveFromParentChildWindows();

View File

@@ -13,7 +13,6 @@
#include "content/public/common/color_parser.h"
#include "shell/browser/api/electron_api_web_contents_view.h"
#include "shell/browser/browser.h"
#include "shell/browser/native_browser_view.h"
#include "shell/browser/web_contents_preferences.h"
#include "shell/browser/window_list.h"
#include "shell/common/color_util.h"
@@ -101,8 +100,6 @@ BrowserWindow::~BrowserWindow() {
// Cleanup the observers if user destroyed this instance directly instead of
// gracefully closing content::WebContents.
api_web_contents_->RemoveObserver(this);
// Destroy the WebContents.
OnCloseContents();
api_web_contents_->Destroy();
}
}
@@ -129,10 +126,6 @@ void BrowserWindow::WebContentsDestroyed() {
CloseImmediately();
}
void BrowserWindow::OnCloseContents() {
BaseWindow::ResetBrowserViews();
}
void BrowserWindow::OnRendererResponsive(content::RenderProcessHost*) {
window_unresponsive_closure_.Cancel();
Emit("responsive");
@@ -187,26 +180,6 @@ void BrowserWindow::OnCloseButtonClicked(bool* prevent_default) {
// Required to make beforeunload handler work.
api_web_contents_->NotifyUserActivation();
// Trigger beforeunload events for associated BrowserViews.
for (NativeBrowserView* view : window_->browser_views()) {
auto* iwc = view->GetInspectableWebContents();
if (!iwc)
continue;
auto* vwc = iwc->GetWebContents();
auto* api_web_contents = api::WebContents::From(vwc);
// Required to make beforeunload handler work.
if (api_web_contents)
api_web_contents->NotifyUserActivation();
if (vwc) {
if (vwc->NeedToFireBeforeUnloadOrUnloadEvents()) {
vwc->DispatchBeforeUnload(false /* auto_cancel */);
}
}
}
if (web_contents()->NeedToFireBeforeUnloadOrUnloadEvents()) {
web_contents()->DispatchBeforeUnload(false /* auto_cancel */);
} else {
@@ -288,16 +261,10 @@ void BrowserWindow::Blur() {
void BrowserWindow::SetBackgroundColor(const std::string& color_name) {
BaseWindow::SetBackgroundColor(color_name);
SkColor color = ParseCSSColor(color_name);
web_contents()->SetPageBaseBackgroundColor(color);
auto* rwhv = web_contents()->GetRenderWidgetHostView();
if (rwhv) {
rwhv->SetBackgroundColor(color);
static_cast<content::RenderWidgetHostViewBase*>(rwhv)
->SetContentBackgroundColor(color);
}
// Also update the web preferences object otherwise the view will be reset on
// the next load URL call
if (api_web_contents_) {
api_web_contents_->SetBackgroundColor(color);
// Also update the web preferences object otherwise the view will be reset
// on the next load URL call
auto* web_preferences =
WebContentsPreferences::From(api_web_contents_->web_contents());
if (web_preferences) {
@@ -306,13 +273,6 @@ void BrowserWindow::SetBackgroundColor(const std::string& color_name) {
}
}
void BrowserWindow::SetBrowserView(
absl::optional<gin::Handle<BrowserView>> browser_view) {
BaseWindow::ResetBrowserViews();
if (browser_view)
BaseWindow::AddBrowserView(*browser_view);
}
void BrowserWindow::FocusOnWebView() {
web_contents()->GetRenderViewHost()->GetWidget()->Focus();
}

View File

@@ -49,7 +49,6 @@ class BrowserWindow : public BaseWindow,
void WebContentsDestroyed() override;
// ExtendedWebContentsObserver:
void OnCloseContents() override;
void OnSetContentBounds(const gfx::Rect& rect) override;
void OnActivateContents() override;
void OnPageTitleUpdated(const std::u16string& title,
@@ -69,8 +68,6 @@ class BrowserWindow : public BaseWindow,
void Focus() override;
void Blur() override;
void SetBackgroundColor(const std::string& color_name) override;
void SetBrowserView(
absl::optional<gin::Handle<BrowserView>> browser_view) override;
void OnWindowShow() override;
void OnWindowHide() override;

View File

@@ -4,34 +4,344 @@
#include "shell/browser/api/electron_api_view.h"
#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include "gin/data_object_builder.h"
#include "gin/wrappable.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/gfx_converter.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/node_includes.h"
#include "ui/views/background.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/layout/layout_manager_base.h"
#if BUILDFLAG(IS_MAC)
#include "shell/browser/animation_util.h"
#endif
namespace gin {
template <>
struct Converter<views::ChildLayout> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
views::ChildLayout* out) {
gin_helper::Dictionary dict;
if (!gin::ConvertFromV8(isolate, val, &dict))
return false;
gin::Handle<electron::api::View> view;
if (!dict.Get("view", &view))
return false;
out->child_view = view->view();
if (dict.Has("bounds"))
dict.Get("bounds", &out->bounds);
out->visible = true;
if (dict.Has("visible"))
dict.Get("visible", &out->visible);
return true;
}
};
template <>
struct Converter<views::ProposedLayout> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
views::ProposedLayout* out) {
gin_helper::Dictionary dict;
if (!gin::ConvertFromV8(isolate, val, &dict))
return false;
if (!dict.Get("size", &out->host_size))
return false;
if (!dict.Get("layouts", &out->child_layouts))
return false;
return true;
}
};
template <>
struct Converter<views::LayoutOrientation> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
views::LayoutOrientation* out) {
std::string orientation = base::ToLowerASCII(gin::V8ToString(isolate, val));
if (orientation == "horizontal") {
*out = views::LayoutOrientation::kHorizontal;
} else if (orientation == "vertical") {
*out = views::LayoutOrientation::kVertical;
} else {
return false;
}
return true;
}
};
template <>
struct Converter<views::LayoutAlignment> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
views::LayoutAlignment* out) {
std::string orientation = base::ToLowerASCII(gin::V8ToString(isolate, val));
if (orientation == "start") {
*out = views::LayoutAlignment::kStart;
} else if (orientation == "center") {
*out = views::LayoutAlignment::kCenter;
} else if (orientation == "end") {
*out = views::LayoutAlignment::kEnd;
} else if (orientation == "stretch") {
*out = views::LayoutAlignment::kStretch;
} else if (orientation == "baseline") {
*out = views::LayoutAlignment::kBaseline;
} else {
return false;
}
return true;
}
};
template <>
struct Converter<views::FlexAllocationOrder> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
views::FlexAllocationOrder* out) {
std::string orientation = base::ToLowerASCII(gin::V8ToString(isolate, val));
if (orientation == "normal") {
*out = views::FlexAllocationOrder::kNormal;
} else if (orientation == "reverse") {
*out = views::FlexAllocationOrder::kReverse;
} else {
return false;
}
return true;
}
};
template <>
struct Converter<views::SizeBound> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const views::SizeBound& in) {
if (in.is_bounded())
return v8::Integer::New(isolate, in.value());
return v8::Number::New(isolate, std::numeric_limits<double>::infinity());
}
};
template <>
struct Converter<views::SizeBounds> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const views::SizeBounds& in) {
return gin::DataObjectBuilder(isolate)
.Set("width", in.width())
.Set("height", in.height())
.Build();
}
};
} // namespace gin
namespace electron::api {
using LayoutCallback = base::RepeatingCallback<views::ProposedLayout(
const views::SizeBounds& size_bounds)>;
class JSLayoutManager : public views::LayoutManagerBase {
public:
explicit JSLayoutManager(LayoutCallback layout_callback)
: layout_callback_(std::move(layout_callback)) {}
~JSLayoutManager() override {}
views::ProposedLayout CalculateProposedLayout(
const views::SizeBounds& size_bounds) const override {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
return layout_callback_.Run(size_bounds);
}
private:
LayoutCallback layout_callback_;
};
View::View(views::View* view) : view_(view) {
view_->set_owned_by_client();
view_->AddObserver(this);
}
View::View() : View(new views::View()) {}
View::~View() {
if (!view_)
return;
view_->RemoveObserver(this);
if (delete_view_)
delete view_;
}
void View::AddChildViewAt(gin::Handle<View> child,
absl::optional<size_t> maybe_index) {
CHECK(view_);
size_t index =
std::min(child_views_.size(), maybe_index.value_or(child_views_.size()));
child_views_.emplace(child_views_.begin() + index, // index
isolate(), child->GetWrapper()); // v8::Global(args...)
#if BUILDFLAG(IS_MAC)
// Disable the implicit CALayer animations that happen by default when adding
// or removing sublayers.
// See
// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CoreAnimation_guide/ReactingtoLayerChanges/ReactingtoLayerChanges.html
// and https://github.com/electron/electron/pull/14911
// TODO(nornagon): Disabling these CALayer animations (which are specific to
// WebContentsView, I think) seems like this is something that remote_cocoa
// or views should be taking care of, but isn't. This should be pushed
// upstream.
ScopedCAActionDisabler disable_animations;
#endif
view_->AddChildViewAt(child->view(), index);
}
void View::RemoveChildView(gin::Handle<View> child) {
CHECK(view_);
auto it = std::find(child_views_.begin(), child_views_.end(), child.ToV8());
if (it != child_views_.end()) {
#if BUILDFLAG(IS_MAC)
ScopedCAActionDisabler disable_animations;
#endif
view_->RemoveChildView(child->view());
child_views_.erase(it);
}
}
void View::SetBounds(const gfx::Rect& bounds) {
CHECK(view_);
view_->SetBoundsRect(bounds);
}
gfx::Rect View::GetBounds() {
CHECK(view_);
return view_->bounds();
}
void View::SetLayout(v8::Isolate* isolate, v8::Local<v8::Object> value) {
CHECK(view_);
gin_helper::Dictionary dict(isolate, value);
LayoutCallback calculate_proposed_layout;
if (dict.Get("calculateProposedLayout", &calculate_proposed_layout)) {
view_->SetLayoutManager(std::make_unique<JSLayoutManager>(
std::move(calculate_proposed_layout)));
} else {
auto* layout =
view_->SetLayoutManager(std::make_unique<views::FlexLayout>());
views::LayoutOrientation orientation;
if (dict.Get("orientation", &orientation))
layout->SetOrientation(orientation);
views::LayoutAlignment main_axis_alignment;
if (dict.Get("mainAxisAlignment", &main_axis_alignment))
layout->SetMainAxisAlignment(main_axis_alignment);
views::LayoutAlignment cross_axis_alignment;
if (dict.Get("crossAxisAlignment", &cross_axis_alignment))
layout->SetCrossAxisAlignment(cross_axis_alignment);
gfx::Insets interior_margin;
if (dict.Get("interiorMargin", &interior_margin))
layout->SetInteriorMargin(interior_margin);
int minimum_cross_axis_size;
if (dict.Get("minimumCrossAxisSize", &minimum_cross_axis_size))
layout->SetMinimumCrossAxisSize(minimum_cross_axis_size);
bool collapse_margins;
if (dict.Has("collapseMargins") &&
dict.Get("collapseMargins", &collapse_margins))
layout->SetCollapseMargins(collapse_margins);
bool include_host_insets_in_layout;
if (dict.Has("includeHostInsetsInLayout") &&
dict.Get("includeHostInsetsInLayout", &include_host_insets_in_layout))
layout->SetIncludeHostInsetsInLayout(include_host_insets_in_layout);
bool ignore_default_main_axis_margins;
if (dict.Has("ignoreDefaultMainAxisMargins") &&
dict.Get("ignoreDefaultMainAxisMargins",
&ignore_default_main_axis_margins))
layout->SetIgnoreDefaultMainAxisMargins(ignore_default_main_axis_margins);
views::FlexAllocationOrder flex_allocation_order;
if (dict.Get("flexAllocationOrder", &flex_allocation_order))
layout->SetFlexAllocationOrder(flex_allocation_order);
}
}
std::vector<v8::Local<v8::Value>> View::GetChildren() {
std::vector<v8::Local<v8::Value>> ret;
ret.reserve(child_views_.size());
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
for (auto& child_view : child_views_)
ret.push_back(child_view.Get(isolate));
return ret;
}
void View::SetBackgroundColor(absl::optional<WrappedSkColor> color) {
CHECK(view_);
view_->SetBackground(color ? views::CreateSolidBackground(*color) : nullptr);
}
void View::SetVisible(bool visible) {
CHECK(view_);
view_->SetVisible(visible);
}
void View::OnViewBoundsChanged(views::View* observed_view) {
Emit("bounds-changed");
}
void View::OnViewIsDeleting(views::View* observed_view) {
DCHECK_EQ(observed_view, view_);
view_ = nullptr;
}
// static
gin_helper::WrappableBase* View::New(gin::Arguments* args) {
auto* view = new View();
View* view = new View();
view->InitWithArgs(args);
return view;
}
// static
v8::Local<v8::Function> View::GetConstructor(v8::Isolate* isolate) {
static base::NoDestructor<v8::Global<v8::Function>> constructor;
if (constructor.get()->IsEmpty()) {
constructor->Reset(isolate, gin_helper::CreateConstructor<View>(
isolate, base::BindRepeating(&View::New)));
}
return v8::Local<v8::Function>::New(isolate, *constructor.get());
}
// static
gin::Handle<View> View::Create(v8::Isolate* isolate) {
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Object> obj;
if (GetConstructor(isolate)->NewInstance(context, 0, nullptr).ToLocal(&obj)) {
gin::Handle<View> view;
if (gin::ConvertFromV8(isolate, obj, &view))
return view;
}
return gin::Handle<View>();
}
// static
void View::BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(gin::StringToV8(isolate, "View"));
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("addChildView", &View::AddChildViewAt)
.SetMethod("removeChildView", &View::RemoveChildView)
.SetProperty("children", &View::GetChildren)
.SetMethod("setBounds", &View::SetBounds)
.SetMethod("getBounds", &View::GetBounds)
.SetMethod("setBackgroundColor", &View::SetBackgroundColor)
.SetMethod("setLayout", &View::SetLayout)
.SetMethod("setVisible", &View::SetVisible);
}
} // namespace electron::api
@@ -45,14 +355,9 @@ void Initialize(v8::Local<v8::Object> exports,
v8::Local<v8::Context> context,
void* priv) {
v8::Isolate* isolate = context->GetIsolate();
View::SetConstructor(isolate, base::BindRepeating(&View::New));
gin_helper::Dictionary constructor(
isolate,
View::GetConstructor(isolate)->GetFunction(context).ToLocalChecked());
gin_helper::Dictionary dict(isolate, exports);
dict.Set("View", constructor);
dict.Set("View", View::GetConstructor(isolate));
}
} // namespace

View File

@@ -7,18 +7,39 @@
#include "base/memory/raw_ptr.h"
#include "gin/handle.h"
#include "shell/common/gin_helper/wrappable.h"
#include "shell/common/color_util.h"
#include "shell/common/gin_helper/event_emitter.h"
#include "ui/views/view.h"
#include "ui/views/view_observer.h"
#include "v8/include/v8-value.h"
namespace electron::api {
class View : public gin_helper::Wrappable<View> {
class View : public gin_helper::EventEmitter<View>, public views::ViewObserver {
public:
static gin_helper::WrappableBase* New(gin::Arguments* args);
static gin::Handle<View> Create(v8::Isolate* isolate);
// Return the cached constructor function.
static v8::Local<v8::Function> GetConstructor(v8::Isolate* isolate);
static void BuildPrototype(v8::Isolate* isolate,
v8::Local<v8::FunctionTemplate> prototype);
void AddChildViewAt(gin::Handle<View> child, absl::optional<size_t> index);
void RemoveChildView(gin::Handle<View> child);
void SetBounds(const gfx::Rect& bounds);
gfx::Rect GetBounds();
void SetLayout(v8::Isolate* isolate, v8::Local<v8::Object> value);
std::vector<v8::Local<v8::Value>> GetChildren();
void SetBackgroundColor(absl::optional<WrappedSkColor> color);
void SetVisible(bool visible);
// views::ViewObserver
void OnViewBoundsChanged(views::View* observed_view) override;
void OnViewIsDeleting(views::View* observed_view) override;
views::View* view() const { return view_; }
// disable copy
@@ -34,6 +55,8 @@ class View : public gin_helper::Wrappable<View> {
void set_delete_view(bool should) { delete_view_ = should; }
private:
std::vector<v8::Global<v8::Object>> child_views_;
bool delete_view_ = true;
raw_ptr<views::View> view_ = nullptr;
};

View File

@@ -695,12 +695,6 @@ bool IsDevToolsFileSystemAdded(content::WebContents* web_contents,
file_system_path);
}
void SetBackgroundColor(content::RenderWidgetHostView* rwhv, SkColor color) {
rwhv->SetBackgroundColor(color);
static_cast<content::RenderWidgetHostViewBase*>(rwhv)
->SetContentBackgroundColor(color);
}
content::RenderFrameHost* GetRenderFrameHost(
content::NavigationHandle* navigation_handle) {
int frame_tree_node_id = navigation_handle->GetFrameTreeNodeId();
@@ -717,7 +711,6 @@ content::RenderFrameHost* GetRenderFrameHost(
return frame_host;
}
} // namespace
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
@@ -818,10 +811,7 @@ WebContents::WebContents(v8::Isolate* isolate,
// Whether to enable DevTools.
options.Get("devTools", &enable_devtools_);
// BrowserViews are not attached to a window initially so they should start
// off as hidden. This is also important for compositor recycling. See:
// https://github.com/electron/electron/pull/21372
bool initially_shown = type_ != Type::kBrowserView;
bool initially_shown = true;
options.Get(options::kShow, &initially_shown);
// Obtain the session.
@@ -1263,8 +1253,7 @@ content::WebContents* WebContents::OpenURLFromTab(
void WebContents::BeforeUnloadFired(content::WebContents* tab,
bool proceed,
bool* proceed_to_fire_unload) {
if (type_ == Type::kBrowserWindow || type_ == Type::kOffScreen ||
type_ == Type::kBrowserView)
if (type_ == Type::kBrowserWindow || type_ == Type::kOffScreen)
*proceed_to_fire_unload = proceed;
else
*proceed_to_fire_unload = true;
@@ -1288,9 +1277,6 @@ void WebContents::CloseContents(content::WebContents* source) {
autofill_driver_factory->CloseAllPopups();
}
for (ExtendedWebContentsObserver& observer : observers_)
observer.OnCloseContents();
// This is handled by the embedder frame.
if (!IsGuest())
Destroy();
@@ -1600,17 +1586,8 @@ void WebContents::HandleNewRenderFrame(
// Set the background color of RenderWidgetHostView.
auto* web_preferences = WebContentsPreferences::From(web_contents());
if (web_preferences) {
auto maybe_color = web_preferences->GetBackgroundColor();
bool guest = IsGuest() || type_ == Type::kBrowserView;
// If webPreferences has no color stored we need to explicitly set guest
// webContents background color to transparent.
auto bg_color =
maybe_color.value_or(guest ? SK_ColorTRANSPARENT : SK_ColorWHITE);
web_contents()->SetPageBaseBackgroundColor(bg_color);
SetBackgroundColor(rwhv, bg_color);
}
if (web_preferences)
SetBackgroundColor(web_preferences->GetBackgroundColor());
if (!background_throttling_)
render_frame_host->GetRenderViewHost()->SetSchedulerThrottling(false);
@@ -2224,6 +2201,11 @@ void WebContents::SetOwnerWindow(NativeWindow* owner_window) {
SetOwnerWindow(GetWebContents(), owner_window);
}
void WebContents::SetOwnerBaseWindow(absl::optional<BaseWindow*> owner_window) {
SetOwnerWindow(GetWebContents(),
owner_window ? (*owner_window)->window() : nullptr);
}
void WebContents::SetOwnerWindow(content::WebContents* web_contents,
NativeWindow* owner_window) {
if (owner_window) {
@@ -3719,6 +3701,22 @@ void WebContents::SetImageAnimationPolicy(const std::string& new_policy) {
web_contents()->OnWebPreferencesChanged();
}
void WebContents::SetBackgroundColor(absl::optional<SkColor> maybe_color) {
web_contents()->SetPageBaseBackgroundColor(maybe_color);
content::RenderFrameHost* rfh = web_contents()->GetPrimaryMainFrame();
if (!rfh)
return;
content::RenderWidgetHostView* rwhv = rfh->GetView();
if (rwhv) {
SkColor color =
maybe_color.value_or(IsGuest() ? SK_ColorTRANSPARENT : SK_ColorWHITE);
rwhv->SetBackgroundColor(color);
static_cast<content::RenderWidgetHostViewBase*>(rwhv)
->SetContentBackgroundColor(color);
}
}
void WebContents::OnInputEvent(const blink::WebInputEvent& event) {
Emit("input-event", event);
}
@@ -4341,6 +4339,7 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
.SetProperty("debugger", &WebContents::Debugger)
.SetProperty("mainFrame", &WebContents::MainFrame)
.SetProperty("opener", &WebContents::Opener)
.SetMethod("_setOwnerWindow", &WebContents::SetOwnerBaseWindow)
.Build();
}
@@ -4424,14 +4423,8 @@ gin::Handle<WebContents> WebContents::CreateFromWebPreferences(
if (gin::ConvertFromV8(isolate, web_preferences.GetHandle(),
&web_preferences_dict)) {
existing_preferences->SetFromDictionary(web_preferences_dict);
absl::optional<SkColor> color =
existing_preferences->GetBackgroundColor();
web_contents->web_contents()->SetPageBaseBackgroundColor(color);
// Because web preferences don't recognize transparency,
// only set rwhv background color if a color exists
auto* rwhv = web_contents->web_contents()->GetRenderWidgetHostView();
if (rwhv && color.has_value())
SetBackgroundColor(rwhv, color.value());
web_contents->SetBackgroundColor(
existing_preferences->GetBackgroundColor());
}
} else {
// Create one if not.

View File

@@ -96,6 +96,8 @@ class OffScreenWebContentsView;
namespace api {
class BaseWindow;
// Wrapper around the content::WebContents.
class WebContents : public ExclusiveAccessContext,
public gin::Wrappable<WebContents>,
@@ -112,7 +114,9 @@ class WebContents : public ExclusiveAccessContext,
enum class Type {
kBackgroundPage, // An extension background page.
kBrowserWindow, // Used by BrowserWindow.
kBrowserView, // Used by BrowserView.
kBrowserView, // Used by the JS implementation of BrowserView for
// backwards compatibility. Otherwise identical to
// kBrowserWindow.
kRemote, // Thin wrap around an existing WebContents.
kWebView, // Used by <webview>.
kOffScreen, // Used for offscreen rendering
@@ -396,6 +400,7 @@ class WebContents : public ExclusiveAccessContext,
void SetOwnerWindow(NativeWindow* owner_window);
void SetOwnerWindow(content::WebContents* web_contents,
NativeWindow* owner_window);
void SetOwnerBaseWindow(absl::optional<BaseWindow*> owner_window);
// Returns the WebContents managed by this delegate.
content::WebContents* GetWebContents() const;
@@ -451,6 +456,7 @@ class WebContents : public ExclusiveAccessContext,
// content::RenderWidgetHost::InputEventObserver:
void OnInputEvent(const blink::WebInputEvent& event) override;
void SetBackgroundColor(absl::optional<SkColor> color);
SkRegion* draggable_region() {
return force_non_draggable_ ? nullptr : draggable_region_.get();
}

View File

@@ -7,15 +7,21 @@
#include "base/no_destructor.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/browser.h"
#include "shell/browser/native_window.h"
#include "shell/browser/ui/inspectable_web_contents_view.h"
#include "shell/browser/web_contents_preferences.h"
#include "shell/common/gin_converters/gfx_converter.h"
#include "shell/common/gin_converters/value_converter.h"
#include "shell/common/gin_helper/constructor.h"
#include "shell/common/gin_helper/dictionary.h"
#include "shell/common/gin_helper/object_template_builder.h"
#include "shell/common/node_includes.h"
#include "shell/common/options_switches.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/base/hit_test.h"
#include "ui/views/layout/flex_layout_types.h"
#include "ui/views/view_class_properties.h"
#include "ui/views/widget/widget.h"
#if BUILDFLAG(IS_MAC)
#include "shell/browser/ui/cocoa/delayed_native_view_host.h"
@@ -40,6 +46,10 @@ WebContentsView::WebContentsView(v8::Isolate* isolate,
// managed by InspectableWebContents.
set_delete_view(false);
#endif
view()->SetProperty(
views::kFlexBehaviorKey,
views::FlexSpecification(views::MinimumFlexSizeRule::kScaleToMinimum,
views::MaximumFlexSizeRule::kUnbounded));
Observe(web_contents->web_contents());
}
@@ -49,7 +59,24 @@ WebContentsView::~WebContentsView() {
}
gin::Handle<WebContents> WebContentsView::GetWebContents(v8::Isolate* isolate) {
return gin::CreateHandle(isolate, api_web_contents_.get());
if (api_web_contents_)
return gin::CreateHandle(isolate, api_web_contents_.get());
else
return gin::Handle<WebContents>();
}
void WebContentsView::SetBackgroundColor(absl::optional<WrappedSkColor> color) {
View::SetBackgroundColor(color);
if (api_web_contents_) {
api_web_contents_->SetBackgroundColor(color);
// Also update the web preferences object otherwise the view will be reset
// on the next load URL call
auto* web_preferences =
WebContentsPreferences::From(api_web_contents_->web_contents());
if (web_preferences) {
web_preferences->SetBackgroundColor(color);
}
}
}
int WebContentsView::NonClientHitTest(const gfx::Point& point) {
@@ -66,6 +93,26 @@ void WebContentsView::WebContentsDestroyed() {
web_contents_.Reset();
}
void WebContentsView::OnViewAddedToWidget(views::View* observed_view) {
DCHECK_EQ(observed_view, view());
views::Widget* widget = view()->GetWidget();
auto* native_window = static_cast<NativeWindow*>(
widget->GetNativeWindowProperty(electron::kElectronNativeWindowKey));
if (!native_window)
return;
native_window->AddDraggableRegionProvider(this);
}
void WebContentsView::OnViewRemovedFromWidget(views::View* observed_view) {
DCHECK_EQ(observed_view, view());
views::Widget* widget = view()->GetWidget();
auto* native_window = static_cast<NativeWindow*>(
widget->GetNativeWindowProperty(kElectronNativeWindowKey));
if (!native_window)
return;
native_window->RemoveDraggableRegionProvider(this);
}
// static
gin::Handle<WebContentsView> WebContentsView::Create(
v8::Isolate* isolate,
@@ -73,6 +120,8 @@ gin::Handle<WebContentsView> WebContentsView::Create(
v8::Local<v8::Context> context = isolate->GetCurrentContext();
v8::Local<v8::Value> arg = gin::ConvertToV8(isolate, web_preferences);
v8::Local<v8::Object> obj;
if (!web_preferences.Has(options::kShow))
gin::Dictionary(isolate, arg.As<v8::Object>()).Set(options::kShow, true);
if (GetConstructor(isolate)->NewInstance(context, 1, &arg).ToLocal(&obj)) {
gin::Handle<WebContentsView> web_contents_view;
if (gin::ConvertFromV8(isolate, obj, &web_contents_view))
@@ -93,9 +142,12 @@ v8::Local<v8::Function> WebContentsView::GetConstructor(v8::Isolate* isolate) {
}
// static
gin_helper::WrappableBase* WebContentsView::New(
gin_helper::Arguments* args,
const gin_helper::Dictionary& web_preferences) {
gin_helper::WrappableBase* WebContentsView::New(gin_helper::Arguments* args) {
gin_helper::Dictionary web_preferences =
gin::Dictionary::CreateEmpty(args->isolate());
args->GetNext(&web_preferences);
if (!web_preferences.Has(options::kShow))
web_preferences.Set(options::kShow, false);
auto web_contents =
WebContents::CreateFromWebPreferences(args->isolate(), web_preferences);
@@ -111,6 +163,7 @@ void WebContentsView::BuildPrototype(
v8::Local<v8::FunctionTemplate> prototype) {
prototype->SetClassName(gin::StringToV8(isolate, "WebContentsView"));
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
.SetMethod("setBackgroundColor", &WebContentsView::SetBackgroundColor)
.SetProperty("webContents", &WebContentsView::GetWebContents);
}

View File

@@ -36,6 +36,7 @@ class WebContentsView : public View,
// Public APIs.
gin::Handle<WebContents> GetWebContents(v8::Isolate* isolate);
void SetBackgroundColor(absl::optional<WrappedSkColor> color);
int NonClientHitTest(const gfx::Point& point) override;
@@ -47,10 +48,12 @@ class WebContentsView : public View,
// content::WebContentsObserver:
void WebContentsDestroyed() override;
// views::ViewObserver
void OnViewAddedToWidget(views::View* view) override;
void OnViewRemovedFromWidget(views::View* view) override;
private:
static gin_helper::WrappableBase* New(
gin_helper::Arguments* args,
const gin_helper::Dictionary& web_preferences);
static gin_helper::WrappableBase* New(gin_helper::Arguments* args);
// Keep a reference to v8 wrapper.
v8::Global<v8::Value> web_contents_;

View File

@@ -11,7 +11,6 @@
#include "content/public/browser/render_widget_host_view.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_browser_view.h"
#include "shell/browser/native_window.h"
namespace electron {
@@ -62,18 +61,18 @@ void AutofillDriver::ShowAutofillPopup(
// Ensure that if the WebContents belongs to a BrowserView,
// the popup is positioned relative to the BrowserView's bounds.
for (NativeBrowserView* bv : owner_window->browser_views()) {
auto* iwc = bv->GetInspectableWebContents();
if (!iwc)
continue;
// for (WebContentsView* bv : owner_window->browser_views()) {
// auto* iwc = bv->GetInspectableWebContents();
// if (!iwc)
// continue;
auto* awc = api::WebContents::From(iwc->GetWebContents());
if (awc == web_contents) {
auto bv_origin = bv->GetBounds().origin();
popup_bounds.Offset(gfx::Vector2dF(bv_origin.x(), bv_origin.y()));
break;
}
}
// auto* awc = api::WebContents::From(iwc->GetWebContents());
// if (awc == web_contents) {
// auto bv_origin = bv->GetBounds().origin();
// popup_bounds.Offset(gfx::Vector2dF(bv_origin.x(), bv_origin.y()));
// break;
// }
// }
autofill_popup_->CreateView(render_frame_host_, embedder_frame_host, osr,
owner_window->content_view(), popup_bounds);

View File

@@ -18,7 +18,6 @@ namespace electron {
// Observer to dispatch those events.
class ExtendedWebContentsObserver : public base::CheckedObserver {
public:
virtual void OnCloseContents() {}
virtual void OnDraggableRegionsUpdated(
const std::vector<mojom::DraggableRegionPtr>& regions) {}
virtual void OnSetContentBounds(const gfx::Rect& rect) {}

View File

@@ -1,30 +0,0 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/native_browser_view.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/ui/inspectable_web_contents.h"
namespace electron {
NativeBrowserView::NativeBrowserView(
InspectableWebContents* inspectable_web_contents)
: inspectable_web_contents_(inspectable_web_contents) {
Observe(inspectable_web_contents_->GetWebContents());
}
NativeBrowserView::~NativeBrowserView() = default;
InspectableWebContentsView* NativeBrowserView::GetInspectableWebContentsView() {
if (!inspectable_web_contents_)
return nullptr;
return inspectable_web_contents_->GetView();
}
void NativeBrowserView::WebContentsDestroyed() {
inspectable_web_contents_ = nullptr;
}
} // namespace electron

View File

@@ -1,61 +0,0 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_H_
#define ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_H_
#include "base/memory/raw_ptr.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_observer.h"
#include "third_party/skia/include/core/SkColor.h"
namespace gfx {
class Rect;
}
namespace electron {
enum AutoResizeFlags {
kAutoResizeWidth = 0x1,
kAutoResizeHeight = 0x2,
kAutoResizeHorizontal = 0x4,
kAutoResizeVertical = 0x8,
};
class InspectableWebContents;
class InspectableWebContentsView;
class NativeBrowserView : public content::WebContentsObserver {
public:
~NativeBrowserView() override;
// disable copy
NativeBrowserView(const NativeBrowserView&) = delete;
NativeBrowserView& operator=(const NativeBrowserView&) = delete;
static NativeBrowserView* Create(
InspectableWebContents* inspectable_web_contents);
InspectableWebContents* GetInspectableWebContents() {
return inspectable_web_contents_;
}
InspectableWebContentsView* GetInspectableWebContentsView();
virtual void SetAutoResizeFlags(uint8_t flags) = 0;
virtual void SetBounds(const gfx::Rect& bounds) = 0;
virtual gfx::Rect GetBounds() = 0;
virtual void SetBackgroundColor(SkColor color) = 0;
protected:
explicit NativeBrowserView(InspectableWebContents* inspectable_web_contents);
// content::WebContentsObserver:
void WebContentsDestroyed() override;
raw_ptr<InspectableWebContents> inspectable_web_contents_;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_H_

View File

@@ -1,28 +0,0 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_MAC_H_
#define ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_MAC_H_
#import <Cocoa/Cocoa.h>
#include "shell/browser/native_browser_view.h"
namespace electron {
class NativeBrowserViewMac : public NativeBrowserView {
public:
explicit NativeBrowserViewMac(
InspectableWebContents* inspectable_web_contents);
~NativeBrowserViewMac() override;
void SetAutoResizeFlags(uint8_t flags) override;
void SetBounds(const gfx::Rect& bounds) override;
gfx::Rect GetBounds() override;
void SetBackgroundColor(SkColor color) override;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_MAC_H_

View File

@@ -1,143 +0,0 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/native_browser_view_views.h"
#include "shell/browser/ui/views/inspectable_web_contents_view_views.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/views/background.h"
#include "ui/views/view.h"
namespace electron {
NativeBrowserViewViews::NativeBrowserViewViews(
InspectableWebContents* inspectable_web_contents)
: NativeBrowserView(inspectable_web_contents) {}
NativeBrowserViewViews::~NativeBrowserViewViews() = default;
void NativeBrowserViewViews::SetAutoResizeFlags(uint8_t flags) {
auto_resize_flags_ = flags;
ResetAutoResizeProportions();
}
void NativeBrowserViewViews::SetAutoResizeProportions(
const gfx::Size& window_size) {
if ((auto_resize_flags_ & AutoResizeFlags::kAutoResizeHorizontal) &&
!auto_horizontal_proportion_set_) {
InspectableWebContentsView* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return;
auto* view = iwc_view->GetView();
auto view_bounds = view->bounds();
auto_horizontal_proportion_width_ =
static_cast<float>(window_size.width()) /
static_cast<float>(view_bounds.width());
auto_horizontal_proportion_left_ = static_cast<float>(window_size.width()) /
static_cast<float>(view_bounds.x());
auto_horizontal_proportion_set_ = true;
}
if ((auto_resize_flags_ & AutoResizeFlags::kAutoResizeVertical) &&
!auto_vertical_proportion_set_) {
InspectableWebContentsView* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return;
auto* view = iwc_view->GetView();
auto view_bounds = view->bounds();
auto_vertical_proportion_height_ =
static_cast<float>(window_size.height()) /
static_cast<float>(view_bounds.height());
auto_vertical_proportion_top_ = static_cast<float>(window_size.height()) /
static_cast<float>(view_bounds.y());
auto_vertical_proportion_set_ = true;
}
}
void NativeBrowserViewViews::AutoResize(const gfx::Rect& new_window,
int width_delta,
int height_delta) {
InspectableWebContentsView* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return;
auto* view = iwc_view->GetView();
const auto flags = GetAutoResizeFlags();
if (!(flags & kAutoResizeWidth)) {
width_delta = 0;
}
if (!(flags & kAutoResizeHeight)) {
height_delta = 0;
}
if (height_delta || width_delta) {
auto new_view_size = view->size();
new_view_size.set_width(new_view_size.width() + width_delta);
new_view_size.set_height(new_view_size.height() + height_delta);
view->SetSize(new_view_size);
}
auto new_view_bounds = view->bounds();
if (flags & kAutoResizeHorizontal) {
new_view_bounds.set_width(new_window.width() /
auto_horizontal_proportion_width_);
new_view_bounds.set_x(new_window.width() /
auto_horizontal_proportion_left_);
}
if (flags & kAutoResizeVertical) {
new_view_bounds.set_height(new_window.height() /
auto_vertical_proportion_height_);
new_view_bounds.set_y(new_window.height() / auto_vertical_proportion_top_);
}
if ((flags & kAutoResizeHorizontal) || (flags & kAutoResizeVertical)) {
view->SetBoundsRect(new_view_bounds);
}
}
void NativeBrowserViewViews::ResetAutoResizeProportions() {
if (auto_resize_flags_ & AutoResizeFlags::kAutoResizeHorizontal) {
auto_horizontal_proportion_set_ = false;
}
if (auto_resize_flags_ & AutoResizeFlags::kAutoResizeVertical) {
auto_vertical_proportion_set_ = false;
}
}
void NativeBrowserViewViews::SetBounds(const gfx::Rect& bounds) {
InspectableWebContentsView* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return;
auto* view = iwc_view->GetView();
view->SetBoundsRect(bounds);
ResetAutoResizeProportions();
view->InvalidateLayout();
view->SchedulePaint();
}
gfx::Rect NativeBrowserViewViews::GetBounds() {
InspectableWebContentsView* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return gfx::Rect();
return iwc_view->GetView()->bounds();
}
void NativeBrowserViewViews::RenderViewReady() {
InspectableWebContentsView* iwc_view = GetInspectableWebContentsView();
if (iwc_view)
iwc_view->GetView()->Layout();
}
void NativeBrowserViewViews::SetBackgroundColor(SkColor color) {
InspectableWebContentsView* iwc_view = GetInspectableWebContentsView();
if (!iwc_view)
return;
auto* view = iwc_view->GetView();
view->SetBackground(views::CreateSolidBackground(color));
view->SchedulePaint();
}
// static
NativeBrowserView* NativeBrowserView::Create(
InspectableWebContents* inspectable_web_contents) {
return new NativeBrowserViewViews(inspectable_web_contents);
}
} // namespace electron

View File

@@ -1,49 +0,0 @@
// Copyright (c) 2017 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_VIEWS_H_
#define ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_VIEWS_H_
#include "shell/browser/native_browser_view.h"
namespace electron {
class NativeBrowserViewViews : public NativeBrowserView {
public:
explicit NativeBrowserViewViews(
InspectableWebContents* inspectable_web_contents);
~NativeBrowserViewViews() override;
void SetAutoResizeProportions(const gfx::Size& window_size);
void AutoResize(const gfx::Rect& new_window,
int width_delta,
int height_delta);
uint8_t GetAutoResizeFlags() { return auto_resize_flags_; }
// NativeBrowserView:
void SetAutoResizeFlags(uint8_t flags) override;
void SetBounds(const gfx::Rect& bounds) override;
gfx::Rect GetBounds() override;
void SetBackgroundColor(SkColor color) override;
// WebContentsObserver:
void RenderViewReady() override;
private:
void ResetAutoResizeProportions();
uint8_t auto_resize_flags_ = 0;
bool auto_horizontal_proportion_set_ = false;
float auto_horizontal_proportion_width_ = 0.;
float auto_horizontal_proportion_left_ = 0.;
bool auto_vertical_proportion_set_ = false;
float auto_vertical_proportion_height_ = 0.;
float auto_vertical_proportion_top_ = 0.;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_NATIVE_BROWSER_VIEW_VIEWS_H_

View File

@@ -90,6 +90,8 @@ gfx::Size GetExpandedWindowSize(const NativeWindow* window, gfx::Size size) {
} // namespace
const char kElectronNativeWindowKey[] = "__ELECTRON_NATIVE_WINDOW__";
NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
NativeWindow* parent)
: widget_(std::make_unique<views::Widget>()), parent_(parent) {

View File

@@ -46,8 +46,9 @@ class PersistentDictionary;
namespace electron {
extern const char kElectronNativeWindowKey[];
class ElectronMenuModel;
class NativeBrowserView;
namespace api {
class BrowserView;
@@ -185,9 +186,6 @@ class NativeWindow : public base::SupportsUserData,
virtual bool IsFocusable();
virtual void SetMenu(ElectronMenuModel* menu);
virtual void SetParentWindow(NativeWindow* parent);
virtual void AddBrowserView(NativeBrowserView* browser_view) = 0;
virtual void RemoveBrowserView(NativeBrowserView* browser_view) = 0;
virtual void SetTopBrowserView(NativeBrowserView* browser_view) = 0;
virtual content::DesktopMediaID GetDesktopMediaID() const = 0;
virtual gfx::NativeView GetNativeView() const = 0;
virtual gfx::NativeWindow GetNativeWindow() const = 0;
@@ -387,8 +385,6 @@ class NativeWindow : public base::SupportsUserData,
NativeWindow* parent() const { return parent_; }
bool is_modal() const { return is_modal_; }
std::list<NativeBrowserView*> browser_views() const { return browser_views_; }
int32_t window_id() const { return next_id_; }
void add_child_window(NativeWindow* child) {
@@ -413,14 +409,6 @@ class NativeWindow : public base::SupportsUserData,
void set_content_view(views::View* view) { content_view_ = view; }
void add_browser_view(NativeBrowserView* browser_view) {
browser_views_.push_back(browser_view);
}
void remove_browser_view(NativeBrowserView* browser_view) {
browser_views_.remove_if(
[&browser_view](NativeBrowserView* n) { return (n == browser_view); });
}
// The boolean parsing of the "titleBarOverlay" option
bool titlebar_overlay_ = false;
@@ -487,9 +475,6 @@ class NativeWindow : public base::SupportsUserData,
// Is this a modal window.
bool is_modal_ = false;
// The browser view layer.
std::list<NativeBrowserView*> browser_views_;
std::list<DraggableRegionProvider*> draggable_region_providers_;
// Observers of this window.

View File

@@ -108,9 +108,6 @@ class NativeWindowMac : public NativeWindow,
void SetContentProtection(bool enable) override;
void SetFocusable(bool focusable) override;
bool IsFocusable() override;
void AddBrowserView(NativeBrowserView* browser_view) override;
void RemoveBrowserView(NativeBrowserView* browser_view) override;
void SetTopBrowserView(NativeBrowserView* browser_view) override;
void SetParentWindow(NativeWindow* parent) override;
content::DesktopMediaID GetDesktopMediaID() const override;
gfx::NativeView GetNativeView() const override;

View File

@@ -24,7 +24,6 @@
#include "content/public/browser/desktop_media_id.h"
#include "shell/browser/browser.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/native_browser_view_mac.h"
#include "shell/browser/ui/cocoa/electron_native_widget_mac.h"
#include "shell/browser/ui/cocoa/electron_ns_window.h"
#include "shell/browser/ui/cocoa/electron_ns_window_delegate.h"
@@ -293,6 +292,7 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
params.native_widget =
new ElectronNativeWidgetMac(this, windowType, styleMask, widget());
widget()->Init(std::move(params));
widget()->SetNativeWindowProperty(kElectronNativeWindowKey, this);
SetCanResize(resizable);
window_ = static_cast<ElectronNSWindow*>(
widget()->GetNativeWindow().GetNativeNSWindow());
@@ -1237,70 +1237,6 @@ bool NativeWindowMac::IsFocusable() {
return ![window_ disableKeyOrMainWindow];
}
void NativeWindowMac::AddBrowserView(NativeBrowserView* view) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
if (!view) {
[CATransaction commit];
return;
}
add_browser_view(view);
if (view->GetInspectableWebContentsView()) {
auto* native_view = view->GetInspectableWebContentsView()
->GetNativeView()
.GetNativeNSView();
[[window_ contentView] addSubview:native_view
positioned:NSWindowAbove
relativeTo:nil];
native_view.hidden = NO;
}
[CATransaction commit];
}
void NativeWindowMac::RemoveBrowserView(NativeBrowserView* view) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
if (!view) {
[CATransaction commit];
return;
}
if (view->GetInspectableWebContentsView())
[view->GetInspectableWebContentsView()->GetNativeView().GetNativeNSView()
removeFromSuperview];
remove_browser_view(view);
[CATransaction commit];
}
void NativeWindowMac::SetTopBrowserView(NativeBrowserView* view) {
[CATransaction begin];
[CATransaction setDisableActions:YES];
if (!view) {
[CATransaction commit];
return;
}
remove_browser_view(view);
add_browser_view(view);
if (view->GetInspectableWebContentsView()) {
auto* native_view = view->GetInspectableWebContentsView()
->GetNativeView()
.GetNativeNSView();
[[window_ contentView] addSubview:native_view
positioned:NSWindowAbove
relativeTo:nil];
native_view.hidden = NO;
}
[CATransaction commit];
}
void NativeWindowMac::SetParentWindow(NativeWindow* parent) {
InternalSetParentWindow(parent, IsVisible());
}

View File

@@ -20,7 +20,6 @@
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/desktop_media_id.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/native_browser_view_views.h"
#include "shell/browser/ui/inspectable_web_contents.h"
#include "shell/browser/ui/views/inspectable_web_contents_view_views.h"
#include "shell/browser/ui/views/root_view.h"
@@ -318,6 +317,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
#endif
widget()->Init(std::move(params));
widget()->SetNativeWindowProperty(kElectronNativeWindowKey, this);
SetCanResize(resizable_);
bool fullscreen = false;
@@ -1333,50 +1333,6 @@ void NativeWindowViews::SetMenu(ElectronMenuModel* menu_model) {
}
}
void NativeWindowViews::AddBrowserView(NativeBrowserView* view) {
if (!content_view())
return;
if (!view) {
return;
}
add_browser_view(view);
if (view->GetInspectableWebContentsView())
content_view()->AddChildView(
view->GetInspectableWebContentsView()->GetView());
}
void NativeWindowViews::RemoveBrowserView(NativeBrowserView* view) {
if (!content_view())
return;
if (!view) {
return;
}
if (view->GetInspectableWebContentsView())
content_view()->RemoveChildView(
view->GetInspectableWebContentsView()->GetView());
remove_browser_view(view);
}
void NativeWindowViews::SetTopBrowserView(NativeBrowserView* view) {
if (!content_view())
return;
if (!view) {
return;
}
remove_browser_view(view);
add_browser_view(view);
if (view->GetInspectableWebContentsView())
content_view()->ReorderChildView(
view->GetInspectableWebContentsView()->GetView(), -1);
}
void NativeWindowViews::SetParentWindow(NativeWindow* parent) {
NativeWindow::SetParentWindow(parent);
@@ -1650,14 +1606,6 @@ void NativeWindowViews::OnWidgetBoundsChanged(views::Widget* changed_widget,
// handle minimized windows on Windows.
const auto new_bounds = GetBounds();
if (widget_size_ != new_bounds.size()) {
int width_delta = new_bounds.width() - widget_size_.width();
int height_delta = new_bounds.height() - widget_size_.height();
for (NativeBrowserView* item : browser_views()) {
auto* native_view = static_cast<NativeBrowserViewViews*>(item);
native_view->SetAutoResizeProportions(widget_size_);
native_view->AutoResize(new_bounds, width_delta, height_delta);
}
NotifyWindowResize();
widget_size_ = new_bounds.size();
}

View File

@@ -124,9 +124,6 @@ class NativeWindowViews : public NativeWindow,
void SetFocusable(bool focusable) override;
bool IsFocusable() override;
void SetMenu(ElectronMenuModel* menu_model) override;
void AddBrowserView(NativeBrowserView* browser_view) override;
void RemoveBrowserView(NativeBrowserView* browser_view) override;
void SetTopBrowserView(NativeBrowserView* browser_view) override;
void SetParentWindow(NativeWindow* parent) override;
gfx::NativeView GetNativeView() const override;
gfx::NativeWindow GetNativeWindow() const override;

View File

@@ -15,9 +15,13 @@ DelayedNativeViewHost::~DelayedNativeViewHost() = default;
void DelayedNativeViewHost::ViewHierarchyChanged(
const views::ViewHierarchyChangedDetails& details) {
NativeViewHost::ViewHierarchyChanged(details);
if (details.is_add && GetWidget())
Attach(native_view_);
// NativeViewHost doesn't expect to have children, so filter the
// ViewHierarchyChanged events before passing them on.
if (details.child == this) {
NativeViewHost::ViewHierarchyChanged(details);
if (details.is_add && GetWidget() && !native_view())
Attach(native_view_);
}
}
bool DelayedNativeViewHost::OnMousePressed(const ui::MouseEvent& ui_event) {

View File

@@ -22,7 +22,6 @@ class RootViewMac : public views::View {
RootViewMac& operator=(const RootViewMac&) = delete;
// views::View:
void Layout() override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;

View File

@@ -4,23 +4,20 @@
#include "shell/browser/ui/cocoa/root_view_mac.h"
#include <memory>
#include "shell/browser/native_window.h"
#include "ui/views/layout/fill_layout.h"
namespace electron {
RootViewMac::RootViewMac(NativeWindow* window) : window_(window) {
set_owned_by_client();
SetLayoutManager(std::make_unique<views::FillLayout>());
}
RootViewMac::~RootViewMac() = default;
void RootViewMac::Layout() {
if (!window_->content_view()) // Not ready yet.
return;
window_->content_view()->SetBoundsRect(gfx::Rect(gfx::Point(), size()));
}
gfx::Size RootViewMac::GetMinimumSize() const {
return window_->GetMinimumSize();
}

View File

@@ -4,7 +4,6 @@
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/native_browser_view_views.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/inspectable_web_contents_view_views.h"
#include "ui/aura/window.h"

View File

@@ -21,10 +21,6 @@ bool IsFakeLocationProviderEnabled() {
return BUILDFLAG(OVERRIDE_LOCATION_PROVIDER);
}
bool IsViewApiEnabled() {
return BUILDFLAG(ENABLE_VIEWS_API);
}
bool IsPrintingEnabled() {
return BUILDFLAG(ENABLE_PRINTING);
}
@@ -50,7 +46,6 @@ void Initialize(v8::Local<v8::Object> exports,
dict.SetMethod("isPDFViewerEnabled", &IsPDFViewerEnabled);
dict.SetMethod("isFakeLocationProviderEnabled",
&IsFakeLocationProviderEnabled);
dict.SetMethod("isViewApiEnabled", &IsViewApiEnabled);
dict.SetMethod("isPrintingEnabled", &IsPrintingEnabled);
dict.SetMethod("isComponentBuild", &IsComponentBuild);
dict.SetMethod("isExtensionsEnabled", &IsExtensionsEnabled);

View File

@@ -9,6 +9,15 @@
#include "third_party/skia/include/core/SkColor.h"
// SkColor is a typedef for uint32_t, this wrapper is to tag an SkColor for
// ease of use in gin converters.
struct WrappedSkColor {
WrappedSkColor() {}
WrappedSkColor(SkColor c) : value(c) {} // NOLINT(runtime/explicit)
SkColor value;
operator SkColor() const { return value; }
};
namespace electron {
// Parses a CSS-style color string from hex, rgb(), rgba(),

View File

@@ -4,9 +4,14 @@
#include "shell/common/gin_converters/gfx_converter.h"
#include <string>
#include "gin/data_object_builder.h"
#include "shell/common/color_util.h"
#include "shell/common/gin_helper/dictionary.h"
#include "ui/display/display.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/point.h"
#include "ui/gfx/geometry/point_f.h"
#include "ui/gfx/geometry/rect.h"
@@ -103,6 +108,35 @@ bool Converter<gfx::Rect>::FromV8(v8::Isolate* isolate,
return true;
}
v8::Local<v8::Value> Converter<gfx::Insets>::ToV8(v8::Isolate* isolate,
const gfx::Insets& val) {
return gin::DataObjectBuilder(isolate)
.Set("top", val.top())
.Set("left", val.left())
.Set("bottom", val.bottom())
.Set("right", val.right())
.Build();
}
bool Converter<gfx::Insets>::FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
gfx::Insets* out) {
gin::Dictionary dict(isolate);
if (!gin::ConvertFromV8(isolate, val, &dict))
return false;
double top, left, right, bottom;
if (!dict.Get("top", &top))
return false;
if (!dict.Get("left", &left))
return false;
if (!dict.Get("bottom", &bottom))
return false;
if (!dict.Get("right", &right))
return false;
*out = gfx::Insets::TLBR(top, left, bottom, right);
return true;
}
template <>
struct Converter<display::Display::AccelerometerSupport> {
static v8::Local<v8::Value> ToV8(
@@ -182,4 +216,14 @@ v8::Local<v8::Value> Converter<gfx::ResizeEdge>::ToV8(
}
}
bool Converter<WrappedSkColor>::FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
WrappedSkColor* out) {
std::string str;
if (!gin::ConvertFromV8(isolate, val, &str))
return false;
*out = electron::ParseCSSColor(str);
return true;
}
} // namespace gin

View File

@@ -6,6 +6,7 @@
#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_GFX_CONVERTER_H_
#include "gin/converter.h"
#include "shell/common/color_util.h"
namespace display {
class Display;
@@ -16,6 +17,7 @@ class Point;
class PointF;
class Size;
class Rect;
class Insets;
enum class ResizeEdge;
} // namespace gfx
@@ -54,6 +56,15 @@ struct Converter<gfx::Rect> {
gfx::Rect* out);
};
template <>
struct Converter<gfx::Insets> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
const gfx::Insets& val);
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
gfx::Insets* out);
};
template <>
struct Converter<display::Display> {
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
@@ -69,6 +80,13 @@ struct Converter<gfx::ResizeEdge> {
const gfx::ResizeEdge& val);
};
template <>
struct Converter<WrappedSkColor> {
static bool FromV8(v8::Isolate* isolate,
v8::Local<v8::Value> val,
WrappedSkColor* out);
};
} // namespace gin
#endif // ELECTRON_SHELL_COMMON_GIN_CONVERTERS_GFX_CONVERTER_H_

View File

@@ -23,7 +23,7 @@ class EventEmitterMixin;
// class Example : public gin::Wrappable<Example>,
// public gin_helper::Constructible<Example> {
// public:
// static gin::Handle<Tray> New(...usual gin method arguments...);
// static gin::Handle<Example> New(...usual gin method arguments...);
// static void FillObjectTemplate(
// v8::Isolate*,
// v8::Local<v8::ObjectTemplate>);

View File

@@ -47,13 +47,13 @@
#define ELECTRON_BROWSER_BINDINGS(V) \
V(electron_browser_app) \
V(electron_browser_auto_updater) \
V(electron_browser_browser_view) \
V(electron_browser_content_tracing) \
V(electron_browser_crash_reporter) \
V(electron_browser_desktop_capturer) \
V(electron_browser_dialog) \
V(electron_browser_event_emitter) \
V(electron_browser_global_shortcut) \
V(electron_browser_image_view) \
V(electron_browser_in_app_purchase) \
V(electron_browser_menu) \
V(electron_browser_message_port) \
@@ -97,9 +97,6 @@
V(electron_renderer_web_frame)
#define ELECTRON_UTILITY_BINDINGS(V) V(electron_utility_parent_port)
#define ELECTRON_VIEWS_BINDINGS(V) V(electron_browser_image_view)
#define ELECTRON_TESTING_BINDINGS(V) V(electron_common_testing)
// This is used to load built-in bindings. Instead of using
@@ -112,9 +109,6 @@ ELECTRON_BROWSER_BINDINGS(V)
ELECTRON_COMMON_BINDINGS(V)
ELECTRON_RENDERER_BINDINGS(V)
ELECTRON_UTILITY_BINDINGS(V)
#if BUILDFLAG(ENABLE_VIEWS_API)
ELECTRON_VIEWS_BINDINGS(V)
#endif
#if DCHECK_IS_ON()
ELECTRON_TESTING_BINDINGS(V)
#endif
@@ -441,9 +435,6 @@ void NodeBindings::RegisterBuiltinBindings() {
#define V(modname) _register_##modname();
if (IsBrowserProcess()) {
ELECTRON_BROWSER_BINDINGS(V)
#if BUILDFLAG(ENABLE_VIEWS_API)
ELECTRON_VIEWS_BINDINGS(V)
#endif
}
ELECTRON_COMMON_BINDINGS(V)
if (IsRendererProcess()) {

View File

@@ -13,7 +13,7 @@ describe('BrowserView module', () => {
let view: BrowserView;
beforeEach(() => {
expect(webContents.getAllWebContents()).to.have.length(0);
expect(webContents.getAllWebContents().length).to.equal(0, 'expected no webContents to exist');
w = new BrowserWindow({
show: false,
width: 400,
@@ -37,7 +37,7 @@ describe('BrowserView module', () => {
await p;
}
expect(webContents.getAllWebContents()).to.have.length(0);
expect(webContents.getAllWebContents().length).to.equal(0, 'expected no webContents to exist');
});
it('sets the correct class name on the prototype', () => {
@@ -49,21 +49,28 @@ describe('BrowserView module', () => {
await wc.loadURL('about:blank');
view = new BrowserView({ webContents: wc } as any);
expect(view.webContents === wc).to.be.true('view.webContents === wc');
expect(view.webContents.getURL()).to.equal('about:blank');
});
it('has type browserView', () => {
view = new BrowserView();
expect(view.webContents.getType()).to.equal('browserView');
});
describe('BrowserView.setBackgroundColor()', () => {
it('does not throw for valid args', () => {
view = new BrowserView();
view.setBackgroundColor('#000');
});
it('throws for invalid args', () => {
// We now treat invalid args as "no background".
it('does not throw for invalid args', () => {
view = new BrowserView();
expect(() => {
view.setBackgroundColor(null as any);
}).to.throw(/conversion failure/);
view.setBackgroundColor({} as any);
}).not.to.throw();
});
// Linux and arm64 platforms (WOA and macOS) do not return any capture sources
@@ -128,7 +135,148 @@ describe('BrowserView module', () => {
view = new BrowserView();
expect(() => {
view.setAutoResize(null as any);
}).to.throw(/conversion failure/);
}).to.throw(/Invalid auto resize options/);
});
it('does not resize when the BrowserView has no AutoResize', () => {
view = new BrowserView();
w.addBrowserView(view);
view.setBounds({ x: 0, y: 0, width: 400, height: 200 });
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 400,
height: 200
});
w.setSize(800, 400);
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 400,
height: 200
});
});
it('resizes horizontally when the window is resized horizontally', () => {
view = new BrowserView();
view.setAutoResize({ width: true, height: false });
w.addBrowserView(view);
view.setBounds({ x: 0, y: 0, width: 400, height: 200 });
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 400,
height: 200
});
w.setSize(800, 400);
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 800,
height: 200
});
});
it('resizes vertically when the window is resized vertically', () => {
view = new BrowserView();
view.setAutoResize({ width: false, height: true });
w.addBrowserView(view);
view.setBounds({ x: 0, y: 0, width: 200, height: 400 });
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 200,
height: 400
});
w.setSize(400, 800);
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 200,
height: 800
});
});
it('resizes both vertically and horizontally when the window is resized', () => {
view = new BrowserView();
view.setAutoResize({ width: true, height: true });
w.addBrowserView(view);
view.setBounds({ x: 0, y: 0, width: 400, height: 400 });
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 400,
height: 400
});
w.setSize(800, 800);
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 800,
height: 800
});
});
it('resizes proportionally', () => {
view = new BrowserView();
view.setAutoResize({ width: true, height: false });
w.addBrowserView(view);
view.setBounds({ x: 0, y: 0, width: 200, height: 100 });
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 200,
height: 100
});
w.setSize(800, 400);
expect(view.getBounds()).to.deep.equal({
x: 0,
y: 0,
width: 600,
height: 100
});
});
it('does not move x if horizontal: false', () => {
view = new BrowserView();
view.setAutoResize({ width: true });
w.addBrowserView(view);
view.setBounds({ x: 200, y: 0, width: 200, height: 100 });
w.setSize(800, 400);
expect(view.getBounds()).to.deep.equal({
x: 200,
y: 0,
width: 600,
height: 100
});
});
it('moves x if horizontal: true', () => {
view = new BrowserView();
view.setAutoResize({ horizontal: true });
w.addBrowserView(view);
view.setBounds({ x: 200, y: 0, width: 200, height: 100 });
w.setSize(800, 400);
expect(view.getBounds()).to.deep.equal({
x: 400,
y: 0,
width: 400,
height: 100
});
});
it('moves x if horizontal: true width: true', () => {
view = new BrowserView();
view.setAutoResize({ horizontal: true, width: true });
w.addBrowserView(view);
view.setBounds({ x: 200, y: 0, width: 200, height: 100 });
w.setSize(800, 400);
expect(view.getBounds()).to.deep.equal({
x: 400,
y: 0,
width: 400,
height: 100
});
});
});
@@ -288,6 +436,16 @@ describe('BrowserView module', () => {
w2.close();
w2.destroy();
});
it('does not cause a crash when used for view with destroyed web contents', async () => {
const w2 = new BrowserWindow({ show: false });
const view = new BrowserView();
view.webContents.close();
w2.addBrowserView(view);
w2.webContents.loadURL('about:blank');
await once(w2.webContents, 'did-finish-load');
w2.close();
});
});
describe('BrowserWindow.removeBrowserView()', () => {

View File

@@ -1,17 +1,19 @@
import { closeWindow } from './lib/window-helpers';
import { closeAllWindows } from './lib/window-helpers';
import { expect } from 'chai';
import { BaseWindow, WebContentsView } from 'electron/main';
describe('WebContentsView', () => {
let w: BaseWindow;
afterEach(closeAllWindows);
// let w: BaseWindow;
afterEach(async () => {
await closeWindow(w as any);
w = null as unknown as BaseWindow;
});
// afterEach(async () => {
// await closeWindow(w as any);
// w = null as unknown as BaseWindow;
// });
it('can be used as content view', () => {
w = new BaseWindow({ show: false });
const w = new BaseWindow({ show: false });
w.setContentView(new WebContentsView({}));
});
@@ -38,4 +40,79 @@ describe('WebContentsView', () => {
done();
});
});
describe('visibilityState', () => {
it('is initially hidden', async () => {
const v = new WebContentsView();
await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>');
expect(await v.webContents.executeJavaScript('initialVisibility')).to.equal('hidden');
});
it('becomes visibile when attached', async () => {
const v = new WebContentsView();
await v.webContents.loadURL('about:blank');
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
const w = new BaseWindow();
w.setContentView(v);
await p;
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
});
it('is initially visible if load happens after attach', async () => {
const w = new BaseWindow();
const v = new WebContentsView();
w.contentView = v;
await v.webContents.loadURL('data:text/html,<script>initialVisibility = document.visibilityState</script>');
expect(await v.webContents.executeJavaScript('initialVisibility')).to.equal('visible');
});
it('becomes hidden when parent window is hidden', async () => {
const w = new BaseWindow();
const v = new WebContentsView();
w.setContentView(v);
await v.webContents.loadURL('about:blank');
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
// We have to wait until the listener above is fully registered before hiding the window.
// On Windows, the executeJavaScript and the visibilitychange can happen out of order
// without this.
await v.webContents.executeJavaScript('0');
w.hide();
await p;
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
});
it('becomes visible when parent window is shown', async () => {
const w = new BaseWindow({ show: false });
const v = new WebContentsView();
w.setContentView(v);
await v.webContents.loadURL('about:blank');
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('hidden');
const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", resolve))');
// We have to wait until the listener above is fully registered before hiding the window.
// On Windows, the executeJavaScript and the visibilitychange can happen out of order
// without this.
await v.webContents.executeJavaScript('0');
w.show();
await p;
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
});
it('does not change when view is moved between two visible windows', async () => {
const w = new BaseWindow();
const v = new WebContentsView();
w.setContentView(v);
await v.webContents.loadURL('about:blank');
expect(await v.webContents.executeJavaScript('document.visibilityState')).to.equal('visible');
const p = v.webContents.executeJavaScript('new Promise(resolve => document.addEventListener("visibilitychange", () => resolve(document.visibilityState)))');
const w2 = new BaseWindow();
w2.setContentView(v);
// A visibilitychange event is triggered, because the page cycled from
// visible -> hidden -> visible, but the page's JS can't observe the
// 'hidden' state.
expect(await p).to.equal('visible');
});
});
});

View File

@@ -1,10 +1,10 @@
import { expect } from 'chai';
import { BrowserWindow } from 'electron/main';
import { BaseWindow, BrowserWindow } from 'electron/main';
import { once } from 'node:events';
async function ensureWindowIsClosed (window: BrowserWindow | null) {
async function ensureWindowIsClosed (window: BaseWindow | null) {
if (window && !window.isDestroyed()) {
if (window.webContents && !window.webContents.isDestroyed()) {
if (window instanceof BrowserWindow && window.webContents && !window.webContents.isDestroyed()) {
// If a window isn't destroyed already, and it has non-destroyed WebContents,
// then calling destroy() won't immediately destroy it, as it may have
// <webview> children which need to be destroyed first. In that case, we
@@ -23,13 +23,13 @@ async function ensureWindowIsClosed (window: BrowserWindow | null) {
}
export const closeWindow = async (
window: BrowserWindow | null = null,
window: BaseWindow | null = null,
{ assertNotWindows } = { assertNotWindows: true }
) => {
await ensureWindowIsClosed(window);
if (assertNotWindows) {
const windows = BrowserWindow.getAllWindows();
const windows = BaseWindow.getAllWindows();
try {
expect(windows).to.have.lengthOf(0);
} finally {
@@ -41,7 +41,7 @@ export const closeWindow = async (
};
export async function closeAllWindows () {
for (const w of BrowserWindow.getAllWindows()) {
for (const w of BaseWindow.getAllWindows()) {
await closeWindow(w, { assertNotWindows: false });
}
}

View File

@@ -1,6 +1,6 @@
import { expect } from 'chai';
import * as cp from 'node:child_process';
import { BrowserWindow, BrowserWindowConstructorOptions, ipcMain } from 'electron/main';
import { BaseWindow, BrowserWindow, BrowserWindowConstructorOptions, ipcMain, WebContents, WebContentsView } from 'electron/main';
import * as path from 'node:path';
import { closeWindow } from './lib/window-helpers';
@@ -11,16 +11,16 @@ import { setTimeout } from 'node:timers/promises';
// visibilityState specs pass on linux with a real window manager but on CI
// the environment does not let these specs pass
ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
let w: BrowserWindow;
let w: BaseWindow & {webContents: WebContents};
afterEach(() => {
return closeWindow(w);
});
const load = () => w.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html'));
const load = () => w.webContents.loadFile(path.resolve(__dirname, 'fixtures', 'chromium', 'visibilitystate.html'));
const itWithOptions = (name: string, options: BrowserWindowConstructorOptions, fn: Mocha.Func) => {
return it(name, async function (...args) {
it(name, async function (...args) {
w = new BrowserWindow({
...options,
paintWhenInitiallyHidden: false,
@@ -32,6 +32,16 @@ ifdescribe(process.platform !== 'linux')('document.visibilityState', () => {
});
await Promise.resolve(fn.apply(this, args));
});
it(name + ' with BaseWindow', async function (...args) {
const baseWindow = new BaseWindow({
...options
});
const wcv = new WebContentsView({ ...(options.webPreferences ?? {}), nodeIntegration: true, contextIsolation: false });
baseWindow.contentView = wcv;
w = Object.assign(baseWindow, { webContents: wcv.webContents });
await Promise.resolve(fn.apply(this, args));
});
};
itWithOptions('should be visible when the window is initially shown by default', {}, async () => {

View File

@@ -1,7 +1,5 @@
declare const BUILDFLAG: (flag: boolean) => boolean;
declare const ENABLE_VIEWS_API: boolean;
declare namespace NodeJS {
interface ModuleInternal extends NodeJS.Module {
new(id: string, parent?: NodeJS.Module | null): NodeJS.Module;
@@ -18,7 +16,6 @@ declare namespace NodeJS {
isBuiltinSpellCheckerEnabled(): boolean;
isPDFViewerEnabled(): boolean;
isFakeLocationProviderEnabled(): boolean;
isViewApiEnabled(): boolean;
isPrintingEnabled(): boolean;
isExtensionsEnabled(): boolean;
isComponentBuild(): boolean;
@@ -218,7 +215,6 @@ declare namespace NodeJS {
_linkedBinding(name: 'electron_common_v8_util'): V8UtilBinding;
_linkedBinding(name: 'electron_browser_app'): { app: Electron.App, App: Function };
_linkedBinding(name: 'electron_browser_auto_updater'): { autoUpdater: Electron.AutoUpdater };
_linkedBinding(name: 'electron_browser_browser_view'): { BrowserView: typeof Electron.BrowserView };
_linkedBinding(name: 'electron_browser_crash_reporter'): CrashReporterBinding;
_linkedBinding(name: 'electron_browser_desktop_capturer'): { createDesktopCapturer(): ElectronInternal.DesktopCapturer; };
_linkedBinding(name: 'electron_browser_event_emitter'): { setEventEmitterPrototype(prototype: Object): void; };

View File

@@ -41,6 +41,13 @@ declare namespace Electron {
frameName: string;
on(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this;
removeListener(event: '-touch-bar-interaction', listener: (event: Event, itemID: string, details: any) => void): this;
_browserViews: BrowserView[];
}
interface BrowserView {
ownerWindow: BrowserWindow | null
webContentsView: WebContentsView
}
interface BrowserWindowConstructorOptions {
@@ -87,6 +94,7 @@ declare namespace Electron {
detachFromOuterFrame(): void;
setEmbedder(embedder: Electron.WebContents): void;
viewInstanceId: number;
_setOwnerWindow(w: BaseWindow | null): void;
}
interface WebFrameMain {
@@ -155,21 +163,6 @@ declare namespace Electron {
_replyChannel: ReplyChannel;
}
class View {}
// Experimental views API
class BaseWindow {
constructor(args: {show: boolean})
setContentView(view: View): void
static fromId(id: number): BaseWindow;
static getAllWindows(): BaseWindow[];
isFocused(): boolean;
static getFocusedWindow(): BaseWindow | undefined;
setMenu(menu: Menu): void;
}
class WebContentsView {
constructor(options: BrowserWindowConstructorOptions)
}
// Deprecated / undocumented BrowserWindow methods
interface BrowserWindow {
@@ -191,12 +184,6 @@ declare namespace Electron {
registerProtocol(scheme: string, handler: any): boolean;
interceptProtocol(scheme: string, handler: any): boolean;
}
namespace Main {
class BaseWindow extends Electron.BaseWindow {}
class View extends Electron.View {}
class WebContentsView extends Electron.WebContentsView {}
}
}
declare namespace ElectronInternal {