mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
44 Commits
v27.0.0-be
...
v27.0.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4ceb644723 | ||
|
|
ae212f41ab | ||
|
|
1f64b7869c | ||
|
|
f282bdac65 | ||
|
|
c0ad9e154f | ||
|
|
6304ea53ef | ||
|
|
4c1e53dfa6 | ||
|
|
96f9cc5157 | ||
|
|
8890ab5119 | ||
|
|
4bb80df378 | ||
|
|
3d10512399 | ||
|
|
30305e4f2b | ||
|
|
40c21e02da | ||
|
|
6de96fb83b | ||
|
|
cf0e974ce1 | ||
|
|
7e2073226a | ||
|
|
8f370cd2e3 | ||
|
|
82d4e9427f | ||
|
|
f0b742f2d6 | ||
|
|
2bc07b098b | ||
|
|
d95f28c194 | ||
|
|
22ea11175b | ||
|
|
5b44e82eb4 | ||
|
|
4c4a3e7594 | ||
|
|
bf7474d3f4 | ||
|
|
3f142ad039 | ||
|
|
10db23828e | ||
|
|
43ba54e61f | ||
|
|
3fdfcbb5dd | ||
|
|
9e8cba981a | ||
|
|
a85c6cf0e5 | ||
|
|
7e536dfd8e | ||
|
|
7d21d113d2 | ||
|
|
7ed33db12e | ||
|
|
66c4f9cb6f | ||
|
|
a00155dcfc | ||
|
|
3e4adb91c5 | ||
|
|
f63f02fbb2 | ||
|
|
a3a6e8ff26 | ||
|
|
e70a25d5a3 | ||
|
|
8e2cf54f29 | ||
|
|
a3614983de | ||
|
|
686adf9d15 | ||
|
|
25dccaa1b1 |
@@ -754,8 +754,8 @@ step-show-goma-stats: &step-show-goma-stats
|
||||
command: |
|
||||
set +e
|
||||
set +o pipefail
|
||||
$GOMA_DIR/goma_ctl.py stat
|
||||
$GOMA_DIR/diagnose_goma_log.py
|
||||
python3 $GOMA_DIR/goma_ctl.py stat
|
||||
python3 $GOMA_DIR/diagnose_goma_log.py
|
||||
true
|
||||
when: always
|
||||
background: true
|
||||
|
||||
2
DEPS
2
DEPS
@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'118.0.5993.18',
|
||||
'118.0.5993.89',
|
||||
'node_version':
|
||||
'v18.17.1',
|
||||
'nan_version':
|
||||
|
||||
@@ -202,6 +202,10 @@ static_library("chrome") {
|
||||
"//chrome/browser/ui/views/status_icons/status_icon_linux_dbus.cc",
|
||||
"//chrome/browser/ui/views/status_icons/status_icon_linux_dbus.h",
|
||||
]
|
||||
sources += [
|
||||
"//chrome/browser/ui/views/dark_mode_manager_linux.cc",
|
||||
"//chrome/browser/ui/views/dark_mode_manager_linux.h",
|
||||
]
|
||||
public_deps += [
|
||||
"//components/dbus/menu",
|
||||
"//components/dbus/thread_linux",
|
||||
@@ -319,6 +323,34 @@ static_library("chrome") {
|
||||
"//components/pdf/renderer",
|
||||
]
|
||||
}
|
||||
} else {
|
||||
# These are required by the webRequest module.
|
||||
sources += [
|
||||
"//extensions/browser/api/declarative_net_request/request_action.cc",
|
||||
"//extensions/browser/api/declarative_net_request/request_action.h",
|
||||
"//extensions/browser/api/web_request/form_data_parser.cc",
|
||||
"//extensions/browser/api/web_request/form_data_parser.h",
|
||||
"//extensions/browser/api/web_request/upload_data_presenter.cc",
|
||||
"//extensions/browser/api/web_request/upload_data_presenter.h",
|
||||
"//extensions/browser/api/web_request/web_request_api_constants.cc",
|
||||
"//extensions/browser/api/web_request/web_request_api_constants.h",
|
||||
"//extensions/browser/api/web_request/web_request_info.cc",
|
||||
"//extensions/browser/api/web_request/web_request_info.h",
|
||||
"//extensions/browser/api/web_request/web_request_resource_type.cc",
|
||||
"//extensions/browser/api/web_request/web_request_resource_type.h",
|
||||
"//extensions/browser/extension_api_frame_id_map.cc",
|
||||
"//extensions/browser/extension_api_frame_id_map.h",
|
||||
"//extensions/browser/extension_navigation_ui_data.cc",
|
||||
"//extensions/browser/extension_navigation_ui_data.h",
|
||||
"//extensions/browser/extensions_browser_client.cc",
|
||||
"//extensions/browser/extensions_browser_client.h",
|
||||
"//extensions/browser/guest_view/web_view/web_view_renderer_state.cc",
|
||||
"//extensions/browser/guest_view/web_view/web_view_renderer_state.h",
|
||||
]
|
||||
|
||||
public_deps += [
|
||||
"//extensions/browser/api/declarative_net_request/flat:extension_ruleset",
|
||||
]
|
||||
}
|
||||
|
||||
if (!is_mas_build) {
|
||||
|
||||
@@ -1134,11 +1134,11 @@ indicates success while any other value indicates failure according to Chromium
|
||||
resolver will attempt to use the system's DNS settings to do DNS lookups
|
||||
itself. Enabled by default on macOS, disabled by default on Windows and
|
||||
Linux.
|
||||
* `secureDnsMode` string (optional) - Can be "off", "automatic" or "secure".
|
||||
Configures the DNS-over-HTTP mode. When "off", no DoH lookups will be
|
||||
performed. When "automatic", DoH lookups will be performed first if DoH is
|
||||
* `secureDnsMode` string (optional) - Can be 'off', 'automatic' or 'secure'.
|
||||
Configures the DNS-over-HTTP mode. When 'off', no DoH lookups will be
|
||||
performed. When 'automatic', DoH lookups will be performed first if DoH is
|
||||
available, and insecure DNS lookups will be performed as a fallback. When
|
||||
"secure", only DoH lookups will be performed. Defaults to "automatic".
|
||||
'secure', only DoH lookups will be performed. Defaults to 'automatic'.
|
||||
* `secureDnsServers` string[] (optional) - A list of DNS-over-HTTP
|
||||
server templates. See [RFC8484 § 3][] for details on the template format.
|
||||
Most servers support the POST method; the template for such servers is
|
||||
|
||||
@@ -505,6 +505,10 @@ events.
|
||||
|
||||
A `Integer` property representing the unique ID of the window. Each ID is unique among all `BrowserWindow` instances of the entire Electron application.
|
||||
|
||||
#### `win.tabbingIdentifier` _macOS_ _Readonly_
|
||||
|
||||
A `string` (optional) property that is equal to the `tabbingIdentifier` passed to the `BrowserWindow` constructor or `undefined` if none was set.
|
||||
|
||||
#### `win.autoHideMenuBar`
|
||||
|
||||
A `boolean` property that determines whether the window menu bar should hide itself automatically. Once set, the menu bar will only show when users press the single `Alt` key.
|
||||
|
||||
@@ -85,6 +85,8 @@ Emitted when the notification is closed by manual intervention from the user.
|
||||
This event is not guaranteed to be emitted in all cases where the notification
|
||||
is closed.
|
||||
|
||||
On Windows, the `close` event can be emitted in one of three ways: programmatic dismissal with `notification.close()`, by the user closing the notification, or via system timeout. If a notification is in the Action Center after the initial `close` event is emitted, a call to `notification.close()` will remove the notification from the action center but the `close` event will not be emitted again.
|
||||
|
||||
#### Event: 'reply' _macOS_
|
||||
|
||||
Returns:
|
||||
@@ -127,6 +129,8 @@ shown notification and create a new one with identical properties.
|
||||
|
||||
Dismisses the notification.
|
||||
|
||||
On Windows, calling `notification.close()` while the notification is visible on screen will dismiss the notification and remove it from the Action Center. If `notification.close()` is called after the notification is no longer visible on screen, calling `notification.close()` will try remove it from the Action Center.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
#### `notification.title`
|
||||
|
||||
@@ -273,7 +273,6 @@ This API is only available on macOS 10.14 Mojave or newer.
|
||||
* `window-frame` - Window frame.
|
||||
* `window-text` - Text in windows.
|
||||
* On **macOS**
|
||||
* `alternate-selected-control-text` - The text on a selected surface in a list or table. _Deprecated_
|
||||
* `control-background` - The background of a large interface element, such as a browser or table.
|
||||
* `control` - The surface of a control.
|
||||
* `control-text` -The text of a control that isn’t disabled.
|
||||
@@ -339,21 +338,6 @@ Returns `string` - Can be `dark`, `light` or `unknown`.
|
||||
Gets the macOS appearance setting that is currently applied to your application,
|
||||
maps to [NSApplication.effectiveAppearance](https://developer.apple.com/documentation/appkit/nsapplication/2967171-effectiveappearance?language=objc)
|
||||
|
||||
### `systemPreferences.getAppLevelAppearance()` _macOS_ _Deprecated_
|
||||
|
||||
Returns `string` | `null` - Can be `dark`, `light` or `unknown`.
|
||||
|
||||
Gets the macOS appearance setting that you have declared you want for
|
||||
your application, maps to [NSApplication.appearance](https://developer.apple.com/documentation/appkit/nsapplication/2967170-appearance?language=objc).
|
||||
You can use the `setAppLevelAppearance` API to set this value.
|
||||
|
||||
### `systemPreferences.setAppLevelAppearance(appearance)` _macOS_ _Deprecated_
|
||||
|
||||
* `appearance` string | null - Can be `dark` or `light`
|
||||
|
||||
Sets the appearance setting for your application, this should override the
|
||||
system default and override the value of `getEffectiveAppearance`.
|
||||
|
||||
### `systemPreferences.canPromptTouchID()` _macOS_
|
||||
|
||||
Returns `boolean` - whether or not this device has the ability to use Touch ID.
|
||||
@@ -417,15 +401,9 @@ Returns an object with system animation settings.
|
||||
|
||||
## Properties
|
||||
|
||||
### `systemPreferences.appLevelAppearance` _macOS_ _Deprecated_
|
||||
### `systemPreferences.accessibilityDisplayShouldReduceTransparency()` _macOS_
|
||||
|
||||
A `string` property that can be `dark`, `light` or `unknown`. It determines the macOS appearance setting for
|
||||
your application. This maps to values in: [NSApplication.appearance](https://developer.apple.com/documentation/appkit/nsapplication/2967170-appearance?language=objc). Setting this will override the
|
||||
system default as well as the value of `getEffectiveAppearance`.
|
||||
|
||||
Possible values that can be set are `dark` and `light`, and possible return values are `dark`, `light`, and `unknown`.
|
||||
|
||||
This property is only available on macOS 10.14 Mojave or newer.
|
||||
A `boolean` property which determines whether the app avoids using semitransparent backgrounds. This maps to [NSWorkspace.accessibilityDisplayShouldReduceTransparency](https://developer.apple.com/documentation/appkit/nsworkspace/1533006-accessibilitydisplayshouldreduce)
|
||||
|
||||
### `systemPreferences.effectiveAppearance` _macOS_ _Readonly_
|
||||
|
||||
|
||||
@@ -1211,7 +1211,7 @@ Returns `string` - The user agent for this web page.
|
||||
|
||||
* `css` string
|
||||
* `options` Object (optional)
|
||||
* `cssOrigin` string (optional) - Can be either 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
* `cssOrigin` string (optional) - Can be 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
|
||||
Returns `Promise<string>` - A promise that resolves with a key for the inserted CSS that can later be used to remove the CSS via `contents.removeInsertedCSS(key)`.
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ webFrame.setSpellCheckProvider('en-US', {
|
||||
|
||||
* `css` string
|
||||
* `options` Object (optional)
|
||||
* `cssOrigin` string (optional) - Can be either 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
* `cssOrigin` string (optional) - Can be 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
|
||||
Returns `string` - A key for the inserted CSS that can later be used to remove
|
||||
the CSS via `webFrame.removeInsertedCSS(key)`.
|
||||
|
||||
@@ -61,6 +61,41 @@ w.webContents.getPrintersAsync().then((printers) => {
|
||||
})
|
||||
```
|
||||
|
||||
### Removed: `systemPreferences.{get,set}AppLevelAppearance` and `systemPreferences.appLevelAppearance`
|
||||
|
||||
The `systemPreferences.getAppLevelAppearance` and `systemPreferences.setAppLevelAppearance`
|
||||
methods have been removed, as well as the `systemPreferences.appLevelAppearance` property.
|
||||
Use the `nativeTheme` module instead.
|
||||
|
||||
```js
|
||||
// Removed
|
||||
systemPreferences.getAppLevelAppearance()
|
||||
// Replace with
|
||||
nativeTheme.shouldUseDarkColors
|
||||
|
||||
// Removed
|
||||
systemPreferences.appLevelAppearance
|
||||
// Replace with
|
||||
nativeTheme.shouldUseDarkColors
|
||||
|
||||
// Removed
|
||||
systemPreferences.setAppLevelAppearance('dark')
|
||||
// Replace with
|
||||
nativeTheme.themeSource = 'dark'
|
||||
```
|
||||
|
||||
### Removed: `alternate-selected-control-text` value for `systemPreferences.getColor`
|
||||
|
||||
The `alternate-selected-control-text` value for `systemPreferences.getColor`
|
||||
has been removed. Use `selected-content-background` instead.
|
||||
|
||||
```js
|
||||
// Removed
|
||||
systemPreferences.getColor('alternate-selected-control-text')
|
||||
// Replace with
|
||||
systemPreferences.getColor('selected-content-background')
|
||||
```
|
||||
|
||||
## Planned Breaking API Changes (26.0)
|
||||
|
||||
### Deprecated: `webContents.getPrinters`
|
||||
@@ -635,6 +670,18 @@ to open synchronously scriptable child windows, among other incompatibilities.
|
||||
See the documentation for [window.open in Electron](api/window-open.md)
|
||||
for more details.
|
||||
|
||||
### Deprecated: `app.runningUnderRosettaTranslation`
|
||||
|
||||
The `app.runningUnderRosettaTranslation` property has been deprecated.
|
||||
Use `app.runningUnderARM64Translation` instead.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
console.log(app.runningUnderRosettaTranslation)
|
||||
// Replace with
|
||||
console.log(app.runningUnderARM64Translation)
|
||||
```
|
||||
|
||||
## Planned Breaking API Changes (14.0)
|
||||
|
||||
### Removed: `remote` module
|
||||
|
||||
@@ -22,34 +22,101 @@ There are a few ways that you can set up testing using WebDriver.
|
||||
Node.js package for testing with WebDriver. Its ecosystem also includes various plugins
|
||||
(e.g. reporter and services) that can help you put together your test setup.
|
||||
|
||||
If you already have an existing WebdriverIO setup, it is recommended to update your dependencies and validate your existing configuration with how it is [outlined in the docs](https://webdriver.io/docs/desktop-testing/electron#configuration).
|
||||
|
||||
#### Install the test runner
|
||||
|
||||
First you need to run the WebdriverIO starter toolkit in your project root directory:
|
||||
If you don't use WebdriverIO in your project yet, you can add it by running the starter toolkit in your project root directory:
|
||||
|
||||
```sh npm2yarn
|
||||
npx wdio . --yes
|
||||
npm init wdio@latest ./
|
||||
```
|
||||
|
||||
This installs all necessary packages for you and generates a `wdio.conf.js` configuration file.
|
||||
This starts a configuration wizard that helps you put together the right setup, installs all necessary packages, and generates a `wdio.conf.js` configuration file. Make sure to select _"Desktop Testing - of Electron Applications"_ on one of the first questions asking _"What type of testing would you like to do?"_.
|
||||
|
||||
#### Connect WDIO to your Electron app
|
||||
|
||||
Update the capabilities in your configuration file to point to your Electron app binary:
|
||||
After running the configuration wizard, your `wdio.conf.js` should include roughly the following content:
|
||||
|
||||
```javascript title='wdio.conf.js'
|
||||
exports.config = {
|
||||
```js title='wdio.conf.js' @ts-nocheck
|
||||
export const config = {
|
||||
// ...
|
||||
services: ['electron'],
|
||||
capabilities: [{
|
||||
browserName: 'chrome',
|
||||
'goog:chromeOptions': {
|
||||
binary: '/path/to/your/electron/binary', // Path to your Electron binary.
|
||||
args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/
|
||||
browserName: 'electron',
|
||||
'wdio:electronServiceOptions': {
|
||||
// WebdriverIO can automatically find your bundled application
|
||||
// if you use Electron Forge or electron-builder, otherwise you
|
||||
// can define it here, e.g.:
|
||||
// appBinaryPath: './path/to/bundled/application.exe',
|
||||
appArgs: ['foo', 'bar=baz']
|
||||
}
|
||||
}]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Write your tests
|
||||
|
||||
Use the [WebdriverIO API](https://webdriver.io/docs/api) to interact with elements on the screen. The framework provides custom "matchers" that make asserting the state of your application easy, e.g.:
|
||||
|
||||
```js @ts-nocheck
|
||||
import { browser, $, expect } from '@wdio/globals'
|
||||
|
||||
describe('keyboard input', () => {
|
||||
it('should detect keyboard input', async () => {
|
||||
await browser.keys(['y', 'o'])
|
||||
await expect($('keypress-count')).toHaveText('YO')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
Furthermore, WebdriverIO allows you to access Electron APIs to get static information about your application:
|
||||
|
||||
```js @ts-nocheck
|
||||
import { browser, $, expect } from '@wdio/globals'
|
||||
|
||||
describe('when the make smaller button is clicked', () => {
|
||||
it('should decrease the window height and width by 10 pixels', async () => {
|
||||
const boundsBefore = await browser.electron.browserWindow('getBounds')
|
||||
expect(boundsBefore.width).toEqual(210)
|
||||
expect(boundsBefore.height).toEqual(310)
|
||||
|
||||
await $('.make-smaller').click()
|
||||
const boundsAfter = await browser.electron.browserWindow('getBounds')
|
||||
expect(boundsAfter.width).toEqual(200)
|
||||
expect(boundsAfter.height).toEqual(300)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
or to retrieve other Electron process information:
|
||||
|
||||
```js @ts-nocheck
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { browser, expect } from '@wdio/globals'
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), { encoding: 'utf-8' }))
|
||||
const { name, version } = packageJson
|
||||
|
||||
describe('electron APIs', () => {
|
||||
it('should retrieve app metadata through the electron API', async () => {
|
||||
const appName = await browser.electron.app('getName')
|
||||
expect(appName).toEqual(name)
|
||||
const appVersion = await browser.electron.app('getVersion')
|
||||
expect(appVersion).toEqual(version)
|
||||
})
|
||||
|
||||
it('should pass args through to the launched application', async () => {
|
||||
// custom args are set in the wdio.conf.js file as they need to be set before WDIO starts
|
||||
const argv = await browser.electron.mainProcess('argv')
|
||||
expect(argv).toContain('--foo')
|
||||
expect(argv).toContain('--bar=baz')
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Run your tests
|
||||
|
||||
To run your tests:
|
||||
@@ -58,6 +125,12 @@ To run your tests:
|
||||
$ npx wdio run wdio.conf.js
|
||||
```
|
||||
|
||||
WebdriverIO helps launch and shut down the application for you.
|
||||
|
||||
#### More documentation
|
||||
|
||||
Find more documentation on Mocking Electron APIs and other useful resources in the [official WebdriverIO documentation](https://webdriver.io/docs/desktop-testing/electron).
|
||||
|
||||
### With Selenium
|
||||
|
||||
[Selenium](https://www.selenium.dev/) is a web automation framework that
|
||||
|
||||
@@ -9,12 +9,13 @@ check out our [Electron Versioning](./electron-versioning.md) doc.
|
||||
|
||||
| Electron | Alpha | Beta | Stable | EOL | Chrome | Node | Supported |
|
||||
| ------- | ----- | ------- | ------ | ------ | ---- | ---- | ---- |
|
||||
| 27.0.0 | 2023-Aug-17 | 2023-Sep-13 | 2023-Oct-10 | TBD | M118 | TBD | ✅ |
|
||||
| 28.0.0 | 2023-Oct-11 | 2023-Nov-6 | 2023-Dec-5 | TBD | M120 | TBD | ✅ |
|
||||
| 27.0.0 | 2023-Aug-17 | 2023-Sep-13 | 2023-Oct-10 | 2024-Apr-16 | M118 | v18.17 | ✅ |
|
||||
| 26.0.0 | 2023-Jun-01 | 2023-Jun-27 | 2023-Aug-15 | 2024-Feb-27 | M116 | v18.16 | ✅ |
|
||||
| 25.0.0 | 2023-Apr-10 | 2023-May-02 | 2023-May-30 | 2024-Jan-02 | M114 | v18.15 | ✅ |
|
||||
| 24.0.0 | 2023-Feb-09 | 2023-Mar-07 | 2023-Apr-04 | 2023-Oct-10 | M112 | v18.14 | ✅ |
|
||||
| 24.0.0 | 2023-Feb-09 | 2023-Mar-07 | 2023-Apr-04 | 2023-Oct-10 | M112 | v18.14 | 🚫 |
|
||||
| 23.0.0 | 2022-Dec-01 | 2023-Jan-10 | 2023-Feb-07 | 2023-Aug-15 | M110 | v18.12 | 🚫 |
|
||||
| 22.0.0 | 2022-Sep-29 | 2022-Oct-25 | 2022-Nov-29 | 2023-Oct-10 | M108 | v16.17 | ✅ |
|
||||
| 22.0.0 | 2022-Sep-29 | 2022-Oct-25 | 2022-Nov-29 | 2023-Oct-10 | M108 | v16.17 | 🚫 |
|
||||
| 21.0.0 | 2022-Aug-04 | 2022-Aug-30 | 2022-Sep-27 | 2023-Apr-04 | M106 | v16.16 | 🚫 |
|
||||
| 20.0.0 | 2022-May-26 | 2022-Jun-21 | 2022-Aug-02 | 2023-Feb-07 | M104 | v16.15 | 🚫 |
|
||||
| 19.0.0 | 2022-Mar-31 | 2022-Apr-26 | 2022-May-24 | 2022-Nov-29 | M102 | v16.14 | 🚫 |
|
||||
|
||||
@@ -228,6 +228,23 @@ channel with a renderer process using [`MessagePort`][]s. An Electron app can
|
||||
always prefer the [UtilityProcess][] API over Node.js [`child_process.fork`][] API when
|
||||
there is need to fork a child process from the main process.
|
||||
|
||||
## Process-specific module aliases (TypeScript)
|
||||
|
||||
Electron's npm package also exports subpaths that contain a subset of
|
||||
Electron's TypeScript type definitions.
|
||||
|
||||
- `electron/main` includes types for all main process modules.
|
||||
- `electron/renderer` includes types for all renderer process modules.
|
||||
- `electron/common` includes types for modules that can run in main and renderer processes.
|
||||
|
||||
These aliases have no impact on runtime, but can be used for typechecking
|
||||
and autocomplete.
|
||||
|
||||
```js title="Usage example"
|
||||
const { app } = require('electron/main')
|
||||
const { shell } = require('electron/common')
|
||||
```
|
||||
|
||||
[window-mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Window
|
||||
[`MessagePort`]: https://developer.mozilla.org/en-US/docs/Web/API/MessagePort
|
||||
[`child_process.fork`]: https://nodejs.org/dist/latest-v16.x/docs/api/child_process.html#child_processforkmodulepath-args-options
|
||||
|
||||
@@ -91,14 +91,14 @@ version: '0.1'
|
||||
summary: Hello World Electron app
|
||||
description: |
|
||||
Simple Hello World Electron app as an example
|
||||
base: core18
|
||||
base: core22
|
||||
confinement: strict
|
||||
grade: stable
|
||||
|
||||
apps:
|
||||
electron-packager-hello-world:
|
||||
command: electron-quick-start/electron-quick-start --no-sandbox
|
||||
extensions: [gnome-3-34]
|
||||
extensions: [gnome]
|
||||
plugs:
|
||||
- browser-support
|
||||
- network
|
||||
@@ -237,6 +237,34 @@ apps:
|
||||
desktop: usr/share/applications/desktop.desktop
|
||||
```
|
||||
|
||||
## Optional: Enabling desktop capture
|
||||
|
||||
Capturing the desktop requires PipeWire library in some Linux configurations that use
|
||||
the Wayland protocol. To bundle PipeWire with your application, ensure that the base
|
||||
snap is set to `core22` or newer. Next, create a part called `pipewire` and add it to
|
||||
the `after` section of your application:
|
||||
|
||||
```yaml
|
||||
pipewire:
|
||||
plugin: nil
|
||||
build-packages: [libpipewire-0.3-dev]
|
||||
stage-packages: [pipewire]
|
||||
prime:
|
||||
- usr/lib/*/pipewire-*
|
||||
- usr/lib/*/spa-*
|
||||
- usr/lib/*/libpipewire*.so*
|
||||
- usr/share/pipewire
|
||||
```
|
||||
|
||||
Finally, configure your application's environment for PipeWire:
|
||||
|
||||
```yaml
|
||||
environment:
|
||||
SPA_PLUGIN_DIR: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/spa-0.2
|
||||
PIPEWIRE_CONFIG_NAME: $SNAP/usr/share/pipewire/pipewire.conf
|
||||
PIPEWIRE_MODULE_DIR: $SNAP/usr/lib/$CRAFT_ARCH_TRIPLET/pipewire-0.3
|
||||
```
|
||||
|
||||
[snapcraft-syntax]: https://docs.snapcraft.io/build-snaps/syntax
|
||||
[electron-packager]: https://github.com/electron/electron-packager
|
||||
[electron-forge]: https://github.com/electron/forge
|
||||
|
||||
@@ -222,14 +222,26 @@ with CommonJS module syntax:
|
||||
- [app][app], which controls your application's event lifecycle.
|
||||
- [BrowserWindow][browser-window], which creates and manages app windows.
|
||||
|
||||
:::info Capitalization conventions
|
||||
<details><summary>Module capitalization conventions</summary>
|
||||
|
||||
You might have noticed the capitalization difference between the **a**pp
|
||||
and **B**rowser**W**indow modules. Electron follows typical JavaScript conventions here,
|
||||
where PascalCase modules are instantiable class constructors (e.g. BrowserWindow, Tray,
|
||||
Notification) whereas camelCase modules are not instantiable (e.g. app, ipcRenderer, webContents).
|
||||
|
||||
:::
|
||||
</details>
|
||||
|
||||
<details><summary>Typed import aliases</summary>
|
||||
|
||||
For better type checking when writing TypeScript code, you can choose to import
|
||||
main process modules from <code>electron/main</code>.
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron/main')
|
||||
```
|
||||
|
||||
For more information, see the [Process Model docs](../tutorial/process-model.md#process-specific-module-aliases-typescript).
|
||||
</details>
|
||||
|
||||
:::warning ES Modules in Electron
|
||||
|
||||
|
||||
@@ -114,5 +114,11 @@ for (const name of events) {
|
||||
}
|
||||
|
||||
// Deprecation.
|
||||
deprecate.event(app, 'gpu-process-crashed', 'child-process-gone');
|
||||
deprecate.event(app, 'renderer-process-crashed', 'render-process-gone');
|
||||
deprecate.event(app, 'gpu-process-crashed', 'child-process-gone', () => {
|
||||
// the old event is still emitted by App::OnGpuProcessCrashed()
|
||||
return undefined;
|
||||
});
|
||||
|
||||
deprecate.event(app, 'renderer-process-crashed', 'render-process-gone', (event: Electron.Event, webContents: Electron.WebContents, details: Electron.RenderProcessGoneDetails) => {
|
||||
return [event, webContents, details.reason === 'killed'];
|
||||
});
|
||||
|
||||
@@ -1,33 +1,5 @@
|
||||
import * as deprecate from '@electron/internal/common/deprecate';
|
||||
|
||||
const { systemPreferences } = process._linkedBinding('electron_browser_system_preferences');
|
||||
|
||||
if ('getAppLevelAppearance' in systemPreferences) {
|
||||
const nativeALAGetter = systemPreferences.getAppLevelAppearance;
|
||||
const nativeALASetter = systemPreferences.setAppLevelAppearance;
|
||||
const warnALA = deprecate.warnOnce('appLevelAppearance');
|
||||
const warnALAGetter = deprecate.warnOnce('getAppLevelAppearance function');
|
||||
const warnALASetter = deprecate.warnOnce('setAppLevelAppearance function');
|
||||
Object.defineProperty(systemPreferences, 'appLevelAppearance', {
|
||||
get: () => {
|
||||
warnALA();
|
||||
return nativeALAGetter.call(systemPreferences);
|
||||
},
|
||||
set: (appearance) => {
|
||||
warnALA();
|
||||
nativeALASetter.call(systemPreferences, appearance);
|
||||
}
|
||||
});
|
||||
systemPreferences.getAppLevelAppearance = () => {
|
||||
warnALAGetter();
|
||||
return nativeALAGetter.call(systemPreferences);
|
||||
};
|
||||
systemPreferences.setAppLevelAppearance = (appearance) => {
|
||||
warnALASetter();
|
||||
nativeALASetter.call(systemPreferences, appearance);
|
||||
};
|
||||
}
|
||||
|
||||
if ('getEffectiveAppearance' in systemPreferences) {
|
||||
const nativeEAGetter = systemPreferences.getEffectiveAppearance;
|
||||
Object.defineProperty(systemPreferences, 'effectiveAppearance', {
|
||||
|
||||
@@ -337,49 +337,53 @@ WebContents.prototype.printToPDF = async function (options) {
|
||||
// TODO(codebytere): deduplicate argument sanitization by moving rest of
|
||||
// print param logic into new file shared between printToPDF and print
|
||||
WebContents.prototype.print = function (options: ElectronInternal.WebContentsPrintOptions, callback) {
|
||||
if (typeof options === 'object') {
|
||||
const pageSize = options.pageSize ?? 'A4';
|
||||
if (typeof pageSize === 'object') {
|
||||
if (!pageSize.height || !pageSize.width) {
|
||||
throw new Error('height and width properties are required for pageSize');
|
||||
}
|
||||
if (typeof options !== 'object') {
|
||||
throw new Error('webContents.print(): Invalid print settings specified.');
|
||||
}
|
||||
|
||||
// Dimensions in Microns - 1 meter = 10^6 microns
|
||||
const height = Math.ceil(pageSize.height);
|
||||
const width = Math.ceil(pageSize.width);
|
||||
if (!isValidCustomPageSize(width, height)) {
|
||||
throw new Error('height and width properties must be minimum 352 microns.');
|
||||
}
|
||||
const printSettings: Record<string, any> = { ...options };
|
||||
|
||||
options.mediaSize = {
|
||||
name: 'CUSTOM',
|
||||
custom_display_name: 'Custom',
|
||||
height_microns: height,
|
||||
width_microns: width,
|
||||
imageable_area_left_microns: 0,
|
||||
imageable_area_bottom_microns: 0,
|
||||
imageable_area_right_microns: width,
|
||||
imageable_area_top_microns: height
|
||||
};
|
||||
} else if (typeof pageSize === 'string' && PDFPageSizes[pageSize]) {
|
||||
const mediaSize = PDFPageSizes[pageSize];
|
||||
options.mediaSize = {
|
||||
...mediaSize,
|
||||
imageable_area_left_microns: 0,
|
||||
imageable_area_bottom_microns: 0,
|
||||
imageable_area_right_microns: mediaSize.width_microns,
|
||||
imageable_area_top_microns: mediaSize.height_microns
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unsupported pageSize: ${pageSize}`);
|
||||
const pageSize = options.pageSize ?? 'A4';
|
||||
if (typeof pageSize === 'object') {
|
||||
if (!pageSize.height || !pageSize.width) {
|
||||
throw new Error('height and width properties are required for pageSize');
|
||||
}
|
||||
|
||||
// Dimensions in Microns - 1 meter = 10^6 microns
|
||||
const height = Math.ceil(pageSize.height);
|
||||
const width = Math.ceil(pageSize.width);
|
||||
if (!isValidCustomPageSize(width, height)) {
|
||||
throw new Error('height and width properties must be minimum 352 microns.');
|
||||
}
|
||||
|
||||
printSettings.mediaSize = {
|
||||
name: 'CUSTOM',
|
||||
custom_display_name: 'Custom',
|
||||
height_microns: height,
|
||||
width_microns: width,
|
||||
imageable_area_left_microns: 0,
|
||||
imageable_area_bottom_microns: 0,
|
||||
imageable_area_right_microns: width,
|
||||
imageable_area_top_microns: height
|
||||
};
|
||||
} else if (typeof pageSize === 'string' && PDFPageSizes[pageSize]) {
|
||||
const mediaSize = PDFPageSizes[pageSize];
|
||||
printSettings.mediaSize = {
|
||||
...mediaSize,
|
||||
imageable_area_left_microns: 0,
|
||||
imageable_area_bottom_microns: 0,
|
||||
imageable_area_right_microns: mediaSize.width_microns,
|
||||
imageable_area_top_microns: mediaSize.height_microns
|
||||
};
|
||||
} else {
|
||||
throw new Error(`Unsupported pageSize: ${pageSize}`);
|
||||
}
|
||||
|
||||
if (this._print) {
|
||||
if (callback) {
|
||||
this._print(options, callback);
|
||||
this._print(printSettings, callback);
|
||||
} else {
|
||||
this._print(options);
|
||||
this._print(printSettings);
|
||||
}
|
||||
} else {
|
||||
console.error('Error: Printing feature is disabled.');
|
||||
@@ -653,8 +657,8 @@ WebContents.prototype._init = function () {
|
||||
ipcMain.emit(channel, event, message);
|
||||
});
|
||||
|
||||
this.on('crashed', (event, ...args) => {
|
||||
app.emit('renderer-process-crashed', event, this, ...args);
|
||||
deprecate.event(this, 'crashed', 'render-process-gone', (event: Electron.Event, details: Electron.RenderProcessGoneDetails) => {
|
||||
return [event, details.reason === 'killed'];
|
||||
});
|
||||
|
||||
this.on('render-process-gone', (event, details) => {
|
||||
|
||||
@@ -66,14 +66,17 @@ export function renameFunction<T extends Function> (fn: T, newName: string): T {
|
||||
}
|
||||
|
||||
// change the name of an event
|
||||
export function event (emitter: NodeJS.EventEmitter, oldName: string, newName: string) {
|
||||
export function event (emitter: NodeJS.EventEmitter, oldName: string, newName: string, transformer: (...args: any[]) => any[] | undefined = (...args) => args) {
|
||||
const warn = newName.startsWith('-') /* internal event */
|
||||
? warnOnce(`${oldName} event`)
|
||||
: warnOnce(`${oldName} event`, `${newName} event`);
|
||||
return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) {
|
||||
if (this.listenerCount(oldName) !== 0) {
|
||||
warn();
|
||||
this.emit(oldName, ...args);
|
||||
const transformedArgs = transformer(...args);
|
||||
if (transformedArgs) {
|
||||
this.emit(oldName, ...transformedArgs);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -131,3 +131,4 @@ build_remove_ent_content_analysis_assert.patch
|
||||
fix_activate_background_material_on_windows.patch
|
||||
fix_move_autopipsettingshelper_behind_branding_buildflag.patch
|
||||
revert_remove_the_allowaggressivethrottlingwithwebsocket_feature.patch
|
||||
revert_same_party_cookie_attribute_removal.patch
|
||||
|
||||
@@ -33,10 +33,10 @@ index 41ce32113ec2679b76d5a4fd69a7109c832ac7a1..1cd35794bf78f3d92b42634d9494c85a
|
||||
"//base",
|
||||
"//build:branding_buildflags",
|
||||
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
|
||||
index 9ab6c039d18f8759b8d7db9742d3570a7734f893..ae347df7a1b1a48723160787f71cfd1340f430ef 100644
|
||||
index 31bf3d854d9a672e535f747fb8734d66ac0c37e9..cad2387217a73ec5a7533fc121ea7b77cd6995ae 100644
|
||||
--- a/chrome/browser/BUILD.gn
|
||||
+++ b/chrome/browser/BUILD.gn
|
||||
@@ -4725,7 +4725,7 @@ static_library("browser") {
|
||||
@@ -4726,7 +4726,7 @@ static_library("browser") {
|
||||
|
||||
# On Windows, the hashes are embedded in //chrome:chrome_initial rather
|
||||
# than here in :chrome_dll.
|
||||
@@ -46,7 +46,7 @@ index 9ab6c039d18f8759b8d7db9742d3570a7734f893..ae347df7a1b1a48723160787f71cfd13
|
||||
sources += [ "certificate_viewer_stub.cc" ]
|
||||
}
|
||||
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
|
||||
index 8f9b476a538dd5a0e91edc725eed4463b4a92e05..be625ce71f271aa47444fff1dc8d861c6cf2f43a 100644
|
||||
index a315f817a8977d36e52b8f7c81d9e6778d3dcca2..dc046a004d6f4b2e9f1ec4b9423f293cfecaff8d 100644
|
||||
--- a/chrome/test/BUILD.gn
|
||||
+++ b/chrome/test/BUILD.gn
|
||||
@@ -6841,7 +6841,6 @@ test("unit_tests") {
|
||||
|
||||
@@ -9,10 +9,10 @@ potentially prevent a window from being created.
|
||||
TODO(loc): this patch is currently broken.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index ad0d9cd11022f4f35e717efc088593df41d46496..40b63b1fbde84b8b3f854ce763474cd044f6ea96 100644
|
||||
index af16b1053e1f42887bf0fde97bba30a377857872..ef79bf0e27bd0c5eb9fe5f0aef1cf6c6a2900e13 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -8206,6 +8206,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
@@ -8224,6 +8224,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
last_committed_origin_, params->window_container_type,
|
||||
params->target_url, params->referrer.To<Referrer>(),
|
||||
params->frame_name, params->disposition, *params->features,
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: disable_hidden.patch
|
||||
Electron uses this to disable background throttling for hidden windows.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
index 9e946e02bca276a877defacf6cf6cb54f882ceba..149ead7bbf52ad127747c5f80f0e35a13203502a 100644
|
||||
index 916c3f324ed2161d1f9ad1a023cc0318a1b72628..2d946e29c94a07a1aa4f4b6018ab1b564440f55d 100644
|
||||
--- a/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
@@ -801,6 +801,9 @@ void RenderWidgetHostImpl::WasHidden() {
|
||||
@@ -807,6 +807,9 @@ void RenderWidgetHostImpl::WasHidden() {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -514,7 +514,7 @@ index 796ae2688436eb07f19909641d1620dd02f10cdb..c9e0eee0b329caf46669b419b1cd10cf
|
||||
waiting_on_draw_ack_ = true;
|
||||
|
||||
diff --git a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
|
||||
index c70b0af70a68386c5d36833e621dd1b5cdc1522a..17e5c96cbb4ef2f575fd5ba7bd8b0c7bd58da5e7 100644
|
||||
index 1c59eb0cdf1786a19f74f165acc3fb071dcf2fa1..ddb2389f548a8558f79d7cf32ffd6ce0a7751553 100644
|
||||
--- a/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
|
||||
+++ b/components/viz/service/frame_sinks/root_compositor_frame_sink_impl.cc
|
||||
@@ -97,7 +97,8 @@ RootCompositorFrameSinkImpl::Create(
|
||||
|
||||
@@ -17,7 +17,7 @@ policy->CanCommitOriginAndUrl.
|
||||
Upstreamed at https://chromium-review.googlesource.com/c/chromium/src/+/3856266.
|
||||
|
||||
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
|
||||
index e91fb6b203b55d4937e9f4b4ed8fd5e8efb5aa10..855cb90260de04b90b8bbf4a8733e0869cda551d 100644
|
||||
index 5cf177fc73dd2b3038754b11d23ad637e7709f88..72103151867a9c1154b83da83b52a5b3b7b988ed 100644
|
||||
--- a/content/browser/renderer_host/navigation_request.cc
|
||||
+++ b/content/browser/renderer_host/navigation_request.cc
|
||||
@@ -7543,10 +7543,11 @@ NavigationRequest::GetOriginForURLLoaderFactoryAfterResponseWithDebugInfo() {
|
||||
|
||||
@@ -25,10 +25,10 @@ index c9a13aee1e64f359cd1d5c2e440b4df204420997..7790c311400fc84bfda6c99e1ebd8da7
|
||||
// Returns true if duplex mode is set.
|
||||
bool SetDuplexModeInPrintSettings(mojom::DuplexMode mode);
|
||||
diff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm
|
||||
index ee900c7541af99c61844920433ae39e2648ae90b..59137008aeddc03d60c58a463206dc91a0ef5c1f 100644
|
||||
index 7c9204db9b4c8f36e4c7405e3bd9c8b311753f04..4bff296ea2c55175416aaa778856e6182866664f 100644
|
||||
--- a/printing/printing_context_mac.mm
|
||||
+++ b/printing/printing_context_mac.mm
|
||||
@@ -464,7 +464,8 @@ void ApplySystemPrintDialogData(
|
||||
@@ -473,7 +473,8 @@ void ApplySystemPrintDialogData(
|
||||
!SetCollateInPrintSettings(settings_->collate()) ||
|
||||
!SetDuplexModeInPrintSettings(settings_->duplex_mode()) ||
|
||||
!SetOutputColor(static_cast<int>(settings_->color())) ||
|
||||
@@ -38,7 +38,7 @@ index ee900c7541af99c61844920433ae39e2648ae90b..59137008aeddc03d60c58a463206dc91
|
||||
return OnError();
|
||||
}
|
||||
}
|
||||
@@ -617,6 +618,22 @@ void ApplySystemPrintDialogData(
|
||||
@@ -626,6 +627,22 @@ void ApplySystemPrintDialogData(
|
||||
return PMSetCopies(print_settings, copies, false) == noErr;
|
||||
}
|
||||
|
||||
|
||||
@@ -238,7 +238,7 @@ index 835cce73b7ab8b38c37d3e2650e12303d9d918e3..4460a00497dfaee0ba90cd5d14888055
|
||||
+
|
||||
#endif // UI_BASE_COCOA_REMOTE_ACCESSIBILITY_API_H_
|
||||
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.h b/ui/views/cocoa/native_widget_mac_ns_window_host.h
|
||||
index 2daaf2b78b7a60d340c2ff1651f8b5450db4af0f..3080adea0402f9d57cbde5d4350605d463ee5c8e 100644
|
||||
index 6c2761b68d895fb1964e98865dd96cc37226fce0..563ee4da217ff4aabf2ef3452322da9e9ccefe4f 100644
|
||||
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.h
|
||||
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.h
|
||||
@@ -30,7 +30,9 @@
|
||||
@@ -251,7 +251,7 @@ index 2daaf2b78b7a60d340c2ff1651f8b5450db4af0f..3080adea0402f9d57cbde5d4350605d4
|
||||
@class NSView;
|
||||
|
||||
namespace remote_cocoa {
|
||||
@@ -454,10 +456,12 @@ class VIEWS_EXPORT NativeWidgetMacNSWindowHost
|
||||
@@ -456,10 +458,12 @@ class VIEWS_EXPORT NativeWidgetMacNSWindowHost
|
||||
mojo::AssociatedRemote<remote_cocoa::mojom::NativeWidgetNSWindow>
|
||||
remote_ns_window_remote_;
|
||||
|
||||
@@ -265,10 +265,10 @@ index 2daaf2b78b7a60d340c2ff1651f8b5450db4af0f..3080adea0402f9d57cbde5d4350605d4
|
||||
// Used to force the NSApplication's focused accessibility element to be the
|
||||
// views::Views accessibility tree when the NSView for this is focused.
|
||||
diff --git a/ui/views/cocoa/native_widget_mac_ns_window_host.mm b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
|
||||
index 53bdb4d62294f128b8f4b7bdcddf1052e15f331f..d052f5bb619bf4092810c6d5dd489a38a60026b0 100644
|
||||
index 6e0f07f5e1107341d0232e4a76b7332620315c04..0135aada72ef0c3a85ab8d134462754eb31ad2df 100644
|
||||
--- a/ui/views/cocoa/native_widget_mac_ns_window_host.mm
|
||||
+++ b/ui/views/cocoa/native_widget_mac_ns_window_host.mm
|
||||
@@ -339,7 +339,11 @@ void HandleAccelerator(const ui::Accelerator& accelerator,
|
||||
@@ -352,7 +352,11 @@ void HandleAccelerator(const ui::Accelerator& accelerator,
|
||||
NativeWidgetMacNSWindowHost::GetNativeViewAccessibleForNSView() const {
|
||||
if (in_process_ns_window_bridge_)
|
||||
return in_process_ns_window_bridge_->ns_view();
|
||||
@@ -280,7 +280,7 @@ index 53bdb4d62294f128b8f4b7bdcddf1052e15f331f..d052f5bb619bf4092810c6d5dd489a38
|
||||
}
|
||||
|
||||
gfx::NativeViewAccessible
|
||||
@@ -354,7 +358,11 @@ void HandleAccelerator(const ui::Accelerator& accelerator,
|
||||
@@ -367,7 +371,11 @@ void HandleAccelerator(const ui::Accelerator& accelerator,
|
||||
return [in_process_ns_window_bridge_->ns_view() window];
|
||||
}
|
||||
|
||||
@@ -292,7 +292,7 @@ index 53bdb4d62294f128b8f4b7bdcddf1052e15f331f..d052f5bb619bf4092810c6d5dd489a38
|
||||
}
|
||||
|
||||
remote_cocoa::mojom::NativeWidgetNSWindow*
|
||||
@@ -1353,20 +1361,24 @@ void HandleAccelerator(const ui::Accelerator& accelerator,
|
||||
@@ -1382,20 +1390,24 @@ void HandleAccelerator(const ui::Accelerator& accelerator,
|
||||
void NativeWidgetMacNSWindowHost::SetRemoteAccessibilityTokens(
|
||||
const std::vector<uint8_t>& window_token,
|
||||
const std::vector<uint8_t>& view_token) {
|
||||
|
||||
@@ -706,7 +706,7 @@ index 3f9a514fb41d72c5d06de6ac989f9d7c0513a4e7..0e7ada9df962808dad7caf074a08ebde
|
||||
// Tells the browser printing failed.
|
||||
PrintingFailed(int32 cookie, PrintFailureReason reason);
|
||||
diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc
|
||||
index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a6700c81d 100644
|
||||
index 5a58210461222ed431624bf161b32770c5ae97e6..aec69c525cefd73e50f50883ac0dc8bee94e78c0 100644
|
||||
--- a/components/printing/renderer/print_render_frame_helper.cc
|
||||
+++ b/components/printing/renderer/print_render_frame_helper.cc
|
||||
@@ -45,6 +45,7 @@
|
||||
@@ -717,7 +717,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
#include "printing/units.h"
|
||||
#include "services/metrics/public/cpp/ukm_source_id.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||
@@ -1279,14 +1280,14 @@ void PrintRenderFrameHelper::ScriptedPrint(bool user_initiated) {
|
||||
@@ -1237,14 +1238,14 @@ void PrintRenderFrameHelper::ScriptedPrint(bool user_initiated) {
|
||||
}
|
||||
|
||||
print_in_progress_ = true;
|
||||
@@ -734,7 +734,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
if (!weak_this) {
|
||||
return;
|
||||
}
|
||||
@@ -1317,7 +1318,7 @@ void PrintRenderFrameHelper::BindPrintRenderFrameReceiver(
|
||||
@@ -1275,7 +1276,7 @@ void PrintRenderFrameHelper::BindPrintRenderFrameReceiver(
|
||||
receivers_.Add(this, std::move(receiver));
|
||||
}
|
||||
|
||||
@@ -743,7 +743,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
ScopedIPC scoped_ipc(weak_ptr_factory_.GetWeakPtr());
|
||||
if (ipc_nesting_level_ > kAllowedIpcDepthForPrint)
|
||||
return;
|
||||
@@ -1332,7 +1333,7 @@ void PrintRenderFrameHelper::PrintRequestedPages() {
|
||||
@@ -1290,7 +1291,7 @@ void PrintRenderFrameHelper::PrintRequestedPages() {
|
||||
// plugin node and print that instead.
|
||||
auto plugin = delegate_->GetPdfElement(frame);
|
||||
|
||||
@@ -752,7 +752,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
|
||||
if (render_frame_gone_) {
|
||||
return;
|
||||
@@ -1419,7 +1420,8 @@ void PrintRenderFrameHelper::PrintForSystemDialog() {
|
||||
@@ -1377,7 +1378,8 @@ void PrintRenderFrameHelper::PrintForSystemDialog() {
|
||||
}
|
||||
|
||||
Print(frame, print_preview_context_.source_node(),
|
||||
@@ -762,7 +762,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
if (render_frame_gone_) {
|
||||
return;
|
||||
}
|
||||
@@ -1482,6 +1484,8 @@ void PrintRenderFrameHelper::PrintPreview(base::Value::Dict settings) {
|
||||
@@ -1440,6 +1442,8 @@ void PrintRenderFrameHelper::PrintPreview(base::Value::Dict settings) {
|
||||
if (ipc_nesting_level_ > kAllowedIpcDepthForPrint)
|
||||
return;
|
||||
|
||||
@@ -771,7 +771,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
print_preview_context_.OnPrintPreview();
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS_ASH)
|
||||
@@ -2107,7 +2111,8 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
|
||||
@@ -2065,7 +2069,8 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
|
||||
}
|
||||
|
||||
Print(duplicate_node.GetDocument().GetFrame(), duplicate_node,
|
||||
@@ -781,7 +781,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
// Check if `this` is still valid.
|
||||
if (!weak_this) {
|
||||
return;
|
||||
@@ -2123,7 +2128,9 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
|
||||
@@ -2081,7 +2086,9 @@ void PrintRenderFrameHelper::PrintNode(const blink::WebNode& node) {
|
||||
|
||||
void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
|
||||
const blink::WebNode& node,
|
||||
@@ -792,7 +792,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
// If still not finished with earlier print request simply ignore.
|
||||
if (prep_frame_view_)
|
||||
return;
|
||||
@@ -2131,7 +2138,7 @@ void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
|
||||
@@ -2089,7 +2096,7 @@ void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
|
||||
FrameReference frame_ref(frame);
|
||||
|
||||
uint32_t expected_page_count = 0;
|
||||
@@ -801,7 +801,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
DidFinishPrinting(PrintingResult::kFailPrintInit);
|
||||
return; // Failed to init print page settings.
|
||||
}
|
||||
@@ -2150,8 +2157,15 @@ void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
|
||||
@@ -2108,8 +2115,15 @@ void PrintRenderFrameHelper::Print(blink::WebLocalFrame* frame,
|
||||
print_pages_params_->params->print_scaling_option;
|
||||
|
||||
auto self = weak_ptr_factory_.GetWeakPtr();
|
||||
@@ -818,7 +818,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
// Check if `this` is still valid.
|
||||
if (!self)
|
||||
return;
|
||||
@@ -2391,35 +2405,47 @@ void PrintRenderFrameHelper::IPCProcessed() {
|
||||
@@ -2349,35 +2363,47 @@ void PrintRenderFrameHelper::IPCProcessed() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -876,7 +876,7 @@ index ec3c62f88268d6cf0d8abadf483dacb31ff09d3d..b0af8b9f2286c95efabe66ecfc0eaa8a
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -2524,7 +2550,7 @@ mojom::PrintPagesParamsPtr PrintRenderFrameHelper::GetPrintSettingsFromUser(
|
||||
@@ -2482,7 +2508,7 @@ mojom::PrintPagesParamsPtr PrintRenderFrameHelper::GetPrintSettingsFromUser(
|
||||
std::move(params),
|
||||
base::BindOnce(
|
||||
[](base::OnceClosure quit_closure, mojom::PrintPagesParamsPtr* output,
|
||||
|
||||
@@ -30,10 +30,10 @@ index fe010b1f001130fbdeaf4ef9ce7798e4baf958b5..28f1305f439be7f669e482ac0e4804c0
|
||||
// RenderWidgetHost on the primary main frame, and false otherwise.
|
||||
virtual bool IsWidgetForPrimaryMainFrame(RenderWidgetHostImpl*);
|
||||
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
index 149ead7bbf52ad127747c5f80f0e35a13203502a..0f2a73f990fd41112d18ab6a9ed5bc43b90c235a 100644
|
||||
index 2d946e29c94a07a1aa4f4b6018ab1b564440f55d..b013e7eaa4a5db79d19378e437d8d3d18dd1b2aa 100644
|
||||
--- a/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
@@ -2121,6 +2121,9 @@ void RenderWidgetHostImpl::SetCursor(const ui::Cursor& cursor) {
|
||||
@@ -2127,6 +2127,9 @@ void RenderWidgetHostImpl::SetCursor(const ui::Cursor& cursor) {
|
||||
if (view_) {
|
||||
view_->UpdateCursor(cursor);
|
||||
}
|
||||
|
||||
2359
patches/chromium/revert_same_party_cookie_attribute_removal.patch
Normal file
2359
patches/chromium/revert_same_party_cookie_attribute_removal.patch
Normal file
File diff suppressed because it is too large
Load Diff
@@ -15,10 +15,10 @@ Note that we also need to manually update embedder's
|
||||
`api::WebContents::IsFullscreenForTabOrPending` value.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index 40b63b1fbde84b8b3f854ce763474cd044f6ea96..5e95682e793616c4d88f62ad4d69c8c9f547e000 100644
|
||||
index ef79bf0e27bd0c5eb9fe5f0aef1cf6c6a2900e13..f121a6b0395d9ee1cf5d5c03ee1dc8968167428a 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -7417,6 +7417,17 @@ void RenderFrameHostImpl::EnterFullscreen(
|
||||
@@ -7435,6 +7435,17 @@ void RenderFrameHostImpl::EnterFullscreen(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
0
patches/libvpx/.patches
Normal file
0
patches/libvpx/.patches
Normal file
@@ -46,3 +46,6 @@ tls_ensure_tls_sockets_are_closed_if_the_underlying_wrap_closes.patch
|
||||
test_deflake_test-tls-socket-close.patch
|
||||
net_fix_crash_due_to_simultaneous_close_shutdown_on_js_stream.patch
|
||||
net_use_asserts_in_js_socket_stream_to_catch_races_in_future.patch
|
||||
lib_fix_broadcastchannel_initialization_location.patch
|
||||
fix_handle_possible_disabled_sharedarraybuffer.patch
|
||||
win_process_avoid_assert_after_spawning_store_app_4152.patch
|
||||
|
||||
@@ -11,7 +11,7 @@ trying to see whether or not the lines are greyed out. One possibility
|
||||
would be to upstream a changed test that doesn't hardcode line numbers.
|
||||
|
||||
diff --git a/test/fixtures/errors/force_colors.snapshot b/test/fixtures/errors/force_colors.snapshot
|
||||
index 0334a0b4faa3633aa8617b9538873e7f3540513b..7f85ddc507c9c38ce85ed2a48f8152eef168717b 100644
|
||||
index 0334a0b4faa3633aa8617b9538873e7f3540513b..fa9989f55980aeddd3fa944318488c0289e3ab6e 100644
|
||||
--- a/test/fixtures/errors/force_colors.snapshot
|
||||
+++ b/test/fixtures/errors/force_colors.snapshot
|
||||
@@ -4,11 +4,12 @@ throw new Error('Should include grayed stack trace')
|
||||
@@ -27,7 +27,7 @@ index 0334a0b4faa3633aa8617b9538873e7f3540513b..7f85ddc507c9c38ce85ed2a48f8152ee
|
||||
+[90m at Object..js (node:internal*modules*cjs*loader:1326:10)[39m
|
||||
+[90m at Module.load (node:internal*modules*cjs*loader:1126:32)[39m
|
||||
+[90m at node:internal*modules*cjs*loader:967:12[39m
|
||||
+[90m at Function._load (node:electron*js2c*asar_bundle:757:32)[39m
|
||||
+[90m at Function._load (node:electron*js2c*asar_bundle:743:32)[39m
|
||||
+[90m at Function.executeUserEntryPoint [as runMain] (node:internal*modules*run_main:96:12)[39m
|
||||
[90m at node:internal*main*run_main_module:23:47[39m
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Mon, 2 Oct 2023 16:03:43 +0200
|
||||
Subject: fix: handle possible disabled SharedArrayBuffer
|
||||
|
||||
It's possible for SharedArrayBuffer to be disabled with the -no-harmony-sharedarraybuffer
|
||||
flag, and so we should guard uses with a check for potential undefined-ness.
|
||||
|
||||
This should be upstreamed to Node.js.
|
||||
|
||||
diff --git a/lib/internal/crypto/webidl.js b/lib/internal/crypto/webidl.js
|
||||
index 9f5340c223902c5ff61def05e8a4f470b4f328e8..d6dbfa482f9ebff3f99fb810e072cf9a03d1cd4d 100644
|
||||
--- a/lib/internal/crypto/webidl.js
|
||||
+++ b/lib/internal/crypto/webidl.js
|
||||
@@ -183,7 +183,10 @@ function isNonSharedArrayBuffer(V) {
|
||||
return ObjectPrototypeIsPrototypeOf(ArrayBufferPrototype, V);
|
||||
}
|
||||
|
||||
+// SharedArrayBuffers can be disabled with --no-harmony-sharedarraybuffer.
|
||||
function isSharedArrayBuffer(V) {
|
||||
+ if (SharedArrayBuffer === undefined)
|
||||
+ return false;
|
||||
return ObjectPrototypeIsPrototypeOf(SharedArrayBuffer.prototype, V);
|
||||
}
|
||||
|
||||
diff --git a/lib/internal/main/worker_thread.js b/lib/internal/main/worker_thread.js
|
||||
index be4d82086199855a10108528b3dacc663b839454..10c33bacc0529e12f52aaf1baf6d42489b2a75a7 100644
|
||||
--- a/lib/internal/main/worker_thread.js
|
||||
+++ b/lib/internal/main/worker_thread.js
|
||||
@@ -108,6 +108,7 @@ port.on('message', (message) => {
|
||||
|
||||
require('internal/worker').assignEnvironmentData(environmentData);
|
||||
|
||||
+ // SharedArrayBuffers can be disabled with --no-harmony-sharedarraybuffer.
|
||||
if (SharedArrayBuffer !== undefined) {
|
||||
// The counter is only passed to the workers created by the main thread,
|
||||
// not to workers created by other workers.
|
||||
@@ -0,0 +1,44 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Mon, 27 Feb 2023 12:56:15 +0100
|
||||
Subject: lib: fix BroadcastChannel initialization location
|
||||
|
||||
Refs https://github.com/nodejs/node/pull/40532.
|
||||
|
||||
Fixes a bug in the above, wherein BroadcastChannel should have been
|
||||
initialized in bootstrap/browser instead of bootstrap/node. That
|
||||
inadvertently made it such that there was incorrect handling of the
|
||||
DOM vs Node.js implementations of BroadcastChannel.
|
||||
|
||||
This will be upstreamed.
|
||||
|
||||
diff --git a/lib/internal/bootstrap/browser.js b/lib/internal/bootstrap/browser.js
|
||||
index 5be4dd6176482c724455cbbeeaa9680e849a091b..29ccee75d77da072735032f0a25363ac88a023ba 100644
|
||||
--- a/lib/internal/bootstrap/browser.js
|
||||
+++ b/lib/internal/bootstrap/browser.js
|
||||
@@ -12,6 +12,10 @@ const {
|
||||
} = require('internal/util');
|
||||
const config = internalBinding('config');
|
||||
|
||||
+// Non-standard extensions:
|
||||
+const { BroadcastChannel } = require('internal/worker/io');
|
||||
+exposeInterface(globalThis, 'BroadcastChannel', BroadcastChannel);
|
||||
+
|
||||
// https://console.spec.whatwg.org/#console-namespace
|
||||
exposeNamespace(globalThis, 'console',
|
||||
createGlobalConsole());
|
||||
diff --git a/lib/internal/bootstrap/node.js b/lib/internal/bootstrap/node.js
|
||||
index 13ea68c96fd415f976aab0f291a1b7c688db1c58..0ca3de08fffb344c0330ce0f8d28b2d3d0b24350 100644
|
||||
--- a/lib/internal/bootstrap/node.js
|
||||
+++ b/lib/internal/bootstrap/node.js
|
||||
@@ -238,10 +238,6 @@ const {
|
||||
queueMicrotask,
|
||||
} = require('internal/process/task_queues');
|
||||
|
||||
-// Non-standard extensions:
|
||||
-const { BroadcastChannel } = require('internal/worker/io');
|
||||
-exposeInterface(globalThis, 'BroadcastChannel', BroadcastChannel);
|
||||
-
|
||||
defineOperation(globalThis, 'queueMicrotask', queueMicrotask);
|
||||
|
||||
const timers = require('timers');
|
||||
@@ -0,0 +1,85 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jameson Nash <vtjnash@gmail.com>
|
||||
Date: Mon, 2 Oct 2023 15:15:18 +0200
|
||||
Subject: win,process: avoid assert after spawning Store app (#4152)
|
||||
|
||||
Make sure this handle is functional. The Windows kernel seems to have a
|
||||
bug that if the first use of AssignProcessToJobObject is for a Windows
|
||||
Store program, subsequent attempts to use the handle with fail with
|
||||
INVALID_PARAMETER (87). This is possilby because all uses of the handle
|
||||
must be for the same Terminal Services session. We can ensure it is
|
||||
tied to our current session now by adding ourself to it. We could
|
||||
remove ourself afterwards, but there doesn't seem to be a reason to.
|
||||
|
||||
Secondly, we start the process suspended so that we can make sure we
|
||||
added it to the job control object before it does anything itself (such
|
||||
as launch more jobs or exit).
|
||||
|
||||
Fixes: https://github.com/JuliaLang/julia/issues/51461
|
||||
|
||||
diff --git a/deps/uv/src/win/process.c b/deps/uv/src/win/process.c
|
||||
index 24c633393fd15dcf87726b174d6b027a969e0f0d..4ad9fec900fa66b0e8c6894701e94f420de903a8 100644
|
||||
--- a/deps/uv/src/win/process.c
|
||||
+++ b/deps/uv/src/win/process.c
|
||||
@@ -102,6 +102,21 @@ static void uv__init_global_job_handle(void) {
|
||||
&info,
|
||||
sizeof info))
|
||||
uv_fatal_error(GetLastError(), "SetInformationJobObject");
|
||||
+
|
||||
+
|
||||
+ if (!AssignProcessToJobObject(uv_global_job_handle_, GetCurrentProcess())) {
|
||||
+ /* Make sure this handle is functional. The Windows kernel has a bug that
|
||||
+ * if the first use of AssignProcessToJobObject is for a Windows Store
|
||||
+ * program, subsequent attempts to use the handle with fail with
|
||||
+ * INVALID_PARAMETER (87). This is possibly because all uses of the handle
|
||||
+ * must be for the same Terminal Services session. We can ensure it is tied
|
||||
+ * to our current session now by adding ourself to it. We could remove
|
||||
+ * ourself afterwards, but there doesn't seem to be a reason to.
|
||||
+ */
|
||||
+ DWORD err = GetLastError();
|
||||
+ if (err != ERROR_ACCESS_DENIED)
|
||||
+ uv_fatal_error(err, "AssignProcessToJobObject");
|
||||
+ }
|
||||
}
|
||||
|
||||
|
||||
@@ -1098,6 +1113,7 @@ int uv_spawn(uv_loop_t* loop,
|
||||
* breakaway.
|
||||
*/
|
||||
process_flags |= DETACHED_PROCESS | CREATE_NEW_PROCESS_GROUP;
|
||||
+ process_flags |= CREATE_SUSPENDED;
|
||||
}
|
||||
|
||||
if (!CreateProcessW(application_path,
|
||||
@@ -1115,11 +1131,6 @@ int uv_spawn(uv_loop_t* loop,
|
||||
goto done;
|
||||
}
|
||||
|
||||
- /* Spawn succeeded. Beyond this point, failure is reported asynchronously. */
|
||||
-
|
||||
- process->process_handle = info.hProcess;
|
||||
- process->pid = info.dwProcessId;
|
||||
-
|
||||
/* If the process isn't spawned as detached, assign to the global job object
|
||||
* so windows will kill it when the parent process dies. */
|
||||
if (!(options->flags & UV_PROCESS_DETACHED)) {
|
||||
@@ -1142,6 +1153,19 @@ int uv_spawn(uv_loop_t* loop,
|
||||
}
|
||||
}
|
||||
|
||||
+ if (process_flags & CREATE_SUSPENDED) {
|
||||
+ if (ResumeThread(info.hThread) == ((DWORD)-1)) {
|
||||
+ err = GetLastError();
|
||||
+ TerminateProcess(info.hProcess, 1);
|
||||
+ goto done;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ /* Spawn succeeded. Beyond this point, failure is reported asynchronously. */
|
||||
+
|
||||
+ process->process_handle = info.hProcess;
|
||||
+ process->pid = info.dwProcessId;
|
||||
+
|
||||
/* Set IPC pid to all IPC pipes. */
|
||||
for (i = 0; i < options->stdio_count; i++) {
|
||||
const uv_stdio_container_t* fdopt = &options->stdio[i];
|
||||
@@ -1,3 +1,4 @@
|
||||
fix_fallback_to_x11_capturer_on_wayland.patch
|
||||
fix_mark_pipewire_capturer_as_failed_after_session_is_closed.patch
|
||||
fix_check_pipewire_init_before_creating_generic_capturer.patch
|
||||
pipewire_capturer_make_restore_tokens_re-usable_more_than_one_time.patch
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jan Grulich <grulja@gmail.com>
|
||||
Date: Mon, 9 Oct 2023 18:54:31 +0200
|
||||
Subject: PipeWire capturer: make restore tokens re-usable more than one time
|
||||
|
||||
Do not automatically remove all tokens once we attempt to use them. This
|
||||
mitigates an issue with Google Meet where an additional instance of a
|
||||
DesktopCapturer is created and destroyed right away, taking away the
|
||||
token we would use otherwise. Also save the token under same SourceId
|
||||
once we get a new (but could be same) token from the restored session.
|
||||
|
||||
Bug: webrtc:15544
|
||||
Change-Id: I565b22f5bf6a4d8a3b7d6d757f9c1046c7a0557d
|
||||
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322621
|
||||
Commit-Queue: Jan Grulich <grulja@gmail.com>
|
||||
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#40892}
|
||||
|
||||
diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
|
||||
index 5a67c18c1d1f62aa5e3162d9778ca665bac4a1bb..a5df76b0cdecd1e2e68f2f25c80c6b17de8bc808 100644
|
||||
--- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
|
||||
+++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
|
||||
@@ -82,8 +82,10 @@ void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result,
|
||||
<< static_cast<uint>(result);
|
||||
} else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
|
||||
if (!screencast_portal->RestoreToken().empty()) {
|
||||
+ const SourceId token_id =
|
||||
+ selected_source_id_ ? selected_source_id_ : source_id_;
|
||||
RestoreTokenManager::GetInstance().AddToken(
|
||||
- source_id_, screencast_portal->RestoreToken());
|
||||
+ token_id, screencast_portal->RestoreToken());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +140,7 @@ void BaseCapturerPipeWire::Start(Callback* callback) {
|
||||
ScreenCastPortal::PersistMode::kTransient);
|
||||
if (selected_source_id_) {
|
||||
screencast_portal->SetRestoreToken(
|
||||
- RestoreTokenManager::GetInstance().TakeToken(selected_source_id_));
|
||||
+ RestoreTokenManager::GetInstance().GetToken(selected_source_id_));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/modules/desktop_capture/linux/wayland/restore_token_manager.cc b/modules/desktop_capture/linux/wayland/restore_token_manager.cc
|
||||
index 5ca9b957a9e4f436bc09d4bc16019b169ae9ba9f..a17d9a49bb031efa340bfd61b4a6f8f5a86d09da 100644
|
||||
--- a/modules/desktop_capture/linux/wayland/restore_token_manager.cc
|
||||
+++ b/modules/desktop_capture/linux/wayland/restore_token_manager.cc
|
||||
@@ -23,10 +23,8 @@ void RestoreTokenManager::AddToken(DesktopCapturer::SourceId id,
|
||||
restore_tokens_.insert({id, token});
|
||||
}
|
||||
|
||||
-std::string RestoreTokenManager::TakeToken(DesktopCapturer::SourceId id) {
|
||||
- std::string token = restore_tokens_[id];
|
||||
- // Remove the token as it cannot be used anymore
|
||||
- restore_tokens_.erase(id);
|
||||
+std::string RestoreTokenManager::GetToken(DesktopCapturer::SourceId id) {
|
||||
+ const std::string token = restore_tokens_[id];
|
||||
return token;
|
||||
}
|
||||
|
||||
diff --git a/modules/desktop_capture/linux/wayland/restore_token_manager.h b/modules/desktop_capture/linux/wayland/restore_token_manager.h
|
||||
index 174bef121f74b7b2b529d681b86c4fb4218586ea..ad4f74790f2a5cfba304fc11d47c9924db9013d8 100644
|
||||
--- a/modules/desktop_capture/linux/wayland/restore_token_manager.h
|
||||
+++ b/modules/desktop_capture/linux/wayland/restore_token_manager.h
|
||||
@@ -27,7 +27,7 @@ class RestoreTokenManager {
|
||||
static RestoreTokenManager& GetInstance();
|
||||
|
||||
void AddToken(DesktopCapturer::SourceId id, const std::string& token);
|
||||
- std::string TakeToken(DesktopCapturer::SourceId id);
|
||||
+ std::string GetToken(DesktopCapturer::SourceId id);
|
||||
|
||||
// Returns a source ID which does not have any token associated with it yet.
|
||||
DesktopCapturer::SourceId GetUnusedId();
|
||||
@@ -953,6 +953,14 @@ void BaseWindow::AddTabbedWindow(NativeWindow* window,
|
||||
args->ThrowError("AddTabbedWindow cannot be called by a window on itself.");
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> BaseWindow::GetTabbingIdentifier() {
|
||||
auto tabbing_id = window_->GetTabbingIdentifier();
|
||||
if (!tabbing_id.has_value())
|
||||
return v8::Undefined(isolate());
|
||||
|
||||
return gin::ConvertToV8(isolate(), tabbing_id.value());
|
||||
}
|
||||
|
||||
void BaseWindow::SetAutoHideMenuBar(bool auto_hide) {
|
||||
window_->SetAutoHideMenuBar(auto_hide);
|
||||
}
|
||||
@@ -1305,6 +1313,7 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("moveTabToNewWindow", &BaseWindow::MoveTabToNewWindow)
|
||||
.SetMethod("toggleTabBar", &BaseWindow::ToggleTabBar)
|
||||
.SetMethod("addTabbedWindow", &BaseWindow::AddTabbedWindow)
|
||||
.SetProperty("tabbingIdentifier", &BaseWindow::GetTabbingIdentifier)
|
||||
.SetMethod("setWindowButtonVisibility",
|
||||
&BaseWindow::SetWindowButtonVisibility)
|
||||
.SetMethod("_getWindowButtonVisibility",
|
||||
|
||||
@@ -198,9 +198,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
|
||||
bool GetWindowButtonVisibility() const;
|
||||
void SetWindowButtonPosition(absl::optional<gfx::Point> position);
|
||||
absl::optional<gfx::Point> GetWindowButtonPosition() const;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
bool IsHiddenInMissionControl();
|
||||
void SetHiddenInMissionControl(bool hidden);
|
||||
#endif
|
||||
@@ -215,6 +213,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
|
||||
void MoveTabToNewWindow();
|
||||
void ToggleTabBar();
|
||||
void AddTabbedWindow(NativeWindow* window, gin_helper::Arguments* args);
|
||||
v8::Local<v8::Value> GetTabbingIdentifier();
|
||||
void SetAutoHideMenuBar(bool auto_hide);
|
||||
bool IsMenuBarAutoHide();
|
||||
void SetMenuBarVisibility(bool visible);
|
||||
|
||||
@@ -216,7 +216,11 @@ void Notification::NotificationClosed() {
|
||||
|
||||
void Notification::Close() {
|
||||
if (notification_) {
|
||||
notification_->Dismiss();
|
||||
if (notification_->is_dismissed()) {
|
||||
notification_->Remove();
|
||||
} else {
|
||||
notification_->Dismiss();
|
||||
}
|
||||
notification_->set_delegate(nullptr);
|
||||
notification_.reset();
|
||||
}
|
||||
|
||||
@@ -90,16 +90,15 @@ gin::ObjectTemplateBuilder SystemPreferences::GetObjectTemplateBuilder(
|
||||
&SystemPreferences::IsSwipeTrackingFromScrollEventsEnabled)
|
||||
.SetMethod("getEffectiveAppearance",
|
||||
&SystemPreferences::GetEffectiveAppearance)
|
||||
.SetMethod("getAppLevelAppearance",
|
||||
&SystemPreferences::GetAppLevelAppearance)
|
||||
.SetMethod("setAppLevelAppearance",
|
||||
&SystemPreferences::SetAppLevelAppearance)
|
||||
.SetMethod("getSystemColor", &SystemPreferences::GetSystemColor)
|
||||
.SetMethod("canPromptTouchID", &SystemPreferences::CanPromptTouchID)
|
||||
.SetMethod("promptTouchID", &SystemPreferences::PromptTouchID)
|
||||
.SetMethod("isTrustedAccessibilityClient",
|
||||
&SystemPreferences::IsTrustedAccessibilityClient)
|
||||
.SetMethod("askForMediaAccess", &SystemPreferences::AskForMediaAccess)
|
||||
.SetProperty(
|
||||
"accessibilityDisplayShouldReduceTransparency",
|
||||
&SystemPreferences::AccessibilityDisplayShouldReduceTransparency)
|
||||
#endif
|
||||
.SetMethod("getAnimationSettings",
|
||||
&SystemPreferences::GetAnimationSettings);
|
||||
|
||||
@@ -96,6 +96,7 @@ class SystemPreferences
|
||||
gin::Arguments* args);
|
||||
void RemoveUserDefault(const std::string& name);
|
||||
bool IsSwipeTrackingFromScrollEventsEnabled();
|
||||
bool AccessibilityDisplayShouldReduceTransparency();
|
||||
|
||||
std::string GetSystemColor(gin_helper::ErrorThrower thrower,
|
||||
const std::string& color);
|
||||
@@ -112,8 +113,6 @@ class SystemPreferences
|
||||
// TODO(MarshallOfSound): Write tests for these methods once we
|
||||
// are running tests on a Mojave machine
|
||||
v8::Local<v8::Value> GetEffectiveAppearance(v8::Isolate* isolate);
|
||||
v8::Local<v8::Value> GetAppLevelAppearance(v8::Isolate* isolate);
|
||||
void SetAppLevelAppearance(gin::Arguments* args);
|
||||
#endif
|
||||
v8::Local<v8::Value> GetAnimationSettings(v8::Isolate* isolate);
|
||||
|
||||
|
||||
@@ -475,14 +475,7 @@ bool SystemPreferences::IsTrustedAccessibilityClient(bool prompt) {
|
||||
std::string SystemPreferences::GetColor(gin_helper::ErrorThrower thrower,
|
||||
const std::string& color) {
|
||||
NSColor* sysColor = nil;
|
||||
if (color == "alternate-selected-control-text") {
|
||||
sysColor = [NSColor alternateSelectedControlTextColor];
|
||||
EmitWarning(
|
||||
node::Environment::GetCurrent(thrower.isolate()),
|
||||
"'alternate-selected-control-text' is deprecated as an input to "
|
||||
"getColor. Use 'selected-content-background' instead.",
|
||||
"electron");
|
||||
} else if (color == "control-background") {
|
||||
if (color == "control-background") {
|
||||
sysColor = [NSColor controlBackgroundColor];
|
||||
} else if (color == "control") {
|
||||
sysColor = [NSColor controlColor];
|
||||
@@ -609,19 +602,9 @@ v8::Local<v8::Value> SystemPreferences::GetEffectiveAppearance(
|
||||
isolate, [NSApplication sharedApplication].effectiveAppearance);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> SystemPreferences::GetAppLevelAppearance(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::ConvertToV8(isolate,
|
||||
[NSApplication sharedApplication].appearance);
|
||||
}
|
||||
|
||||
void SystemPreferences::SetAppLevelAppearance(gin::Arguments* args) {
|
||||
NSAppearance* appearance;
|
||||
if (args->GetNext(&appearance)) {
|
||||
[[NSApplication sharedApplication] setAppearance:appearance];
|
||||
} else {
|
||||
args->ThrowError();
|
||||
}
|
||||
bool SystemPreferences::AccessibilityDisplayShouldReduceTransparency() {
|
||||
return [[NSWorkspace sharedWorkspace]
|
||||
accessibilityDisplayShouldReduceTransparency];
|
||||
}
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
#include "components/security_state/content/content_utils.h"
|
||||
#include "components/security_state/core/security_state.h"
|
||||
#include "content/browser/renderer_host/frame_tree_node.h" // nogncheck
|
||||
#include "content/browser/renderer_host/navigation_controller_impl.h" // nogncheck
|
||||
#include "content/browser/renderer_host/render_frame_host_manager.h" // nogncheck
|
||||
#include "content/browser/renderer_host/render_widget_host_impl.h" // nogncheck
|
||||
#include "content/browser/renderer_host/render_widget_host_view_base.h" // nogncheck
|
||||
@@ -468,9 +469,16 @@ base::IDMap<WebContents*>& GetAllWebContents() {
|
||||
void OnCapturePageDone(gin_helper::Promise<gfx::Image> promise,
|
||||
base::ScopedClosureRunner capture_handle,
|
||||
const SkBitmap& bitmap) {
|
||||
auto ui_task_runner = content::GetUIThreadTaskRunner({});
|
||||
if (!ui_task_runner->RunsTasksInCurrentSequence()) {
|
||||
ui_task_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(&OnCapturePageDone, std::move(promise),
|
||||
std::move(capture_handle), bitmap));
|
||||
return;
|
||||
}
|
||||
|
||||
// Hack to enable transparency in captured image
|
||||
promise.Resolve(gfx::Image::CreateFrom1xBitmap(bitmap));
|
||||
|
||||
capture_handle.RunAndReset();
|
||||
}
|
||||
|
||||
@@ -1726,13 +1734,6 @@ void WebContents::RenderViewDeleted(content::RenderViewHost* render_view_host) {
|
||||
|
||||
void WebContents::PrimaryMainFrameRenderProcessGone(
|
||||
base::TerminationStatus status) {
|
||||
auto weak_this = GetWeakPtr();
|
||||
Emit("crashed", status == base::TERMINATION_STATUS_PROCESS_WAS_KILLED);
|
||||
|
||||
// User might destroy WebContents in the crashed event.
|
||||
if (!weak_this || !web_contents())
|
||||
return;
|
||||
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
gin_helper::Dictionary details = gin_helper::Dictionary::CreateEmpty(isolate);
|
||||
@@ -2406,8 +2407,20 @@ void WebContents::LoadURL(const GURL& url,
|
||||
params.transition_type = ui::PageTransitionFromInt(
|
||||
ui::PAGE_TRANSITION_TYPED | ui::PAGE_TRANSITION_FROM_ADDRESS_BAR);
|
||||
params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE;
|
||||
// Discard non-committed entries to ensure that we don't re-use a pending
|
||||
// entry
|
||||
|
||||
// It's not safe to start a new navigation or otherwise discard the current
|
||||
// one while the call that started it is still on the stack. See
|
||||
// http://crbug.com/347742.
|
||||
auto& ctrl_impl = static_cast<content::NavigationControllerImpl&>(
|
||||
web_contents()->GetController());
|
||||
if (ctrl_impl.in_navigate_to_pending_entry()) {
|
||||
Emit("did-fail-load", static_cast<int>(net::ERR_FAILED),
|
||||
net::ErrorToShortString(net::ERR_FAILED), url.possibly_invalid_spec(),
|
||||
true);
|
||||
return;
|
||||
}
|
||||
|
||||
// Discard non-committed entries to ensure we don't re-use a pending entry.
|
||||
web_contents()->GetController().DiscardNonCommittedEntries();
|
||||
web_contents()->GetController().LoadURLWithParams(params);
|
||||
|
||||
@@ -3427,22 +3440,16 @@ v8::Local<v8::Promise> WebContents::CapturePage(gin::Arguments* args) {
|
||||
}
|
||||
|
||||
auto* const view = web_contents()->GetRenderWidgetHostView();
|
||||
if (!view) {
|
||||
if (!view || view->GetViewBounds().size().IsEmpty()) {
|
||||
promise.Resolve(gfx::Image());
|
||||
return handle;
|
||||
}
|
||||
|
||||
#if !BUILDFLAG(IS_MAC)
|
||||
// If the view's renderer is suspended this may fail on Windows/Linux -
|
||||
// bail if so. See CopyFromSurface in
|
||||
// content/public/browser/render_widget_host_view.h.
|
||||
auto* rfh = web_contents()->GetPrimaryMainFrame();
|
||||
if (rfh &&
|
||||
rfh->GetVisibilityState() == blink::mojom::PageVisibilityState::kHidden) {
|
||||
promise.Resolve(gfx::Image());
|
||||
if (!view->IsSurfaceAvailableForCopy()) {
|
||||
promise.RejectWithErrorMessage(
|
||||
"Current display surface not available for capture");
|
||||
return handle;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
|
||||
auto capture_handle = web_contents()->IncrementCapturerCount(
|
||||
rect.size(), stay_hidden, stay_awake);
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
#include "crypto/crypto_buildflags.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "electron/shell/common/api/api.mojom.h"
|
||||
#include "extensions/browser/api/messaging/messaging_api_message_filter.h"
|
||||
#include "extensions/browser/extension_navigation_ui_data.h"
|
||||
#include "mojo/public/cpp/bindings/binder_map.h"
|
||||
#include "net/ssl/ssl_cert_request_info.h"
|
||||
#include "net/ssl/ssl_private_key.h"
|
||||
@@ -114,6 +114,7 @@
|
||||
#include "shell/common/options_switches.h"
|
||||
#include "shell/common/platform_util.h"
|
||||
#include "shell/common/thread_restrictions.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
|
||||
#include "third_party/blink/public/common/loader/url_loader_throttle.h"
|
||||
#include "third_party/blink/public/common/renderer_preferences/renderer_preferences.h"
|
||||
#include "third_party/blink/public/common/tokens/tokens.h"
|
||||
@@ -147,6 +148,7 @@
|
||||
#include "content/public/browser/child_process_security_policy.h"
|
||||
#include "content/public/browser/file_url_loader.h"
|
||||
#include "content/public/browser/web_ui_url_loader_factory.h"
|
||||
#include "extensions/browser/api/messaging/messaging_api_message_filter.h"
|
||||
#include "extensions/browser/api/mime_handler_private/mime_handler_private.h"
|
||||
#include "extensions/browser/api/web_request/web_request_api.h"
|
||||
#include "extensions/browser/browser_context_keyed_api_factory.h"
|
||||
@@ -154,7 +156,6 @@
|
||||
#include "extensions/browser/extension_host.h"
|
||||
#include "extensions/browser/extension_message_filter.h"
|
||||
#include "extensions/browser/extension_navigation_throttle.h"
|
||||
#include "extensions/browser/extension_navigation_ui_data.h"
|
||||
#include "extensions/browser/extension_prefs.h"
|
||||
#include "extensions/browser/extension_protocols.h"
|
||||
#include "extensions/browser/extension_registry.h"
|
||||
@@ -172,7 +173,6 @@
|
||||
#include "shell/browser/extensions/electron_extension_message_filter.h"
|
||||
#include "shell/browser/extensions/electron_extension_system.h"
|
||||
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_registry.h"
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLUGINS)
|
||||
@@ -1220,12 +1220,12 @@ void ElectronBrowserClient::
|
||||
protocol_registry->RegisterURLLoaderFactories(factories,
|
||||
false /* allow_file_access */);
|
||||
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
factories->emplace(
|
||||
extensions::kExtensionScheme,
|
||||
extensions::CreateExtensionServiceWorkerScriptURLLoaderFactory(
|
||||
browser_context));
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
}
|
||||
|
||||
bool ElectronBrowserClient::ShouldTreatURLSchemeAsFirstPartyWhenTopLevel(
|
||||
@@ -1413,9 +1413,10 @@ void ElectronBrowserClient::OverrideURLLoaderFactoryParams(
|
||||
factory_params->disable_web_security = true;
|
||||
}
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
extensions::URLLoaderFactoryManager::OverrideURLLoaderFactoryParams(
|
||||
browser_context, origin, is_for_isolated_world, factory_params);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ElectronBrowserClient::
|
||||
@@ -1473,7 +1474,7 @@ void ElectronBrowserClient::
|
||||
},
|
||||
&render_frame_host));
|
||||
#endif
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
associated_registry.AddInterface<extensions::mojom::LocalFrameHost>(
|
||||
base::BindRepeating(
|
||||
[](content::RenderFrameHost* render_frame_host,
|
||||
|
||||
@@ -79,12 +79,12 @@
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
#include "base/environment.h"
|
||||
#include "chrome/browser/ui/views/dark_mode_manager_linux.h"
|
||||
#include "device/bluetooth/bluetooth_adapter_factory.h"
|
||||
#include "device/bluetooth/dbus/dbus_bluez_manager_wrapper_linux.h"
|
||||
#include "electron/electron_gtk_stubs.h"
|
||||
#include "ui/base/cursor/cursor_factory.h"
|
||||
#include "ui/base/ime/linux/linux_input_method_context_factory.h"
|
||||
#include "ui/gfx/color_utils.h"
|
||||
#include "ui/gtk/gtk_compat.h" // nogncheck
|
||||
#include "ui/gtk/gtk_util.h" // nogncheck
|
||||
#include "ui/linux/linux_ui.h"
|
||||
@@ -169,36 +169,8 @@ std::u16string MediaStringProvider(media::MessageId id) {
|
||||
}
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// GTK does not provide a way to check if current theme is dark, so we compare
|
||||
// the text and background luminosity to get a result.
|
||||
// This trick comes from FireFox.
|
||||
void UpdateDarkThemeSetting() {
|
||||
float bg = color_utils::GetRelativeLuminance(gtk::GetBgColor("GtkLabel"));
|
||||
float fg = color_utils::GetRelativeLuminance(gtk::GetFgColor("GtkLabel"));
|
||||
bool is_dark = fg > bg;
|
||||
// Pass it to NativeUi theme, which is used by the nativeTheme module and most
|
||||
// places in Electron.
|
||||
ui::NativeTheme::GetInstanceForNativeUi()->set_use_dark_colors(is_dark);
|
||||
// Pass it to Web Theme, to make "prefers-color-scheme" media query work.
|
||||
ui::NativeTheme::GetInstanceForWeb()->set_use_dark_colors(is_dark);
|
||||
}
|
||||
#endif
|
||||
|
||||
} // namespace
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
class DarkThemeObserver : public ui::NativeThemeObserver {
|
||||
public:
|
||||
DarkThemeObserver() = default;
|
||||
|
||||
// ui::NativeThemeObserver:
|
||||
void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override {
|
||||
UpdateDarkThemeSetting();
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
// static
|
||||
ElectronBrowserMainParts* ElectronBrowserMainParts::self_ = nullptr;
|
||||
|
||||
@@ -432,17 +404,10 @@ void ElectronBrowserMainParts::ToolkitInitialized() {
|
||||
CHECK(electron::IsElectron_gdk_pixbufInitialized())
|
||||
<< "Failed to initialize libgdk_pixbuf-2.0.so.0";
|
||||
|
||||
// Chromium does not respect GTK dark theme setting, but they may change
|
||||
// in future and this code might be no longer needed. Check the Chromium
|
||||
// issue to keep updated:
|
||||
// https://bugs.chromium.org/p/chromium/issues/detail?id=998903
|
||||
UpdateDarkThemeSetting();
|
||||
// Update the native theme when GTK theme changes. The GetNativeTheme
|
||||
// here returns a NativeThemeGtk, which monitors GTK settings.
|
||||
dark_theme_observer_ = std::make_unique<DarkThemeObserver>();
|
||||
auto* linux_ui_theme = ui::LinuxUiTheme::GetForProfile(nullptr);
|
||||
CHECK(linux_ui_theme);
|
||||
linux_ui_theme->GetNativeTheme()->AddObserver(dark_theme_observer_.get());
|
||||
// source theme changes from system settings, including settings portal:
|
||||
// https://flatpak.github.io/xdg-desktop-portal/#gdbus-org.freedesktop.portal.Settings
|
||||
dark_mode_manager_ = std::make_unique<ui::DarkModeManagerLinux>();
|
||||
|
||||
ui::LinuxUi::SetInstance(linux_ui);
|
||||
|
||||
// Cursor theme changes are tracked by LinuxUI (via a CursorThemeManager
|
||||
|
||||
@@ -43,7 +43,8 @@ class Environment;
|
||||
|
||||
namespace ui {
|
||||
class LinuxUiGetter;
|
||||
}
|
||||
class DarkModeManagerLinux;
|
||||
} // namespace ui
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -65,10 +66,6 @@ class ViewsDelegate;
|
||||
class ViewsDelegateMac;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
class DarkThemeObserver;
|
||||
#endif
|
||||
|
||||
class ElectronBrowserMainParts : public content::BrowserMainParts {
|
||||
public:
|
||||
ElectronBrowserMainParts();
|
||||
@@ -145,9 +142,7 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// Used to notify the native theme of changes to dark mode.
|
||||
std::unique_ptr<DarkThemeObserver> dark_theme_observer_;
|
||||
|
||||
std::unique_ptr<ui::DarkModeManagerLinux> dark_mode_manager_;
|
||||
std::unique_ptr<ui::LinuxUiGetter> linux_ui_getter_;
|
||||
#endif
|
||||
|
||||
|
||||
@@ -13,15 +13,15 @@
|
||||
#include "chrome/grit/generated_resources.h"
|
||||
#include "components/strings/grit/components_strings.h"
|
||||
#include "components/zoom/page_zoom_constants.h"
|
||||
#include "pdf/buildflags.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "printing/buildflags/buildflags.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/webui/web_ui_util.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF)
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
#include "chrome/browser/pdf/pdf_extension_util.h"
|
||||
#include "pdf/pdf_features.h"
|
||||
#endif // BUILDFLAG(ENABLE_PDF)
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
|
||||
// To add a new component to this API, simply:
|
||||
// 1. Add your component to the Component enum in
|
||||
@@ -48,7 +48,7 @@ ExtensionFunction::ResponseAction ResourcesPrivateGetStringsFunction::Run() {
|
||||
|
||||
switch (component) {
|
||||
case api::resources_private::COMPONENT_PDF:
|
||||
#if BUILDFLAG(ENABLE_PDF)
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
pdf_extension_util::AddStrings(
|
||||
pdf_extension_util::PdfViewerContext::kPdfViewer, &dict);
|
||||
pdf_extension_util::AddAdditionalData(true, false, &dict);
|
||||
|
||||
@@ -7,20 +7,27 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/strings/pattern.h"
|
||||
#include "base/types/expected_macros.h"
|
||||
#include "chrome/common/url_constants.h"
|
||||
#include "components/url_formatter/url_fixer.h"
|
||||
#include "content/public/browser/navigation_entry.h"
|
||||
#include "extensions/browser/extension_api_frame_id_map.h"
|
||||
#include "extensions/browser/extension_prefs.h"
|
||||
#include "extensions/common/error_utils.h"
|
||||
#include "extensions/common/extension_features.h"
|
||||
#include "extensions/common/feature_switch.h"
|
||||
#include "extensions/common/manifest_constants.h"
|
||||
#include "extensions/common/mojom/host_id.mojom.h"
|
||||
#include "extensions/common/permissions/permissions_data.h"
|
||||
#include "extensions/common/switches.h"
|
||||
#include "shell/browser/api/electron_api_web_contents.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/browser/web_contents_zoom_controller.h"
|
||||
#include "shell/browser/window_list.h"
|
||||
#include "shell/common/extensions/api/tabs.h"
|
||||
#include "third_party/blink/public/common/chrome_debug_urls.h"
|
||||
#include "third_party/blink/public/common/page/page_zoom.h"
|
||||
#include "url/gurl.h"
|
||||
|
||||
@@ -468,17 +475,25 @@ bool IsKillURL(const GURL& url) {
|
||||
DCHECK(url.IsAboutBlank() || url.IsAboutSrcdoc());
|
||||
#endif
|
||||
|
||||
static const char* const kill_hosts[] = {
|
||||
chrome::kChromeUICrashHost, chrome::kChromeUIDelayedHangUIHost,
|
||||
chrome::kChromeUIHangUIHost, chrome::kChromeUIKillHost,
|
||||
// Disallow common renderer debug URLs.
|
||||
// Note: this would also disallow JavaScript URLs, but we already explicitly
|
||||
// check for those before calling into here from PrepareURLForNavigation.
|
||||
if (blink::IsRendererDebugURL(url)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!url.SchemeIs(content::kChromeUIScheme)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Also disallow a few more hosts which are not covered by the check above.
|
||||
static const char* const kKillHosts[] = {
|
||||
chrome::kChromeUIDelayedHangUIHost, chrome::kChromeUIHangUIHost,
|
||||
chrome::kChromeUIQuitHost, chrome::kChromeUIRestartHost,
|
||||
content::kChromeUIBrowserCrashHost, content::kChromeUIMemoryExhaustHost,
|
||||
};
|
||||
|
||||
if (!url.SchemeIs(content::kChromeUIScheme))
|
||||
return false;
|
||||
|
||||
return base::Contains(kill_hosts, url.host_piece());
|
||||
return base::Contains(kKillHosts, url.host_piece());
|
||||
}
|
||||
|
||||
GURL ResolvePossiblyRelativeURL(const std::string& url_string,
|
||||
@@ -489,10 +504,18 @@ GURL ResolvePossiblyRelativeURL(const std::string& url_string,
|
||||
|
||||
return url;
|
||||
}
|
||||
bool PrepareURLForNavigation(const std::string& url_string,
|
||||
const Extension* extension,
|
||||
GURL* return_url,
|
||||
std::string* error) {
|
||||
|
||||
bool AllowFileAccess(const ExtensionId& extension_id,
|
||||
content::BrowserContext* context) {
|
||||
return base::CommandLine::ForCurrentProcess()->HasSwitch(
|
||||
switches::kDisableExtensionsFileAccessCheck) ||
|
||||
ExtensionPrefs::Get(context)->AllowFileAccess(extension_id);
|
||||
}
|
||||
|
||||
base::expected<GURL, std::string> PrepareURLForNavigation(
|
||||
const std::string& url_string,
|
||||
const Extension* extension,
|
||||
content::BrowserContext* browser_context) {
|
||||
GURL url = ResolvePossiblyRelativeURL(url_string, extension);
|
||||
|
||||
// Ideally, the URL would only be "fixed" for user input (e.g. for URLs
|
||||
@@ -504,34 +527,62 @@ bool PrepareURLForNavigation(const std::string& url_string,
|
||||
// Reject invalid URLs.
|
||||
if (!url.is_valid()) {
|
||||
const char kInvalidUrlError[] = "Invalid url: \"*\".";
|
||||
*error = ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string);
|
||||
return false;
|
||||
return base::unexpected(
|
||||
ErrorUtils::FormatErrorMessage(kInvalidUrlError, url_string));
|
||||
}
|
||||
|
||||
// Don't let the extension use JavaScript URLs in API triggered navigations.
|
||||
if (url.SchemeIs(url::kJavaScriptScheme)) {
|
||||
const char kJavaScriptUrlsNotAllowedInExtensionNavigations[] =
|
||||
"JavaScript URLs are not allowed in API based extension navigations. "
|
||||
"Use "
|
||||
"chrome.scripting.executeScript instead.";
|
||||
return base::unexpected(kJavaScriptUrlsNotAllowedInExtensionNavigations);
|
||||
}
|
||||
|
||||
// Don't let the extension crash the browser or renderers.
|
||||
if (IsKillURL(url)) {
|
||||
const char kNoCrashBrowserError[] =
|
||||
"I'm sorry. I'm afraid I can't do that.";
|
||||
*error = kNoCrashBrowserError;
|
||||
return false;
|
||||
return base::unexpected(kNoCrashBrowserError);
|
||||
}
|
||||
|
||||
// Don't let the extension navigate directly to devtools scheme pages, unless
|
||||
// they have applicable permissions.
|
||||
if (url.SchemeIs(content::kChromeDevToolsScheme) &&
|
||||
!(extension->permissions_data()->HasAPIPermission(
|
||||
extensions::mojom::APIPermissionID::kDevtools) ||
|
||||
extension->permissions_data()->HasAPIPermission(
|
||||
extensions::mojom::APIPermissionID::kDebugger))) {
|
||||
const char kCannotNavigateToDevtools[] =
|
||||
"Cannot navigate to a devtools:// page without either the devtools or "
|
||||
"debugger permission.";
|
||||
*error = kCannotNavigateToDevtools;
|
||||
return false;
|
||||
if (url.SchemeIs(content::kChromeDevToolsScheme)) {
|
||||
bool has_permission =
|
||||
extension && (extension->permissions_data()->HasAPIPermission(
|
||||
mojom::APIPermissionID::kDevtools) ||
|
||||
extension->permissions_data()->HasAPIPermission(
|
||||
mojom::APIPermissionID::kDebugger));
|
||||
if (!has_permission) {
|
||||
const char kCannotNavigateToDevtools[] =
|
||||
"Cannot navigate to a devtools:// page without either the devtools "
|
||||
"or "
|
||||
"debugger permission.";
|
||||
return base::unexpected(kCannotNavigateToDevtools);
|
||||
}
|
||||
}
|
||||
|
||||
return_url->Swap(&url);
|
||||
return true;
|
||||
// Don't let the extension navigate directly to chrome-untrusted scheme pages.
|
||||
if (url.SchemeIs(content::kChromeUIUntrustedScheme)) {
|
||||
const char kCannotNavigateToChromeUntrusted[] =
|
||||
"Cannot navigate to a chrome-untrusted:// page.";
|
||||
return base::unexpected(kCannotNavigateToChromeUntrusted);
|
||||
}
|
||||
|
||||
// Don't let the extension navigate directly to file scheme pages, unless
|
||||
// they have file access.
|
||||
if (url.SchemeIsFile() &&
|
||||
!AllowFileAccess(extension->id(), browser_context) &&
|
||||
base::FeatureList::IsEnabled(
|
||||
extensions_features::kRestrictFileURLNavigation)) {
|
||||
const char kFileUrlsNotAllowedInExtensionNavigations[] =
|
||||
"Cannot navigate to a file URL without local file access.";
|
||||
return base::unexpected(kFileUrlsNotAllowedInExtensionNavigations);
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
TabsUpdateFunction::TabsUpdateFunction() : web_contents_(nullptr) {}
|
||||
@@ -566,22 +617,14 @@ ExtensionFunction::ResponseAction TabsUpdateFunction::Run() {
|
||||
bool TabsUpdateFunction::UpdateURL(const std::string& url_string,
|
||||
int tab_id,
|
||||
std::string* error) {
|
||||
GURL url;
|
||||
if (!PrepareURLForNavigation(url_string, extension(), &url, error)) {
|
||||
auto url =
|
||||
PrepareURLForNavigation(url_string, extension(), browser_context());
|
||||
if (!url.has_value()) {
|
||||
*error = std::move(url.error());
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool is_javascript_scheme = url.SchemeIs(url::kJavaScriptScheme);
|
||||
// JavaScript URLs are forbidden in chrome.tabs.update().
|
||||
if (is_javascript_scheme) {
|
||||
const char kJavaScriptUrlsNotAllowedInTabsUpdate[] =
|
||||
"JavaScript URLs are not allowed in chrome.tabs.update. Use "
|
||||
"chrome.tabs.executeScript instead.";
|
||||
*error = kJavaScriptUrlsNotAllowedInTabsUpdate;
|
||||
return false;
|
||||
}
|
||||
|
||||
content::NavigationController::LoadURLParams load_params(url);
|
||||
content::NavigationController::LoadURLParams load_params(*url);
|
||||
|
||||
// Treat extension-initiated navigations as renderer-initiated so that the URL
|
||||
// does not show in the omnibox until it commits. This avoids URL spoofs
|
||||
|
||||
@@ -9,8 +9,10 @@
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "chrome/common/chrome_features.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "services/device/public/cpp/hid/hid_switches.h"
|
||||
#include "shell/browser/electron_permission_manager.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
@@ -19,9 +21,9 @@
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
#include "third_party/blink/public/common/permissions/permission_utils.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
#include "extensions/common/constants.h"
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -35,6 +37,70 @@ electron::HidChooserContext* GetChooserContext(
|
||||
|
||||
namespace electron {
|
||||
|
||||
// Manages the HidDelegate observers for a single browser context.
|
||||
class ElectronHidDelegate::ContextObservation
|
||||
: public HidChooserContext::DeviceObserver {
|
||||
public:
|
||||
ContextObservation(ElectronHidDelegate* parent,
|
||||
content::BrowserContext* browser_context)
|
||||
: parent_(parent), browser_context_(browser_context) {
|
||||
auto* chooser_context = GetChooserContext(browser_context_);
|
||||
device_observation_.Observe(chooser_context);
|
||||
}
|
||||
|
||||
ContextObservation(ContextObservation&) = delete;
|
||||
ContextObservation& operator=(ContextObservation&) = delete;
|
||||
~ContextObservation() override = default;
|
||||
|
||||
// HidChooserContext::DeviceObserver:
|
||||
void OnDeviceAdded(const device::mojom::HidDeviceInfo& device_info) override {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceAdded(device_info);
|
||||
}
|
||||
|
||||
void OnDeviceRemoved(
|
||||
const device::mojom::HidDeviceInfo& device_info) override {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceRemoved(device_info);
|
||||
}
|
||||
|
||||
void OnDeviceChanged(
|
||||
const device::mojom::HidDeviceInfo& device_info) override {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceChanged(device_info);
|
||||
}
|
||||
|
||||
void OnHidManagerConnectionError() override {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnHidManagerConnectionError();
|
||||
}
|
||||
|
||||
void OnHidChooserContextShutdown() override {
|
||||
parent_->observations_.erase(browser_context_);
|
||||
// Return since `this` is now deleted.
|
||||
}
|
||||
|
||||
void AddObserver(content::HidDelegate::Observer* observer) {
|
||||
observer_list_.AddObserver(observer);
|
||||
}
|
||||
|
||||
void RemoveObserver(content::HidDelegate::Observer* observer) {
|
||||
observer_list_.RemoveObserver(observer);
|
||||
}
|
||||
|
||||
private:
|
||||
// Safe because `parent_` owns `this`.
|
||||
const raw_ptr<ElectronHidDelegate> parent_;
|
||||
|
||||
// Safe because `this` is destroyed when the context is lost.
|
||||
const raw_ptr<content::BrowserContext> browser_context_;
|
||||
|
||||
base::ScopedObservation<HidChooserContext, HidChooserContext::DeviceObserver>
|
||||
device_observation_{this};
|
||||
|
||||
base::ObserverList<content::HidDelegate::Observer> observer_list_;
|
||||
};
|
||||
|
||||
ElectronHidDelegate::ElectronHidDelegate() = default;
|
||||
|
||||
ElectronHidDelegate::~ElectronHidDelegate() = default;
|
||||
@@ -44,11 +110,11 @@ std::unique_ptr<content::HidChooser> ElectronHidDelegate::RunChooser(
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> filters,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> exclusion_filters,
|
||||
content::HidChooser::Callback callback) {
|
||||
DCHECK(render_frame_host);
|
||||
auto* chooser_context =
|
||||
GetChooserContext(render_frame_host->GetBrowserContext());
|
||||
if (!device_observation_.IsObserving())
|
||||
device_observation_.Observe(chooser_context);
|
||||
auto* browser_context = render_frame_host->GetBrowserContext();
|
||||
|
||||
// Start observing HidChooserContext for permission and device events.
|
||||
GetContextObserver(browser_context);
|
||||
DCHECK(base::Contains(observations_, browser_context));
|
||||
|
||||
HidChooserController* controller = ControllerForFrame(render_frame_host);
|
||||
if (controller) {
|
||||
@@ -100,16 +166,14 @@ device::mojom::HidManager* ElectronHidDelegate::GetHidManager(
|
||||
|
||||
void ElectronHidDelegate::AddObserver(content::BrowserContext* browser_context,
|
||||
Observer* observer) {
|
||||
observer_list_.AddObserver(observer);
|
||||
auto* chooser_context = GetChooserContext(browser_context);
|
||||
if (!device_observation_.IsObserving())
|
||||
device_observation_.Observe(chooser_context);
|
||||
GetContextObserver(browser_context)->AddObserver(observer);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::RemoveObserver(
|
||||
content::BrowserContext* browser_context,
|
||||
content::HidDelegate::Observer* observer) {
|
||||
observer_list_.RemoveObserver(observer);
|
||||
DCHECK(base::Contains(observations_, browser_context));
|
||||
GetContextObserver(browser_context)->RemoveObserver(observer);
|
||||
}
|
||||
|
||||
const device::mojom::HidDeviceInfo* ElectronHidDelegate::GetDeviceInfo(
|
||||
@@ -128,44 +192,25 @@ bool ElectronHidDelegate::IsFidoAllowedForOrigin(
|
||||
|
||||
bool ElectronHidDelegate::IsServiceWorkerAllowedForOrigin(
|
||||
const url::Origin& origin) {
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
// WebHID is only available on extension service workers with feature flag
|
||||
// enabled for now.
|
||||
if (base::FeatureList::IsEnabled(
|
||||
features::kEnableWebHidOnExtensionServiceWorker) &&
|
||||
origin.scheme() == extensions::kExtensionScheme)
|
||||
return true;
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
return false;
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnDeviceAdded(
|
||||
const device::mojom::HidDeviceInfo& device_info) {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceAdded(device_info);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnDeviceRemoved(
|
||||
const device::mojom::HidDeviceInfo& device_info) {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceRemoved(device_info);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnDeviceChanged(
|
||||
const device::mojom::HidDeviceInfo& device_info) {
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnDeviceChanged(device_info);
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnHidManagerConnectionError() {
|
||||
device_observation_.Reset();
|
||||
|
||||
for (auto& observer : observer_list_)
|
||||
observer.OnHidManagerConnectionError();
|
||||
}
|
||||
|
||||
void ElectronHidDelegate::OnHidChooserContextShutdown() {
|
||||
device_observation_.Reset();
|
||||
ElectronHidDelegate::ContextObservation*
|
||||
ElectronHidDelegate::GetContextObserver(
|
||||
content::BrowserContext* browser_context) {
|
||||
if (!base::Contains(observations_, browser_context)) {
|
||||
observations_.emplace(browser_context, std::make_unique<ContextObservation>(
|
||||
this, browser_context));
|
||||
}
|
||||
return observations_[browser_context].get();
|
||||
}
|
||||
|
||||
HidChooserController* ElectronHidDelegate::ControllerForFrame(
|
||||
|
||||
@@ -10,17 +10,24 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "base/observer_list.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "content/public/browser/hid_chooser.h"
|
||||
#include "content/public/browser/hid_delegate.h"
|
||||
#include "services/device/public/mojom/hid.mojom-forward.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
#include "third_party/blink/public/mojom/hid/hid.mojom-forward.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
class RenderFrameHost;
|
||||
} // namespace content
|
||||
|
||||
namespace electron {
|
||||
|
||||
class HidChooserController;
|
||||
|
||||
class ElectronHidDelegate : public content::HidDelegate,
|
||||
public HidChooserContext::DeviceObserver {
|
||||
class ElectronHidDelegate : public content::HidDelegate {
|
||||
public:
|
||||
ElectronHidDelegate();
|
||||
ElectronHidDelegate(ElectronHidDelegate&) = delete;
|
||||
@@ -54,13 +61,6 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
bool IsFidoAllowedForOrigin(content::BrowserContext* browser_context,
|
||||
const url::Origin& origin) override;
|
||||
bool IsServiceWorkerAllowedForOrigin(const url::Origin& origin) override;
|
||||
|
||||
// HidChooserContext::DeviceObserver:
|
||||
void OnDeviceAdded(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnDeviceRemoved(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnDeviceChanged(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnHidManagerConnectionError() override;
|
||||
void OnHidChooserContextShutdown() override;
|
||||
void IncrementConnectionCount(content::BrowserContext* browser_context,
|
||||
const url::Origin& origin) override {}
|
||||
void DecrementConnectionCount(content::BrowserContext* browser_context,
|
||||
@@ -69,6 +69,14 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
void DeleteControllerForFrame(content::RenderFrameHost* render_frame_host);
|
||||
|
||||
private:
|
||||
class ContextObservation;
|
||||
|
||||
ContextObservation* GetContextObserver(
|
||||
content::BrowserContext* browser_context);
|
||||
|
||||
base::flat_map<content::BrowserContext*, std::unique_ptr<ContextObservation>>
|
||||
observations_;
|
||||
|
||||
HidChooserController* ControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
@@ -78,10 +86,6 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> exclusion_filters,
|
||||
content::HidChooser::Callback callback);
|
||||
|
||||
base::ScopedObservation<HidChooserContext, HidChooserContext::DeviceObserver>
|
||||
device_observation_{this};
|
||||
base::ObserverList<content::HidDelegate::Observer> observer_list_;
|
||||
|
||||
std::unordered_map<content::RenderFrameHost*,
|
||||
std::unique_ptr<HidChooserController>>
|
||||
controller_map_;
|
||||
@@ -91,23 +95,4 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
|
||||
} // namespace electron
|
||||
|
||||
namespace base {
|
||||
|
||||
template <>
|
||||
struct ScopedObservationTraits<electron::HidChooserContext,
|
||||
electron::HidChooserContext::DeviceObserver> {
|
||||
static void AddObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->AddDeviceObserver(observer);
|
||||
}
|
||||
static void RemoveObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->RemoveDeviceObserver(observer);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_HID_ELECTRON_HID_DELEGATE_H_
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/scoped_observation_traits.h"
|
||||
#include "base/unguessable_token.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
@@ -138,4 +139,23 @@ class HidChooserContext : public KeyedService,
|
||||
|
||||
} // namespace electron
|
||||
|
||||
namespace base {
|
||||
|
||||
template <>
|
||||
struct ScopedObservationTraits<electron::HidChooserContext,
|
||||
electron::HidChooserContext::DeviceObserver> {
|
||||
static void AddObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->AddDeviceObserver(observer);
|
||||
}
|
||||
static void RemoveObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->RemoveDeviceObserver(observer);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_HID_HID_CHOOSER_CONTEXT_H_
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "content/public/browser/global_routing_id.h"
|
||||
#include "content/public/browser/hid_chooser.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
|
||||
@@ -99,6 +99,11 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
|
||||
options.Get(options::kTransparent, &transparent_);
|
||||
options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_);
|
||||
options.Get(options::kTitleBarStyle, &title_bar_style_);
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
options.Get(options::kBackgroundMaterial, &background_material_);
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
options.Get(options::kVibrancyType, &vibrancy_);
|
||||
#endif
|
||||
|
||||
v8::Local<v8::Value> titlebar_overlay;
|
||||
if (options.Get(options::ktitleBarOverlay, &titlebar_overlay)) {
|
||||
@@ -352,10 +357,14 @@ void NativeWindow::SetContentSizeConstraints(
|
||||
size_constraints_.reset();
|
||||
}
|
||||
|
||||
// Windows/Linux:
|
||||
// The return value of GetContentSizeConstraints will be passed to Chromium
|
||||
// to set min/max sizes of window. Note that we are returning content size
|
||||
// instead of window size because that is what Chromium expects, see the
|
||||
// comment of |WidgetSizeIsClientSize| in Chromium's codebase to learn more.
|
||||
//
|
||||
// macOS:
|
||||
// The min/max sizes are set directly by calling NSWindow's methods.
|
||||
extensions::SizeConstraints NativeWindow::GetContentSizeConstraints() const {
|
||||
if (content_size_constraints_)
|
||||
return *content_size_constraints_;
|
||||
@@ -475,6 +484,10 @@ bool NativeWindow::AddTabbedWindow(NativeWindow* window) {
|
||||
return true; // for non-Mac platforms
|
||||
}
|
||||
|
||||
absl::optional<std::string> NativeWindow::GetTabbingIdentifier() const {
|
||||
return ""; // for non-Mac platforms
|
||||
}
|
||||
|
||||
void NativeWindow::SetVibrancy(const std::string& type) {
|
||||
vibrancy_ = type;
|
||||
}
|
||||
@@ -522,7 +535,7 @@ void NativeWindow::PreviewFile(const std::string& path,
|
||||
|
||||
void NativeWindow::CloseFilePreview() {}
|
||||
|
||||
gfx::Rect NativeWindow::GetWindowControlsOverlayRect() {
|
||||
absl::optional<gfx::Rect> NativeWindow::GetWindowControlsOverlayRect() {
|
||||
return overlay_rect_;
|
||||
}
|
||||
|
||||
@@ -650,6 +663,7 @@ void NativeWindow::NotifyWindowMoved() {
|
||||
}
|
||||
|
||||
void NativeWindow::NotifyWindowEnterFullScreen() {
|
||||
NotifyLayoutWindowControlsOverlay();
|
||||
for (NativeWindowObserver& observer : observers_)
|
||||
observer.OnWindowEnterFullScreen();
|
||||
}
|
||||
@@ -675,6 +689,7 @@ void NativeWindow::NotifyWindowSheetEnd() {
|
||||
}
|
||||
|
||||
void NativeWindow::NotifyWindowLeaveFullScreen() {
|
||||
NotifyLayoutWindowControlsOverlay();
|
||||
for (NativeWindowObserver& observer : observers_)
|
||||
observer.OnWindowLeaveFullScreen();
|
||||
}
|
||||
@@ -718,10 +733,10 @@ void NativeWindow::NotifyWindowSystemContextMenu(int x,
|
||||
}
|
||||
|
||||
void NativeWindow::NotifyLayoutWindowControlsOverlay() {
|
||||
gfx::Rect bounding_rect = GetWindowControlsOverlayRect();
|
||||
if (!bounding_rect.IsEmpty()) {
|
||||
auto bounding_rect = GetWindowControlsOverlayRect();
|
||||
if (bounding_rect.has_value()) {
|
||||
for (NativeWindowObserver& observer : observers_)
|
||||
observer.UpdateWindowControlsOverlay(bounding_rect);
|
||||
observer.UpdateWindowControlsOverlay(bounding_rect.value());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -255,6 +255,7 @@ class NativeWindow : public base::SupportsUserData,
|
||||
virtual void MoveTabToNewWindow();
|
||||
virtual void ToggleTabBar();
|
||||
virtual bool AddTabbedWindow(NativeWindow* window);
|
||||
virtual absl::optional<std::string> GetTabbingIdentifier() const;
|
||||
|
||||
// Toggle the menu bar.
|
||||
virtual void SetAutoHideMenuBar(bool auto_hide);
|
||||
@@ -284,7 +285,7 @@ class NativeWindow : public base::SupportsUserData,
|
||||
return weak_factory_.GetWeakPtr();
|
||||
}
|
||||
|
||||
virtual gfx::Rect GetWindowControlsOverlayRect();
|
||||
virtual absl::optional<gfx::Rect> GetWindowControlsOverlayRect();
|
||||
virtual void SetWindowControlsOverlayRect(const gfx::Rect& overlay_rect);
|
||||
|
||||
// Methods called by the WebContents.
|
||||
|
||||
@@ -58,6 +58,8 @@ class NativeWindowMac : public NativeWindow,
|
||||
gfx::Rect GetBounds() override;
|
||||
bool IsNormal() override;
|
||||
gfx::Rect GetNormalBounds() override;
|
||||
void SetSizeConstraints(
|
||||
const extensions::SizeConstraints& window_constraints) override;
|
||||
void SetContentSizeConstraints(
|
||||
const extensions::SizeConstraints& size_constraints) override;
|
||||
void SetResizable(bool resizable) override;
|
||||
@@ -143,6 +145,7 @@ class NativeWindowMac : public NativeWindow,
|
||||
void MoveTabToNewWindow() override;
|
||||
void ToggleTabBar() override;
|
||||
bool AddTabbedWindow(NativeWindow* window) override;
|
||||
absl::optional<std::string> GetTabbingIdentifier() const override;
|
||||
void SetAspectRatio(double aspect_ratio,
|
||||
const gfx::Size& extra_size) override;
|
||||
void PreviewFile(const std::string& path,
|
||||
@@ -150,7 +153,7 @@ class NativeWindowMac : public NativeWindow,
|
||||
void CloseFilePreview() override;
|
||||
gfx::Rect ContentBoundsToWindowBounds(const gfx::Rect& bounds) const override;
|
||||
gfx::Rect WindowBoundsToContentBounds(const gfx::Rect& bounds) const override;
|
||||
gfx::Rect GetWindowControlsOverlayRect() override;
|
||||
absl::optional<gfx::Rect> GetWindowControlsOverlayRect() override;
|
||||
void NotifyWindowEnterFullScreen() override;
|
||||
void NotifyWindowLeaveFullScreen() override;
|
||||
void SetActive(bool is_key) override;
|
||||
@@ -186,6 +189,9 @@ class NativeWindowMac : public NativeWindow,
|
||||
has_deferred_window_close_ = defer_close;
|
||||
}
|
||||
|
||||
void set_wants_to_be_visible(bool visible) { wants_to_be_visible_ = visible; }
|
||||
bool wants_to_be_visible() const { return wants_to_be_visible_; }
|
||||
|
||||
enum class VisualEffectState {
|
||||
kFollowWindow,
|
||||
kActive,
|
||||
@@ -252,6 +258,10 @@ class NativeWindowMac : public NativeWindow,
|
||||
// transition is complete.
|
||||
bool has_deferred_window_close_ = false;
|
||||
|
||||
// If true, the window is either visible, or wants to be visible but is
|
||||
// currently hidden due to having a hidden parent.
|
||||
bool wants_to_be_visible_ = false;
|
||||
|
||||
NSInteger attention_request_id_ = 0; // identifier from requestUserAttention
|
||||
|
||||
// The presentation options before entering kiosk mode.
|
||||
|
||||
@@ -496,6 +496,8 @@ void NativeWindowMac::Show() {
|
||||
return;
|
||||
}
|
||||
|
||||
set_wants_to_be_visible(true);
|
||||
|
||||
// Reattach the window to the parent to actually show it.
|
||||
if (parent())
|
||||
InternalSetParentWindow(parent(), true);
|
||||
@@ -528,6 +530,10 @@ void NativeWindowMac::Hide() {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the window wants to be visible and has a parent, then the parent may
|
||||
// order it back in (in the period between orderOut: and close).
|
||||
set_wants_to_be_visible(false);
|
||||
|
||||
DetachChildren();
|
||||
|
||||
// Detach the window from the parent before.
|
||||
@@ -663,6 +669,9 @@ void NativeWindowMac::RemoveChildFromParentWindow() {
|
||||
|
||||
void NativeWindowMac::AttachChildren() {
|
||||
for (auto* child : child_windows_) {
|
||||
if (!static_cast<NativeWindowMac*>(child)->wants_to_be_visible())
|
||||
continue;
|
||||
|
||||
auto* child_nswindow = child->GetNativeWindow().GetNativeNSWindow();
|
||||
if ([child_nswindow parentWindow] == window_)
|
||||
continue;
|
||||
@@ -676,8 +685,6 @@ void NativeWindowMac::AttachChildren() {
|
||||
}
|
||||
|
||||
void NativeWindowMac::DetachChildren() {
|
||||
DCHECK(child_windows_.size() == [[window_ childWindows] count]);
|
||||
|
||||
// Hide all children before hiding/minimizing the window.
|
||||
// NativeWidgetNSWindowBridge::NotifyVisibilityChangeDown()
|
||||
// will DCHECK otherwise.
|
||||
@@ -774,6 +781,16 @@ gfx::Rect NativeWindowMac::GetNormalBounds() {
|
||||
// return widget()->GetRestoredBounds();
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetSizeConstraints(
|
||||
const extensions::SizeConstraints& window_constraints) {
|
||||
// Apply the size constraints to NSWindow.
|
||||
if (window_constraints.HasMinimumSize())
|
||||
[window_ setMinSize:window_constraints.GetMinimumSize().ToCGSize()];
|
||||
if (window_constraints.HasMaximumSize())
|
||||
[window_ setMaxSize:window_constraints.GetMaximumSize().ToCGSize()];
|
||||
NativeWindow::SetSizeConstraints(window_constraints);
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetContentSizeConstraints(
|
||||
const extensions::SizeConstraints& size_constraints) {
|
||||
auto convertSize = [this](const gfx::Size& size) {
|
||||
@@ -788,6 +805,7 @@ void NativeWindowMac::SetContentSizeConstraints(
|
||||
}
|
||||
};
|
||||
|
||||
// Apply the size constraints to NSWindow.
|
||||
NSView* content = [window_ contentView];
|
||||
if (size_constraints.HasMinimumSize()) {
|
||||
NSSize min_size = convertSize(size_constraints.GetMinimumSize());
|
||||
@@ -1422,11 +1440,21 @@ void NativeWindowMac::UpdateVibrancyRadii(bool fullscreen) {
|
||||
|
||||
if (vibrantView != nil && !vibrancy_type_.empty()) {
|
||||
const bool no_rounded_corner = !HasStyleMask(NSWindowStyleMaskTitled);
|
||||
if (!has_frame() && !is_modal() && !no_rounded_corner) {
|
||||
const int macos_version = base::mac::MacOSMajorVersion();
|
||||
|
||||
// Modal window corners are rounded on macOS >= 11 or higher if the user
|
||||
// hasn't passed noRoundedCorners.
|
||||
bool should_round_modal =
|
||||
!no_rounded_corner && (macos_version >= 11 ? true : !is_modal());
|
||||
// Nonmodal window corners are rounded if they're frameless and the user
|
||||
// hasn't passed noRoundedCorners.
|
||||
bool should_round_nonmodal = !no_rounded_corner && !has_frame();
|
||||
|
||||
if (should_round_nonmodal || should_round_modal) {
|
||||
CGFloat radius;
|
||||
if (fullscreen) {
|
||||
radius = 0.0f;
|
||||
} else if (@available(macOS 11.0, *)) {
|
||||
} else if (macos_version >= 11) {
|
||||
radius = 9.0f;
|
||||
} else {
|
||||
// Smaller corner radius on versions prior to Big Sur.
|
||||
@@ -1635,6 +1663,13 @@ bool NativeWindowMac::AddTabbedWindow(NativeWindow* window) {
|
||||
return true;
|
||||
}
|
||||
|
||||
absl::optional<std::string> NativeWindowMac::GetTabbingIdentifier() const {
|
||||
if ([window_ tabbingMode] == NSWindowTabbingModeDisallowed)
|
||||
return absl::nullopt;
|
||||
|
||||
return base::SysNSStringToUTF8([window_ tabbingIdentifier]);
|
||||
}
|
||||
|
||||
void NativeWindowMac::SetAspectRatio(double aspect_ratio,
|
||||
const gfx::Size& extra_size) {
|
||||
NativeWindow::SetAspectRatio(aspect_ratio, extra_size);
|
||||
@@ -1875,23 +1910,33 @@ void NativeWindowMac::SetForwardMouseMessages(bool forward) {
|
||||
[window_ setAcceptsMouseMovedEvents:forward];
|
||||
}
|
||||
|
||||
gfx::Rect NativeWindowMac::GetWindowControlsOverlayRect() {
|
||||
if (titlebar_overlay_ && buttons_proxy_ &&
|
||||
window_button_visibility_.value_or(true)) {
|
||||
absl::optional<gfx::Rect> NativeWindowMac::GetWindowControlsOverlayRect() {
|
||||
if (!titlebar_overlay_)
|
||||
return absl::nullopt;
|
||||
|
||||
// On macOS, when in fullscreen mode, window controls (the menu bar, title
|
||||
// bar, and toolbar) are attached to a separate NSView that slides down from
|
||||
// the top of the screen, independent of, and overlapping the WebContents.
|
||||
// Disable WCO when in fullscreen, because this space is inaccessible to
|
||||
// WebContents. https://crbug.com/915110.
|
||||
if (IsFullscreen())
|
||||
return gfx::Rect();
|
||||
|
||||
if (buttons_proxy_ && window_button_visibility_.value_or(true)) {
|
||||
NSRect buttons = [buttons_proxy_ getButtonsContainerBounds];
|
||||
gfx::Rect overlay;
|
||||
overlay.set_width(GetContentSize().width() - NSWidth(buttons));
|
||||
if ([buttons_proxy_ useCustomHeight]) {
|
||||
overlay.set_height(titlebar_overlay_height());
|
||||
} else {
|
||||
overlay.set_height(NSHeight(buttons));
|
||||
}
|
||||
overlay.set_height([buttons_proxy_ useCustomHeight]
|
||||
? titlebar_overlay_height()
|
||||
: NSHeight(buttons));
|
||||
|
||||
if (!base::i18n::IsRTL())
|
||||
overlay.set_x(NSMaxX(buttons));
|
||||
|
||||
return overlay;
|
||||
}
|
||||
return gfx::Rect();
|
||||
|
||||
return absl::nullopt;
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include "base/win/atl.h" // Must be before UIAutomationCore.h
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "content/public/browser/browser_accessibility_state.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
@@ -165,10 +166,41 @@ gfx::ResizeEdge GetWindowResizeEdge(WPARAM param) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsMutexPresent(const wchar_t* name) {
|
||||
base::win::ScopedHandle mutex_holder(::CreateMutex(nullptr, false, name));
|
||||
return ::GetLastError() == ERROR_ALREADY_EXISTS;
|
||||
}
|
||||
|
||||
bool IsLibraryLoaded(const wchar_t* name) {
|
||||
HMODULE hmodule = nullptr;
|
||||
::GetModuleHandleEx(GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT, name,
|
||||
&hmodule);
|
||||
return hmodule != nullptr;
|
||||
}
|
||||
|
||||
// The official way to get screen reader status is to call:
|
||||
// SystemParametersInfo(SPI_GETSCREENREADER) && UiaClientsAreListening()
|
||||
// However it has false positives (for example when user is using touch screens)
|
||||
// and will cause performance issues in some apps.
|
||||
bool IsScreenReaderActive() {
|
||||
UINT screenReader = 0;
|
||||
SystemParametersInfo(SPI_GETSCREENREADER, 0, &screenReader, 0);
|
||||
return screenReader && UiaClientsAreListening();
|
||||
if (IsMutexPresent(L"NarratorRunning"))
|
||||
return true;
|
||||
|
||||
static const wchar_t* names[] = {// NVDA
|
||||
L"nvdaHelperRemote.dll",
|
||||
// JAWS
|
||||
L"jhook.dll",
|
||||
// Window-Eyes
|
||||
L"gwhk64.dll", L"gwmhook.dll",
|
||||
// ZoomText
|
||||
L"AiSquared.Infuser.HookLib.dll"};
|
||||
|
||||
for (auto* name : names) {
|
||||
if (IsLibraryLoaded(name))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -27,10 +27,14 @@ void Notification::NotificationClicked() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Notification::NotificationDismissed() {
|
||||
void Notification::NotificationDismissed(bool should_destroy) {
|
||||
if (delegate())
|
||||
delegate()->NotificationClosed();
|
||||
Destroy();
|
||||
|
||||
set_is_dismissed(true);
|
||||
|
||||
if (should_destroy)
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Notification::NotificationFailed(const std::string& error) {
|
||||
@@ -39,6 +43,8 @@ void Notification::NotificationFailed(const std::string& error) {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Notification::Remove() {}
|
||||
|
||||
void Notification::Destroy() {
|
||||
presenter()->RemoveNotification(this);
|
||||
}
|
||||
|
||||
@@ -50,13 +50,19 @@ class Notification {
|
||||
|
||||
// Shows the notification.
|
||||
virtual void Show(const NotificationOptions& options) = 0;
|
||||
// Closes the notification, this instance will be destroyed after the
|
||||
// notification gets closed.
|
||||
|
||||
// Dismisses the notification. On some platforms this will result in full
|
||||
// removal and destruction of the notification, but if the initial dismissal
|
||||
// does not fully get rid of the notification it will be destroyed in Remove.
|
||||
virtual void Dismiss() = 0;
|
||||
|
||||
// Removes the notification if it was not fully removed during dismissal,
|
||||
// as can happen on some platforms including Windows.
|
||||
virtual void Remove();
|
||||
|
||||
// Should be called by derived classes.
|
||||
void NotificationClicked();
|
||||
void NotificationDismissed();
|
||||
void NotificationDismissed(bool should_destroy = true);
|
||||
void NotificationFailed(const std::string& error = "");
|
||||
|
||||
// delete this.
|
||||
@@ -68,10 +74,12 @@ class Notification {
|
||||
|
||||
void set_delegate(NotificationDelegate* delegate) { delegate_ = delegate; }
|
||||
void set_notification_id(const std::string& id) { notification_id_ = id; }
|
||||
void set_is_dismissed(bool dismissed) { is_dismissed_ = dismissed; }
|
||||
|
||||
NotificationDelegate* delegate() const { return delegate_; }
|
||||
NotificationPresenter* presenter() const { return presenter_; }
|
||||
const std::string& notification_id() const { return notification_id_; }
|
||||
bool is_dismissed() const { return is_dismissed_; }
|
||||
|
||||
// disable copy
|
||||
Notification(const Notification&) = delete;
|
||||
@@ -85,6 +93,7 @@ class Notification {
|
||||
raw_ptr<NotificationDelegate> delegate_;
|
||||
raw_ptr<NotificationPresenter> presenter_;
|
||||
std::string notification_id_;
|
||||
bool is_dismissed_ = false;
|
||||
|
||||
base::WeakPtrFactory<Notification> weak_factory_{this};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "shell/browser/notifications/win/windows_toast_notification.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <wrl\wrappers\corewrappers.h>
|
||||
|
||||
@@ -34,6 +36,8 @@ using ABI::Windows::Data::Xml::Dom::IXmlNodeList;
|
||||
using ABI::Windows::Data::Xml::Dom::IXmlText;
|
||||
using Microsoft::WRL::Wrappers::HStringReference;
|
||||
|
||||
namespace winui = ABI::Windows::UI;
|
||||
|
||||
#define RETURN_IF_FAILED(hr) \
|
||||
do { \
|
||||
HRESULT _hrTemp = hr; \
|
||||
@@ -47,8 +51,7 @@ using Microsoft::WRL::Wrappers::HStringReference;
|
||||
std::string _msgTemp = msg; \
|
||||
if (FAILED(_hrTemp)) { \
|
||||
std::string _err = _msgTemp + ",ERROR " + std::to_string(_hrTemp); \
|
||||
if (IsDebuggingNotifications()) \
|
||||
LOG(INFO) << _err; \
|
||||
DebugLog(_err); \
|
||||
Notification::NotificationFailed(_err); \
|
||||
return _hrTemp; \
|
||||
} \
|
||||
@@ -58,17 +61,23 @@ namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsDebuggingNotifications() {
|
||||
return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS");
|
||||
// This string needs to be max 16 characters to work on Windows 10 prior to
|
||||
// applying Creators Update (build 15063).
|
||||
constexpr wchar_t kGroup[] = L"Notifications";
|
||||
|
||||
void DebugLog(std::string_view log_msg) {
|
||||
if (base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS"))
|
||||
LOG(INFO) << log_msg;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationManagerStatics>
|
||||
ComPtr<winui::Notifications::IToastNotificationManagerStatics>
|
||||
WindowsToastNotification::toast_manager_;
|
||||
|
||||
// static
|
||||
ComPtr<ABI::Windows::UI::Notifications::IToastNotifier>
|
||||
ComPtr<winui::Notifications::IToastNotifier>
|
||||
WindowsToastNotification::toast_notifier_;
|
||||
|
||||
// static
|
||||
@@ -112,17 +121,37 @@ WindowsToastNotification::~WindowsToastNotification() {
|
||||
|
||||
void WindowsToastNotification::Show(const NotificationOptions& options) {
|
||||
if (SUCCEEDED(ShowInternal(options))) {
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Notification created";
|
||||
DebugLog("Notification created");
|
||||
|
||||
if (delegate())
|
||||
delegate()->NotificationDisplayed();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowsToastNotification::Remove() {
|
||||
DebugLog("Removing notification from action center");
|
||||
|
||||
ComPtr<winui::Notifications::IToastNotificationManagerStatics2>
|
||||
toast_manager2;
|
||||
if (FAILED(toast_manager_.As(&toast_manager2)))
|
||||
return;
|
||||
|
||||
ComPtr<winui::Notifications::IToastNotificationHistory> notification_history;
|
||||
if (FAILED(toast_manager2->get_History(¬ification_history)))
|
||||
return;
|
||||
|
||||
ScopedHString app_id;
|
||||
if (!GetAppUserModelID(&app_id))
|
||||
return;
|
||||
|
||||
ScopedHString group(kGroup);
|
||||
ScopedHString tag(base::as_wcstr(base::UTF8ToUTF16(notification_id())));
|
||||
notification_history->RemoveGroupedTagWithId(tag, group, app_id);
|
||||
}
|
||||
|
||||
void WindowsToastNotification::Dismiss() {
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Hiding notification";
|
||||
DebugLog("Hiding notification");
|
||||
|
||||
toast_notifier_->Hide(toast_notification_.Get());
|
||||
}
|
||||
|
||||
@@ -151,8 +180,7 @@ HRESULT WindowsToastNotification::ShowInternal(
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationFactory>
|
||||
toast_factory;
|
||||
ComPtr<winui::Notifications::IToastNotificationFactory> toast_factory;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
Windows::Foundation::GetActivationFactory(toast_str, &toast_factory),
|
||||
"WinAPI: GetActivationFactory failed");
|
||||
@@ -161,6 +189,19 @@ HRESULT WindowsToastNotification::ShowInternal(
|
||||
toast_xml.Get(), &toast_notification_),
|
||||
"WinAPI: CreateToastNotification failed");
|
||||
|
||||
ComPtr<winui::Notifications::IToastNotification2> toast2;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
toast_notification_->QueryInterface(IID_PPV_ARGS(&toast2)),
|
||||
"WinAPI: Getting Notification interface failed");
|
||||
|
||||
ScopedHString group(kGroup);
|
||||
REPORT_AND_RETURN_IF_FAILED(toast2->put_Group(group),
|
||||
"WinAPI: Setting group failed");
|
||||
|
||||
ScopedHString tag(base::as_wcstr(base::UTF8ToUTF16(notification_id())));
|
||||
REPORT_AND_RETURN_IF_FAILED(toast2->put_Tag(tag),
|
||||
"WinAPI: Setting tag failed");
|
||||
|
||||
REPORT_AND_RETURN_IF_FAILED(SetupCallbacks(toast_notification_.Get()),
|
||||
"WinAPI: SetupCallbacks failed");
|
||||
|
||||
@@ -170,22 +211,20 @@ HRESULT WindowsToastNotification::ShowInternal(
|
||||
}
|
||||
|
||||
HRESULT WindowsToastNotification::GetToastXml(
|
||||
ABI::Windows::UI::Notifications::IToastNotificationManagerStatics*
|
||||
toastManager,
|
||||
winui::Notifications::IToastNotificationManagerStatics* toastManager,
|
||||
const std::u16string& title,
|
||||
const std::u16string& msg,
|
||||
const std::wstring& icon_path,
|
||||
const std::u16string& timeout_type,
|
||||
bool silent,
|
||||
IXmlDocument** toast_xml) {
|
||||
ABI::Windows::UI::Notifications::ToastTemplateType template_type;
|
||||
winui::Notifications::ToastTemplateType template_type;
|
||||
if (title.empty() || msg.empty()) {
|
||||
// Single line toast.
|
||||
template_type =
|
||||
icon_path.empty()
|
||||
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText01
|
||||
: ABI::Windows::UI::Notifications::
|
||||
ToastTemplateType_ToastImageAndText01;
|
||||
? winui::Notifications::ToastTemplateType_ToastText01
|
||||
: winui::Notifications::ToastTemplateType_ToastImageAndText01;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
toast_manager_->GetTemplateContent(template_type, toast_xml),
|
||||
"XML: Fetching XML ToastImageAndText01 template failed");
|
||||
@@ -199,9 +238,8 @@ HRESULT WindowsToastNotification::GetToastXml(
|
||||
// Title and body toast.
|
||||
template_type =
|
||||
icon_path.empty()
|
||||
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText02
|
||||
: ABI::Windows::UI::Notifications::
|
||||
ToastTemplateType_ToastImageAndText02;
|
||||
? winui::Notifications::ToastTemplateType_ToastText02
|
||||
: winui::Notifications::ToastTemplateType_ToastImageAndText02;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
toastManager->GetTemplateContent(template_type, toast_xml),
|
||||
"XML: Fetching XML ToastImageAndText02 template failed");
|
||||
@@ -567,7 +605,7 @@ HRESULT WindowsToastNotification::XmlDocumentFromString(
|
||||
}
|
||||
|
||||
HRESULT WindowsToastNotification::SetupCallbacks(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* toast) {
|
||||
winui::Notifications::IToastNotification* toast) {
|
||||
event_handler_ = Make<ToastEventHandler>(this);
|
||||
RETURN_IF_FAILED(
|
||||
toast->add_Activated(event_handler_.Get(), &activated_token_));
|
||||
@@ -578,7 +616,7 @@ HRESULT WindowsToastNotification::SetupCallbacks(
|
||||
}
|
||||
|
||||
bool WindowsToastNotification::RemoveCallbacks(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* toast) {
|
||||
winui::Notifications::IToastNotification* toast) {
|
||||
if (FAILED(toast->remove_Activated(activated_token_)))
|
||||
return false;
|
||||
|
||||
@@ -597,32 +635,29 @@ ToastEventHandler::ToastEventHandler(Notification* notification)
|
||||
ToastEventHandler::~ToastEventHandler() = default;
|
||||
|
||||
IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* sender,
|
||||
winui::Notifications::IToastNotification* sender,
|
||||
IInspectable* args) {
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&Notification::NotificationClicked, notification_));
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Notification clicked";
|
||||
DebugLog("Notification clicked");
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* sender,
|
||||
ABI::Windows::UI::Notifications::IToastDismissedEventArgs* e) {
|
||||
winui::Notifications::IToastNotification* sender,
|
||||
winui::Notifications::IToastDismissedEventArgs* e) {
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&Notification::NotificationDismissed, notification_));
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Notification dismissed";
|
||||
|
||||
FROM_HERE, base::BindOnce(&Notification::NotificationDismissed,
|
||||
notification_, false));
|
||||
DebugLog("Notification dismissed");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* sender,
|
||||
ABI::Windows::UI::Notifications::IToastFailedEventArgs* e) {
|
||||
winui::Notifications::IToastNotification* sender,
|
||||
winui::Notifications::IToastFailedEventArgs* e) {
|
||||
HRESULT error;
|
||||
e->get_ErrorCode(&error);
|
||||
std::string errorMessage =
|
||||
@@ -630,8 +665,7 @@ IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE, base::BindOnce(&Notification::NotificationFailed,
|
||||
notification_, errorMessage));
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << errorMessage;
|
||||
DebugLog(errorMessage);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class WindowsToastNotification : public Notification {
|
||||
// Notification:
|
||||
void Show(const NotificationOptions& options) override;
|
||||
void Dismiss() override;
|
||||
void Remove() override;
|
||||
|
||||
private:
|
||||
friend class ToastEventHandler;
|
||||
|
||||
@@ -9,11 +9,11 @@
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/values.h"
|
||||
#include "content/public/common/webplugininfo.h"
|
||||
#include "extensions/buildflags/buildflags.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "url/gurl.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
#include "extensions/browser/extension_registry.h"
|
||||
#include "extensions/browser/extension_util.h"
|
||||
#include "extensions/common/constants.h"
|
||||
@@ -36,7 +36,7 @@ base::flat_map<std::string, std::string>
|
||||
PluginUtils::GetMimeTypeToExtensionIdMap(
|
||||
content::BrowserContext* browser_context) {
|
||||
base::flat_map<std::string, std::string> mime_type_to_extension_id_map;
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
std::vector<std::string> allowed_extension_ids =
|
||||
MimeTypesHandler::GetMIMETypeAllowlist();
|
||||
// Go through the white-listed extensions and try to use them to intercept
|
||||
|
||||
@@ -262,6 +262,7 @@ using FullScreenTransitionState =
|
||||
[super windowDidMiniaturize:notification];
|
||||
is_minimized_ = true;
|
||||
|
||||
shell_->set_wants_to_be_visible(false);
|
||||
shell_->NotifyWindowMinimize();
|
||||
}
|
||||
|
||||
@@ -269,6 +270,7 @@ using FullScreenTransitionState =
|
||||
[super windowDidDeminiaturize:notification];
|
||||
is_minimized_ = false;
|
||||
|
||||
shell_->set_wants_to_be_visible(true);
|
||||
shell_->AttachChildren();
|
||||
shell_->SetWindowLevel(level_);
|
||||
shell_->NotifyWindowRestore();
|
||||
|
||||
@@ -142,8 +142,11 @@ void InspectableWebContentsViewViews::CloseDevTools() {
|
||||
|
||||
devtools_visible_ = false;
|
||||
if (devtools_window_) {
|
||||
inspectable_web_contents()->SaveDevToolsBounds(
|
||||
devtools_window_->GetWindowBoundsInScreen());
|
||||
auto save_bounds = devtools_window_->IsMinimized()
|
||||
? devtools_window_->GetRestoredBounds()
|
||||
: devtools_window_->GetWindowBoundsInScreen();
|
||||
inspectable_web_contents()->SaveDevToolsBounds(save_bounds);
|
||||
|
||||
devtools_window_.reset();
|
||||
devtools_window_web_view_ = nullptr;
|
||||
devtools_window_delegate_ = nullptr;
|
||||
@@ -216,6 +219,8 @@ const std::u16string InspectableWebContentsViewViews::GetTitle() {
|
||||
void InspectableWebContentsViewViews::Layout() {
|
||||
if (!devtools_web_view_->GetVisible()) {
|
||||
contents_web_view_->SetBoundsRect(GetContentsBounds());
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
View::Layout();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -233,6 +238,9 @@ void InspectableWebContentsViewViews::Layout() {
|
||||
devtools_web_view_->SetBoundsRect(new_devtools_bounds);
|
||||
contents_web_view_->SetBoundsRect(new_contents_bounds);
|
||||
|
||||
// Propagate layout call to all children, for example browser views.
|
||||
View::Layout();
|
||||
|
||||
if (GetDelegate())
|
||||
GetDelegate()->DevToolsResized();
|
||||
}
|
||||
|
||||
@@ -133,4 +133,14 @@ void ElectronDesktopWindowTreeHostWin::OnNativeThemeUpdated(
|
||||
}
|
||||
}
|
||||
|
||||
bool ElectronDesktopWindowTreeHostWin::ShouldWindowContentsBeTransparent()
|
||||
const {
|
||||
// Window should be marked as opaque if no transparency setting has been set,
|
||||
// otherwise videos rendered in the window will trigger a DirectComposition
|
||||
// redraw for every frame.
|
||||
// https://github.com/electron/electron/pull/39895
|
||||
return native_window_view_->GetOpacity() < 1.0 ||
|
||||
native_window_view_->transparent();
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -40,6 +40,7 @@ class ElectronDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin,
|
||||
|
||||
// ui::NativeThemeObserver:
|
||||
void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
|
||||
bool ShouldWindowContentsBeTransparent() const override;
|
||||
|
||||
private:
|
||||
raw_ptr<NativeWindowViews> native_window_view_; // weak ref
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
#include "base/scoped_observation.h"
|
||||
#include "content/public/browser/render_frame_host.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "extensions/buildflags/buildflags.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "services/device/public/mojom/usb_enumeration_options.mojom.h"
|
||||
#include "shell/browser/electron_permission_manager.h"
|
||||
#include "shell/browser/usb/usb_chooser_context.h"
|
||||
@@ -21,7 +21,7 @@
|
||||
#include "shell/browser/usb/usb_chooser_controller.h"
|
||||
#include "shell/browser/web_contents_permission_helper.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
#include "base/containers/fixed_flat_set.h"
|
||||
#include "chrome/common/chrome_features.h"
|
||||
#include "extensions/browser/extension_registry.h"
|
||||
@@ -41,7 +41,7 @@ electron::UsbChooserContext* GetChooserContext(
|
||||
browser_context);
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
// These extensions can claim the smart card USB class and automatically gain
|
||||
// permissions for devices that have an interface with this class.
|
||||
constexpr auto kSmartCardPrivilegedExtensionIds =
|
||||
@@ -65,12 +65,12 @@ bool DeviceHasInterfaceWithClass(
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
|
||||
bool IsDevicePermissionAutoGranted(
|
||||
const url::Origin& origin,
|
||||
const device::mojom::UsbDeviceInfo& device_info) {
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
// Note: The `DeviceHasInterfaceWithClass()` call is made after checking the
|
||||
// origin, since that method call is expensive.
|
||||
if (origin.scheme() == extensions::kExtensionScheme &&
|
||||
@@ -79,7 +79,7 @@ bool IsDevicePermissionAutoGranted(
|
||||
device::mojom::kUsbSmartCardClass)) {
|
||||
return true;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
|
||||
return false;
|
||||
}
|
||||
@@ -251,14 +251,14 @@ ElectronUsbDelegate::GetContextObserver(
|
||||
|
||||
bool ElectronUsbDelegate::IsServiceWorkerAllowedForOrigin(
|
||||
const url::Origin& origin) {
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
// WebUSB is only available on extension service workers for now.
|
||||
if (base::FeatureList::IsEnabled(
|
||||
features::kEnableWebUsbOnExtensionServiceWorker) &&
|
||||
origin.scheme() == extensions::kExtensionScheme) {
|
||||
return true;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -289,7 +289,7 @@ void ElectronUsbDelegate::DeleteControllerForFrame(
|
||||
|
||||
bool ElectronUsbDelegate::PageMayUseUsb(content::Page& page) {
|
||||
content::RenderFrameHost& main_rfh = page.GetMainDocument();
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
// WebViewGuests have no mechanism to show permission prompts and their
|
||||
// embedder can't grant USB access through its permissionrequest API. Also
|
||||
// since webviews use a separate StoragePartition, they must not gain access
|
||||
@@ -297,7 +297,7 @@ bool ElectronUsbDelegate::PageMayUseUsb(content::Page& page) {
|
||||
if (extensions::WebViewGuest::FromRenderFrameHost(&main_rfh)) {
|
||||
return false;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
|
||||
// USB permissions are scoped to a BrowserContext instead of a
|
||||
// StoragePartition, so we need to be careful about usage across
|
||||
|
||||
@@ -7,15 +7,15 @@
|
||||
#include <utility>
|
||||
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "extensions/buildflags/buildflags.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "third_party/blink/public/web/web_element.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
#include "chrome/common/pdf_util.h"
|
||||
#include "extensions/common/constants.h"
|
||||
#include "extensions/renderer/guest_view/mime_handler_view/post_message_support.h"
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -26,14 +26,14 @@ PrintRenderFrameHelperDelegate::~PrintRenderFrameHelperDelegate() = default;
|
||||
// Return the PDF object element if |frame| is the out of process PDF extension.
|
||||
blink::WebElement PrintRenderFrameHelperDelegate::GetPdfElement(
|
||||
blink::WebLocalFrame* frame) {
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
if (frame->Parent() &&
|
||||
IsPdfInternalPluginAllowedOrigin(frame->Parent()->GetSecurityOrigin())) {
|
||||
auto plugin_element = frame->GetDocument().QuerySelector("embed");
|
||||
DCHECK(!plugin_element.IsNull());
|
||||
return plugin_element;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
return blink::WebElement();
|
||||
}
|
||||
|
||||
@@ -43,7 +43,7 @@ bool PrintRenderFrameHelperDelegate::IsPrintPreviewEnabled() {
|
||||
|
||||
bool PrintRenderFrameHelperDelegate::OverridePrint(
|
||||
blink::WebLocalFrame* frame) {
|
||||
#if BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
auto* post_message_support =
|
||||
extensions::PostMessageSupport::FromWebLocalFrame(frame);
|
||||
if (post_message_support) {
|
||||
@@ -56,7 +56,7 @@ bool PrintRenderFrameHelperDelegate::OverridePrint(
|
||||
post_message_support->PostMessageFromValue(base::Value(std::move(message)));
|
||||
return true;
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_EXTENSIONS)
|
||||
#endif // BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -235,14 +235,6 @@ void RendererClientBase::RenderThreadStarted() {
|
||||
extensions::ExtensionsRendererClient::Set(extensions_renderer_client_.get());
|
||||
|
||||
thread->AddObserver(extensions_renderer_client_->GetDispatcher());
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
||||
spellcheck_ = std::make_unique<SpellCheck>(this);
|
||||
#endif
|
||||
|
||||
blink::WebCustomElement::AddEmbedderCustomElementName("webview");
|
||||
blink::WebCustomElement::AddEmbedderCustomElementName("browserplugin");
|
||||
|
||||
WTF::String extension_scheme(extensions::kExtensionScheme);
|
||||
// Extension resources are HTTP-like and safe to expose to the fetch API. The
|
||||
@@ -255,6 +247,14 @@ void RendererClientBase::RenderThreadStarted() {
|
||||
extension_scheme);
|
||||
blink::SchemeRegistry::RegisterURLSchemeAsBypassingContentSecurityPolicy(
|
||||
extension_scheme);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
||||
spellcheck_ = std::make_unique<SpellCheck>(this);
|
||||
#endif
|
||||
|
||||
blink::WebCustomElement::AddEmbedderCustomElementName("webview");
|
||||
blink::WebCustomElement::AddEmbedderCustomElementName("browserplugin");
|
||||
|
||||
std::vector<std::string> fetch_enabled_schemes =
|
||||
ParseSchemesCLISwitch(command_line, switches::kFetchSchemes);
|
||||
|
||||
@@ -2066,10 +2066,7 @@ describe('BrowserWindow module', () => {
|
||||
beforeEach(() => {
|
||||
w = new BrowserWindow({ show: false });
|
||||
});
|
||||
afterEach(async () => {
|
||||
await closeWindow(w);
|
||||
w = null as unknown as BrowserWindow;
|
||||
});
|
||||
afterEach(closeAllWindows);
|
||||
|
||||
describe('BrowserWindow.selectPreviousTab()', () => {
|
||||
it('does not throw', () => {
|
||||
@@ -2138,6 +2135,18 @@ describe('BrowserWindow module', () => {
|
||||
}).to.throw('AddTabbedWindow cannot be called by a window on itself.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('BrowserWindow.tabbingIdentifier', () => {
|
||||
it('is undefined if no tabbingIdentifier was set', () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
expect(w.tabbingIdentifier).to.be.undefined('tabbingIdentifier');
|
||||
});
|
||||
|
||||
it('returns the window tabbingIdentifier', () => {
|
||||
const w = new BrowserWindow({ show: false, tabbingIdentifier: 'group1' });
|
||||
expect(w.tabbingIdentifier).to.equal('group1');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('autoHideMenuBar state', () => {
|
||||
@@ -2221,7 +2230,22 @@ describe('BrowserWindow module', () => {
|
||||
expect(visible).to.equal('hidden');
|
||||
});
|
||||
|
||||
it('resolves after the window is hidden', async () => {
|
||||
it('resolves when the window is occluded', async () => {
|
||||
const w1 = new BrowserWindow({ show: false });
|
||||
w1.loadFile(path.join(fixtures, 'pages', 'a.html'));
|
||||
await once(w1, 'ready-to-show');
|
||||
w1.show();
|
||||
|
||||
const w2 = new BrowserWindow({ show: false });
|
||||
w2.loadFile(path.join(fixtures, 'pages', 'a.html'));
|
||||
await once(w2, 'ready-to-show');
|
||||
w2.show();
|
||||
|
||||
const visibleImage = await w1.capturePage();
|
||||
expect(visibleImage.isEmpty()).to.equal(false);
|
||||
});
|
||||
|
||||
it('resolves when the window is not visible', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.loadFile(path.join(fixtures, 'pages', 'a.html'));
|
||||
await once(w, 'ready-to-show');
|
||||
@@ -2230,21 +2254,10 @@ describe('BrowserWindow module', () => {
|
||||
const visibleImage = await w.capturePage();
|
||||
expect(visibleImage.isEmpty()).to.equal(false);
|
||||
|
||||
w.hide();
|
||||
w.minimize();
|
||||
|
||||
const hiddenImage = await w.capturePage();
|
||||
const isEmpty = process.platform !== 'darwin';
|
||||
expect(hiddenImage.isEmpty()).to.equal(isEmpty);
|
||||
});
|
||||
|
||||
it('resolves after the window is hidden and capturer count is non-zero', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
w.webContents.setBackgroundThrottling(false);
|
||||
w.loadFile(path.join(fixtures, 'pages', 'a.html'));
|
||||
await once(w, 'ready-to-show');
|
||||
|
||||
const image = await w.capturePage();
|
||||
expect(image.isEmpty()).to.equal(false);
|
||||
expect(hiddenImage.isEmpty()).to.equal(false);
|
||||
});
|
||||
|
||||
it('preserves transparency', async () => {
|
||||
@@ -4716,7 +4729,27 @@ describe('BrowserWindow module', () => {
|
||||
expect(w.getChildWindows().length).to.equal(0);
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('child window matches visibility when visibility changes', async () => {
|
||||
ifit(process.platform === 'darwin')('only shows the intended window when a child with siblings is shown', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const childOne = new BrowserWindow({ show: false, parent: w });
|
||||
const childTwo = new BrowserWindow({ show: false, parent: w });
|
||||
|
||||
const parentShown = once(w, 'show');
|
||||
w.show();
|
||||
await parentShown;
|
||||
|
||||
expect(childOne.isVisible()).to.be.false('childOne is visible');
|
||||
expect(childTwo.isVisible()).to.be.false('childTwo is visible');
|
||||
|
||||
const childOneShown = once(childOne, 'show');
|
||||
childOne.show();
|
||||
await childOneShown;
|
||||
|
||||
expect(childOne.isVisible()).to.be.true('childOne is not visible');
|
||||
expect(childTwo.isVisible()).to.be.false('childTwo is visible');
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('child matches parent visibility when parent visibility changes', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w });
|
||||
|
||||
@@ -4743,7 +4776,7 @@ describe('BrowserWindow module', () => {
|
||||
expect(c.isVisible()).to.be.true('child is visible');
|
||||
});
|
||||
|
||||
ifit(process.platform === 'darwin')('matches child window visibility when visibility changes', async () => {
|
||||
ifit(process.platform === 'darwin')('parent matches child visibility when child visibility changes', async () => {
|
||||
const w = new BrowserWindow({ show: false });
|
||||
const c = new BrowserWindow({ show: false, parent: w });
|
||||
|
||||
@@ -6375,7 +6408,7 @@ describe('BrowserWindow module', () => {
|
||||
foregroundWindow.loadFile(path.join(__dirname, 'fixtures', 'pages', 'css-transparent.html'));
|
||||
await once(ipcMain, 'set-transparent');
|
||||
|
||||
await setTimeout();
|
||||
await setTimeout(1000);
|
||||
const screenCapture = await captureScreen();
|
||||
const centerColor = getPixelColor(screenCapture, {
|
||||
x: display.size.width / 2,
|
||||
@@ -6398,7 +6431,7 @@ describe('BrowserWindow module', () => {
|
||||
await once(window, 'show');
|
||||
await window.webContents.loadURL('data:text/html,<head><meta name="color-scheme" content="dark"></head>');
|
||||
|
||||
await setTimeout(500);
|
||||
await setTimeout(1000);
|
||||
const screenCapture = await captureScreen();
|
||||
const centerColor = getPixelColor(screenCapture, {
|
||||
x: display.size.width / 2,
|
||||
@@ -6428,6 +6461,7 @@ describe('BrowserWindow module', () => {
|
||||
w.loadURL('about:blank');
|
||||
await once(w, 'ready-to-show');
|
||||
|
||||
await setTimeout(1000);
|
||||
const screenCapture = await captureScreen();
|
||||
const centerColor = getPixelColor(screenCapture, {
|
||||
x: display.size.width / 2,
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { expect } from 'chai';
|
||||
import { nativeTheme, systemPreferences, BrowserWindow, ipcMain } from 'electron/main';
|
||||
import { nativeTheme, BrowserWindow, ipcMain } from 'electron/main';
|
||||
import { once } from 'node:events';
|
||||
import * as path from 'node:path';
|
||||
import { setTimeout } from 'node:timers/promises';
|
||||
|
||||
import { expectDeprecationMessages } from './lib/deprecate-helpers';
|
||||
import { ifdescribe } from './lib/spec-helpers';
|
||||
import { closeAllWindows } from './lib/window-helpers';
|
||||
|
||||
describe('nativeTheme module', () => {
|
||||
@@ -59,20 +57,6 @@ describe('nativeTheme module', () => {
|
||||
expect(called).to.equal(false);
|
||||
});
|
||||
|
||||
ifdescribe(process.platform === 'darwin')('on macOS', () => {
|
||||
it('should update appLevelAppearance when set', async () => {
|
||||
await expectDeprecationMessages(
|
||||
() => {
|
||||
nativeTheme.themeSource = 'dark';
|
||||
expect(systemPreferences.appLevelAppearance).to.equal('dark');
|
||||
nativeTheme.themeSource = 'light';
|
||||
expect(systemPreferences.appLevelAppearance).to.equal('light');
|
||||
},
|
||||
"(electron) 'appLevelAppearance' is deprecated and will be removed."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
const getPrefersColorSchemeIsDark = async (w: Electron.BrowserWindow) => {
|
||||
const isDark: boolean = await w.webContents.executeJavaScript(
|
||||
'matchMedia("(prefers-color-scheme: dark)").matches'
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { expect } from 'chai';
|
||||
import { systemPreferences } from 'electron/main';
|
||||
import { expectDeprecationMessages } from './lib/deprecate-helpers';
|
||||
import { ifdescribe } from './lib/spec-helpers';
|
||||
|
||||
describe('systemPreferences module', () => {
|
||||
@@ -215,52 +214,6 @@ describe('systemPreferences module', () => {
|
||||
const sysColor = systemPreferences.getColor(color);
|
||||
expect(sysColor).to.be.a('string');
|
||||
});
|
||||
|
||||
await expectDeprecationMessages(
|
||||
() => {
|
||||
const sysColor = systemPreferences.getColor('alternate-selected-control-text');
|
||||
expect(sysColor).to.be.a('string');
|
||||
},
|
||||
"'alternate-selected-control-text' is deprecated as an input to getColor. Use 'selected-content-background' instead."
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
ifdescribe(process.platform === 'darwin')('systemPreferences.appLevelAppearance', () => {
|
||||
const options = ['dark', 'light', 'unknown', null];
|
||||
describe('with properties', () => {
|
||||
it('returns a valid appearance', () => {
|
||||
const appearance = systemPreferences.appLevelAppearance;
|
||||
expect(options).to.include(appearance);
|
||||
});
|
||||
|
||||
it('can be changed', () => {
|
||||
systemPreferences.appLevelAppearance = 'dark';
|
||||
expect(systemPreferences.appLevelAppearance).to.eql('dark');
|
||||
});
|
||||
});
|
||||
|
||||
describe('with functions', () => {
|
||||
it('returns a valid appearance', async () => {
|
||||
await expectDeprecationMessages(
|
||||
() => {
|
||||
const appearance = systemPreferences.getAppLevelAppearance();
|
||||
expect(options).to.include(appearance);
|
||||
},
|
||||
"(electron) 'getAppLevelAppearance function' is deprecated and will be removed."
|
||||
);
|
||||
});
|
||||
|
||||
it('can be changed', async () => {
|
||||
await expectDeprecationMessages(
|
||||
() => {
|
||||
systemPreferences.setAppLevelAppearance('dark');
|
||||
const appearance = systemPreferences.getAppLevelAppearance();
|
||||
expect(appearance).to.eql('dark');
|
||||
},
|
||||
"(electron) 'setAppLevelAppearance function' is deprecated and will be removed."
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -431,6 +431,19 @@ describe('webContents module', () => {
|
||||
}
|
||||
});
|
||||
|
||||
it('fails if loadURL is called inside a non-reentrant critical section', (done) => {
|
||||
w.webContents.once('did-fail-load', (_event, _errorCode, _errorDescription, validatedURL) => {
|
||||
expect(validatedURL).to.contain('blank.html');
|
||||
done();
|
||||
});
|
||||
|
||||
w.webContents.once('did-start-loading', () => {
|
||||
w.loadURL(`file://${fixturesPath}/pages/blank.html`);
|
||||
});
|
||||
|
||||
w.loadURL('data:text/html,<h1>HELLO</h1>');
|
||||
});
|
||||
|
||||
it('sets appropriate error information on rejection', async () => {
|
||||
let err: any;
|
||||
try {
|
||||
@@ -2270,13 +2283,13 @@ describe('webContents module', () => {
|
||||
});
|
||||
|
||||
describe('crashed event', () => {
|
||||
it('does not crash main process when destroying WebContents in it', async () => {
|
||||
it('does not crash main process when destroying WebContents in it', (done) => {
|
||||
const contents = (webContents as typeof ElectronInternal.WebContents).create({ nodeIntegration: true });
|
||||
const crashEvent = once(contents, 'render-process-gone');
|
||||
await contents.loadURL('about:blank');
|
||||
contents.forcefullyCrashRenderer();
|
||||
await crashEvent;
|
||||
contents.destroy();
|
||||
contents.once('crashed', () => {
|
||||
contents.destroy();
|
||||
done();
|
||||
});
|
||||
contents.loadURL('about:blank').then(() => contents.forcefullyCrashRenderer());
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1039,33 +1039,77 @@ describe('chrome extensions', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('update', async () => {
|
||||
await w.loadURL(url);
|
||||
describe('update', () => {
|
||||
it('can update muted status', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
const message = { method: 'update', args: [{ muted: true }] };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
const message = { method: 'update', args: [{ muted: true }] };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
|
||||
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||
const response = JSON.parse(responseString);
|
||||
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||
const response = JSON.parse(responseString);
|
||||
|
||||
expect(response).to.have.property('url').that.is.a('string');
|
||||
expect(response).to.have.property('title').that.is.a('string');
|
||||
expect(response).to.have.property('active').that.is.a('boolean');
|
||||
expect(response).to.have.property('autoDiscardable').that.is.a('boolean');
|
||||
expect(response).to.have.property('discarded').that.is.a('boolean');
|
||||
expect(response).to.have.property('groupId').that.is.a('number');
|
||||
expect(response).to.have.property('highlighted').that.is.a('boolean');
|
||||
expect(response).to.have.property('id').that.is.a('number');
|
||||
expect(response).to.have.property('incognito').that.is.a('boolean');
|
||||
expect(response).to.have.property('index').that.is.a('number');
|
||||
expect(response).to.have.property('pinned').that.is.a('boolean');
|
||||
expect(response).to.have.property('selected').that.is.a('boolean');
|
||||
expect(response).to.have.property('windowId').that.is.a('number');
|
||||
expect(response).to.have.property('mutedInfo').that.is.a('object');
|
||||
const { mutedInfo } = response;
|
||||
expect(mutedInfo).to.deep.eq({
|
||||
muted: true,
|
||||
reason: 'user'
|
||||
expect(response).to.have.property('mutedInfo').that.is.a('object');
|
||||
const { mutedInfo } = response;
|
||||
expect(mutedInfo).to.deep.eq({
|
||||
muted: true,
|
||||
reason: 'user'
|
||||
});
|
||||
});
|
||||
|
||||
it('fails when navigating to an invalid url', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
const message = { method: 'update', args: [{ url: 'chrome://crash' }] };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
|
||||
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||
const { error } = JSON.parse(responseString);
|
||||
expect(error).to.eq('I\'m sorry. I\'m afraid I can\'t do that.');
|
||||
});
|
||||
|
||||
it('fails when navigating to prohibited url', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
const message = { method: 'update', args: [{ url: 'chrome://crash' }] };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
|
||||
const [,, responseString] = await once(w.webContents, 'console-message');
|
||||
const { error } = JSON.parse(responseString);
|
||||
expect(error).to.eq('I\'m sorry. I\'m afraid I can\'t do that.');
|
||||
});
|
||||
|
||||
it('fails when navigating to a devtools url without permission', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
const message = { method: 'update', args: [{ url: 'devtools://blah' }] };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
|
||||
const [, , responseString] = await once(w.webContents, 'console-message');
|
||||
const { error } = JSON.parse(responseString);
|
||||
expect(error).to.eq('Cannot navigate to a devtools:// page without either the devtools or debugger permission.');
|
||||
});
|
||||
|
||||
it('fails when navigating to a chrome-untrusted url', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
const message = { method: 'update', args: [{ url: 'chrome-untrusted://blah' }] };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
|
||||
const [, , responseString] = await once(w.webContents, 'console-message');
|
||||
const { error } = JSON.parse(responseString);
|
||||
expect(error).to.eq('Cannot navigate to a chrome-untrusted:// page.');
|
||||
});
|
||||
|
||||
it('fails when navigating to a file url withotut file access', async () => {
|
||||
await w.loadURL(url);
|
||||
|
||||
const message = { method: 'update', args: [{ url: 'file://blah' }] };
|
||||
w.webContents.executeJavaScript(`window.postMessage('${JSON.stringify(message)}', '*')`);
|
||||
|
||||
const [, , responseString] = await once(w.webContents, 'console-message');
|
||||
const { error } = JSON.parse(responseString);
|
||||
expect(error).to.eq('Cannot navigate to a file URL without local file access.');
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/* global chrome */
|
||||
|
||||
const handleRequest = (request, sender, sendResponse) => {
|
||||
const handleRequest = async (request, sender, sendResponse) => {
|
||||
const { method, args = [] } = request;
|
||||
const tabId = sender.tab.id;
|
||||
|
||||
@@ -53,7 +53,12 @@ const handleRequest = (request, sender, sendResponse) => {
|
||||
|
||||
case 'update': {
|
||||
const [params] = args;
|
||||
chrome.tabs.update(tabId, params).then(sendResponse);
|
||||
try {
|
||||
const response = await chrome.tabs.update(tabId, params);
|
||||
sendResponse(response);
|
||||
} catch (error) {
|
||||
sendResponse({ error: error.message });
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -377,6 +377,14 @@ if (process.platform === 'darwin') {
|
||||
console.log(value);
|
||||
const value2 = systemPreferences.getUserDefault('Foo', 'boolean');
|
||||
console.log(value2);
|
||||
// @ts-expect-error Removed API
|
||||
console.log(systemPreferences.getAppLevelAppearance());
|
||||
// @ts-expect-error Removed API
|
||||
systemPreferences.setAppLevelAppearance('dark');
|
||||
// @ts-expect-error Removed API
|
||||
console.log(systemPreferences.appLevelAppearance);
|
||||
// @ts-expect-error Removed API
|
||||
console.log(systemPreferences.getColor('alternate-selected-control-text'));
|
||||
}
|
||||
|
||||
// Create the window.
|
||||
@@ -421,6 +429,11 @@ win2.once('ready-to-show', () => {
|
||||
app.relaunch({ args: process.argv.slice(1).concat(['--relaunch']) });
|
||||
app.exit(0);
|
||||
|
||||
app.configureHostResolver({ secureDnsMode: 'off' });
|
||||
|
||||
// @ts-expect-error Invalid type value
|
||||
app.configureHostResolver({ secureDnsMode: 'foo' });
|
||||
|
||||
// auto-updater
|
||||
// https://github.com/electron/electron/blob/main/docs/api/auto-updater.md
|
||||
|
||||
@@ -1273,6 +1286,11 @@ win4.webContents.on('devtools-open-url', (event, url) => {
|
||||
console.log(url);
|
||||
});
|
||||
|
||||
win4.webContents.insertCSS('body {}', { cssOrigin: 'user' });
|
||||
|
||||
// @ts-expect-error Invalid type value
|
||||
win4.webContents.insertCSS('body {}', { cssOrigin: 'foo' });
|
||||
|
||||
win4.loadURL('http://github.com');
|
||||
|
||||
// @ts-expect-error Removed API
|
||||
|
||||
Reference in New Issue
Block a user