Compare commits

...

22 Commits

Author SHA1 Message Date
Sudowoodo Release Bot
735a01edce Bump v14.0.1 2021-09-13 08:47:43 -07:00
trop[bot]
5e9dca52a5 refactor: remove dead code in NeedsCompleteGpuInfoCollection (#30876)
Refs https://chromium-review.googlesource.com/c/chromium/src/+/1208362

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-09-08 15:19:38 +09:00
Mark Lee
6f67e50242 docs(protocols): simplify packaging section & cleanup (#30882)
Backport of #30832
2021-09-08 15:18:47 +09:00
trop[bot]
5451744c64 docs: Update windows info for auto-updater (#30869)
Squirrel.Mac can be served from a static host as well.

Co-authored-by: Finn Behrens <me@kloenk.dev>
2021-09-08 08:44:15 +09:00
trop[bot]
8c4f467c8c fix: show maximized frameless window (#30864)
* fix: show maximized frameless window

* test: show maximized transparent window

* fix: test using wrong bounds

BrowserWindow will be sized to the workArea when the Windows taskbar is
visible.

Co-authored-by: samuelmaddock <samuel.maddock@gmail.com>
2021-09-07 17:02:38 +02:00
trop[bot]
81ee511031 docs: updated webview tag documentation to accurately reflect preload behavior with asar archives (#30849)
Co-authored-by: ike <isaactaylor124@gmail.com>
2021-09-06 19:18:25 +09:00
trop[bot]
1bdd60e91b fix: devtools not resizable on Windows (#30847)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-09-06 19:17:18 +09:00
trop[bot]
cffc1a2381 fix: BrowserWindow backgroundColor (#30819)
Co-authored-by: samuelmaddock <samuel.maddock@gmail.com>
2021-09-03 13:50:11 -07:00
Samuel Maddock
9799589648 fix: remove CHECK_EQ for swapped RFH (#30820)
|old_host| may be nullptr if the previous RFH was shutdown.
2021-09-03 13:48:51 -07:00
Erick Zhao
c0da647170 docs: fix code example in process-model.md (#30690) (#30800)
* Update process-model.md

the demo have two error:
- at macos, close all window, the app will not quite, unless press cmd + q
- attach preload.js, use preload prop that is member of `webPreferences` property of `BrowserWindow` controller argument

* Update docs/tutorial/process-model.md

Co-authored-by: Erick Zhao <erick@hotmail.ca>

Co-authored-by: Cheng Zhao <github@zcbenz.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>

Co-authored-by: Cheng Zhao <github@zcbenz.com>
2021-09-01 16:53:18 -07:00
electron-roller[bot]
4553ce050f chore: bump chromium to 93.0.4577.63 (14-x-y) (#30794)
* chore: bump chromium in DEPS to 93.0.4577.63

* chore: update patches

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2021-09-01 15:03:49 -04:00
trop[bot]
bdf156894a ci: ignore pdb download failure (#30795)
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2021-09-01 13:25:12 -04:00
trop[bot]
d2da6de114 fix(linux): OpenURI portal support for shell.showItemInFolder() (#30783)
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2021-09-01 20:49:09 +09:00
trop[bot]
42d8be64fc docs: improve documentation about macOS entitlement usage security (#30780)
Co-authored-by: ComplexSpaces <complexspacescode@gmail.com>
2021-09-01 20:47:26 +09:00
trop[bot]
663a260976 docs: add remote removal to E14 breaking changes (#30771)
* docs: add remote removal to E14 breaking changes

* docs: trigger lint check

Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
2021-08-31 11:50:55 -07:00
Sudowoodo Release Bot
9aa2608ca7 Bump v14.0.0 2021-08-30 12:40:15 -07:00
trop[bot]
8c6240ad4a fix: repl crash when SharedArrayBuffer disabled (#30484)
* repl: fix crash when SharedArrayBuffer disabled

* fixup node .patches file

* update patch for 14-x-y

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2021-08-30 12:21:33 -07:00
electron-roller[bot]
1d84b38c71 chore: bump chromium to 93.0.4577.58 (14-x-y) (#30695)
* chore: bump chromium in DEPS to 93.0.4577.58

* chore: update patches

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2021-08-30 14:04:26 -04:00
trop[bot]
3cdb74e9bd feat: add webContents.fromDevToolsTargetId() (#30732)
* feat: add webContents.fromDevToolsTargetId()

* refactor: avoid using FromOrCreate

Co-authored-by: samuelmaddock <samuel.maddock@gmail.com>
2021-08-30 12:43:45 -04:00
John Kleinschmidt
9cc2d3f60e feat: enable windows control overlay on Windows (#30678)
cherry-picked from 41646d1

Co-Authored-By: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>

Co-authored-by: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>
2021-08-30 10:08:00 -04:00
Sudowoodo Release Bot
d73539bbc4 Bump v14.0.0-beta.25 2021-08-30 06:32:09 -07:00
Jeremy Rose
b4d7fbcb04 fix: remove ipc wrapper for nativeImage.createThumbnailFromPath (#30735) 2021-08-30 19:31:56 +09:00
52 changed files with 1421 additions and 171 deletions

2
DEPS
View File

@@ -15,7 +15,7 @@ gclient_gn_args = [
vars = {
'chromium_version':
'93.0.4577.51',
'93.0.4577.63',
'node_version':
'v14.17.0',
'nan_version':

View File

@@ -1 +1 @@
14.0.0-beta.24
14.0.1

View File

@@ -54,11 +54,17 @@ steps:
APPVEYOR_TOKEN: $(APPVEYOR_TOKEN)
- powershell: |
$localArtifactPath = "$pwd\src\pdb.zip"
$serverArtifactPath = "$env:APPVEYOR_URL/buildjobs/$env:APPVEYOR_JOB_ID/artifacts/pdb.zip"
Invoke-RestMethod -Method Get -Uri $serverArtifactPath -OutFile $localArtifactPath -Headers @{ "Authorization" = "Bearer $env:APPVEYOR_TOKEN" }
cd src
& "${env:ProgramFiles(x86)}\7-Zip\7z.exe" x -y pdb.zip
try {
$localArtifactPath = "$pwd\src\pdb.zip"
$serverArtifactPath = "$env:APPVEYOR_URL/buildjobs/$env:APPVEYOR_JOB_ID/artifacts/pdb.zip"
Invoke-RestMethod -Method Get -Uri $serverArtifactPath -OutFile $localArtifactPath -Headers @{ "Authorization" = "Bearer $env:APPVEYOR_TOKEN" }
cd src
& "${env:ProgramFiles(x86)}\7-Zip\7z.exe" x -y pdb.zip
} catch {
Write-Host "There was an exception encountered while downloading pdb files:" $_.Exception.Message
} finally {
$global:LASTEXITCODE = 0
}
displayName: 'Download pdb files for detailed stacktraces'
env:
APPVEYOR_TOKEN: $(APPVEYOR_TOKEN)

View File

@@ -64,8 +64,11 @@ static_library("chrome") {
"//chrome/browser/extensions/global_shortcut_listener_win.cc",
"//chrome/browser/extensions/global_shortcut_listener_win.h",
"//chrome/browser/icon_loader_win.cc",
"//chrome/browser/ui/frame/window_frame_util.h",
"//chrome/browser/ui/view_ids.h",
"//chrome/browser/win/chrome_process_finder.cc",
"//chrome/browser/win/chrome_process_finder.h",
"//chrome/browser/win/titlebar_config.h",
"//chrome/child/v8_crashpad_support_win.cc",
"//chrome/child/v8_crashpad_support_win.h",
]

View File

@@ -43,7 +43,7 @@ The installer generated with Squirrel will create a shortcut icon with an
same ID for your app with `app.setAppUserModelId` API, otherwise Windows will
not be able to pin your app properly in task bar.
Unlike Squirrel.Mac, Windows can host updates on S3 or any other static file host.
Like Squirrel.Mac, Windows can host updates on S3 or any other static file host.
You can read the documents of [Squirrel.Windows][squirrel-windows] to get more details
about how Squirrel.Windows works.

View File

@@ -213,16 +213,13 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
* `followWindow` - The backdrop should automatically appear active when the window is active, and inactive when it is not. This is the default.
* `active` - The backdrop should always appear active.
* `inactive` - The backdrop should always appear inactive.
* `titleBarStyle` String (optional) - The style of window title bar.
* `titleBarStyle` String (optional) _macOS_ _Windows_ - The style of window title bar.
Default is `default`. Possible values are:
* `default` - Results in the standard gray opaque Mac title
bar.
* `hidden` - Results in a hidden title bar and a full size content window, yet
the title bar still has the standard window controls ("traffic lights") in
the top left.
* `hiddenInset` - Results in a hidden title bar with an alternative look
* `default` - Results in the standard title bar for macOS or Windows respectively.
* `hidden` - Results in a hidden title bar and a full size content window. On macOS, the window still has the standard window controls (“traffic lights”) in the top left. On Windows, when combined with `titleBarOverlay: true` it will activate the Window Controls Overlay (see `titleBarOverlay` for more information), otherwise no window controls will be shown.
* `hiddenInset` - Only on macOS, results in a hidden title bar with an alternative look
where the traffic light buttons are slightly more inset from the window edge.
* `customButtonsOnHover` - Results in a hidden title bar and a full size
* `customButtonsOnHover` - Only on macOS, results in a hidden title bar and a full size
content window, the traffic light buttons will display when being hovered
over in the top left of the window. **Note:** This option is currently
experimental.
@@ -392,10 +389,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
contain the layout of the document—without requiring scrolling. Enabling
this will cause the `preferred-size-changed` event to be emitted on the
`WebContents` when the preferred size changes. Default is `false`.
* `titleBarOverlay` Boolean (optional) - On macOS, when using a frameless window in conjunction with
`win.setWindowButtonVisibility(true)` or using a `titleBarStyle` so that the traffic lights are visible,
this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and
[CSS Environment Variables][overlay-css-env-vars]. Default is `false`.
* `titleBarOverlay` [OverlayOptions](structures/overlay-options.md) | Boolean (optional) - When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS or using a `titleBarStyle` so that the standard window controls ("traffic lights" on macOS) are visible, this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and [CSS Environment Variables][overlay-css-env-vars]. Specifying `true` will result in an overlay with default system colors. Default is `false`. On Windows, the [OverlayOptions](structures/overlay-options.md) can be used instead of a boolean to specify colors for the overlay.
When setting minimum or maximum window size with `minWidth`/`maxWidth`/
`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from

View File

@@ -18,17 +18,17 @@ const win = new BrowserWindow({ width: 800, height: 600, frame: false })
win.show()
```
### Alternatives on macOS
### Alternatives
There's an alternative way to specify a chromeless window.
There's an alternative way to specify a chromeless window on macOS and Windows.
Instead of setting `frame` to `false` which disables both the titlebar and window controls,
you may want to have the title bar hidden and your content extend to the full window size,
yet still preserve the window controls ("traffic lights") for standard window actions.
yet still preserve the window controls ("traffic lights" on macOS) for standard window actions.
You can do so by specifying the `titleBarStyle` option:
#### `hidden`
Results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls (“traffic lights”) in the top left.
Results in a hidden title bar and a full size content window. On macOS, the title bar still has the standard window controls (“traffic lights”) in the top left.
```javascript
const { BrowserWindow } = require('electron')
@@ -36,6 +36,8 @@ const win = new BrowserWindow({ titleBarStyle: 'hidden' })
win.show()
```
### Alternatives on macOS
#### `hiddenInset`
Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge.
@@ -63,19 +65,33 @@ win.show()
## Windows Control Overlay
On macOS, when using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` or using one of the `titleBarStyle`s described above so
that the traffic lights are visible, you can access the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and
[CSS Environment Variables][overlay-css-env-vars] by setting the `titleBarOverlay` option to true:
When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS, using one of the `titleBarStyle`s as described above so
that the traffic lights are visible, or using `titleBarStyle: hidden` on Windows, you can access the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and
[CSS Environment Variables][overlay-css-env-vars] by setting the `titleBarOverlay` option to true. Specifying `true` will result in an overlay with default system colors.
On Windows, you can also specify the color of the overlay and its symbols by setting `titleBarOverlay` to an object with the options `color` and `symbolColor`. If an option is not specified, the color will default to its system color for the window control buttons:
```javascript
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
titleBarStyle: 'hiddenInset',
titleBarStyle: 'hidden',
titleBarOverlay: true
})
win.show()
```
```javascript
const { BrowserWindow } = require('electron')
const win = new BrowserWindow({
titleBarStyle: 'hidden',
titleBarOverlay: {
color: '#2f3241',
symbolColor: '#74b1be'
}
})
win.show()
```
## Transparent window
By setting the `transparent` option to `true`, you can also make the frameless

View File

@@ -0,0 +1,4 @@
# OverlayOptions Object
* `color` String (optional) _Windows_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color.
* `symbolColor` String (optional) _Windows_ - The CSS color of the symbols on the Window Controls Overlay when enabled. Default is the system color.

View File

@@ -45,6 +45,26 @@ returns `null`.
Returns `WebContents` | undefined - A WebContents instance with the given ID, or
`undefined` if there is no WebContents associated with the given ID.
### `webContents.fromDevToolsTargetId(targetId)`
* `targetId` String - The Chrome DevTools Protocol [TargetID](https://chromedevtools.github.io/devtools-protocol/tot/Target/#type-TargetID) associated with the WebContents instance.
Returns `WebContents` | undefined - A WebContents instance with the given TargetID, or
`undefined` if there is no WebContents associated with the given TargetID.
When communicating with the [Chrome DevTools Protocol](https://chromedevtools.github.io/devtools-protocol/),
it can be useful to lookup a WebContents instance based on its assigned TargetID.
```js
async function lookupTargetId (browserWindow) {
const wc = browserWindow.webContents
await wc.debugger.attach('1.3')
const { targetInfo } = await wc.debugger.sendCommand('Target.getTargetInfo')
const { targetId } = targetInfo
const targetWebContents = await webContents.fromDevToolsTargetId(targetId)
}
```
## Class: WebContents
> Render and control the contents of a BrowserWindow instance.

View File

@@ -142,12 +142,16 @@ browser plugins. Plugins are disabled by default.
### `preload`
```html
<!-- from a file -->
<webview src="https://www.github.com/" preload="./test.js"></webview>
<!-- or if you want to load from an asar archive -->
<webview src="https://www.github.com/" preload="./app.asar/test.js"></webview>
```
A `String` that specifies a script that will be loaded before other scripts run in the guest
page. The protocol of script's URL must be either `file:` or `asar:`, because it
will be loaded by `require` in guest page under the hood.
page. The protocol of script's URL must be `file:` (even when using `asar:` archives) because
it will be loaded by Node's `require` under the hood, which treats `asar:` archives as virtual
directories.
When the guest page doesn't have node integration this script will still have
access to all Node APIs, but global objects injected by Node will be deleted

View File

@@ -26,6 +26,25 @@ for more details.
## Planned Breaking API Changes (14.0)
### Removed: `remote` module
The `remote` module was deprecated in Electron 12, and will be removed in
Electron 14. It is replaced by the
[`@electron/remote`](https://github.com/electron/remote) module.
```js
// Deprecated in Electron 12:
const { BrowserWindow } = require('electron').remote
```
```js
// Replace with:
const { BrowserWindow } = require('@electron/remote')
// In the main process:
require('@electron/remote/main').initialize()
```
### Removed: `app.allowRendererProcessReuse`
The `app.allowRendererProcessReuse` property will be removed as part of our plan to
@@ -49,8 +68,8 @@ If you were using this parameter to set the title of a window, you can instead u
### Removed: `worldSafeExecuteJavaScript`
In Electron 14, `worldSafeExecuteJavaScript` will be removed. There is no alternative, please
ensure your code works with this property enabled. It has been enabled by default since Electron
In Electron 14, `worldSafeExecuteJavaScript` will be removed. There is no alternative, please
ensure your code works with this property enabled. It has been enabled by default since Electron
12.
You will be affected by this change if you use either `webFrame.executeJavaScript` or `webFrame.executeJavaScriptInIsolatedWorld`. You will need to ensure that values returned by either of those methods are supported by the [Context Bridge API](api/context-bridge.md#parameter--error--return-type-support) as these methods use the same value passing semantics.

View File

@@ -88,14 +88,15 @@ without meaning any harm:
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>
```
Note that up until Electron 12, the `com.apple.security.cs.allow-unsigned-executable-memory` entitlement was required
as well. However, it should not be used anymore if it can be avoided.
To see all of this in action, check out Electron Fiddle's source code,
[especially its `electron-forge` configuration
file](https://github.com/electron/fiddle/blob/master/forge.config.js).
@@ -165,14 +166,15 @@ without meaning any harm:
<dict>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
<true/>
<key>com.apple.security.cs.debugger</key>
<true/>
</dict>
</plist>
```
Up until Electron 12, the `com.apple.security.cs.allow-unsigned-executable-memory` entitlement was required
as well. However, it should not be used anymore if it can be avoided.
## Mac App Store
See the [Mac App Store Guide].

View File

@@ -1,5 +1,5 @@
---
title: Launching Your Electron App From A URL In Another App
title: Launching Your Electron App From a URL In Another App
description: This guide will take you through the process of setting your electron app as the default handler for a specific protocol.
slug: launch-app-from-url-in-another-app
hide_title: true
@@ -11,7 +11,7 @@ hide_title: true
<!-- ✍ Update this section if you want to provide more details -->
This guide will take you through the process of setting your electron app as the default
This guide will take you through the process of setting your Electron app as the default
handler for a specific [protocol](https://www.electronjs.org/docs/api/protocol).
By the end of this tutorial, we will have set our app to intercept and handle
@@ -22,16 +22,17 @@ we will use will be "`electron-fiddle://`".
### Main Process (main.js)
First we will import the required modules from `electron`. These modules help control our application life and create a native browser window.
First, we will import the required modules from `electron`. These modules help
control our application lifecycle and create a native browser window.
```js
```javascript
const { app, BrowserWindow, shell } = require('electron')
const path = require('path')
```
Next, we will proceed to register our application to handle all "`electron-fiddle://`" protocols.
```js
```javascript
if (process.defaultApp) {
if (process.argv.length >= 2) {
app.setAsDefaultProtocolClient('electron-fiddle', process.execPath, [path.resolve(process.argv[1])])
@@ -43,8 +44,8 @@ if (process.defaultApp) {
We will now define the function in charge of creating our browser window and load our application's `index.html` file.
```js
function createWindow () {
```javascript
const createWindow = () => {
// Create the browser window.
mainWindow = new BrowserWindow({
width: 800,
@@ -60,11 +61,11 @@ function createWindow () {
In this next step, we will create our `BrowserWindow` and tell our application how to handle an event in which an external protocol is clicked.
This code will be different in WindowsOS compared to MacOS and Linux. This is due to Windows requiring additional code in order to open the contents of the protocol link within the same electron instance. Read more about this [here](https://www.electronjs.org/docs/api/app#apprequestsingleinstancelock).
This code will be different in Windows compared to MacOS and Linux. This is due to Windows requiring additional code in order to open the contents of the protocol link within the same Electron instance. Read more about this [here](https://www.electronjs.org/docs/api/app#apprequestsingleinstancelock).
### Windows code:
#### Windows code:
```js
```javascript
const gotTheLock = app.requestSingleInstanceLock()
if (!gotTheLock) {
@@ -83,16 +84,16 @@ if (!gotTheLock) {
createWindow()
})
// handling the protocol. In this case, we choose to show an Error Box.
// Handle the protocol. In this case, we choose to show an Error Box.
app.on('open-url', (event, url) => {
dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})
}
```
### MacOS and Linux code:
#### MacOS and Linux code:
```js
```javascript
// This method will be called when Electron has finished
// initialization and is ready to create browser windows.
// Some APIs can only be used after this event occurs.
@@ -100,57 +101,101 @@ app.whenReady().then(() => {
createWindow()
})
// handling the protocol. In this case, we choose to show an Error Box.
// Handle the protocol. In this case, we choose to show an Error Box.
app.on('open-url', (event, url) => {
dialog.showErrorBox('Welcome Back', `You arrived from: ${url}`)
})
```
Finally, we will add some additional code to handle when someone closes our application
Finally, we will add some additional code to handle when someone closes our application.
```js
```javascript
// Quit when all windows are closed, except on macOS. There, it's common
// for applications and their menu bar to stay active until the user quits
// explicitly with Cmd + Q.
app.on('window-all-closed', function () {
app.on('window-all-closed', () => {
if (process.platform !== 'darwin') app.quit()
})
```
## Important Note:
## Important notes
### Packaging
This feature will only work on macOS when your app is packaged. It will not work when you're launching it in development from the command-line. When you package your app you'll need to make sure the macOS `plist` for the app is updated to include the new protocol handler. If you're using [`electron-packager`](https://github.com/electron/electron-packager) then you
can add the flag `--extend-info` with a path to the `plist` you've created. The one for this app is below:
On macOS and Linux, this feature will only work when your app is packaged. It will not work when
you're launching it in development from the command-line. When you package your app you'll need to
make sure the macOS `Info.plist` and the Linux `.desktop` files for the app are updated to include
the new protocol handler. Some of the Electron tools for bundling and distributing apps handle
this for you.
### Plist
#### [Electron Forge](https://electronforge.io)
```XML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLSchemes</key>
<array>
<string>electron-api-demos</string>
</array>
<key>CFBundleURLName</key>
<string>Electron API Demos Protocol</string>
</dict>
</array>
<key>ElectronTeamID</key>
<string>VEKTX9H2N7</string>
</dict>
</plist>
If you're using Electron Forge, adjust `packagerConfig` for macOS support, and the configuration for
the appropriate Linux makers for Linux support, in your [Forge
configuration](https://www.electronforge.io/configuration) _(please note the following example only
shows the bare minimum needed to add the configuration changes)_:
```json
{
"config": {
"forge": {
"packagerConfig": {
"protocols": [
{
"name": "Electron Fiddle",
"schemes": ["electron-fiddle"]
}
]
},
"makers": [
{
"name": "@electron-forge/maker-deb",
"config": {
"mimeType": ["x-scheme-handler/electron-fiddle"]
}
}
]
}
}
}
```
#### [Electron Packager](https://github.com/electron/electron-packager)
For macOS support:
If you're using Electron Packager's API, adding support for protocol handlers is similar to how
Electron Forge is handled, except
`protocols` is part of the Packager options passed to the `packager` function.
```javascript
const packager = require('electron-packager')
packager({
// ...other options...
protocols: [
{
name: 'Electron Fiddle',
schemes: ['electron-fiddle']
}
]
}).then(paths => console.log(`SUCCESS: Created ${paths.join(', ')}`))
.catch(err => console.error(`ERROR: ${err.message}`))
```
If you're using Electron Packager's CLI, use the `--protocol` and `--protocol-name` flags. For
example:
```shell
npx electron-packager . --protocol=electron-fiddle --protocol-name="Electron Fiddle"
```
## Conclusion
After you start your electron app, you can now enter in a URL in your browser that contains the custom protocol, for example `"electron-fiddle://open"` and observe that the application will respond and show an error dialog box.
After you start your Electron app, you can enter in a URL in your browser that contains the custom
protocol, for example `"electron-fiddle://open"` and observe that the application will respond and
show an error dialog box.
<!--
Because Electron examples usually require multiple files (HTML, CSS, JS

View File

@@ -83,7 +83,7 @@ As a practical example, the app shown in the [quick start guide][quick-start-lif
uses `app` APIs to create a more native application window experience.
```js title='main.js'
// quitting the app when no windows are open on macOS
// quitting the app when no windows are open on non-macOS platforms
app.on('window-all-closed', function () {
if (process.platform !== 'darwin') app.quit()
})
@@ -148,7 +148,9 @@ A preload script can be attached to the main process in the `BrowserWindow` cons
const { BrowserWindow } = require('electron')
//...
const win = new BrowserWindow({
preload: 'path/to/preload.js'
webPreferences: {
preload: 'path/to/preload.js'
}
})
//...
```

View File

@@ -70,9 +70,9 @@ until the maintainers feel the maintenance burden is too high to continue doing
### Currently supported versions
* 14.x.y
* 13.x.y
* 12.x.y
* 11.x.y
### End-of-life

View File

@@ -1,5 +1,19 @@
<?xml version="1.0" encoding="utf-8"?>
<grit-part>
<!-- Windows Caption Buttons -->
<message name="IDS_APP_ACCNAME_CLOSE" desc="The accessible name for the Close button.">
Close
</message>
<message name="IDS_APP_ACCNAME_MINIMIZE" desc="The accessible name for the Minimize button.">
Minimize
</message>
<message name="IDS_APP_ACCNAME_MAXIMIZE" desc="The accessible name for the Maximize button.">
Maximize
</message>
<message name="IDS_APP_ACCNAME_RESTORE" desc="The accessible name for the Restore button.">
Restore
</message>
<!-- Printing Service -->
<message name="IDS_UTILITY_PROCESS_PRINTING_SERVICE_NAME" desc="The name of the utility process used for printing conversions.">
Printing Service

View File

@@ -100,6 +100,7 @@ auto_filenames = {
"docs/api/structures/new-window-web-contents-event.md",
"docs/api/structures/notification-action.md",
"docs/api/structures/notification-response.md",
"docs/api/structures/overlay-options.md",
"docs/api/structures/point.md",
"docs/api/structures/post-body.md",
"docs/api/structures/printer-info.md",

View File

@@ -90,6 +90,10 @@ filenames = {
"shell/browser/ui/views/electron_views_delegate_win.cc",
"shell/browser/ui/views/win_frame_view.cc",
"shell/browser/ui/views/win_frame_view.h",
"shell/browser/ui/views/win_caption_button.cc",
"shell/browser/ui/views/win_caption_button.h",
"shell/browser/ui/views/win_caption_button_container.cc",
"shell/browser/ui/views/win_caption_button_container.h",
"shell/browser/ui/win/dialog_thread.cc",
"shell/browser/ui/win/dialog_thread.h",
"shell/browser/ui/win/electron_desktop_native_widget_aura.cc",

View File

@@ -769,6 +769,10 @@ export function fromId (id: string) {
return binding.fromId(id);
}
export function fromDevToolsTargetId (targetId: string) {
return binding.fromDevToolsTargetId(targetId);
}
export function getFocusedWebContents () {
let focused = null;
for (const contents of binding.getAllWebContents()) {

View File

@@ -1,6 +1,6 @@
import { app } from 'electron/main';
import type { WebContents } from 'electron/main';
import { clipboard, nativeImage } from 'electron/common';
import { clipboard } from 'electron/common';
import * as fs from 'fs';
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
@@ -105,7 +105,3 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_SANDBOX_LOAD, async function (event
ipcMainInternal.on(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, function (event, preloadPath: string, error: Error) {
event.sender.emit('preload-error', event, preloadPath, error);
});
ipcMainInternal.handle(IPC_MESSAGES.NATIVE_IMAGE_CREATE_THUMBNAIL_FROM_PATH, async (_, path: string, size: Electron.Size) => {
return typeUtils.serialize(await nativeImage.createThumbnailFromPath(path, size));
});

View File

@@ -34,5 +34,4 @@ export const enum IPC_MESSAGES {
INSPECTOR_SELECT_FILE = 'INSPECTOR_SELECT_FILE',
DESKTOP_CAPTURER_GET_SOURCES = 'DESKTOP_CAPTURER_GET_SOURCES',
NATIVE_IMAGE_CREATE_THUMBNAIL_FROM_PATH = 'NATIVE_IMAGE_CREATE_THUMBNAIL_FROM_PATH',
}

View File

@@ -1,11 +1,3 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
import { deserialize } from '@electron/internal/common/type-utils';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
const { nativeImage } = process._linkedBinding('electron_common_native_image');
nativeImage.createThumbnailFromPath = async (path: string, size: Electron.Size) => {
return deserialize(await ipcRendererInternal.invoke(IPC_MESSAGES.NATIVE_IMAGE_CREATE_THUMBNAIL_FROM_PATH, path, size));
};
export default nativeImage;

View File

@@ -1,6 +1,6 @@
{
"name": "electron",
"version": "14.0.0-beta.24",
"version": "14.0.1",
"repository": "https://github.com/electron/electron",
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
"devDependencies": {

View File

@@ -362,7 +362,7 @@ index bb90319f768ed2f3a3e530d64bf622de585ce163..d84de1d218267887f6b8624f913438eb
content::RenderFrameHost* opener,
content::SiteInstance* source_site_instance,
diff --git a/fuchsia/engine/browser/frame_impl.cc b/fuchsia/engine/browser/frame_impl.cc
index 3fe03fbabb182fa38f0bb17d4904f645531a536f..3f6c4bb2437b5032b758a0ba486fb0967408ecb7 100644
index 5be75c8a6f45b7f9ba4d169baf5880146ad1d1e1..554a9c001abf638e1ea4140210fcb405236b6267 100644
--- a/fuchsia/engine/browser/frame_impl.cc
+++ b/fuchsia/engine/browser/frame_impl.cc
@@ -388,8 +388,7 @@ bool FrameImpl::IsWebContentsCreationOverridden(

View File

@@ -392,7 +392,7 @@ index 6102a8844f65556e9a911b9ece136edc20cd27ce..bf3244b706365b98233a34d387fd2a42
- (id)accessibilityFocusedUIElement {
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm
index a6b5a26aab2467ae074070d2ddee4ef8ac05922c..c737936326d455422d81497e1f544ca5f1e3aee0 100644
index cfdebf86e1cd3b7e5473ce004add42ac5f62d2a0..1eca0d6048e51a8f24761879ca7c3f1f78cc77c4 100644
--- a/content/browser/accessibility/browser_accessibility_manager_mac.mm
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm
@@ -500,7 +500,7 @@ void PostAnnouncementNotification(NSString* announcement) {

View File

@@ -35,3 +35,4 @@ fix_handle_new_tostring_behavior_in_v8_serdes_test.patch
node-api_faster_threadsafe_function.patch
src_remove_extra_semi_after_member_fn.patch
errors_refactor_to_use_more_primordials.patch
repl_fix_crash_when_sharedarraybuffer_disabled.patch

View File

@@ -0,0 +1,54 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Mon, 9 Aug 2021 18:42:15 +0200
Subject: repl: fix crash when SharedArrayBuffer disabled
It's possible for SharedArrayBuffers to be disabled with
--no-harmony-sharedarraybuffer so we first need to check that this
isn't the case before attempting to use them in the repl or a crash occurs.
Upstreamed at https://github.com/nodejs/node/pull/39718.
diff --git a/benchmark/worker/atomics-wait.js b/benchmark/worker/atomics-wait.js
index a771b1813731edf4f0dd60f3505799e389f1d876..b9461677e2d7d1df192e752496e62cca837717b5 100644
--- a/benchmark/worker/atomics-wait.js
+++ b/benchmark/worker/atomics-wait.js
@@ -7,6 +7,10 @@ const bench = common.createBenchmark(main, {
});
function main({ n }) {
+ if (typeof SharedArrayBuffer === 'undefined') {
+ throw new Error('SharedArrayBuffers must be enabled to run this benchmark');
+ }
+
const i32arr = new Int32Array(new SharedArrayBuffer(4));
bench.start();
for (let i = 0; i < n; i++)
diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js
index 6f8cb6b942b5f295e6195e18059736df4bff8756..a2faa5c4a12f3af84dbfc4a3e4485d4ee5ce0167 100644
--- a/lib/internal/main/worker_thread.js
+++ b/lib/internal/main/worker_thread.js
@@ -132,6 +132,9 @@ port.on('message', (message) => {
const originalCwd = process.cwd;
process.cwd = function() {
+ // SharedArrayBuffers can be disabled with --no-harmony-sharedarraybuffer.
+ if (typeof SharedArrayBuffer === 'undefined') return originalCwd();
+
const currentCounter = Atomics.load(cwdCounter, 0);
if (currentCounter === lastCounter)
return cachedCwd;
diff --git a/lib/internal/worker.js b/lib/internal/worker.js
index d38649c7fb158361096d5a7a3b5bd629ba7b6d0b..468e85cacb09f3e4b3dd5603d5f54a010c7ff751 100644
--- a/lib/internal/worker.js
+++ b/lib/internal/worker.js
@@ -81,7 +81,8 @@ let debug = require('internal/util/debuglog').debuglog('worker', (fn) => {
let cwdCounter;
-if (isMainThread) {
+// SharedArrayBuffers can be disabled with --no-harmony-sharedarraybuffer.
+if (isMainThread && typeof SharedArrayBuffer !== 'undefined') {
cwdCounter = new Uint32Array(new SharedArrayBuffer(4));
const originalChdir = process.chdir;
process.chdir = function(path) {

View File

@@ -362,9 +362,7 @@ void BrowserWindow::Blur() {
void BrowserWindow::SetBackgroundColor(const std::string& color_name) {
BaseWindow::SetBackgroundColor(color_name);
auto* view = web_contents()->GetRenderWidgetHostView();
if (view)
view->SetBackgroundColor(ParseHexColor(color_name));
web_contents()->SetPageBaseBackgroundColor(ParseHexColor(color_name));
// Also update the web preferences object otherwise the view will be reset on
// the next load URL call
if (api_web_contents_) {

View File

@@ -1373,8 +1373,16 @@ void WebContents::HandleNewRenderFrame(
std::string color_name;
if (web_preferences->GetPreference(options::kBackgroundColor,
&color_name)) {
rwhv->SetBackgroundColor(ParseHexColor(color_name));
web_contents()->SetPageBaseBackgroundColor(ParseHexColor(color_name));
} else {
web_contents()->SetPageBaseBackgroundColor(absl::nullopt);
}
// When a page base background color is set, transparency needs to be
// explicitly set by calling
// RenderWidgetHostOwnerDelegate::SetBackgroundOpaque(false).
// RenderWidgetHostViewBase::SetBackgroundColor() will do this for us.
if (web_preferences->IsEnabled(options::kTransparent)) {
rwhv->SetBackgroundColor(SK_ColorTRANSPARENT);
}
}
@@ -1421,12 +1429,11 @@ void WebContents::RenderFrameHostChanged(content::RenderFrameHost* old_host,
// If an instance of WebFrameMain exists, it will need to have its RFH
// swapped as well.
//
// |old_host| can be a nullptr in so we use |new_host| for looking up the
// |old_host| can be a nullptr so we use |new_host| for looking up the
// WebFrameMain instance.
auto* web_frame =
WebFrameMain::FromFrameTreeNodeId(new_host->GetFrameTreeNodeId());
if (web_frame) {
CHECK_EQ(web_frame->render_frame_host(), old_host);
web_frame->UpdateRenderFrameHost(new_host);
}
}
@@ -3909,6 +3916,16 @@ gin::Handle<WebContents> WebContentsFromID(v8::Isolate* isolate, int32_t id) {
: gin::Handle<WebContents>();
}
gin::Handle<WebContents> WebContentsFromDevToolsTargetID(
v8::Isolate* isolate,
std::string target_id) {
auto agent_host = content::DevToolsAgentHost::GetForId(target_id);
WebContents* contents =
agent_host ? WebContents::From(agent_host->GetWebContents()) : nullptr;
return contents ? gin::CreateHandle(isolate, contents)
: gin::Handle<WebContents>();
}
std::vector<gin::Handle<WebContents>> GetAllWebContentsAsV8(
v8::Isolate* isolate) {
std::vector<gin::Handle<WebContents>> list;
@@ -3927,6 +3944,7 @@ void Initialize(v8::Local<v8::Object> exports,
gin_helper::Dictionary dict(isolate, exports);
dict.Set("WebContents", WebContents::GetConstructor(context));
dict.SetMethod("fromId", &WebContentsFromID);
dict.SetMethod("fromDevToolsTargetId", &WebContentsFromDevToolsTargetID);
dict.SetMethod("getAllWebContents", &GetAllWebContentsAsV8);
}

View File

@@ -31,9 +31,7 @@ GPUInfoManager::~GPUInfoManager() {
// Based on
// https://chromium.googlesource.com/chromium/src.git/+/69.0.3497.106/content/browser/gpu/gpu_data_manager_impl_private.cc#838
bool GPUInfoManager::NeedsCompleteGpuInfoCollection() const {
#if defined(OS_MAC)
return gpu_data_manager_->GetGPUInfo().gl_vendor.empty();
#elif defined(OS_WIN)
#if defined(OS_WIN)
return gpu_data_manager_->DxdiagDx12VulkanRequested() &&
gpu_data_manager_->GetGPUInfo().dx_diagnostics.IsEmpty();
#else

View File

@@ -24,6 +24,34 @@
#include "ui/display/win/screen_win.h"
#endif
namespace gin {
template <>
struct Converter<electron::NativeWindow::TitleBarStyle> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
electron::NativeWindow::TitleBarStyle* out) {
using TitleBarStyle = electron::NativeWindow::TitleBarStyle;
std::string title_bar_style;
if (!ConvertFromV8(isolate, val, &title_bar_style))
return false;
if (title_bar_style == "hidden") {
*out = TitleBarStyle::kHidden;
#if defined(OS_MAC)
} else if (title_bar_style == "hiddenInset") {
*out = TitleBarStyle::kHiddenInset;
} else if (title_bar_style == "customButtonsOnHover") {
*out = TitleBarStyle::kCustomButtonsOnHover;
#endif
} else {
return false;
}
return true;
}
};
} // namespace gin
namespace electron {
namespace {
@@ -54,7 +82,19 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
options.Get(options::kFrame, &has_frame_);
options.Get(options::kTransparent, &transparent_);
options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_);
options.Get(options::ktitleBarOverlay, &titlebar_overlay_);
options.Get(options::kTitleBarStyle, &title_bar_style_);
v8::Local<v8::Value> titlebar_overlay;
if (options.Get(options::ktitleBarOverlay, &titlebar_overlay)) {
if (titlebar_overlay->IsBoolean()) {
options.Get(options::ktitleBarOverlay, &titlebar_overlay_);
} else if (titlebar_overlay->IsObject()) {
titlebar_overlay_ = true;
#if !defined(OS_WIN)
DCHECK(false);
#endif
}
}
if (parent)
options.Get("modal", &is_modal_);

View File

@@ -316,6 +316,14 @@ class NativeWindow : public base::SupportsUserData,
views::Widget* widget() const { return widget_.get(); }
views::View* content_view() const { return content_view_; }
enum class TitleBarStyle {
kNormal,
kHidden,
kHiddenInset,
kCustomButtonsOnHover,
};
TitleBarStyle title_bar_style() const { return title_bar_style_; }
bool has_frame() const { return has_frame_; }
void set_has_frame(bool has_frame) { has_frame_ = has_frame; }
@@ -347,8 +355,12 @@ class NativeWindow : public base::SupportsUserData,
[&browser_view](NativeBrowserView* n) { return (n == browser_view); });
}
// The boolean parsing of the "titleBarOverlay" option
bool titlebar_overlay_ = false;
// The "titleBarStyle" option.
TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal;
private:
std::unique_ptr<views::Widget> widget_;

View File

@@ -184,14 +184,6 @@ class NativeWindowMac : public NativeWindow,
kInactive,
};
enum class TitleBarStyle {
kNormal,
kHidden,
kHiddenInset,
kCustomButtonsOnHover,
};
TitleBarStyle title_bar_style() const { return title_bar_style_; }
ElectronPreviewItem* preview_item() const { return preview_item_.get(); }
ElectronTouchBar* touch_bar() const { return touch_bar_.get(); }
bool zoom_to_page_width() const { return zoom_to_page_width_; }
@@ -249,9 +241,6 @@ class NativeWindowMac : public NativeWindow,
// The presentation options before entering kiosk mode.
NSApplicationPresentationOptions kiosk_options_;
// The "titleBarStyle" option.
TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal;
// The "visualEffectState" option.
VisualEffectState visual_effect_state_ = VisualEffectState::kFollowWindow;

View File

@@ -163,28 +163,6 @@
namespace gin {
template <>
struct Converter<electron::NativeWindowMac::TitleBarStyle> {
static bool FromV8(v8::Isolate* isolate,
v8::Handle<v8::Value> val,
electron::NativeWindowMac::TitleBarStyle* out) {
using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
std::string title_bar_style;
if (!ConvertFromV8(isolate, val, &title_bar_style))
return false;
if (title_bar_style == "hidden") {
*out = TitleBarStyle::kHidden;
} else if (title_bar_style == "hiddenInset") {
*out = TitleBarStyle::kHiddenInset;
} else if (title_bar_style == "customButtonsOnHover") {
*out = TitleBarStyle::kCustomButtonsOnHover;
} else {
return false;
}
return true;
}
};
template <>
struct Converter<electron::NativeWindowMac::VisualEffectState> {
static bool FromV8(v8::Isolate* isolate,
@@ -274,7 +252,6 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
bool resizable = true;
options.Get(options::kResizable, &resizable);
options.Get(options::kTitleBarStyle, &title_bar_style_);
options.Get(options::kZoomToPageWidth, &zoom_to_page_width_);
options.Get(options::kSimpleFullScreen, &always_simple_fullscreen_);
options.GetOptional(options::kTrafficLightPosition, &traffic_light_position_);

View File

@@ -71,12 +71,14 @@
#elif defined(OS_WIN)
#include "base/win/win_util.h"
#include "extensions/common/image_util.h"
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/browser/ui/win/electron_desktop_native_widget_aura.h"
#include "skia/ext/skia_utils_win.h"
#include "ui/base/win/shell.h"
#include "ui/display/screen.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/color_utils.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#endif
@@ -165,6 +167,37 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
options.Get("thickFrame", &thick_frame_);
if (transparent())
thick_frame_ = false;
overlay_button_color_ = color_utils::GetSysSkColor(COLOR_BTNFACE);
overlay_symbol_color_ = color_utils::GetSysSkColor(COLOR_BTNTEXT);
v8::Local<v8::Value> titlebar_overlay;
if (options.Get(options::ktitleBarOverlay, &titlebar_overlay) &&
titlebar_overlay->IsObject()) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
gin_helper::Dictionary titlebar_overlay_obj =
gin::Dictionary::CreateEmpty(isolate);
options.Get(options::ktitleBarOverlay, &titlebar_overlay_obj);
std::string overlay_color_string;
if (titlebar_overlay_obj.Get(options::kOverlayButtonColor,
&overlay_color_string)) {
bool success = extensions::image_util::ParseCssColorString(
overlay_color_string, &overlay_button_color_);
DCHECK(success);
}
std::string overlay_symbol_color_string;
if (titlebar_overlay_obj.Get(options::kOverlaySymbolColor,
&overlay_symbol_color_string)) {
bool success = extensions::image_util::ParseCssColorString(
overlay_symbol_color_string, &overlay_symbol_color_);
DCHECK(success);
}
}
if (title_bar_style_ != TitleBarStyle::kNormal)
set_has_frame(false);
#endif
if (enable_larger_than_screen())
@@ -1596,8 +1629,17 @@ void NativeWindowViews::OnMouseEvent(ui::MouseEvent* event) {
}
ui::WindowShowState NativeWindowViews::GetRestoredState() {
if (IsMaximized())
if (IsMaximized()) {
#if defined(OS_WIN)
// Only restore Maximized state when window is NOT transparent style
if (!transparent()) {
return ui::SHOW_STATE_MAXIMIZED;
}
#else
return ui::SHOW_STATE_MAXIMIZED;
#endif
}
if (IsFullscreen())
return ui::SHOW_STATE_FULLSCREEN;

View File

@@ -19,6 +19,7 @@
#if defined(OS_WIN)
#include "base/win/scoped_gdi_object.h"
#include "shell/browser/ui/win/taskbar_host.h"
#endif
namespace views {
@@ -175,6 +176,15 @@ class NativeWindowViews : public NativeWindow,
TaskbarHost& taskbar_host() { return taskbar_host_; }
#endif
#if defined(OS_WIN)
bool IsWindowControlsOverlayEnabled() const {
return (title_bar_style_ == NativeWindowViews::TitleBarStyle::kHidden) &&
titlebar_overlay_;
}
SkColor overlay_button_color() const { return overlay_button_color_; }
SkColor overlay_symbol_color() const { return overlay_symbol_color_; }
#endif
private:
// views::WidgetObserver:
void OnWidgetActivationChanged(views::Widget* widget, bool active) override;
@@ -293,6 +303,11 @@ class NativeWindowViews : public NativeWindow,
// Whether the window is currently being moved.
bool is_moving_ = false;
// The color to use as the theme and symbol colors respectively for Window
// Controls Overlay if enabled on Windows.
SkColor overlay_button_color_;
SkColor overlay_symbol_color_;
#endif
// Handles unhandled keyboard messages coming back from the renderer process.

View File

@@ -50,8 +50,8 @@ END
//
VS_VERSION_INFO VERSIONINFO
FILEVERSION 14,0,0,24
PRODUCTVERSION 14,0,0,24
FILEVERSION 14,0,1,0
PRODUCTVERSION 14,0,1,0
FILEFLAGSMASK 0x3fL
#ifdef _DEBUG
FILEFLAGS 0x1L
@@ -68,12 +68,12 @@ BEGIN
BEGIN
VALUE "CompanyName", "GitHub, Inc."
VALUE "FileDescription", "Electron"
VALUE "FileVersion", "14.0.0"
VALUE "FileVersion", "14.0.1"
VALUE "InternalName", "electron.exe"
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
VALUE "OriginalFilename", "electron.exe"
VALUE "ProductName", "Electron"
VALUE "ProductVersion", "14.0.0"
VALUE "ProductVersion", "14.0.1"
VALUE "SquirrelAwareVersion", "1"
END
END

View File

@@ -85,17 +85,17 @@ int FramelessView::NonClientHitTest(const gfx::Point& cursor) {
return HTCAPTION;
}
// Support resizing frameless window by dragging the border.
int frame_component = ResizingBorderHitTest(cursor);
if (frame_component != HTNOWHERE)
return frame_component;
// Check for possible draggable region in the client area for the frameless
// window.
SkRegion* draggable_region = window_->draggable_region();
if (draggable_region && draggable_region->contains(cursor.x(), cursor.y()))
return HTCAPTION;
// Support resizing frameless window by dragging the border.
int frame_component = ResizingBorderHitTest(cursor);
if (frame_component != HTNOWHERE)
return frame_component;
return HTCLIENT;
}

View File

@@ -48,6 +48,8 @@ class FramelessView : public views::NonClientFrameView {
NativeWindowViews* window_ = nullptr;
views::Widget* frame_ = nullptr;
friend class NativeWindowsViews;
private:
DISALLOW_COPY_AND_ASSIGN(FramelessView);
};

View File

@@ -43,8 +43,6 @@ class DevToolsWindowDelegate : public views::ClientView,
// views::WidgetDelegate:
views::View* GetInitiallyFocusedView() override { return view_; }
bool CanMaximize() const override { return true; }
bool CanMinimize() const override { return true; }
std::u16string GetWindowTitle() const override { return shell_->GetTitle(); }
ui::ImageModel GetWindowAppIcon() override { return GetWindowIcon(); }
ui::ImageModel GetWindowIcon() override { return icon_; }
@@ -193,6 +191,7 @@ void InspectableWebContentsViewViews::SetIsDocked(bool docked, bool activate) {
devtools_window_->Init(std::move(params));
devtools_window_->UpdateWindowIcon();
devtools_window_->widget_delegate()->SetHasWindowSizeControls(true);
}
ShowDevTools(activate);

View File

@@ -0,0 +1,220 @@
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/views/win_caption_button.h"
#include <utility>
#include "base/i18n/rtl.h"
#include "base/numerics/safe_conversions.h"
#include "chrome/browser/ui/frame/window_frame_util.h"
#include "chrome/grit/theme_resources.h"
#include "shell/browser/ui/views/win_frame_view.h"
#include "shell/common/color_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/theme_provider.h"
#include "ui/gfx/animation/tween.h"
#include "ui/gfx/color_utils.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/scoped_canvas.h"
namespace electron {
WinCaptionButton::WinCaptionButton(PressedCallback callback,
WinFrameView* frame_view,
ViewID button_type,
const std::u16string& accessible_name)
: views::Button(std::move(callback)),
frame_view_(frame_view),
button_type_(button_type) {
SetAnimateOnStateChange(true);
// Not focusable by default, only for accessibility.
SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY);
SetAccessibleName(accessible_name);
}
gfx::Size WinCaptionButton::CalculatePreferredSize() const {
// TODO(bsep): The sizes in this function are for 1x device scale and don't
// match Windows button sizes at hidpi.
int height = WindowFrameUtil::kWindows10GlassCaptionButtonHeightRestored;
int base_width = WindowFrameUtil::kWindows10GlassCaptionButtonWidth;
return gfx::Size(base_width + GetBetweenButtonSpacing(), height);
}
void WinCaptionButton::OnPaintBackground(gfx::Canvas* canvas) {
// Paint the background of the button (the semi-transparent rectangle that
// appears when you hover or press the button).
const SkColor bg_color = frame_view_->window()->overlay_button_color();
const SkAlpha theme_alpha = SkColorGetA(bg_color);
gfx::Rect bounds = GetContentsBounds();
bounds.Inset(0, 0, 0, 0);
canvas->FillRect(bounds, SkColorSetA(bg_color, theme_alpha));
SkColor base_color;
SkAlpha hovered_alpha, pressed_alpha;
if (button_type_ == VIEW_ID_CLOSE_BUTTON) {
base_color = SkColorSetRGB(0xE8, 0x11, 0x23);
hovered_alpha = SK_AlphaOPAQUE;
pressed_alpha = 0x98;
} else {
// Match the native buttons.
base_color = frame_view_->GetReadableFeatureColor(bg_color);
hovered_alpha = 0x1A;
pressed_alpha = 0x33;
if (theme_alpha > 0) {
// Theme buttons have slightly increased opacity to make them stand out
// against a visually-busy frame image.
constexpr float kAlphaScale = 1.3f;
hovered_alpha = base::ClampRound<SkAlpha>(hovered_alpha * kAlphaScale);
pressed_alpha = base::ClampRound<SkAlpha>(pressed_alpha * kAlphaScale);
}
}
SkAlpha alpha;
if (GetState() == STATE_PRESSED)
alpha = pressed_alpha;
else
alpha = gfx::Tween::IntValueBetween(hover_animation().GetCurrentValue(),
SK_AlphaTRANSPARENT, hovered_alpha);
canvas->FillRect(bounds, SkColorSetA(base_color, alpha));
}
void WinCaptionButton::PaintButtonContents(gfx::Canvas* canvas) {
PaintSymbol(canvas);
}
int WinCaptionButton::GetBetweenButtonSpacing() const {
const int display_order_index = GetButtonDisplayOrderIndex();
return display_order_index == 0
? 0
: WindowFrameUtil::kWindows10GlassCaptionButtonVisualSpacing;
}
int WinCaptionButton::GetButtonDisplayOrderIndex() const {
int button_display_order = 0;
switch (button_type_) {
case VIEW_ID_MINIMIZE_BUTTON:
button_display_order = 0;
break;
case VIEW_ID_MAXIMIZE_BUTTON:
case VIEW_ID_RESTORE_BUTTON:
button_display_order = 1;
break;
case VIEW_ID_CLOSE_BUTTON:
button_display_order = 2;
break;
default:
NOTREACHED();
return 0;
}
// Reverse the ordering if we're in RTL mode
if (base::i18n::IsRTL())
button_display_order = 2 - button_display_order;
return button_display_order;
}
namespace {
// Canvas::DrawRect's stroke can bleed out of |rect|'s bounds, so this draws a
// rectangle inset such that the result is constrained to |rect|'s size.
void DrawRect(gfx::Canvas* canvas,
const gfx::Rect& rect,
const cc::PaintFlags& flags) {
gfx::RectF rect_f(rect);
float stroke_half_width = flags.getStrokeWidth() / 2;
rect_f.Inset(stroke_half_width, stroke_half_width);
canvas->DrawRect(rect_f, flags);
}
} // namespace
void WinCaptionButton::PaintSymbol(gfx::Canvas* canvas) {
SkColor symbol_color = frame_view_->window()->overlay_symbol_color();
if (button_type_ == VIEW_ID_CLOSE_BUTTON &&
hover_animation().is_animating()) {
symbol_color = gfx::Tween::ColorValueBetween(
hover_animation().GetCurrentValue(), symbol_color, SK_ColorWHITE);
} else if (button_type_ == VIEW_ID_CLOSE_BUTTON &&
(GetState() == STATE_HOVERED || GetState() == STATE_PRESSED)) {
symbol_color = SK_ColorWHITE;
}
gfx::ScopedCanvas scoped_canvas(canvas);
const float scale = canvas->UndoDeviceScaleFactor();
const int symbol_size_pixels = std::round(10 * scale);
gfx::RectF bounds_rect(GetContentsBounds());
bounds_rect.Scale(scale);
gfx::Rect symbol_rect(gfx::ToEnclosingRect(bounds_rect));
symbol_rect.ClampToCenteredSize(
gfx::Size(symbol_size_pixels, symbol_size_pixels));
cc::PaintFlags flags;
flags.setAntiAlias(false);
flags.setColor(symbol_color);
flags.setStyle(cc::PaintFlags::kStroke_Style);
// Stroke width jumps up a pixel every time we reach a new integral scale.
const int stroke_width = std::floor(scale);
flags.setStrokeWidth(stroke_width);
switch (button_type_) {
case VIEW_ID_MINIMIZE_BUTTON: {
const int y = symbol_rect.CenterPoint().y();
const gfx::Point p1 = gfx::Point(symbol_rect.x(), y);
const gfx::Point p2 = gfx::Point(symbol_rect.right(), y);
canvas->DrawLine(p1, p2, flags);
return;
}
case VIEW_ID_MAXIMIZE_BUTTON:
DrawRect(canvas, symbol_rect, flags);
return;
case VIEW_ID_RESTORE_BUTTON: {
// Bottom left ("in front") square.
const int separation = std::floor(2 * scale);
symbol_rect.Inset(0, separation, separation, 0);
DrawRect(canvas, symbol_rect, flags);
// Top right ("behind") square.
canvas->ClipRect(symbol_rect, SkClipOp::kDifference);
symbol_rect.Offset(separation, -separation);
DrawRect(canvas, symbol_rect, flags);
return;
}
case VIEW_ID_CLOSE_BUTTON: {
flags.setAntiAlias(true);
// The close button's X is surrounded by a "halo" of transparent pixels.
// When the X is white, the transparent pixels need to be a bit brighter
// to be visible.
const float stroke_halo =
stroke_width * (symbol_color == SK_ColorWHITE ? 0.1f : 0.05f);
flags.setStrokeWidth(stroke_width + stroke_halo);
// TODO(bsep): This sometimes draws misaligned at fractional device scales
// because the button's origin isn't necessarily aligned to pixels.
canvas->ClipRect(symbol_rect);
SkPath path;
path.moveTo(symbol_rect.x(), symbol_rect.y());
path.lineTo(symbol_rect.right(), symbol_rect.bottom());
path.moveTo(symbol_rect.right(), symbol_rect.y());
path.lineTo(symbol_rect.x(), symbol_rect.bottom());
canvas->DrawPath(path, flags);
return;
}
default:
NOTREACHED();
return;
}
}
} // namespace electron

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2016 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_
#define SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_
#include "chrome/browser/ui/view_ids.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/gfx/canvas.h"
#include "ui/views/controls/button/button.h"
namespace electron {
class WinFrameView;
class WinCaptionButton : public views::Button {
public:
WinCaptionButton(PressedCallback callback,
WinFrameView* frame_view,
ViewID button_type,
const std::u16string& accessible_name);
WinCaptionButton(const WinCaptionButton&) = delete;
WinCaptionButton& operator=(const WinCaptionButton&) = delete;
// // views::Button:
gfx::Size CalculatePreferredSize() const override;
void OnPaintBackground(gfx::Canvas* canvas) override;
void PaintButtonContents(gfx::Canvas* canvas) override;
// private:
// Returns the amount we should visually reserve on the left (right in RTL)
// for spacing between buttons. We do this instead of repositioning the
// buttons to avoid the sliver of deadspace that would result.
int GetBetweenButtonSpacing() const;
// Returns the order in which this button will be displayed (with 0 being
// drawn farthest to the left, and larger indices being drawn to the right of
// smaller indices).
int GetButtonDisplayOrderIndex() const;
// The base color to use for the button symbols and background blending. Uses
// the more readable of black and white.
SkColor GetBaseColor() const;
// Paints the minimize/maximize/restore/close icon for the button.
void PaintSymbol(gfx::Canvas* canvas);
WinFrameView* frame_view_;
ViewID button_type_;
};
} // namespace electron
#endif // SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_

View File

@@ -0,0 +1,143 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "shell/browser/ui/views/win_caption_button_container.h"
#include <memory>
#include <utility>
#include "shell/browser/ui/views/win_caption_button.h"
#include "shell/browser/ui/views/win_frame_view.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/layout/flex_layout.h"
#include "ui/views/view_class_properties.h"
namespace electron {
namespace {
std::unique_ptr<WinCaptionButton> CreateCaptionButton(
views::Button::PressedCallback callback,
WinFrameView* frame_view,
ViewID button_type,
int accessible_name_resource_id) {
return std::make_unique<WinCaptionButton>(
std::move(callback), frame_view, button_type,
l10n_util::GetStringUTF16(accessible_name_resource_id));
}
bool HitTestCaptionButton(WinCaptionButton* button, const gfx::Point& point) {
return button && button->GetVisible() && button->bounds().Contains(point);
}
} // anonymous namespace
WinCaptionButtonContainer::WinCaptionButtonContainer(WinFrameView* frame_view)
: frame_view_(frame_view),
minimize_button_(AddChildView(CreateCaptionButton(
base::BindRepeating(&views::Widget::Minimize,
base::Unretained(frame_view_->frame())),
frame_view_,
VIEW_ID_MINIMIZE_BUTTON,
IDS_APP_ACCNAME_MINIMIZE))),
maximize_button_(AddChildView(CreateCaptionButton(
base::BindRepeating(&views::Widget::Maximize,
base::Unretained(frame_view_->frame())),
frame_view_,
VIEW_ID_MAXIMIZE_BUTTON,
IDS_APP_ACCNAME_MAXIMIZE))),
restore_button_(AddChildView(CreateCaptionButton(
base::BindRepeating(&views::Widget::Restore,
base::Unretained(frame_view_->frame())),
frame_view_,
VIEW_ID_RESTORE_BUTTON,
IDS_APP_ACCNAME_RESTORE))),
close_button_(AddChildView(CreateCaptionButton(
base::BindRepeating(&views::Widget::CloseWithReason,
base::Unretained(frame_view_->frame()),
views::Widget::ClosedReason::kCloseButtonClicked),
frame_view_,
VIEW_ID_CLOSE_BUTTON,
IDS_APP_ACCNAME_CLOSE))) {
// Layout is horizontal, with buttons placed at the trailing end of the view.
// This allows the container to expand to become a faux titlebar/drag handle.
auto* const layout = SetLayoutManager(std::make_unique<views::FlexLayout>());
layout->SetOrientation(views::LayoutOrientation::kHorizontal)
.SetMainAxisAlignment(views::LayoutAlignment::kEnd)
.SetCrossAxisAlignment(views::LayoutAlignment::kStart)
.SetDefault(
views::kFlexBehaviorKey,
views::FlexSpecification(views::LayoutOrientation::kHorizontal,
views::MinimumFlexSizeRule::kPreferred,
views::MaximumFlexSizeRule::kPreferred,
/* adjust_width_for_height */ false,
views::MinimumFlexSizeRule::kScaleToZero));
}
WinCaptionButtonContainer::~WinCaptionButtonContainer() {}
int WinCaptionButtonContainer::NonClientHitTest(const gfx::Point& point) const {
DCHECK(HitTestPoint(point))
<< "should only be called with a point inside this view's bounds";
if (HitTestCaptionButton(minimize_button_, point)) {
return HTMINBUTTON;
}
if (HitTestCaptionButton(maximize_button_, point)) {
return HTMAXBUTTON;
}
if (HitTestCaptionButton(restore_button_, point)) {
return HTMAXBUTTON;
}
if (HitTestCaptionButton(close_button_, point)) {
return HTCLOSE;
}
return HTCAPTION;
}
void WinCaptionButtonContainer::ResetWindowControls() {
minimize_button_->SetState(views::Button::STATE_NORMAL);
maximize_button_->SetState(views::Button::STATE_NORMAL);
restore_button_->SetState(views::Button::STATE_NORMAL);
close_button_->SetState(views::Button::STATE_NORMAL);
InvalidateLayout();
}
void WinCaptionButtonContainer::AddedToWidget() {
views::Widget* const widget = GetWidget();
DCHECK(!widget_observation_.IsObserving());
widget_observation_.Observe(widget);
UpdateButtons();
if (frame_view_->window()->IsWindowControlsOverlayEnabled()) {
SetPaintToLayer();
}
}
void WinCaptionButtonContainer::RemovedFromWidget() {
DCHECK(widget_observation_.IsObserving());
widget_observation_.Reset();
}
void WinCaptionButtonContainer::OnWidgetBoundsChanged(
views::Widget* widget,
const gfx::Rect& new_bounds) {
UpdateButtons();
}
void WinCaptionButtonContainer::UpdateButtons() {
const bool is_maximized = frame_view_->frame()->IsMaximized();
restore_button_->SetVisible(is_maximized);
maximize_button_->SetVisible(!is_maximized);
// In touch mode, windows cannot be taken out of fullscreen or tiled mode, so
// the maximize/restore button should be disabled.
const bool is_touch = ui::TouchUiController::Get()->touch_ui();
restore_button_->SetEnabled(!is_touch);
maximize_button_->SetEnabled(!is_touch);
InvalidateLayout();
}
} // namespace electron

View File

@@ -0,0 +1,70 @@
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_
#define SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_
#include "base/scoped_observation.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/pointer/touch_ui_controller.h"
#include "ui/views/controls/button/button.h"
#include "ui/views/view.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace electron {
class WinFrameView;
class WinCaptionButton;
// Provides a container for Windows 10 caption buttons that can be moved between
// frame and browser window as needed. When extended horizontally, becomes a
// grab bar for moving the window.
class WinCaptionButtonContainer : public views::View,
public views::WidgetObserver {
public:
explicit WinCaptionButtonContainer(WinFrameView* frame_view);
~WinCaptionButtonContainer() override;
// Tests to see if the specified |point| (which is expressed in this view's
// coordinates and which must be within this view's bounds) is within one of
// the caption buttons. Returns one of HitTestCompat enum defined in
// ui/base/hit_test.h, HTCAPTION if the area hit would be part of the window's
// drag handle, and HTNOWHERE otherwise.
// See also ClientView::NonClientHitTest.
int NonClientHitTest(const gfx::Point& point) const;
private:
// views::View:
void AddedToWidget() override;
void RemovedFromWidget() override;
// views::WidgetObserver:
void OnWidgetBoundsChanged(views::Widget* widget,
const gfx::Rect& new_bounds) override;
void ResetWindowControls();
// Sets caption button visibility and enabled state based on window state.
// Only one of maximize or restore button should ever be visible at the same
// time, and both are disabled in tablet UI mode.
void UpdateButtons();
WinFrameView* const frame_view_;
WinCaptionButton* const minimize_button_;
WinCaptionButton* const maximize_button_;
WinCaptionButton* const restore_button_;
WinCaptionButton* const close_button_;
base::ScopedObservation<views::Widget, views::WidgetObserver>
widget_observation_{this};
base::CallbackListSubscription subscription_ =
ui::TouchUiController::Get()->RegisterCallback(
base::BindRepeating(&WinCaptionButtonContainer::UpdateButtons,
base::Unretained(this)));
};
} // namespace electron
#endif // SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_

View File

@@ -1,11 +1,24 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
//
// Portions of this file are sourced from
// chrome/browser/ui/views/frame/glass_browser_frame_view.cc,
// Copyright (c) 2012 The Chromium Authors,
// which is governed by a BSD-style license
#include "shell/browser/ui/views/win_frame_view.h"
#include <dwmapi.h>
#include <memory>
#include "base/win/windows_version.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/win_caption_button_container.h"
#include "ui/base/win/hwnd_metrics.h"
#include "ui/display/win/dpi.h"
#include "ui/display/win/screen_win.h"
#include "ui/gfx/geometry/dip_util.h"
#include "ui/views/widget/widget.h"
#include "ui/views/win/hwnd_util.h"
@@ -17,6 +30,30 @@ WinFrameView::WinFrameView() {}
WinFrameView::~WinFrameView() {}
void WinFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
window_ = window;
frame_ = frame;
if (window->IsWindowControlsOverlayEnabled()) {
caption_button_container_ =
AddChildView(std::make_unique<WinCaptionButtonContainer>(this));
} else {
caption_button_container_ = nullptr;
}
}
SkColor WinFrameView::GetReadableFeatureColor(SkColor background_color) {
// color_utils::GetColorWithMaxContrast()/IsDark() aren't used here because
// they switch based on the Chrome light/dark endpoints, while we want to use
// the system native behavior below.
const auto windows_luma = [](SkColor c) {
return 0.25f * SkColorGetR(c) + 0.625f * SkColorGetG(c) +
0.125f * SkColorGetB(c);
};
return windows_luma(background_color) <= 128.0f ? SK_ColorWHITE
: SK_ColorBLACK;
}
gfx::Rect WinFrameView::GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const {
return views::GetWindowBoundsForClientBounds(
@@ -24,15 +61,194 @@ gfx::Rect WinFrameView::GetWindowBoundsForClientBounds(
client_bounds);
}
int WinFrameView::FrameBorderThickness() const {
return (IsMaximized() || frame()->IsFullscreen())
? 0
: display::win::ScreenWin::GetSystemMetricsInDIP(SM_CXSIZEFRAME);
}
int WinFrameView::NonClientHitTest(const gfx::Point& point) {
if (window_->has_frame())
return frame_->client_view()->NonClientHitTest(point);
else
return FramelessView::NonClientHitTest(point);
if (ShouldCustomDrawSystemTitlebar()) {
// See if the point is within any of the window controls.
if (caption_button_container_) {
gfx::Point local_point = point;
ConvertPointToTarget(parent(), caption_button_container_, &local_point);
if (caption_button_container_->HitTestPoint(local_point)) {
const int hit_test_result =
caption_button_container_->NonClientHitTest(local_point);
if (hit_test_result != HTNOWHERE)
return hit_test_result;
}
}
// On Windows 8+, the caption buttons are almost butted up to the top right
// corner of the window. This code ensures the mouse isn't set to a size
// cursor while hovering over the caption buttons, thus giving the incorrect
// impression that the user can resize the window.
if (base::win::GetVersion() >= base::win::Version::WIN8) {
RECT button_bounds = {0};
if (SUCCEEDED(DwmGetWindowAttribute(
views::HWNDForWidget(frame()), DWMWA_CAPTION_BUTTON_BOUNDS,
&button_bounds, sizeof(button_bounds)))) {
gfx::RectF button_bounds_in_dips = gfx::ConvertRectToDips(
gfx::Rect(button_bounds), display::win::GetDPIScale());
// TODO(crbug.com/1131681): GetMirroredRect() requires an integer rect,
// but the size in DIPs may not be an integer with a fractional device
// scale factor. If we want to keep using integers, the choice to use
// ToFlooredRectDeprecated() seems to be doing the wrong thing given the
// comment below about insetting 1 DIP instead of 1 physical pixel. We
// should probably use ToEnclosedRect() and then we could have inset 1
// physical pixel here.
gfx::Rect buttons = GetMirroredRect(
gfx::ToFlooredRectDeprecated(button_bounds_in_dips));
// There is a small one-pixel strip right above the caption buttons in
// which the resize border "peeks" through.
constexpr int kCaptionButtonTopInset = 1;
// The sizing region at the window edge above the caption buttons is
// 1 px regardless of scale factor. If we inset by 1 before converting
// to DIPs, the precision loss might eliminate this region entirely. The
// best we can do is to inset after conversion. This guarantees we'll
// show the resize cursor when resizing is possible. The cost of which
// is also maybe showing it over the portion of the DIP that isn't the
// outermost pixel.
buttons.Inset(0, kCaptionButtonTopInset, 0, 0);
if (buttons.Contains(point))
return HTNOWHERE;
}
}
int top_border_thickness = FrameTopBorderThickness(false);
// At the window corners the resize area is not actually bigger, but the 16
// pixels at the end of the top and bottom edges trigger diagonal resizing.
constexpr int kResizeCornerWidth = 16;
int window_component = GetHTComponentForFrame(
point, gfx::Insets(top_border_thickness, 0, 0, 0), top_border_thickness,
kResizeCornerWidth - FrameBorderThickness(),
frame()->widget_delegate()->CanResize());
if (window_component != HTNOWHERE)
return window_component;
}
// Use the parent class's hittest last
return FramelessView::NonClientHitTest(point);
}
const char* WinFrameView::GetClassName() const {
return kViewClassName;
}
bool WinFrameView::IsMaximized() const {
return frame()->IsMaximized();
}
bool WinFrameView::ShouldCustomDrawSystemTitlebar() const {
return window()->IsWindowControlsOverlayEnabled();
}
void WinFrameView::Layout() {
LayoutCaptionButtons();
if (window()->IsWindowControlsOverlayEnabled()) {
LayoutWindowControlsOverlay();
}
NonClientFrameView::Layout();
}
int WinFrameView::FrameTopBorderThickness(bool restored) const {
// Mouse and touch locations are floored but GetSystemMetricsInDIP is rounded,
// so we need to floor instead or else the difference will cause the hittest
// to fail when it ought to succeed.
return std::floor(
FrameTopBorderThicknessPx(restored) /
display::win::ScreenWin::GetScaleFactorForHWND(HWNDForView(this)));
}
int WinFrameView::FrameTopBorderThicknessPx(bool restored) const {
// Distinct from FrameBorderThickness() because we can't inset the top
// border, otherwise Windows will give us a standard titlebar.
// For maximized windows this is not true, and the top border must be
// inset in order to avoid overlapping the monitor above.
// See comments in BrowserDesktopWindowTreeHostWin::GetClientAreaInsets().
const bool needs_no_border =
(ShouldCustomDrawSystemTitlebar() && frame()->IsMaximized()) ||
frame()->IsFullscreen();
if (needs_no_border && !restored)
return 0;
// Note that this method assumes an equal resize handle thickness on all
// sides of the window.
// TODO(dfried): Consider having it return a gfx::Insets object instead.
return ui::GetFrameThickness(
MonitorFromWindow(HWNDForView(this), MONITOR_DEFAULTTONEAREST));
}
int WinFrameView::TitlebarMaximizedVisualHeight() const {
int maximized_height =
display::win::ScreenWin::GetSystemMetricsInDIP(SM_CYCAPTION);
return maximized_height;
}
int WinFrameView::TitlebarHeight(bool restored) const {
if (frame()->IsFullscreen() && !restored)
return 0;
return TitlebarMaximizedVisualHeight() + FrameTopBorderThickness(false);
}
int WinFrameView::WindowTopY() const {
// The window top is SM_CYSIZEFRAME pixels when maximized (see the comment in
// FrameTopBorderThickness()) and floor(system dsf) pixels when restored.
// Unfortunately we can't represent either of those at hidpi without using
// non-integral dips, so we return the closest reasonable values instead.
if (IsMaximized())
return FrameTopBorderThickness(false);
return 1;
}
void WinFrameView::LayoutCaptionButtons() {
if (!caption_button_container_)
return;
// Non-custom system titlebar already contains caption buttons.
if (!ShouldCustomDrawSystemTitlebar()) {
caption_button_container_->SetVisible(false);
return;
}
caption_button_container_->SetVisible(true);
const gfx::Size preferred_size =
caption_button_container_->GetPreferredSize();
int height = preferred_size.height();
height = IsMaximized() ? TitlebarMaximizedVisualHeight()
: TitlebarHeight(false) - WindowTopY();
// TODO(mlaurencin): This -1 creates a 1 pixel gap between the right
// edge of the overlay and the edge of the window, allowing for this edge
// portion to return the correct hit test and be manually resized properly.
// Alternatives can be explored, but the differences in view structures
// between Electron and Chromium may result in this as the best option.
caption_button_container_->SetBounds(width() - preferred_size.width(),
WindowTopY(), preferred_size.width() - 1,
height);
}
void WinFrameView::LayoutWindowControlsOverlay() {
int overlay_height = caption_button_container_->size().height();
int overlay_width = caption_button_container_->size().width();
int bounding_rect_width = width() - overlay_width;
auto bounding_rect =
GetMirroredRect(gfx::Rect(0, 0, bounding_rect_width, overlay_height));
window()->SetWindowControlsOverlayRect(bounding_rect);
window()->NotifyLayoutWindowControlsOverlay();
}
} // namespace electron

View File

@@ -1,11 +1,18 @@
// Copyright (c) 2014 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
//
// Portions of this file are sourced from
// chrome/browser/ui/views/frame/glass_browser_frame_view.h,
// Copyright (c) 2012 The Chromium Authors,
// which is governed by a BSD-style license
#ifndef SHELL_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_
#define SHELL_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/win_caption_button.h"
namespace electron {
@@ -15,6 +22,14 @@ class WinFrameView : public FramelessView {
WinFrameView();
~WinFrameView() override;
void Init(NativeWindowViews* window, views::Widget* frame) override;
// Alpha to use for features in the titlebar (the window title and caption
// buttons) when the window is inactive. They are opaque when active.
static constexpr SkAlpha kInactiveTitlebarFeatureAlpha = 0x66;
SkColor GetReadableFeatureColor(SkColor background_color);
// views::NonClientFrameView:
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override;
@@ -23,7 +38,50 @@ class WinFrameView : public FramelessView {
// views::View:
const char* GetClassName() const override;
NativeWindowViews* window() const { return window_; }
views::Widget* frame() const { return frame_; }
bool IsMaximized() const;
bool ShouldCustomDrawSystemTitlebar() const;
// Visual height of the titlebar when the window is maximized (i.e. excluding
// the area above the top of the screen).
int TitlebarMaximizedVisualHeight() const;
protected:
// views::View:
void Layout() override;
private:
friend class WinCaptionButtonContainer;
int FrameBorderThickness() const;
// Returns the thickness of the window border for the top edge of the frame,
// which is sometimes different than FrameBorderThickness(). Does not include
// the titlebar/tabstrip area. If |restored| is true, this is calculated as if
// the window was restored, regardless of its current state.
int FrameTopBorderThickness(bool restored) const;
int FrameTopBorderThicknessPx(bool restored) const;
// Returns the height of the titlebar for popups or other browser types that
// don't have tabs.
int TitlebarHeight(bool restored) const;
// Returns the y coordinate for the top of the frame, which in maximized mode
// is the top of the screen and in restored mode is 1 pixel below the top of
// the window to leave room for the visual border that Windows draws.
int WindowTopY() const;
void LayoutCaptionButtons();
void LayoutWindowControlsOverlay();
// The container holding the caption buttons (minimize, maximize, close, etc.)
// May be null if the caption button container is destroyed before the frame
// view. Always check for validity before using!
WinCaptionButtonContainer* caption_button_container_;
DISALLOW_COPY_AND_ASSIGN(WinFrameView);
};

View File

@@ -12,6 +12,7 @@
#include <string>
#include <vector>
#include "base/win/scoped_com_initializer.h"
#include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_helper/promise.h"
#include "shell/common/skia_util.h"
@@ -26,6 +27,8 @@ v8::Local<v8::Promise> NativeImage::CreateThumbnailFromPath(
v8::Isolate* isolate,
const base::FilePath& path,
const gfx::Size& size) {
base::win::ScopedCOMInitializer scoped_com_initializer;
gin_helper::Promise<gfx::Image> promise(isolate);
v8::Local<v8::Promise> handle = promise.GetHandle();
HRESULT hr;

View File

@@ -31,6 +31,11 @@ const char kFullscreen[] = "fullscreen";
const char kTrafficLightPosition[] = "trafficLightPosition";
const char kRoundedCorners[] = "roundedCorners";
// The color to use as the theme and symbol colors respectively for Window
// Controls Overlay if enabled on Windows.
const char kOverlayButtonColor[] = "color";
const char kOverlaySymbolColor[] = "symbolColor";
// Whether the window should show in taskbar.
const char kSkipTaskbar[] = "skipTaskbar";

View File

@@ -58,6 +58,8 @@ extern const char kVisualEffectState[];
extern const char kTrafficLightPosition[];
extern const char kRoundedCorners[];
extern const char ktitleBarOverlay[];
extern const char kOverlayButtonColor[];
extern const char kOverlaySymbolColor[];
// WebPreferences.
extern const char kZoomFactor[];

View File

@@ -4,13 +4,19 @@
#include "shell/common/platform_util.h"
#include <fcntl.h>
#include <stdio.h>
#include <string>
#include <vector>
#include "base/cancelable_callback.h"
#include "base/containers/contains.h"
#include "base/environment.h"
#include "base/files/file_util.h"
#include "base/nix/xdg_util.h"
#include "base/no_destructor.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/kill.h"
#include "base/process/launch.h"
#include "base/threading/thread_restrictions.h"
@@ -20,6 +26,7 @@
#include "dbus/message.h"
#include "dbus/object_proxy.h"
#include "shell/common/platform_util_internal.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "ui/gtk/gtk_util.h"
#include "url/gurl.h"
@@ -31,11 +38,20 @@ void OpenFolder(const base::FilePath& full_path);
namespace {
const char kMethodListActivatableNames[] = "ListActivatableNames";
const char kMethodNameHasOwner[] = "NameHasOwner";
const char kFreedesktopFileManagerName[] = "org.freedesktop.FileManager1";
const char kFreedesktopFileManagerPath[] = "/org/freedesktop/FileManager1";
const char kMethodShowItems[] = "ShowItems";
const char kFreedesktopPortalName[] = "org.freedesktop.portal.Desktop";
const char kFreedesktopPortalPath[] = "/org/freedesktop/portal/desktop";
const char kFreedesktopPortalOpenURI[] = "org.freedesktop.portal.OpenURI";
const char kMethodOpenDirectory[] = "OpenDirectory";
class ShowItemHelper {
public:
static ShowItemHelper& GetInstance() {
@@ -58,8 +74,136 @@ class ShowItemHelper {
bus_ = base::MakeRefCounted<dbus::Bus>(bus_options);
}
if (!filemanager_proxy_) {
filemanager_proxy_ =
if (!dbus_proxy_) {
dbus_proxy_ = bus_->GetObjectProxy(DBUS_SERVICE_DBUS,
dbus::ObjectPath(DBUS_PATH_DBUS));
}
if (prefer_filemanager_interface_.has_value()) {
if (prefer_filemanager_interface_.value()) {
ShowItemUsingFileManager(full_path);
} else {
ShowItemUsingFreedesktopPortal(full_path);
}
} else {
CheckFileManagerRunning(full_path);
}
}
private:
void CheckFileManagerRunning(const base::FilePath& full_path) {
dbus::MethodCall method_call(DBUS_INTERFACE_DBUS, kMethodNameHasOwner);
dbus::MessageWriter writer(&method_call);
writer.AppendString(kFreedesktopFileManagerName);
dbus_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&ShowItemHelper::CheckFileManagerRunningResponse,
base::Unretained(this), full_path));
}
void CheckFileManagerRunningResponse(const base::FilePath& full_path,
dbus::Response* response) {
if (prefer_filemanager_interface_.has_value()) {
ShowItemInFolder(full_path);
return;
}
bool is_running = false;
if (!response) {
LOG(ERROR) << "Failed to call " << kMethodNameHasOwner;
} else {
dbus::MessageReader reader(response);
bool owned = false;
if (!reader.PopBool(&owned)) {
LOG(ERROR) << "Failed to read " << kMethodNameHasOwner << " resposne";
} else if (owned) {
is_running = true;
}
}
if (is_running) {
prefer_filemanager_interface_ = true;
ShowItemInFolder(full_path);
} else {
CheckFileManagerActivatable(full_path);
}
}
void CheckFileManagerActivatable(const base::FilePath& full_path) {
dbus::MethodCall method_call(DBUS_INTERFACE_DBUS,
kMethodListActivatableNames);
dbus_proxy_->CallMethod(
&method_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&ShowItemHelper::CheckFileManagerActivatableResponse,
base::Unretained(this), full_path));
}
void CheckFileManagerActivatableResponse(const base::FilePath& full_path,
dbus::Response* response) {
if (prefer_filemanager_interface_.has_value()) {
ShowItemInFolder(full_path);
return;
}
bool is_activatable = false;
if (!response) {
LOG(ERROR) << "Failed to call " << kMethodListActivatableNames;
} else {
dbus::MessageReader reader(response);
std::vector<std::string> names;
if (!reader.PopArrayOfStrings(&names)) {
LOG(ERROR) << "Failed to read " << kMethodListActivatableNames
<< " response";
} else if (base::Contains(names, kFreedesktopFileManagerName)) {
is_activatable = true;
}
}
prefer_filemanager_interface_ = is_activatable;
ShowItemInFolder(full_path);
}
void ShowItemUsingFreedesktopPortal(const base::FilePath& full_path) {
if (!object_proxy_) {
object_proxy_ = bus_->GetObjectProxy(
kFreedesktopPortalName, dbus::ObjectPath(kFreedesktopPortalPath));
}
base::ScopedFD fd(
HANDLE_EINTR(open(full_path.value().c_str(), O_RDONLY | O_CLOEXEC)));
if (!fd.is_valid()) {
LOG(ERROR) << "Failed to open " << full_path << " for URI portal";
// If the call fails, at least open the parent folder.
platform_util::OpenFolder(full_path.DirName());
return;
}
dbus::MethodCall open_directory_call(kFreedesktopPortalOpenURI,
kMethodOpenDirectory);
dbus::MessageWriter writer(&open_directory_call);
writer.AppendString("");
// Note that AppendFileDescriptor() duplicates the fd, so we shouldn't
// release ownership of it here.
writer.AppendFileDescriptor(fd.get());
dbus::MessageWriter options_writer(nullptr);
writer.OpenArray("{sv}", &options_writer);
writer.CloseContainer(&options_writer);
ShowItemUsingBusCall(&open_directory_call, full_path);
}
void ShowItemUsingFileManager(const base::FilePath& full_path) {
if (!object_proxy_) {
object_proxy_ =
bus_->GetObjectProxy(kFreedesktopFileManagerName,
dbus::ObjectPath(kFreedesktopFileManagerPath));
}
@@ -72,25 +216,33 @@ class ShowItemHelper {
{"file://" + full_path.value()}); // List of file(s) to highlight.
writer.AppendString({}); // startup-id
filemanager_proxy_->CallMethod(
&show_items_call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&ShowItemHelper::ShowItemInFolderResponse,
base::Unretained(this), full_path));
ShowItemUsingBusCall(&show_items_call, full_path);
}
void ShowItemUsingBusCall(dbus::MethodCall* call,
const base::FilePath& full_path) {
object_proxy_->CallMethod(
call, dbus::ObjectProxy::TIMEOUT_USE_DEFAULT,
base::BindOnce(&ShowItemHelper::ShowItemInFolderResponse,
base::Unretained(this), full_path, call->GetMember()));
}
private:
void ShowItemInFolderResponse(const base::FilePath& full_path,
const std::string& method,
dbus::Response* response) {
if (response)
return;
LOG(ERROR) << "Error calling " << kMethodShowItems;
// If the FileManager1 call fails, at least open the parent folder.
LOG(ERROR) << "Error calling " << method;
// If the bus call fails, at least open the parent folder.
platform_util::OpenFolder(full_path.DirName());
}
scoped_refptr<dbus::Bus> bus_;
dbus::ObjectProxy* filemanager_proxy_ = nullptr;
dbus::ObjectProxy* dbus_proxy_ = nullptr;
dbus::ObjectProxy* object_proxy_ = nullptr;
absl::optional<bool> prefer_filemanager_interface_;
};
// Descriptions pulled from https://linux.die.net/man/1/xdg-open

View File

@@ -5,6 +5,7 @@ import * as fs from 'fs';
import * as os from 'os';
import * as qs from 'querystring';
import * as http from 'http';
import * as semver from 'semver';
import { AddressInfo } from 'net';
import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents, BrowserWindowConstructorOptions } from 'electron/main';
@@ -1877,7 +1878,7 @@ describe('BrowserWindow module', () => {
});
});
ifdescribe(process.platform === 'darwin' && parseInt(os.release().split('.')[0]) >= 14)('"titleBarStyle" option', () => {
ifdescribe(process.platform === 'win32' || (process.platform === 'darwin' && semver.gte(os.release(), '14.0.0')))('"titleBarStyle" option', () => {
const testWindowsOverlay = async (style: any) => {
const w = new BrowserWindow({
show: false,
@@ -1891,12 +1892,22 @@ describe('BrowserWindow module', () => {
titleBarOverlay: true
});
const overlayHTML = path.join(__dirname, 'fixtures', 'pages', 'overlay.html');
await w.loadFile(overlayHTML);
if (process.platform === 'darwin') {
await w.loadFile(overlayHTML);
} else {
const overlayReady = emittedOnce(ipcMain, 'geometrychange');
await w.loadFile(overlayHTML);
await overlayReady;
}
const overlayEnabled = await w.webContents.executeJavaScript('navigator.windowControlsOverlay.visible');
expect(overlayEnabled).to.be.true('overlayEnabled');
const overlayRect = await w.webContents.executeJavaScript('getJSOverlayProperties()');
expect(overlayRect.y).to.equal(0);
expect(overlayRect.x).to.be.greaterThan(0);
if (process.platform === 'darwin') {
expect(overlayRect.x).to.be.greaterThan(0);
} else {
expect(overlayRect.x).to.equal(0);
}
expect(overlayRect.width).to.be.greaterThan(0);
expect(overlayRect.height).to.be.greaterThan(0);
const cssOverlayRect = await w.webContents.executeJavaScript('getCssOverlayProperties();');
@@ -1918,7 +1929,7 @@ describe('BrowserWindow module', () => {
const contentSize = w.getContentSize();
expect(contentSize).to.deep.equal([400, 400]);
});
it('creates browser window with hidden inset title bar', () => {
ifit(process.platform === 'darwin')('creates browser window with hidden inset title bar', () => {
const w = new BrowserWindow({
show: false,
width: 400,
@@ -1931,7 +1942,7 @@ describe('BrowserWindow module', () => {
it('sets Window Control Overlay with hidden title bar', async () => {
await testWindowsOverlay('hidden');
});
it('sets Window Control Overlay with hidden inset title bar', async () => {
ifit(process.platform === 'darwin')('sets Window Control Overlay with hidden inset title bar', async () => {
await testWindowsOverlay('hiddenInset');
});
});
@@ -4648,4 +4659,32 @@ describe('BrowserWindow module', () => {
});
});
});
describe('"transparent" option', () => {
afterEach(closeAllWindows);
// Only applicable on Windows where transparent windows can't be maximized.
ifit(process.platform === 'win32')('can show maximized frameless window', async () => {
const display = screen.getPrimaryDisplay();
const w = new BrowserWindow({
...display.bounds,
frame: false,
transparent: true,
show: true
});
w.loadURL('about:blank');
await emittedOnce(w, 'ready-to-show');
expect(w.isMaximized()).to.be.true();
// Fails when the transparent HWND is in an invalid maximized state.
expect(w.getBounds()).to.deep.equal(display.workArea);
const newBounds = { width: 256, height: 256, x: 0, y: 0 };
w.setBounds(newBounds);
expect(w.getBounds()).to.deep.equal(newBounds);
});
});
});

View File

@@ -47,6 +47,24 @@ describe('webContents module', () => {
});
});
describe('fromDevToolsTargetId()', () => {
it('returns WebContents for attached DevTools target', async () => {
const w = new BrowserWindow({ show: false });
await w.loadURL('about:blank');
try {
await w.webContents.debugger.attach('1.3');
const { targetInfo } = await w.webContents.debugger.sendCommand('Target.getTargetInfo');
expect(webContents.fromDevToolsTargetId(targetInfo.targetId)).to.equal(w.webContents);
} finally {
await w.webContents.debugger.detach();
}
});
it('returns undefined for an unknown id', () => {
expect(webContents.fromDevToolsTargetId('nope')).to.be.undefined();
});
});
describe('will-prevent-unload event', function () {
afterEach(closeAllWindows);
it('does not emit if beforeunload returns undefined in a BrowserWindow', async () => {