Compare commits

...

15 Commits

Author SHA1 Message Date
trop[bot]
0310156be6 docs: fix represented file fiddle (#40252)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2023-10-19 10:03:23 +02:00
trop[bot]
714e6ea49c chore: implement no-op chrome.action extension APIs (#40261)
chore: implement no-op chrome.action extension APIs

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-10-18 21:39:10 +02:00
trop[bot]
3cf8652027 fix: correctly track receiver for methods called via ctx bridge (#40263)
* fix: correctly track receiver for methods called via ctx bridge

Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>

* spec: test for correct contextBridge passage

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-10-18 21:24:16 +02:00
trop[bot]
f622bd9927 build: re-enable partition alloc on mac (#40230)
* build: re-enable partition alloc on mac

Co-authored-by: VerteDinde <vertedinde@electronjs.org>

* Trigger Build

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: VerteDinde <vertedinde@electronjs.org>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2023-10-18 12:50:12 -04:00
trop[bot]
9ddd08ba89 fix: support the throwIfNoEntry option to statSync and lstatSync in asar files (#40224)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <samuel.r.attard@gmail.com>
2023-10-18 11:33:48 +02:00
trop[bot]
a98d66dd23 refactor: partition HidDelegate observers by browser context (#40238)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-10-18 10:06:16 +02:00
trop[bot]
1e51ee974d fix: Windows Toast notification dismissal from Action Center (#40243)
* fix: Windows Toast notification dismissal from Action Center

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* docs: note Toast behavior in event

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* chore: address feedback from review

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-10-18 10:03:57 +02:00
trop[bot]
6732f63ac0 docs: Update docs on testing Electron apps with WebdriverIO (#40227)
* docs: Updated docs on testing Electron using WebdriverIO

Co-authored-by: Christian Bromann <github@christian-bromann.com>

* Update docs/tutorial/automated-testing.md

Co-authored-by: Felix Rieseberg <fr@makenotion.com>

Co-authored-by: Christian Bromann <github@christian-bromann.com>

* Update docs/tutorial/automated-testing.md

Co-authored-by: Felix Rieseberg <fr@makenotion.com>

Co-authored-by: Christian Bromann <github@christian-bromann.com>

* more updates

Co-authored-by: Christian Bromann <git@bromann.dev>

* address PR comments

Co-authored-by: Christian Bromann <git@bromann.dev>

* Update docs/tutorial/automated-testing.md

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

Co-authored-by: Christian Bromann <github@christian-bromann.com>

* Update docs/tutorial/automated-testing.md

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

Co-authored-by: Christian Bromann <github@christian-bromann.com>

* Update docs/tutorial/automated-testing.md

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

Co-authored-by: Christian Bromann <github@christian-bromann.com>

* Update docs/tutorial/automated-testing.md

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

Co-authored-by: Christian Bromann <github@christian-bromann.com>

* Update docs/tutorial/automated-testing.md

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

Co-authored-by: Christian Bromann <github@christian-bromann.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Christian Bromann <github@christian-bromann.com>
Co-authored-by: Christian Bromann <git@bromann.dev>
2023-10-17 16:54:41 +02:00
trop[bot]
641249f384 fix: incorrect wco bounds in macOS fullscreen (#40219)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-10-16 15:06:39 +02:00
electron-roller[bot]
f6b135e1ac chore: bump chromium to 119.0.6045.21 (28-x-y) (#40178)
* chore: bump chromium in DEPS to 119.0.6045.9

* chore: update patches

* chore: bump chromium in DEPS to 119.0.6045.21

* chore: update patches

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2023-10-16 14:53:20 +02:00
trop[bot]
a5f01f0328 docs: fix some string union typings (#40199)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2023-10-16 14:10:13 +09:00
trop[bot]
d65b8761f8 chore: formally deprecate gpu-process-crashed event (#40195)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Milan Burda <milan.burda@gmail.com>
2023-10-16 09:19:27 +09:00
trop[bot]
f8d4c45f8e fix: ensure MessagePorts get GCed when not referenced (#40201)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-10-16 09:18:36 +09:00
trop[bot]
6625666e40 fix: store portal restore token under the right source ID (#40192)
XDG Desktop Portal provides restore tokens to restore a previously
selected PipeWire stream instead of prompting the user again. This
restore token is single use only and it has to be replaced when the
stream is completed/stopped.

BaseCapturerPipewire maintains two source IDs: one is initialized by
the constructor for new sources (source_id_) and another is for
capturing previously selected sources (selected_source_id_). The
restore token was always being stored under `source_id_`, even if the
capture was ongoing for `selected_source_id_`. This prevents a stream
from being restored more than once. Fix that by storing the restore
token under the selected source ID if it exists.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Athul Iddya <athul@iddya.com>
2023-10-13 22:09:09 +02:00
trop[bot]
e3e7bf3786 fix: webContents.capturePage() for hidden windows on Windows/Linux (#40185)
fix: capturePage for hidden windows on Windows/Linux

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2023-10-12 13:54:43 +02:00
53 changed files with 1888 additions and 277 deletions

2
DEPS
View File

@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'119.0.6045.0',
'119.0.6045.21',
'node_version':
'v18.18.0',
'nan_version':

View File

@@ -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[]&#32;(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

View File

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

View File

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

View File

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

View File

@@ -103,6 +103,19 @@ win.webContents.on('render-process-gone', (event, details) => { /* ... */ })
webview.addEventListener('render-process-gone', (event) => { /* ... */ })
```
### Deprecated: `gpu-process-crashed` event on `app`
The `gpu-process-crashed` event on `app` has been deprecated.
Use the new `child-process-gone` event instead.
```js
// Deprecated
app.on('gpu-process-crashed', (event, killed) => { /* ... */ })
// Replace with
app.on('child-process-gone', (event, details) => { /* ... */ })
```
## Planned Breaking API Changes (27.0)
### Removed: macOS 10.13 / 10.14 support

View File

@@ -7,14 +7,20 @@ function createWindow () {
height: 600
})
win.setRepresentedFilename(os.homedir())
win.setDocumentEdited(true)
win.loadFile('index.html')
}
app.whenReady().then(() => {
const win = new BrowserWindow()
createWindow()
win.setRepresentedFilename(os.homedir())
win.setDocumentEdited(true)
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})
})
app.on('window-all-closed', () => {
@@ -22,9 +28,3 @@ app.on('window-all-closed', () => {
app.quit()
}
})
app.on('activate', () => {
if (BrowserWindow.getAllWindows().length === 0) {
createWindow()
}
})

View File

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

View File

@@ -709,6 +709,8 @@ filenames = {
]
lib_sources_extensions = [
"shell/browser/extensions/api/extension_action/extension_action_api.cc",
"shell/browser/extensions/api/extension_action/extension_action_api.h",
"shell/browser/extensions/api/management/electron_management_api_delegate.cc",
"shell/browser/extensions/api/management/electron_management_api_delegate.h",
"shell/browser/extensions/api/resources_private/resources_private_api.cc",

View File

@@ -261,6 +261,14 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
fs.writeSync(logFDs.get(asarPath), `${offset}: ${filePath}\n`);
};
const shouldThrowStatError = (options: any) => {
if (options && typeof options === 'object' && options.throwIfNoEntry === false) {
return false;
}
return true;
};
const { lstatSync } = fs;
fs.lstatSync = (pathArgument: string, options: any) => {
const pathInfo = splitPath(pathArgument);
@@ -268,10 +276,16 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
const { asarPath, filePath } = pathInfo;
const archive = getOrCreateArchive(asarPath);
if (!archive) throw createError(AsarError.INVALID_ARCHIVE, { asarPath });
if (!archive) {
if (shouldThrowStatError(options)) throw createError(AsarError.INVALID_ARCHIVE, { asarPath });
return null;
}
const stats = archive.stat(filePath);
if (!stats) throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
if (!stats) {
if (shouldThrowStatError(options)) throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
return null;
}
return asarStatsToFsStats(stats);
};

View File

@@ -90,7 +90,6 @@ fix_adapt_exclusive_access_for_electron_needs.patch
fix_aspect_ratio_with_max_size.patch
fix_crash_when_saving_edited_pdf_files.patch
port_autofill_colors_to_the_color_pipeline.patch
build_disable_partition_alloc_on_mac.patch
fix_non-client_mouse_tracking_and_message_bubbling_on_windows.patch
build_make_libcxx_abi_unstable_false_for_electron.patch
introduce_ozoneplatform_electron_can_call_x11_property.patch
@@ -135,3 +134,4 @@ fix_activate_background_material_on_windows.patch
fix_move_autopipsettingshelper_behind_branding_buildflag.patch
revert_remove_the_allowaggressivethrottlingwithwebsocket_feature.patch
fix_handle_no_top_level_aura_window_in_webcontentsimpl.patch
ensure_messageports_get_gced_when_not_referenced.patch

View File

@@ -1,23 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: VerteDinde <vertedinde@electronjs.org>
Date: Tue, 1 Mar 2022 12:07:25 -0800
Subject: build: disable partition alloc on mac
Enabling partition alloc caused a crash when spawning
a child process. This patch disables partition alloc for mac,
and can be removed when the crash in fork is resolved.
Related issue: https://github.com/electron/electron/issues/32718
diff --git a/build_overrides/partition_alloc.gni b/build_overrides/partition_alloc.gni
index a6306a0e042d9fa58568d690fdc76f973782299f..72e47348b644d49e1f63b0693fad01df3bb24f01 100644
--- a/build_overrides/partition_alloc.gni
+++ b/build_overrides/partition_alloc.gni
@@ -46,7 +46,7 @@ _disable_partition_alloc_everywhere =
if (is_ios) {
_is_partition_alloc_everywhere_platform = ios_partition_alloc_enabled
} else {
- _is_partition_alloc_everywhere_platform = !is_nacl
+ _is_partition_alloc_everywhere_platform = !is_nacl && !is_mac
}
# Under Windows debug build, the allocator shim is not compatible with CRT.

View File

@@ -33,10 +33,10 @@ index dcf02923c21e1c4c292eb800f6325e4bb764c823..8738860b44f179685198d2c0b9729fcc
"//base",
"//build:branding_buildflags",
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
index 157ee5cadc0a7bf97be0fc8bf34adb858beea1f1..20303e1712ff917a6c145ab9b1bd71524d998a7f 100644
index 6d52b553b21b06a00c0853ce4f4cb02cf47fd7dd..4c1002ff33cc67fa4374fbfd4c016b28b077baa4 100644
--- a/chrome/browser/BUILD.gn
+++ b/chrome/browser/BUILD.gn
@@ -4797,7 +4797,7 @@ static_library("browser") {
@@ -4800,7 +4800,7 @@ static_library("browser") {
# On Windows, the hashes are embedded in //chrome:chrome_initial rather
# than here in :chrome_dll.
@@ -46,10 +46,10 @@ index 157ee5cadc0a7bf97be0fc8bf34adb858beea1f1..20303e1712ff917a6c145ab9b1bd7152
sources += [ "certificate_viewer_stub.cc" ]
}
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 6b98993d6737c22883bd9ddb31eda60060795e94..d1c6cce7ed68d1d523bd6683529e6ab003ace8e7 100644
index c967801bade79f71859ad215e62030e6bb2d8ea6..94f6b4f7345a27b5b73e429ef43556223529d1bd 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -6899,7 +6899,6 @@ test("unit_tests") {
@@ -6900,7 +6900,6 @@ test("unit_tests") {
deps += [
"//chrome:other_version",
@@ -57,7 +57,7 @@ index 6b98993d6737c22883bd9ddb31eda60060795e94..d1c6cce7ed68d1d523bd6683529e6ab0
"//chrome//services/util_win:unit_tests",
"//chrome/app:chrome_dll_resources",
"//chrome/app:win_unit_tests",
@@ -6920,6 +6919,10 @@ test("unit_tests") {
@@ -6921,6 +6920,10 @@ test("unit_tests") {
"//ui/resources",
]

View File

@@ -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 f9295c24641e9a7322f088c869d90423bcd36991..5ea6972f03fd450272de52c84f783be9113608bc 100644
index 4f64b22c22880812318b095b8ae943489d8a3c07..eba5428f82966230252ac01c49a049e9775996b2 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -8101,6 +8101,7 @@ void RenderFrameHostImpl::CreateNewWindow(
@@ -8119,6 +8119,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,

View File

@@ -81,7 +81,7 @@ index e4deb71ea3afa1ef9d6ddac9c61f5916ff608514..d2e6854ac2aaa3cc83c0b72ebc03193b
!command_line->HasSwitch(switches::kUIDisablePartialSwap);
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
index 3cc40e4b0183bf8f6861a6d1240df7d2972362a9..7468b1ec1b4c4e7f29baef2fc2decd2ec692351c 100644
index 981c012dc8c3c8d1106c02570e75c7b065f6b814..02dbede2d9291a702dd3d692e849938e3f501761 100644
--- a/content/browser/gpu/gpu_process_host.cc
+++ b/content/browser/gpu/gpu_process_host.cc
@@ -227,6 +227,7 @@ GpuTerminationStatus ConvertToGpuTerminationStatus(

View File

@@ -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 7a99c04fcf5bba1eafab94bd7d2b5ee9c58d4d85..80224052d4a3c195eabeadfb0d8863a2cf31b6e2 100644
index d7b85d4a6adbe718fe5a219a5c273d05676c3ec6..b85d2f53e55131491c32eff5475cffa7c9d65e25 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;
}

View File

@@ -0,0 +1,73 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Yoav Weiss <yoavweiss@chromium.org>
Date: Mon, 9 Oct 2023 14:21:44 +0000
Subject: Ensure MessagePorts get GCed when not referenced
[1] regressed MessagePort memory and caused them to no longer be
collected after not being referenced.
This CL fixes that by turning the mutual pointers between attached
MessagePorts to be WeakMembers.
[1] https://chromium-review.googlesource.com/4919476
Bug: 1487835
Change-Id: Icd7eba09a217ad5d588958504d5c4794d7d8a295
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/4919476
Commit-Queue: Yoav Weiss <yoavweiss@chromium.org>
Reviewed-by: Michael Lippautz <mlippautz@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1207067}
diff --git a/third_party/blink/renderer/core/messaging/message_port.cc b/third_party/blink/renderer/core/messaging/message_port.cc
index 0fd5bbf170efa02d3e710de3cb6733158faec858..15f5f0f6aa05d3b17adae87286c92df9cc26a712 100644
--- a/third_party/blink/renderer/core/messaging/message_port.cc
+++ b/third_party/blink/renderer/core/messaging/message_port.cc
@@ -162,8 +162,10 @@ MessagePortChannel MessagePort::Disentangle() {
DCHECK(!IsNeutered());
port_descriptor_.GiveDisentangledHandle(connector_->PassMessagePipe());
connector_ = nullptr;
- if (initially_entangled_port_) {
- initially_entangled_port_->OnEntangledPortDisconnected();
+ // Using a variable here places the WeakMember pointer on the stack, ensuring
+ // it doesn't get GCed while it's being used.
+ if (auto* entangled_port = initially_entangled_port_.Get()) {
+ entangled_port->OnEntangledPortDisconnected();
}
OnEntangledPortDisconnected();
return MessagePortChannel(std::move(port_descriptor_));
@@ -340,7 +342,10 @@ bool MessagePort::Accept(mojo::Message* mojo_message) {
// lifetime of this function.
std::unique_ptr<scheduler::TaskAttributionTracker::TaskScope>
task_attribution_scope;
- if (initially_entangled_port_ && message.sender_origin &&
+ // Using a variable here places the WeakMember pointer on the stack, ensuring
+ // it doesn't get GCed while it's being used.
+ auto* entangled_port = initially_entangled_port_.Get();
+ if (entangled_port && message.sender_origin &&
message.sender_origin->IsSameOriginWith(context->GetSecurityOrigin()) &&
context->IsSameAgentCluster(message.sender_agent_cluster_id) &&
context->IsWindow()) {
@@ -364,9 +369,9 @@ bool MessagePort::Accept(mojo::Message* mojo_message) {
ThreadScheduler::Current()->GetTaskAttributionTracker()) {
// Since `initially_entangled_port_` is not nullptr, neither should be
// its `post_message_task_container_`.
- CHECK(initially_entangled_port_->post_message_task_container_);
+ CHECK(entangled_port->post_message_task_container_);
scheduler::TaskAttributionInfo* parent_task =
- initially_entangled_port_->post_message_task_container_
+ entangled_port->post_message_task_container_
->GetAndDecrementPostMessageTask(message.parent_task_id);
task_attribution_scope = tracker->CreateTaskScope(
script_state, parent_task,
diff --git a/third_party/blink/renderer/core/messaging/message_port.h b/third_party/blink/renderer/core/messaging/message_port.h
index 98ed58a9a765f5101d9b421507bf25db4359d7e5..a178d15c11b1e5fb1ff74d182021fe39e9d9b107 100644
--- a/third_party/blink/renderer/core/messaging/message_port.h
+++ b/third_party/blink/renderer/core/messaging/message_port.h
@@ -195,7 +195,7 @@ class CORE_EXPORT MessagePort : public EventTarget,
// The entangled port. Only set on initial entanglement, and gets unset as
// soon as the ports are disentangled.
- Member<MessagePort> initially_entangled_port_;
+ WeakMember<MessagePort> initially_entangled_port_;
Member<PostMessageTaskContainer> post_message_task_container_;
};

View File

@@ -40,7 +40,7 @@ index c57305681efb469d296c90df68b6cdbea927580d..6dda1a5465e08df64b539ee203b7c403
// Called from BrowserMainLoop::PostCreateThreads().
// TODO(content/browser/gpu/OWNERS): This should probably use a
diff --git a/content/browser/gpu/gpu_data_manager_impl_private.cc b/content/browser/gpu/gpu_data_manager_impl_private.cc
index ef2ff9a1dd1a2b18d9896cb7e9e8adb06bf2bd18..dbf021231b9373233e55c040ea2fdb2406b90647 100644
index 5982540cdc27d7a983076af1a1ca76c996ec9082..11a888de262a8e185579fa83aabd6b5d9047985b 100644
--- a/content/browser/gpu/gpu_data_manager_impl_private.cc
+++ b/content/browser/gpu/gpu_data_manager_impl_private.cc
@@ -1222,6 +1222,12 @@ void GpuDataManagerImplPrivate::TerminateInfoCollectionGpuProcess() {

View File

@@ -6,10 +6,10 @@ Subject: mas: avoid usage of CGDisplayUsesForceToGray
Removes usage of the CGDisplayUsesForceToGray private API.
diff --git a/ui/display/mac/screen_mac.mm b/ui/display/mac/screen_mac.mm
index 074d92bd07e39d4f7d5561dae5cb09aa157b870a..6d92d63437ebf8ce68612e6fbbc1c767f23a7727 100644
index 2653ce451ee21f55824f30ee19c7b46b4c29c75e..e9144eba98b3b038f48b0c431af3bc39678ebec6 100644
--- a/ui/display/mac/screen_mac.mm
+++ b/ui/display/mac/screen_mac.mm
@@ -159,7 +159,17 @@ DisplayMac BuildDisplayForScreen(NSScreen* screen) {
@@ -170,7 +170,17 @@ DisplayMac BuildDisplayForScreen(NSScreen* screen) {
display.set_color_depth(Display::kDefaultBitsPerPixel);
display.set_depth_per_component(Display::kDefaultBitsPerComponent);
}

View File

@@ -30,10 +30,10 @@ index c4255d8dfc2e3c4f1f32506e4e9edbb90a74340a..142398a8adafc94e6724ee750b612a66
// 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 80224052d4a3c195eabeadfb0d8863a2cf31b6e2..11fde625a13a78dec5ae8245ada514e8ca6ab2c6 100644
index b85d2f53e55131491c32eff5475cffa7c9d65e25..73d9f46fd52a9bda3322d1883207a56171507692 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -2122,6 +2122,9 @@ void RenderWidgetHostImpl::SetCursor(const ui::Cursor& cursor) {
@@ -2128,6 +2128,9 @@ void RenderWidgetHostImpl::SetCursor(const ui::Cursor& cursor) {
if (view_) {
view_->UpdateCursor(cursor);
}

View File

@@ -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 5ea6972f03fd450272de52c84f783be9113608bc..72dc42e2919fcb7bdfb15512b1ff38ea48807c7f 100644
index eba5428f82966230252ac01c49a049e9775996b2..710739875dd45e943c43647ff3346bd375d23232 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -7316,6 +7316,17 @@ void RenderFrameHostImpl::EnterFullscreen(
@@ -7334,6 +7334,17 @@ void RenderFrameHostImpl::EnterFullscreen(
}
}

View File

@@ -26,7 +26,7 @@ index a4130ad4dc8158f8256b55fdd87f577687135626..3139aa65807cee23f0e8dbc85243566e
// An empty URL is returned if the URL is not overriden.
virtual GURL OverrideFlashEmbedWithHTML(const GURL& url);
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index cddb519c4c893607e56ea44e17e75783fb33d373..0a3a36d2650d7aa3edb50f37898be20500fc99e5 100644
index 4ed1a50637e40df4d3a9c1a1cfb63ad948df413c..bc446fa9f6dcbaa618a06040dd215ac928388341 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -788,6 +788,12 @@ void RendererBlinkPlatformImpl::WillStopWorkerThread() {

View File

@@ -35,7 +35,7 @@ index 3139aa65807cee23f0e8dbc85243566ef9de89b9..19707edb1283f2432f3c0059f80fabd5
// from the worker thread.
virtual void WillDestroyWorkerContextOnWorkerThread(
diff --git a/content/renderer/renderer_blink_platform_impl.cc b/content/renderer/renderer_blink_platform_impl.cc
index 0a3a36d2650d7aa3edb50f37898be20500fc99e5..63fe14db69116258a874117232c193f4be7d8f83 100644
index bc446fa9f6dcbaa618a06040dd215ac928388341..77a9172d12af687235f303f3d38912d9b96ba6a8 100644
--- a/content/renderer/renderer_blink_platform_impl.cc
+++ b/content/renderer/renderer_blink_platform_impl.cc
@@ -800,6 +800,12 @@ void RendererBlinkPlatformImpl::WorkerContextCreated(

View File

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

View File

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

View File

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

View File

@@ -469,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();
}
@@ -3462,22 +3469,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);

View File

@@ -0,0 +1,197 @@
// Copyright (c) 2023 Microsoft, GmbH
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/extensions/api/extension_action/extension_action_api.h"
#include <stddef.h>
#include <memory>
#include <utility>
#include "base/functional/bind.h"
#include "base/lazy_instance.h"
#include "base/observer_list.h"
#include "extensions/browser/event_router.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/browser/extension_util.h"
#include "extensions/common/mojom/view_type.mojom.h"
using content::WebContents;
namespace extensions {
//
// ExtensionActionAPI::Observer
//
void ExtensionActionAPI::Observer::OnExtensionActionUpdated(
ExtensionAction* extension_action,
content::WebContents* web_contents,
content::BrowserContext* browser_context) {}
void ExtensionActionAPI::Observer::OnExtensionActionAPIShuttingDown() {}
ExtensionActionAPI::Observer::~Observer() {}
//
// ExtensionActionAPI
//
static base::LazyInstance<BrowserContextKeyedAPIFactory<ExtensionActionAPI>>::
DestructorAtExit g_extension_action_api_factory = LAZY_INSTANCE_INITIALIZER;
ExtensionActionAPI::ExtensionActionAPI(content::BrowserContext* context)
: browser_context_(context), extension_prefs_(nullptr) {}
ExtensionActionAPI::~ExtensionActionAPI() {}
// static
BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
ExtensionActionAPI::GetFactoryInstance() {
return g_extension_action_api_factory.Pointer();
}
// static
ExtensionActionAPI* ExtensionActionAPI::Get(content::BrowserContext* context) {
return BrowserContextKeyedAPIFactory<ExtensionActionAPI>::Get(context);
}
ExtensionPrefs* ExtensionActionAPI::GetExtensionPrefs() {
return nullptr;
}
void ExtensionActionAPI::Shutdown() {}
//
// ExtensionActionFunction
//
ExtensionActionFunction::ExtensionActionFunction() {}
ExtensionActionFunction::~ExtensionActionFunction() {}
ExtensionFunction::ResponseAction ExtensionActionFunction::Run() {
return RunExtensionAction();
}
ExtensionFunction::ResponseAction
ExtensionActionShowFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.show is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ExtensionActionHideFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.hide is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ActionIsEnabledFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.isEnabled is not supported in Electron";
return RespondNow(WithArguments(false));
}
ExtensionFunction::ResponseAction
ExtensionActionSetIconFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.setIcon is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ExtensionActionSetTitleFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.setTitle is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ExtensionActionSetPopupFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.setPopup is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ExtensionActionSetBadgeTextFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.setBadgeText is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ExtensionActionSetBadgeBackgroundColorFunction::RunExtensionAction() {
LOG(INFO)
<< "chrome.action.setBadgeBackgroundColor is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ActionSetBadgeTextColorFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.setBadgeTextColor is not supported in Electron";
return RespondNow(NoArguments());
}
ExtensionFunction::ResponseAction
ExtensionActionGetTitleFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.getTitle is not supported in Electron";
return RespondNow(WithArguments(""));
}
ExtensionFunction::ResponseAction
ExtensionActionGetPopupFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.getPopup is not supported in Electron";
return RespondNow(WithArguments(""));
}
ExtensionFunction::ResponseAction
ExtensionActionGetBadgeTextFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.getBadgeText is not supported in Electron";
return RespondNow(WithArguments(""));
}
ExtensionFunction::ResponseAction
ExtensionActionGetBadgeBackgroundColorFunction::RunExtensionAction() {
LOG(INFO)
<< "chrome.action.getBadgeBackgroundColor is not supported in Electron";
base::Value::List list;
return RespondNow(WithArguments(std::move(list)));
}
ExtensionFunction::ResponseAction
ActionGetBadgeTextColorFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.getBadgeTextColor is not supported in Electron";
base::Value::List list;
return RespondNow(WithArguments(std::move(list)));
}
ActionGetUserSettingsFunction::ActionGetUserSettingsFunction() = default;
ActionGetUserSettingsFunction::~ActionGetUserSettingsFunction() = default;
ExtensionFunction::ResponseAction ActionGetUserSettingsFunction::Run() {
LOG(INFO) << "chrome.action.getUserSettings is not supported in Electron";
base::Value::Dict ui_settings;
return RespondNow(WithArguments(std::move(ui_settings)));
}
ExtensionFunction::ResponseAction
ActionOpenPopupFunction::RunExtensionAction() {
LOG(INFO) << "chrome.action.openPopup is not supported in Electron";
return RespondNow(NoArguments());
}
} // namespace extensions

View File

@@ -0,0 +1,520 @@
// Copyright (c) 2023 Microsoft, GmbH
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef SHELL_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_ACTION_API_H_
#define SHELL_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_ACTION_API_H_
#include <string>
#include "base/memory/raw_ptr.h"
#include "base/observer_list.h"
#include "base/values.h"
#include "extensions/browser/browser_context_keyed_api_factory.h"
#include "extensions/browser/extension_action.h"
#include "extensions/browser/extension_function.h"
#include "extensions/browser/extension_host_registry.h"
namespace content {
class BrowserContext;
class WebContents;
} // namespace content
namespace extensions {
class ExtensionHost;
class ExtensionPrefs;
class ExtensionActionAPI : public BrowserContextKeyedAPI {
public:
class Observer {
public:
virtual void OnExtensionActionUpdated(
ExtensionAction* extension_action,
content::WebContents* web_contents,
content::BrowserContext* browser_context);
virtual void OnExtensionActionAPIShuttingDown();
protected:
virtual ~Observer();
};
explicit ExtensionActionAPI(content::BrowserContext* context);
ExtensionActionAPI(const ExtensionActionAPI&) = delete;
ExtensionActionAPI& operator=(const ExtensionActionAPI&) = delete;
~ExtensionActionAPI() override;
// Convenience method to get the instance for a profile.
static ExtensionActionAPI* Get(content::BrowserContext* context);
static BrowserContextKeyedAPIFactory<ExtensionActionAPI>*
GetFactoryInstance();
// Add or remove observers.
void AddObserver(Observer* observer) {}
void RemoveObserver(Observer* observer) {}
// Notifies that there has been a change in the given |extension_action|.
void NotifyChange(ExtensionAction* extension_action,
content::WebContents* web_contents,
content::BrowserContext* browser_context) {}
// Dispatches the onClicked event for extension that owns the given action.
void DispatchExtensionActionClicked(const ExtensionAction& extension_action,
content::WebContents* web_contents,
const Extension* extension) {}
// Clears the values for all ExtensionActions for the tab associated with the
// given |web_contents| (and signals that page actions changed).
void ClearAllValuesForTab(content::WebContents* web_contents) {}
private:
friend class BrowserContextKeyedAPIFactory<ExtensionActionAPI>;
ExtensionPrefs* GetExtensionPrefs();
// BrowserContextKeyedAPI implementation.
void Shutdown() override;
static const char* service_name() { return "ExtensionActionAPI"; }
static const bool kServiceRedirectedInIncognito = true;
raw_ptr<content::BrowserContext> browser_context_;
raw_ptr<ExtensionPrefs> extension_prefs_;
};
// Implementation of the browserAction and pageAction APIs.
class ExtensionActionFunction : public ExtensionFunction {
protected:
ExtensionActionFunction();
~ExtensionActionFunction() override;
ResponseAction Run() override;
virtual ResponseAction RunExtensionAction() = 0;
};
//
// Implementations of each extension action API.
//
// pageAction and browserAction bindings are created for these by extending them
// then declaring an EXTENSION_FUNCTION_NAME.
//
// show
class ExtensionActionShowFunction : public ExtensionActionFunction {
protected:
~ExtensionActionShowFunction() override {}
ResponseAction RunExtensionAction() override;
};
// hide
class ExtensionActionHideFunction : public ExtensionActionFunction {
protected:
~ExtensionActionHideFunction() override {}
ResponseAction RunExtensionAction() override;
};
// setIcon
class ExtensionActionSetIconFunction : public ExtensionActionFunction {
public:
static void SetReportErrorForInvisibleIconForTesting(bool value);
protected:
~ExtensionActionSetIconFunction() override {}
ResponseAction RunExtensionAction() override;
};
// setTitle
class ExtensionActionSetTitleFunction : public ExtensionActionFunction {
protected:
~ExtensionActionSetTitleFunction() override {}
ResponseAction RunExtensionAction() override;
};
// setPopup
class ExtensionActionSetPopupFunction : public ExtensionActionFunction {
protected:
~ExtensionActionSetPopupFunction() override {}
ResponseAction RunExtensionAction() override;
};
// setBadgeText
class ExtensionActionSetBadgeTextFunction : public ExtensionActionFunction {
protected:
~ExtensionActionSetBadgeTextFunction() override {}
ResponseAction RunExtensionAction() override;
};
// setBadgeBackgroundColor
class ExtensionActionSetBadgeBackgroundColorFunction
: public ExtensionActionFunction {
protected:
~ExtensionActionSetBadgeBackgroundColorFunction() override {}
ResponseAction RunExtensionAction() override;
};
// getTitle
class ExtensionActionGetTitleFunction : public ExtensionActionFunction {
protected:
~ExtensionActionGetTitleFunction() override {}
ResponseAction RunExtensionAction() override;
};
// getPopup
class ExtensionActionGetPopupFunction : public ExtensionActionFunction {
protected:
~ExtensionActionGetPopupFunction() override {}
ResponseAction RunExtensionAction() override;
};
// openPopup
class ExtensionActionOpenPopupFunction : public ExtensionActionFunction {
protected:
~ExtensionActionOpenPopupFunction() override {}
ResponseAction RunExtensionAction() override;
};
// getBadgeText
class ExtensionActionGetBadgeTextFunction : public ExtensionActionFunction {
protected:
~ExtensionActionGetBadgeTextFunction() override {}
ResponseAction RunExtensionAction() override;
};
// getBadgeBackgroundColor
class ExtensionActionGetBadgeBackgroundColorFunction
: public ExtensionActionFunction {
protected:
~ExtensionActionGetBadgeBackgroundColorFunction() override {}
ResponseAction RunExtensionAction() override;
};
//
// action.* aliases for supported action APIs.
//
class ActionSetIconFunction : public ExtensionActionSetIconFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.setIcon", ACTION_SETICON)
protected:
~ActionSetIconFunction() override {}
};
class ActionGetPopupFunction : public ExtensionActionGetPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.getPopup", ACTION_GETPOPUP)
protected:
~ActionGetPopupFunction() override {}
};
class ActionSetPopupFunction : public ExtensionActionSetPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.setPopup", ACTION_SETPOPUP)
protected:
~ActionSetPopupFunction() override {}
};
class ActionGetTitleFunction : public ExtensionActionGetTitleFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.getTitle", ACTION_GETTITLE)
protected:
~ActionGetTitleFunction() override {}
};
class ActionSetTitleFunction : public ExtensionActionSetTitleFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.setTitle", ACTION_SETTITLE)
protected:
~ActionSetTitleFunction() override {}
};
class ActionGetBadgeTextFunction : public ExtensionActionGetBadgeTextFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.getBadgeText", ACTION_GETBADGETEXT)
protected:
~ActionGetBadgeTextFunction() override {}
};
class ActionSetBadgeTextFunction : public ExtensionActionSetBadgeTextFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.setBadgeText", ACTION_SETBADGETEXT)
protected:
~ActionSetBadgeTextFunction() override {}
};
class ActionGetBadgeBackgroundColorFunction
: public ExtensionActionGetBadgeBackgroundColorFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.getBadgeBackgroundColor",
ACTION_GETBADGEBACKGROUNDCOLOR)
protected:
~ActionGetBadgeBackgroundColorFunction() override {}
};
class ActionSetBadgeBackgroundColorFunction
: public ExtensionActionSetBadgeBackgroundColorFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.setBadgeBackgroundColor",
ACTION_SETBADGEBACKGROUNDCOLOR)
protected:
~ActionSetBadgeBackgroundColorFunction() override {}
};
class ActionGetBadgeTextColorFunction : public ExtensionActionFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.getBadgeTextColor",
ACTION_GETBADGETEXTCOLOR)
protected:
~ActionGetBadgeTextColorFunction() override = default;
ResponseAction RunExtensionAction() override;
};
class ActionSetBadgeTextColorFunction : public ExtensionActionFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.setBadgeTextColor",
ACTION_SETBADGETEXTCOLOR)
protected:
~ActionSetBadgeTextColorFunction() override = default;
ResponseAction RunExtensionAction() override;
};
class ActionEnableFunction : public ExtensionActionShowFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.enable", ACTION_ENABLE)
protected:
~ActionEnableFunction() override {}
};
class ActionDisableFunction : public ExtensionActionHideFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.disable", ACTION_DISABLE)
protected:
~ActionDisableFunction() override {}
};
class ActionIsEnabledFunction : public ExtensionActionFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.isEnabled", ACTION_ISENABLED)
protected:
~ActionIsEnabledFunction() override = default;
ResponseAction RunExtensionAction() override;
};
class ActionGetUserSettingsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.getUserSettings", ACTION_GETUSERSETTINGS)
ActionGetUserSettingsFunction();
ActionGetUserSettingsFunction(const ActionGetUserSettingsFunction&) = delete;
ActionGetUserSettingsFunction& operator=(
const ActionGetUserSettingsFunction&) = delete;
ResponseAction Run() override;
protected:
~ActionGetUserSettingsFunction() override;
};
class ActionOpenPopupFunction : public ExtensionActionOpenPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("action.openPopup", ACTION_OPENPOPUP)
protected:
~ActionOpenPopupFunction() override = default;
ResponseAction RunExtensionAction() override;
};
//
// browserAction.* aliases for supported browserAction APIs.
//
class BrowserActionSetIconFunction : public ExtensionActionSetIconFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.setIcon", BROWSERACTION_SETICON)
protected:
~BrowserActionSetIconFunction() override {}
};
class BrowserActionSetTitleFunction : public ExtensionActionSetTitleFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.setTitle", BROWSERACTION_SETTITLE)
protected:
~BrowserActionSetTitleFunction() override {}
};
class BrowserActionSetPopupFunction : public ExtensionActionSetPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.setPopup", BROWSERACTION_SETPOPUP)
protected:
~BrowserActionSetPopupFunction() override {}
};
class BrowserActionGetTitleFunction : public ExtensionActionGetTitleFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.getTitle", BROWSERACTION_GETTITLE)
protected:
~BrowserActionGetTitleFunction() override {}
};
class BrowserActionGetPopupFunction : public ExtensionActionGetPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.getPopup", BROWSERACTION_GETPOPUP)
protected:
~BrowserActionGetPopupFunction() override {}
};
class BrowserActionSetBadgeTextFunction
: public ExtensionActionSetBadgeTextFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.setBadgeText",
BROWSERACTION_SETBADGETEXT)
protected:
~BrowserActionSetBadgeTextFunction() override {}
};
class BrowserActionSetBadgeBackgroundColorFunction
: public ExtensionActionSetBadgeBackgroundColorFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.setBadgeBackgroundColor",
BROWSERACTION_SETBADGEBACKGROUNDCOLOR)
protected:
~BrowserActionSetBadgeBackgroundColorFunction() override {}
};
class BrowserActionGetBadgeTextFunction
: public ExtensionActionGetBadgeTextFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.getBadgeText",
BROWSERACTION_GETBADGETEXT)
protected:
~BrowserActionGetBadgeTextFunction() override {}
};
class BrowserActionGetBadgeBackgroundColorFunction
: public ExtensionActionGetBadgeBackgroundColorFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.getBadgeBackgroundColor",
BROWSERACTION_GETBADGEBACKGROUNDCOLOR)
protected:
~BrowserActionGetBadgeBackgroundColorFunction() override {}
};
class BrowserActionEnableFunction : public ExtensionActionShowFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.enable", BROWSERACTION_ENABLE)
protected:
~BrowserActionEnableFunction() override {}
};
class BrowserActionDisableFunction : public ExtensionActionHideFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.disable", BROWSERACTION_DISABLE)
protected:
~BrowserActionDisableFunction() override {}
};
class BrowserActionOpenPopupFunction : public ExtensionActionOpenPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("browserAction.openPopup",
BROWSERACTION_OPEN_POPUP)
protected:
~BrowserActionOpenPopupFunction() override {}
};
} // namespace extensions
//
// pageAction.* aliases for supported pageAction APIs.
//
class PageActionShowFunction : public extensions::ExtensionActionShowFunction {
public:
DECLARE_EXTENSION_FUNCTION("pageAction.show", PAGEACTION_SHOW)
protected:
~PageActionShowFunction() override {}
};
class PageActionHideFunction : public extensions::ExtensionActionHideFunction {
public:
DECLARE_EXTENSION_FUNCTION("pageAction.hide", PAGEACTION_HIDE)
protected:
~PageActionHideFunction() override {}
};
class PageActionSetIconFunction
: public extensions::ExtensionActionSetIconFunction {
public:
DECLARE_EXTENSION_FUNCTION("pageAction.setIcon", PAGEACTION_SETICON)
protected:
~PageActionSetIconFunction() override {}
};
class PageActionSetTitleFunction
: public extensions::ExtensionActionSetTitleFunction {
public:
DECLARE_EXTENSION_FUNCTION("pageAction.setTitle", PAGEACTION_SETTITLE)
protected:
~PageActionSetTitleFunction() override {}
};
class PageActionSetPopupFunction
: public extensions::ExtensionActionSetPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("pageAction.setPopup", PAGEACTION_SETPOPUP)
protected:
~PageActionSetPopupFunction() override {}
};
class PageActionGetTitleFunction
: public extensions::ExtensionActionGetTitleFunction {
public:
DECLARE_EXTENSION_FUNCTION("pageAction.getTitle", PAGEACTION_GETTITLE)
protected:
~PageActionGetTitleFunction() override {}
};
class PageActionGetPopupFunction
: public extensions::ExtensionActionGetPopupFunction {
public:
DECLARE_EXTENSION_FUNCTION("pageAction.getPopup", PAGEACTION_GETPOPUP)
protected:
~PageActionGetPopupFunction() override {}
};
#endif // SHELL_BROWSER_EXTENSIONS_API_EXTENSION_ACTION_EXTENSION_ACTION_API_H_

View File

@@ -6,6 +6,7 @@
#include "extensions/browser/api/i18n/i18n_api.h"
#include "extensions/browser/extension_function_registry.h"
#include "shell/browser/extensions/api/extension_action/extension_action_api.h"
#include "shell/browser/extensions/api/generated_api_registration.h"
#include "shell/browser/extensions/api/scripting/scripting_api.h"
#include "shell/browser/extensions/api/tabs/tabs_api.h"

View File

@@ -9,6 +9,7 @@
#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"
@@ -36,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;
@@ -45,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) {
@@ -101,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(
@@ -140,33 +203,14 @@ bool ElectronHidDelegate::IsServiceWorkerAllowedForOrigin(
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(

View File

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

View File

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

View File

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

View File

@@ -537,7 +537,7 @@ void NativeWindow::PreviewFile(const std::string& path,
void NativeWindow::CloseFilePreview() {}
gfx::Rect NativeWindow::GetWindowControlsOverlayRect() {
absl::optional<gfx::Rect> NativeWindow::GetWindowControlsOverlayRect() {
return overlay_rect_;
}
@@ -665,6 +665,7 @@ void NativeWindow::NotifyWindowMoved() {
}
void NativeWindow::NotifyWindowEnterFullScreen() {
NotifyLayoutWindowControlsOverlay();
for (NativeWindowObserver& observer : observers_)
observer.OnWindowEnterFullScreen();
}
@@ -690,6 +691,7 @@ void NativeWindow::NotifyWindowSheetEnd() {
}
void NativeWindow::NotifyWindowLeaveFullScreen() {
NotifyLayoutWindowControlsOverlay();
for (NativeWindowObserver& observer : observers_)
observer.OnWindowLeaveFullScreen();
}
@@ -733,10 +735,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());
}
}

View File

@@ -286,7 +286,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.

View File

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

View File

@@ -1899,23 +1899,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

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,6 +37,7 @@ group("extensions_features") {
generated_json_strings("generated_api_json_strings") {
sources = [
"action.json",
"extension.json",
"resources_private.idl",
"scripting.idl",

View File

@@ -0,0 +1,483 @@
// Copyright (c) 2023 Microsoft, GmbH
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
[
{
"namespace": "action",
"description": "Use the <code>chrome.action</code> API to control the extension's icon in the Google Chrome toolbar.",
"compiler_options": {
"implemented_in": "shell/browser/extensions/api/extension_action/extension_action_api.h"
},
"types": [
{
"id": "TabDetails",
"type": "object",
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "The ID of the tab to query state for. If no tab is specified, the non-tab-specific state is returned."
}
}
},
{
"id": "UserSettings",
"type": "object",
"properties": {
"isOnToolbar": {
"type": "boolean",
"description": "Whether the extension's action icon is visible on browser windows' top-level toolbar (i.e., whether the extension has been 'pinned' by the user)."
}
},
"description": "The collection of user-specified settings relating to an extension's action."
},
{
"id": "OpenPopupOptions",
"type": "object",
"properties": {
"windowId": {
"type": "integer",
"description": "The id of the window to open the action popup in. Defaults to the currently-active window if unspecified.",
"optional": true
}
}
}
],
"functions": [
{
"name": "setTitle",
"deprecated": "chrome.action.setTitle is not supported in Electron",
"type": "function",
"description": "Sets the title of the action. This shows up in the tooltip.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "The string the action should display when moused over."
},
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
],
"returns_async": {
"name": "callback",
"parameters": [],
"optional": true
}
},
{
"name": "getTitle",
"deprecated": "chrome.action.getTitle is not supported in Electron",
"type": "function",
"description": "Gets the title of the action.",
"parameters": [
{
"name": "details",
"$ref": "TabDetails"
}
],
"returns_async": {
"name": "callback",
"parameters": [
{
"name": "result",
"type": "string"
}
]
}
},
{
"name": "setIcon",
"deprecated": "chrome.action.setIcon is not supported in Electron",
"type": "function",
"description": "Sets the icon for the action. The icon can be specified either as the path to an image file or as the pixel data from a canvas element, or as dictionary of either one of those. Either the <b>path</b> or the <b>imageData</b> property must be specified.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"imageData": {
"choices": [
{
"$ref": "browserAction.ImageDataType"
},
{
"type": "object",
"additionalProperties": {
"type": "any"
}
}
],
"optional": true,
"description": "Either an ImageData object or a dictionary {size -> ImageData} representing icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * n will be selected, where n is the size of the icon in the UI. At least one image must be specified. Note that 'details.imageData = foo' is equivalent to 'details.imageData = {'16': foo}'"
},
"path": {
"choices": [
{
"type": "string"
},
{
"type": "object",
"additionalProperties": {
"type": "any"
}
}
],
"optional": true,
"description": "Either a relative image path or a dictionary {size -> relative image path} pointing to icon to be set. If the icon is specified as a dictionary, the actual image to be used is chosen depending on screen's pixel density. If the number of image pixels that fit into one screen space unit equals <code>scale</code>, then image with size <code>scale</code> * n will be selected, where n is the size of the icon in the UI. At least one image must be specified. Note that 'details.path = foo' is equivalent to 'details.path = {'16': foo}'"
},
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
],
"returns_async": {
"name": "callback",
"optional": true,
"parameters": []
}
},
{
"name": "setPopup",
"deprecated": "chrome.action.setPopup is not supported in Electron",
"type": "function",
"description": "Sets the HTML document to be opened as a popup when the user clicks on the action's icon.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
},
"popup": {
"type": "string",
"description": "The relative path to the HTML file to show in a popup. If set to the empty string (<code>''</code>), no popup is shown."
}
}
}
],
"returns_async": {
"name": "callback",
"parameters": [],
"optional": true
}
},
{
"name": "getPopup",
"deprecated": "chrome.action.getPopup is not supported in Electron",
"type": "function",
"description": "Gets the html document set as the popup for this action.",
"parameters": [
{
"name": "details",
"$ref": "TabDetails"
}
],
"returns_async": {
"name": "callback",
"parameters": [
{
"name": "result",
"type": "string"
}
]
}
},
{
"name": "setBadgeText",
"deprecated": "chrome.action.setBadgeText is not supported in Electron",
"type": "function",
"description": "Sets the badge text for the action. The badge is displayed on top of the icon.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"text": {
"type": "string",
"optional": true,
"description": "Any number of characters can be passed, but only about four can fit in the space. If an empty string (<code>''</code>) is passed, the badge text is cleared. If <code>tabId</code> is specified and <code>text</code> is null, the text for the specified tab is cleared and defaults to the global badge text."
},
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
],
"returns_async": {
"name": "callback",
"parameters": [],
"optional": true
}
},
{
"name": "getBadgeText",
"deprecated": "chrome.action.getBadgeText is not supported in Electron",
"type": "function",
"description": "Gets the badge text of the action. If no tab is specified, the non-tab-specific badge text is returned. If <a href='declarativeNetRequest#setExtensionActionOptions'>displayActionCountAsBadgeText</a> is enabled, a placeholder text will be returned unless the <a href='declare_permissions#declarativeNetRequestFeedback'>declarativeNetRequestFeedback</a> permission is present or tab-specific badge text was provided.",
"parameters": [
{
"name": "details",
"$ref": "TabDetails"
}
],
"returns_async": {
"name": "callback",
"parameters": [
{
"name": "result",
"type": "string"
}
]
}
},
{
"name": "setBadgeBackgroundColor",
"deprecated": "chrome.action.setBadgeBackgroundColor is not supported in Electron",
"type": "function",
"description": "Sets the background color for the badge.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"color": {
"description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>.",
"choices": [
{
"type": "string"
},
{
"$ref": "browserAction.ColorArray"
}
]
},
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
],
"returns_async": {
"name": "callback",
"parameters": [],
"optional": true
}
},
{
"name": "getBadgeBackgroundColor",
"deprecated": "chrome.action.getBadgeBackgroundColor is not supported in Electron",
"type": "function",
"description": "Gets the background color of the action.",
"parameters": [
{
"name": "details",
"$ref": "TabDetails"
}
],
"returns_async": {
"name": "callback",
"parameters": [
{
"name": "result",
"$ref": "browserAction.ColorArray"
}
]
}
},
{
"name": "setBadgeTextColor",
"deprecated": "chrome.action.setBadgeTextColor is not supported in Electron",
"type": "function",
"description": "Sets the text color for the badge.",
"parameters": [
{
"name": "details",
"type": "object",
"properties": {
"color": {
"description": "An array of four integers in the range [0,255] that make up the RGBA color of the badge. For example, opaque red is <code>[255, 0, 0, 255]</code>. Can also be a string with a CSS value, with opaque red being <code>#FF0000</code> or <code>#F00</code>. Not setting this value will cause a color to be automatically chosen that will contrast with the badge's background color so the text will be visible. Colors with alpha values equivalent to 0 will not be set and will return an error.",
"choices": [
{
"type": "string"
},
{
"$ref": "browserAction.ColorArray"
}
]
},
"tabId": {
"type": "integer",
"optional": true,
"minimum": 0,
"description": "Limits the change to when a particular tab is selected. Automatically resets when the tab is closed."
}
}
}
],
"returns_async": {
"name": "callback",
"parameters": [],
"optional": true
}
},
{
"name": "getBadgeTextColor",
"deprecated": "chrome.action.getBadgeTextColor is not supported in Electron",
"type": "function",
"description": "Gets the text color of the action.",
"parameters": [
{
"name": "details",
"$ref": "TabDetails"
}
],
"returns_async": {
"name": "callback",
"parameters": [
{
"name": "result",
"$ref": "browserAction.ColorArray"
}
]
}
},
{
"name": "enable",
"deprecated": "chrome.action.enable is not supported in Electron",
"type": "function",
"description": "Enables the action for a tab. By default, actions are enabled.",
"parameters": [
{
"type": "integer",
"optional": true,
"name": "tabId",
"minimum": 0,
"description": "The id of the tab for which you want to modify the action."
}
],
"returns_async": {
"name": "callback",
"parameters": [],
"optional": true
}
},
{
"name": "disable",
"deprecated": "chrome.action.disable is not supported in Electron",
"type": "function",
"description": "Disables the action for a tab.",
"parameters": [
{
"type": "integer",
"optional": true,
"name": "tabId",
"minimum": 0,
"description": "The id of the tab for which you want to modify the action."
}
],
"returns_async": {
"name": "callback",
"parameters": [],
"optional": true
}
},
{
"name": "isEnabled",
"deprecated": "chrome.action.isEnabled is not supported in Electron",
"type": "function",
"description": "Indicates whether the extension action is enabled for a tab (or globally if no <code>tabId</code> is provided). Actions enabled using only $(ref:declarativeContent) always return false.",
"parameters": [
{
"type": "integer",
"optional": true,
"name": "tabId",
"minimum": 0,
"description": "The id of the tab for which you want check enabled status."
}
],
"returns_async": {
"name": "callback",
"parameters": [
{
"name": "isEnabled",
"type": "boolean",
"description": "True if the extension action is enabled."
}
]
}
},
{
"name": "getUserSettings",
"deprecated": "chrome.action.getUserSettings is not supported in Electron",
"type": "function",
"description": "Returns the user-specified settings relating to an extension's action.",
"parameters": [],
"returns_async": {
"name": "callback",
"parameters": [
{
"name": "userSettings",
"$ref": "UserSettings"
}
]
}
},
{
"name": "openPopup",
"deprecated": "chrome.action.openPopup is not supported in Electron",
"type": "function",
"description": "Opens the extension's popup.",
"parameters": [
{
"$ref": "OpenPopupOptions",
"name": "options",
"optional": true,
"description": "Specifies options for opening the popup."
}
],
"returns_async": {
"name": "callback",
"parameters": []
}
}
],
"events": [
{
"name": "onClicked",
"deprecated": "chrome.action.onClicked is not supported in Electron",
"type": "function",
"description": "Fired when an action icon is clicked. This event will not fire if the action has a popup.",
"parameters": [
{
"name": "tab",
"$ref": "tabs.Tab"
}
]
}
]
}
]

View File

@@ -44,6 +44,8 @@ namespace api {
namespace context_bridge {
const char kProxyFunctionPrivateKey[] = "electron_contextBridge_proxy_fn";
const char kProxyFunctionReceiverPrivateKey[] =
"electron_contextBridge_proxy_fn_receiver";
const char kSupportsDynamicPropertiesPrivateKey[] =
"electron_contextBridge_supportsDynamicProperties";
const char kOriginalFunctionPrivateKey[] = "electron_contextBridge_original_fn";
@@ -138,6 +140,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Context> source_context,
v8::Local<v8::Context> destination_context,
v8::Local<v8::Value> value,
v8::Local<v8::Value> parent_value,
context_bridge::ObjectCache* object_cache,
bool support_dynamic_properties,
int recursion_depth,
@@ -199,6 +202,9 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Object::New(destination_context->GetIsolate());
SetPrivate(destination_context, state,
context_bridge::kProxyFunctionPrivateKey, func);
SetPrivate(destination_context, state,
context_bridge::kProxyFunctionReceiverPrivateKey,
parent_value);
SetPrivate(destination_context, state,
context_bridge::kSupportsDynamicPropertiesPrivateKey,
gin::ConvertToV8(destination_context->GetIsolate(),
@@ -246,10 +252,12 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::MaybeLocal<v8::Value> val;
{
v8::TryCatch try_catch(isolate);
v8::Local<v8::Context> source_context =
global_source_context.Get(isolate);
val = PassValueToOtherContext(
global_source_context.Get(isolate),
global_destination_context.Get(isolate), result, &object_cache,
false, 0, BridgeErrorTarget::kDestination);
source_context, global_destination_context.Get(isolate), result,
source_context->Global(), &object_cache, false, 0,
BridgeErrorTarget::kDestination);
if (try_catch.HasCaught()) {
if (try_catch.Message().IsEmpty()) {
proxied_promise->RejectWithErrorMessage(
@@ -292,10 +300,12 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::MaybeLocal<v8::Value> val;
{
v8::TryCatch try_catch(isolate);
v8::Local<v8::Context> source_context =
global_source_context.Get(isolate);
val = PassValueToOtherContext(
global_source_context.Get(isolate),
global_destination_context.Get(isolate), result, &object_cache,
false, 0, BridgeErrorTarget::kDestination);
source_context, global_destination_context.Get(isolate), result,
source_context->Global(), &object_cache, false, 0,
BridgeErrorTarget::kDestination);
if (try_catch.HasCaught()) {
if (try_catch.Message().IsEmpty()) {
proxied_promise->RejectWithErrorMessage(
@@ -362,7 +372,7 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
for (size_t i = 0; i < length; i++) {
auto value_for_array = PassValueToOtherContext(
source_context, destination_context,
arr->Get(source_context, i).ToLocalChecked(), object_cache,
arr->Get(source_context, i).ToLocalChecked(), value, object_cache,
support_dynamic_properties, recursion_depth + 1, error_target);
if (value_for_array.IsEmpty())
return v8::MaybeLocal<v8::Value>();
@@ -442,8 +452,10 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
context_bridge::kSupportsDynamicPropertiesPrivateKey);
v8::MaybeLocal<v8::Value> maybe_func = GetPrivate(
calling_context, data, context_bridge::kProxyFunctionPrivateKey);
v8::MaybeLocal<v8::Value> maybe_recv = GetPrivate(
calling_context, data, context_bridge::kProxyFunctionReceiverPrivateKey);
v8::Local<v8::Value> func_value;
if (sdp_value.IsEmpty() || maybe_func.IsEmpty() ||
if (sdp_value.IsEmpty() || maybe_func.IsEmpty() || maybe_recv.IsEmpty() ||
!gin::ConvertFromV8(args.isolate(), sdp_value.ToLocalChecked(),
&support_dynamic_properties) ||
!maybe_func.ToLocal(&func_value))
@@ -463,8 +475,9 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
for (auto value : original_args) {
auto arg = PassValueToOtherContext(
calling_context, func_owning_context, value, &object_cache,
support_dynamic_properties, 0, BridgeErrorTarget::kSource);
calling_context, func_owning_context, value,
calling_context->Global(), &object_cache, support_dynamic_properties,
0, BridgeErrorTarget::kSource);
if (arg.IsEmpty())
return;
proxied_args.push_back(arg.ToLocalChecked());
@@ -475,8 +488,9 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::Local<v8::Value> error_message;
{
v8::TryCatch try_catch(args.isolate());
maybe_return_value = func->Call(func_owning_context, func,
proxied_args.size(), proxied_args.data());
maybe_return_value =
func->Call(func_owning_context, maybe_recv.ToLocalChecked(),
proxied_args.size(), proxied_args.data());
if (try_catch.HasCaught()) {
did_error = true;
v8::Local<v8::Value> exception = try_catch.Exception();
@@ -531,6 +545,7 @@ void ProxyFunctionWrapper(const v8::FunctionCallbackInfo<v8::Value>& info) {
v8::TryCatch try_catch(args.isolate());
ret = PassValueToOtherContext(func_owning_context, calling_context,
maybe_return_value.ToLocalChecked(),
func_owning_context->Global(),
&object_cache, support_dynamic_properties,
0, BridgeErrorTarget::kDestination);
if (try_catch.HasCaught()) {
@@ -607,18 +622,18 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
v8::Local<v8::Value> getter_proxy;
v8::Local<v8::Value> setter_proxy;
if (!getter.IsEmpty()) {
if (!PassValueToOtherContext(source_context, destination_context,
getter, object_cache,
support_dynamic_properties, 1,
error_target)
if (!PassValueToOtherContext(
source_context, destination_context, getter,
api.GetHandle(), object_cache,
support_dynamic_properties, 1, error_target)
.ToLocal(&getter_proxy))
continue;
}
if (!setter.IsEmpty()) {
if (!PassValueToOtherContext(source_context, destination_context,
setter, object_cache,
support_dynamic_properties, 1,
error_target)
if (!PassValueToOtherContext(
source_context, destination_context, setter,
api.GetHandle(), object_cache,
support_dynamic_properties, 1, error_target)
.ToLocal(&setter_proxy))
continue;
}
@@ -635,8 +650,9 @@ v8::MaybeLocal<v8::Object> CreateProxyForAPI(
continue;
auto passed_value = PassValueToOtherContext(
source_context, destination_context, value, object_cache,
support_dynamic_properties, recursion_depth + 1, error_target);
source_context, destination_context, value, api.GetHandle(),
object_cache, support_dynamic_properties, recursion_depth + 1,
error_target);
if (passed_value.IsEmpty())
return v8::MaybeLocal<v8::Object>();
proxy.Set(key, passed_value.ToLocalChecked());
@@ -683,7 +699,8 @@ void ExposeAPIInWorld(v8::Isolate* isolate,
v8::Context::Scope target_context_scope(target_context);
v8::MaybeLocal<v8::Value> maybe_proxy = PassValueToOtherContext(
electron_isolated_context, target_context, api, &object_cache, false, 0,
electron_isolated_context, target_context, api,
electron_isolated_context->Global(), &object_cache, false, 0,
BridgeErrorTarget::kSource);
if (maybe_proxy.IsEmpty())
return;
@@ -732,9 +749,11 @@ void OverrideGlobalValueFromIsolatedWorld(
{
v8::Context::Scope main_context_scope(main_context);
context_bridge::ObjectCache object_cache;
v8::Local<v8::Context> source_context = value->GetCreationContextChecked();
v8::MaybeLocal<v8::Value> maybe_proxy = PassValueToOtherContext(
value->GetCreationContextChecked(), main_context, value, &object_cache,
support_dynamic_properties, 1, BridgeErrorTarget::kSource);
source_context, main_context, value, source_context->Global(),
&object_cache, support_dynamic_properties, 1,
BridgeErrorTarget::kSource);
DCHECK(!maybe_proxy.IsEmpty());
auto proxy = maybe_proxy.ToLocalChecked();
@@ -768,15 +787,19 @@ bool OverrideGlobalPropertyFromIsolatedWorld(
v8::Local<v8::Value> getter_proxy;
v8::Local<v8::Value> setter_proxy;
if (!getter->IsNullOrUndefined()) {
v8::Local<v8::Context> source_context =
getter->GetCreationContextChecked();
v8::MaybeLocal<v8::Value> maybe_getter_proxy = PassValueToOtherContext(
getter->GetCreationContextChecked(), main_context, getter,
source_context, main_context, getter, source_context->Global(),
&object_cache, false, 1, BridgeErrorTarget::kSource);
DCHECK(!maybe_getter_proxy.IsEmpty());
getter_proxy = maybe_getter_proxy.ToLocalChecked();
}
if (!setter->IsNullOrUndefined() && setter->IsObject()) {
v8::Local<v8::Context> source_context =
getter->GetCreationContextChecked();
v8::MaybeLocal<v8::Value> maybe_setter_proxy = PassValueToOtherContext(
getter->GetCreationContextChecked(), main_context, setter,
source_context, main_context, setter, source_context->Global(),
&object_cache, false, 1, BridgeErrorTarget::kSource);
DCHECK(!maybe_setter_proxy.IsEmpty());
setter_proxy = maybe_setter_proxy.ToLocalChecked();

View File

@@ -36,6 +36,14 @@ v8::MaybeLocal<v8::Value> PassValueToOtherContext(
v8::Local<v8::Context> source_context,
v8::Local<v8::Context> destination_context,
v8::Local<v8::Value> value,
/**
* Used to automatically bind a function across
* worlds to its appropriate default "this" value.
*
* If this value is the root of a tree going over
* the bridge set this to the "context" of the value.
*/
v8::Local<v8::Value> parent_value,
context_bridge::ObjectCache* object_cache,
bool support_dynamic_properties,
int recursion_depth,

View File

@@ -140,9 +140,12 @@ class ScriptExecutionCallback {
{
v8::TryCatch try_catch(isolate);
context_bridge::ObjectCache object_cache;
maybe_result = PassValueToOtherContext(
result->GetCreationContextChecked(), promise_.GetContext(), result,
&object_cache, false, 0, BridgeErrorTarget::kSource);
v8::Local<v8::Context> source_context =
result->GetCreationContextChecked();
maybe_result =
PassValueToOtherContext(source_context, promise_.GetContext(), result,
source_context->Global(), &object_cache,
false, 0, BridgeErrorTarget::kSource);
if (maybe_result.IsEmpty() || try_catch.HasCaught()) {
success = false;
}

View File

@@ -608,8 +608,8 @@ void RendererClientBase::SetupMainWorldOverrides(
if (global.GetHidden("guestViewInternal", &guest_view_internal)) {
api::context_bridge::ObjectCache object_cache;
auto result = api::PassValueToOtherContext(
source_context, context, guest_view_internal, &object_cache, false, 0,
api::BridgeErrorTarget::kSource);
source_context, context, guest_view_internal, source_context->Global(),
&object_cache, false, 0, api::BridgeErrorTarget::kSource);
if (!result.IsEmpty()) {
isolated_api.Set("guestViewInternal", result.ToLocalChecked());
}

View File

@@ -2224,7 +2224,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');
@@ -2233,21 +2248,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 () => {

View File

@@ -317,11 +317,7 @@ describe('ipc module', () => {
await once(ipcMain, 'closed');
});
// TODO(@vertedinde): This broke upstream in CL https://chromium-review.googlesource.com/c/chromium/src/+/4831380
// The behavior seems to be an intentional change, we need to either A) implement the task_container_ model in
// our renderer message ports or B) patch how we handle renderer message ports being garbage collected
// crbug: https://bugs.chromium.org/p/chromium/issues/detail?id=1487835
it.skip('is emitted when the other end of a port is garbage-collected', async () => {
it('is emitted when the other end of a port is garbage-collected', async () => {
const w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true, contextIsolation: false } });
w.loadURL('about:blank');
await w.webContents.executeJavaScript(`(${async function () {

View File

@@ -490,6 +490,15 @@ describe('asar package', function () {
}).to.throw(/ENOENT/);
}
});
itremote('returns null when can not find file with throwIfNoEntry === false', function () {
const ref2 = ['file4', 'file5', path.join('dir1', 'file4')];
for (let j = 0, len = ref2.length; j < len; j++) {
const file = ref2[j];
const p = path.join(asarDir, 'a.asar', file);
expect(fs.lstatSync(p, { throwIfNoEntry: false })).to.equal(null);
}
});
});
describe('fs.lstat', function () {

View File

@@ -429,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' });
// @ts-expect-error Removed API
console.log(app.runningUnderRosettaTranslation);
@@ -1284,6 +1289,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

View File

@@ -2020,25 +2020,34 @@ describe('<webview> tag', function () {
// TODO(miniak): figure out why this is failing on windows
ifdescribe(process.platform !== 'win32')('<webview>.capturePage()', () => {
it('returns a Promise with a NativeImage', async () => {
it('returns a Promise with a NativeImage', async function () {
this.retries(5);
const src = 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E';
await loadWebViewAndWaitForEvent(w, { src }, 'did-stop-loading');
// Retry a few times due to flake.
for (let i = 0; i < 5; i++) {
try {
const image = await w.executeJavaScript('webview.capturePage()');
const imgBuffer = image.toPNG();
const image = await w.executeJavaScript('webview.capturePage()');
expect(image.isEmpty()).to.be.false();
// Check the 25th byte in the PNG.
// Values can be 0,2,3,4, or 6. We want 6, which is RGB + Alpha
expect(imgBuffer[25]).to.equal(6);
return;
} catch {
/* drop the error */
}
}
expect(false).to.be.true('could not successfully capture the page');
// Check the 25th byte in the PNG.
// Values can be 0,2,3,4, or 6. We want 6, which is RGB + Alpha
const imgBuffer = image.toPNG();
expect(imgBuffer[25]).to.equal(6);
});
it('returns a Promise with a NativeImage in the renderer', async function () {
this.retries(5);
const src = 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E';
await loadWebViewAndWaitForEvent(w, { src }, 'did-stop-loading');
const byte = await w.executeJavaScript(`new Promise(resolve => {
webview.capturePage().then(image => {
resolve(image.toPNG()[25])
});
})`);
expect(byte).to.equal(6);
});
});