mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
22 Commits
v14.0.0-be
...
v14.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
735a01edce | ||
|
|
5e9dca52a5 | ||
|
|
6f67e50242 | ||
|
|
5451744c64 | ||
|
|
8c4f467c8c | ||
|
|
81ee511031 | ||
|
|
1bdd60e91b | ||
|
|
cffc1a2381 | ||
|
|
9799589648 | ||
|
|
c0da647170 | ||
|
|
4553ce050f | ||
|
|
bdf156894a | ||
|
|
d2da6de114 | ||
|
|
42d8be64fc | ||
|
|
663a260976 | ||
|
|
9aa2608ca7 | ||
|
|
8c6240ad4a | ||
|
|
1d84b38c71 | ||
|
|
3cdb74e9bd | ||
|
|
9cc2d3f60e | ||
|
|
d73539bbc4 | ||
|
|
b4d7fbcb04 |
2
DEPS
2
DEPS
@@ -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':
|
||||
|
||||
@@ -1 +1 @@
|
||||
14.0.0-beta.24
|
||||
14.0.1
|
||||
@@ -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)
|
||||
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
4
docs/api/structures/overlay-options.md
Normal file
4
docs/api/structures/overlay-options.md
Normal 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.
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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].
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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'
|
||||
}
|
||||
})
|
||||
//...
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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()) {
|
||||
|
||||
@@ -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));
|
||||
});
|
||||
|
||||
@@ -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',
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
@@ -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_) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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_;
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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_);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
220
shell/browser/ui/views/win_caption_button.cc
Normal file
220
shell/browser/ui/views/win_caption_button.cc
Normal 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
|
||||
54
shell/browser/ui/views/win_caption_button.h
Normal file
54
shell/browser/ui/views/win_caption_button.h
Normal 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_
|
||||
143
shell/browser/ui/views/win_caption_button_container.cc
Normal file
143
shell/browser/ui/views/win_caption_button_container.cc
Normal 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
|
||||
70
shell/browser/ui/views/win_caption_button_container.h
Normal file
70
shell/browser/ui/views/win_caption_button_container.h
Normal 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_
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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[];
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
Reference in New Issue
Block a user