mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
15 Commits
feat/games
...
v28.0.0-al
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0310156be6 | ||
|
|
714e6ea49c | ||
|
|
3cf8652027 | ||
|
|
f622bd9927 | ||
|
|
9ddd08ba89 | ||
|
|
a98d66dd23 | ||
|
|
1e51ee974d | ||
|
|
6732f63ac0 | ||
|
|
641249f384 | ||
|
|
f6b135e1ac | ||
|
|
a5f01f0328 | ||
|
|
d65b8761f8 | ||
|
|
f8d4c45f8e | ||
|
|
6625666e40 | ||
|
|
e3e7bf3786 |
2
DEPS
2
DEPS
@@ -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':
|
||||
|
||||
@@ -1134,11 +1134,11 @@ indicates success while any other value indicates failure according to Chromium
|
||||
resolver will attempt to use the system's DNS settings to do DNS lookups
|
||||
itself. Enabled by default on macOS, disabled by default on Windows and
|
||||
Linux.
|
||||
* `secureDnsMode` string (optional) - Can be "off", "automatic" or "secure".
|
||||
Configures the DNS-over-HTTP mode. When "off", no DoH lookups will be
|
||||
performed. When "automatic", DoH lookups will be performed first if DoH is
|
||||
* `secureDnsMode` string (optional) - Can be 'off', 'automatic' or 'secure'.
|
||||
Configures the DNS-over-HTTP mode. When 'off', no DoH lookups will be
|
||||
performed. When 'automatic', DoH lookups will be performed first if DoH is
|
||||
available, and insecure DNS lookups will be performed as a fallback. When
|
||||
"secure", only DoH lookups will be performed. Defaults to "automatic".
|
||||
'secure', only DoH lookups will be performed. Defaults to 'automatic'.
|
||||
* `secureDnsServers` string[] (optional) - A list of DNS-over-HTTP
|
||||
server templates. See [RFC8484 § 3][] for details on the template format.
|
||||
Most servers support the POST method; the template for such servers is
|
||||
|
||||
@@ -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`
|
||||
|
||||
@@ -1211,7 +1211,7 @@ Returns `string` - The user agent for this web page.
|
||||
|
||||
* `css` string
|
||||
* `options` Object (optional)
|
||||
* `cssOrigin` string (optional) - Can be either 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
* `cssOrigin` string (optional) - Can be 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
|
||||
Returns `Promise<string>` - A promise that resolves with a key for the inserted CSS that can later be used to remove the CSS via `contents.removeInsertedCSS(key)`.
|
||||
|
||||
|
||||
@@ -113,7 +113,7 @@ webFrame.setSpellCheckProvider('en-US', {
|
||||
|
||||
* `css` string
|
||||
* `options` Object (optional)
|
||||
* `cssOrigin` string (optional) - Can be either 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
* `cssOrigin` string (optional) - Can be 'user' or 'author'. Sets the [cascade origin](https://www.w3.org/TR/css3-cascade/#cascade-origin) of the inserted stylesheet. Default is 'author'.
|
||||
|
||||
Returns `string` - A key for the inserted CSS that can later be used to remove
|
||||
the CSS via `webFrame.removeInsertedCSS(key)`.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
@@ -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",
|
||||
]
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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_;
|
||||
};
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
fix_fallback_to_x11_capturer_on_wayland.patch
|
||||
fix_mark_pipewire_capturer_as_failed_after_session_is_closed.patch
|
||||
fix_check_pipewire_init_before_creating_generic_capturer.patch
|
||||
pipewire_capturer_make_restore_tokens_re-usable_more_than_one_time.patch
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jan Grulich <grulja@gmail.com>
|
||||
Date: Mon, 9 Oct 2023 18:54:31 +0200
|
||||
Subject: PipeWire capturer: make restore tokens re-usable more than one time
|
||||
|
||||
Do not automatically remove all tokens once we attempt to use them. This
|
||||
mitigates an issue with Google Meet where an additional instance of a
|
||||
DesktopCapturer is created and destroyed right away, taking away the
|
||||
token we would use otherwise. Also save the token under same SourceId
|
||||
once we get a new (but could be same) token from the restored session.
|
||||
|
||||
Bug: webrtc:15544
|
||||
Change-Id: I565b22f5bf6a4d8a3b7d6d757f9c1046c7a0557d
|
||||
Reviewed-on: https://webrtc-review.googlesource.com/c/src/+/322621
|
||||
Commit-Queue: Jan Grulich <grulja@gmail.com>
|
||||
Reviewed-by: Alexander Cooper <alcooper@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#40892}
|
||||
|
||||
diff --git a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
|
||||
index 5a67c18c1d1f62aa5e3162d9778ca665bac4a1bb..a5df76b0cdecd1e2e68f2f25c80c6b17de8bc808 100644
|
||||
--- a/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
|
||||
+++ b/modules/desktop_capture/linux/wayland/base_capturer_pipewire.cc
|
||||
@@ -82,8 +82,10 @@ void BaseCapturerPipeWire::OnScreenCastRequestResult(RequestResponse result,
|
||||
<< static_cast<uint>(result);
|
||||
} else if (ScreenCastPortal* screencast_portal = GetScreenCastPortal()) {
|
||||
if (!screencast_portal->RestoreToken().empty()) {
|
||||
+ const SourceId token_id =
|
||||
+ selected_source_id_ ? selected_source_id_ : source_id_;
|
||||
RestoreTokenManager::GetInstance().AddToken(
|
||||
- source_id_, screencast_portal->RestoreToken());
|
||||
+ token_id, screencast_portal->RestoreToken());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -138,7 +140,7 @@ void BaseCapturerPipeWire::Start(Callback* callback) {
|
||||
ScreenCastPortal::PersistMode::kTransient);
|
||||
if (selected_source_id_) {
|
||||
screencast_portal->SetRestoreToken(
|
||||
- RestoreTokenManager::GetInstance().TakeToken(selected_source_id_));
|
||||
+ RestoreTokenManager::GetInstance().GetToken(selected_source_id_));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/modules/desktop_capture/linux/wayland/restore_token_manager.cc b/modules/desktop_capture/linux/wayland/restore_token_manager.cc
|
||||
index 5ca9b957a9e4f436bc09d4bc16019b169ae9ba9f..a17d9a49bb031efa340bfd61b4a6f8f5a86d09da 100644
|
||||
--- a/modules/desktop_capture/linux/wayland/restore_token_manager.cc
|
||||
+++ b/modules/desktop_capture/linux/wayland/restore_token_manager.cc
|
||||
@@ -23,10 +23,8 @@ void RestoreTokenManager::AddToken(DesktopCapturer::SourceId id,
|
||||
restore_tokens_.insert({id, token});
|
||||
}
|
||||
|
||||
-std::string RestoreTokenManager::TakeToken(DesktopCapturer::SourceId id) {
|
||||
- std::string token = restore_tokens_[id];
|
||||
- // Remove the token as it cannot be used anymore
|
||||
- restore_tokens_.erase(id);
|
||||
+std::string RestoreTokenManager::GetToken(DesktopCapturer::SourceId id) {
|
||||
+ const std::string token = restore_tokens_[id];
|
||||
return token;
|
||||
}
|
||||
|
||||
diff --git a/modules/desktop_capture/linux/wayland/restore_token_manager.h b/modules/desktop_capture/linux/wayland/restore_token_manager.h
|
||||
index 174bef121f74b7b2b529d681b86c4fb4218586ea..ad4f74790f2a5cfba304fc11d47c9924db9013d8 100644
|
||||
--- a/modules/desktop_capture/linux/wayland/restore_token_manager.h
|
||||
+++ b/modules/desktop_capture/linux/wayland/restore_token_manager.h
|
||||
@@ -27,7 +27,7 @@ class RestoreTokenManager {
|
||||
static RestoreTokenManager& GetInstance();
|
||||
|
||||
void AddToken(DesktopCapturer::SourceId id, const std::string& token);
|
||||
- std::string TakeToken(DesktopCapturer::SourceId id);
|
||||
+ std::string GetToken(DesktopCapturer::SourceId id);
|
||||
|
||||
// Returns a source ID which does not have any token associated with it yet.
|
||||
DesktopCapturer::SourceId GetUnusedId();
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
@@ -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_
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -10,17 +10,24 @@
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include "base/observer_list.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "base/containers/flat_map.h"
|
||||
#include "content/public/browser/hid_chooser.h"
|
||||
#include "content/public/browser/hid_delegate.h"
|
||||
#include "services/device/public/mojom/hid.mojom-forward.h"
|
||||
#include "shell/browser/hid/hid_chooser_context.h"
|
||||
#include "third_party/blink/public/mojom/hid/hid.mojom-forward.h"
|
||||
#include "url/origin.h"
|
||||
|
||||
namespace content {
|
||||
class BrowserContext;
|
||||
class RenderFrameHost;
|
||||
} // namespace content
|
||||
|
||||
namespace electron {
|
||||
|
||||
class HidChooserController;
|
||||
|
||||
class ElectronHidDelegate : public content::HidDelegate,
|
||||
public HidChooserContext::DeviceObserver {
|
||||
class ElectronHidDelegate : public content::HidDelegate {
|
||||
public:
|
||||
ElectronHidDelegate();
|
||||
ElectronHidDelegate(ElectronHidDelegate&) = delete;
|
||||
@@ -54,13 +61,6 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
bool IsFidoAllowedForOrigin(content::BrowserContext* browser_context,
|
||||
const url::Origin& origin) override;
|
||||
bool IsServiceWorkerAllowedForOrigin(const url::Origin& origin) override;
|
||||
|
||||
// HidChooserContext::DeviceObserver:
|
||||
void OnDeviceAdded(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnDeviceRemoved(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnDeviceChanged(const device::mojom::HidDeviceInfo&) override;
|
||||
void OnHidManagerConnectionError() override;
|
||||
void OnHidChooserContextShutdown() override;
|
||||
void IncrementConnectionCount(content::BrowserContext* browser_context,
|
||||
const url::Origin& origin) override {}
|
||||
void DecrementConnectionCount(content::BrowserContext* browser_context,
|
||||
@@ -69,6 +69,14 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
void DeleteControllerForFrame(content::RenderFrameHost* render_frame_host);
|
||||
|
||||
private:
|
||||
class ContextObservation;
|
||||
|
||||
ContextObservation* GetContextObserver(
|
||||
content::BrowserContext* browser_context);
|
||||
|
||||
base::flat_map<content::BrowserContext*, std::unique_ptr<ContextObservation>>
|
||||
observations_;
|
||||
|
||||
HidChooserController* ControllerForFrame(
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
@@ -78,10 +86,6 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
std::vector<blink::mojom::HidDeviceFilterPtr> exclusion_filters,
|
||||
content::HidChooser::Callback callback);
|
||||
|
||||
base::ScopedObservation<HidChooserContext, HidChooserContext::DeviceObserver>
|
||||
device_observation_{this};
|
||||
base::ObserverList<content::HidDelegate::Observer> observer_list_;
|
||||
|
||||
std::unordered_map<content::RenderFrameHost*,
|
||||
std::unique_ptr<HidChooserController>>
|
||||
controller_map_;
|
||||
@@ -91,23 +95,4 @@ class ElectronHidDelegate : public content::HidDelegate,
|
||||
|
||||
} // namespace electron
|
||||
|
||||
namespace base {
|
||||
|
||||
template <>
|
||||
struct ScopedObservationTraits<electron::HidChooserContext,
|
||||
electron::HidChooserContext::DeviceObserver> {
|
||||
static void AddObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->AddDeviceObserver(observer);
|
||||
}
|
||||
static void RemoveObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->RemoveDeviceObserver(observer);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_HID_ELECTRON_HID_DELEGATE_H_
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/scoped_observation_traits.h"
|
||||
#include "base/unguessable_token.h"
|
||||
#include "components/keyed_service/core/keyed_service.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
@@ -138,4 +139,23 @@ class HidChooserContext : public KeyedService,
|
||||
|
||||
} // namespace electron
|
||||
|
||||
namespace base {
|
||||
|
||||
template <>
|
||||
struct ScopedObservationTraits<electron::HidChooserContext,
|
||||
electron::HidChooserContext::DeviceObserver> {
|
||||
static void AddObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->AddDeviceObserver(observer);
|
||||
}
|
||||
static void RemoveObserver(
|
||||
electron::HidChooserContext* source,
|
||||
electron::HidChooserContext::DeviceObserver* observer) {
|
||||
source->RemoveDeviceObserver(observer);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace base
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_HID_HID_CHOOSER_CONTEXT_H_
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/scoped_observation.h"
|
||||
#include "content/public/browser/global_routing_id.h"
|
||||
#include "content/public/browser/hid_chooser.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -27,10 +27,14 @@ void Notification::NotificationClicked() {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Notification::NotificationDismissed() {
|
||||
void Notification::NotificationDismissed(bool should_destroy) {
|
||||
if (delegate())
|
||||
delegate()->NotificationClosed();
|
||||
Destroy();
|
||||
|
||||
set_is_dismissed(true);
|
||||
|
||||
if (should_destroy)
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Notification::NotificationFailed(const std::string& error) {
|
||||
@@ -39,6 +43,8 @@ void Notification::NotificationFailed(const std::string& error) {
|
||||
Destroy();
|
||||
}
|
||||
|
||||
void Notification::Remove() {}
|
||||
|
||||
void Notification::Destroy() {
|
||||
presenter()->RemoveNotification(this);
|
||||
}
|
||||
|
||||
@@ -50,13 +50,19 @@ class Notification {
|
||||
|
||||
// Shows the notification.
|
||||
virtual void Show(const NotificationOptions& options) = 0;
|
||||
// Closes the notification, this instance will be destroyed after the
|
||||
// notification gets closed.
|
||||
|
||||
// Dismisses the notification. On some platforms this will result in full
|
||||
// removal and destruction of the notification, but if the initial dismissal
|
||||
// does not fully get rid of the notification it will be destroyed in Remove.
|
||||
virtual void Dismiss() = 0;
|
||||
|
||||
// Removes the notification if it was not fully removed during dismissal,
|
||||
// as can happen on some platforms including Windows.
|
||||
virtual void Remove();
|
||||
|
||||
// Should be called by derived classes.
|
||||
void NotificationClicked();
|
||||
void NotificationDismissed();
|
||||
void NotificationDismissed(bool should_destroy = true);
|
||||
void NotificationFailed(const std::string& error = "");
|
||||
|
||||
// delete this.
|
||||
@@ -68,10 +74,12 @@ class Notification {
|
||||
|
||||
void set_delegate(NotificationDelegate* delegate) { delegate_ = delegate; }
|
||||
void set_notification_id(const std::string& id) { notification_id_ = id; }
|
||||
void set_is_dismissed(bool dismissed) { is_dismissed_ = dismissed; }
|
||||
|
||||
NotificationDelegate* delegate() const { return delegate_; }
|
||||
NotificationPresenter* presenter() const { return presenter_; }
|
||||
const std::string& notification_id() const { return notification_id_; }
|
||||
bool is_dismissed() const { return is_dismissed_; }
|
||||
|
||||
// disable copy
|
||||
Notification(const Notification&) = delete;
|
||||
@@ -85,6 +93,7 @@ class Notification {
|
||||
raw_ptr<NotificationDelegate> delegate_;
|
||||
raw_ptr<NotificationPresenter> presenter_;
|
||||
std::string notification_id_;
|
||||
bool is_dismissed_ = false;
|
||||
|
||||
base::WeakPtrFactory<Notification> weak_factory_{this};
|
||||
};
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include "shell/browser/notifications/win/windows_toast_notification.h"
|
||||
|
||||
#include <string_view>
|
||||
|
||||
#include <shlobj.h>
|
||||
#include <wrl\wrappers\corewrappers.h>
|
||||
|
||||
@@ -34,6 +36,8 @@ using ABI::Windows::Data::Xml::Dom::IXmlNodeList;
|
||||
using ABI::Windows::Data::Xml::Dom::IXmlText;
|
||||
using Microsoft::WRL::Wrappers::HStringReference;
|
||||
|
||||
namespace winui = ABI::Windows::UI;
|
||||
|
||||
#define RETURN_IF_FAILED(hr) \
|
||||
do { \
|
||||
HRESULT _hrTemp = hr; \
|
||||
@@ -47,8 +51,7 @@ using Microsoft::WRL::Wrappers::HStringReference;
|
||||
std::string _msgTemp = msg; \
|
||||
if (FAILED(_hrTemp)) { \
|
||||
std::string _err = _msgTemp + ",ERROR " + std::to_string(_hrTemp); \
|
||||
if (IsDebuggingNotifications()) \
|
||||
LOG(INFO) << _err; \
|
||||
DebugLog(_err); \
|
||||
Notification::NotificationFailed(_err); \
|
||||
return _hrTemp; \
|
||||
} \
|
||||
@@ -58,17 +61,23 @@ namespace electron {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsDebuggingNotifications() {
|
||||
return base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS");
|
||||
// This string needs to be max 16 characters to work on Windows 10 prior to
|
||||
// applying Creators Update (build 15063).
|
||||
constexpr wchar_t kGroup[] = L"Notifications";
|
||||
|
||||
void DebugLog(std::string_view log_msg) {
|
||||
if (base::Environment::Create()->HasVar("ELECTRON_DEBUG_NOTIFICATIONS"))
|
||||
LOG(INFO) << log_msg;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationManagerStatics>
|
||||
ComPtr<winui::Notifications::IToastNotificationManagerStatics>
|
||||
WindowsToastNotification::toast_manager_;
|
||||
|
||||
// static
|
||||
ComPtr<ABI::Windows::UI::Notifications::IToastNotifier>
|
||||
ComPtr<winui::Notifications::IToastNotifier>
|
||||
WindowsToastNotification::toast_notifier_;
|
||||
|
||||
// static
|
||||
@@ -112,17 +121,37 @@ WindowsToastNotification::~WindowsToastNotification() {
|
||||
|
||||
void WindowsToastNotification::Show(const NotificationOptions& options) {
|
||||
if (SUCCEEDED(ShowInternal(options))) {
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Notification created";
|
||||
DebugLog("Notification created");
|
||||
|
||||
if (delegate())
|
||||
delegate()->NotificationDisplayed();
|
||||
}
|
||||
}
|
||||
|
||||
void WindowsToastNotification::Remove() {
|
||||
DebugLog("Removing notification from action center");
|
||||
|
||||
ComPtr<winui::Notifications::IToastNotificationManagerStatics2>
|
||||
toast_manager2;
|
||||
if (FAILED(toast_manager_.As(&toast_manager2)))
|
||||
return;
|
||||
|
||||
ComPtr<winui::Notifications::IToastNotificationHistory> notification_history;
|
||||
if (FAILED(toast_manager2->get_History(¬ification_history)))
|
||||
return;
|
||||
|
||||
ScopedHString app_id;
|
||||
if (!GetAppUserModelID(&app_id))
|
||||
return;
|
||||
|
||||
ScopedHString group(kGroup);
|
||||
ScopedHString tag(base::as_wcstr(base::UTF8ToUTF16(notification_id())));
|
||||
notification_history->RemoveGroupedTagWithId(tag, group, app_id);
|
||||
}
|
||||
|
||||
void WindowsToastNotification::Dismiss() {
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Hiding notification";
|
||||
DebugLog("Hiding notification");
|
||||
|
||||
toast_notifier_->Hide(toast_notification_.Get());
|
||||
}
|
||||
|
||||
@@ -151,8 +180,7 @@ HRESULT WindowsToastNotification::ShowInternal(
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
ComPtr<ABI::Windows::UI::Notifications::IToastNotificationFactory>
|
||||
toast_factory;
|
||||
ComPtr<winui::Notifications::IToastNotificationFactory> toast_factory;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
Windows::Foundation::GetActivationFactory(toast_str, &toast_factory),
|
||||
"WinAPI: GetActivationFactory failed");
|
||||
@@ -161,6 +189,19 @@ HRESULT WindowsToastNotification::ShowInternal(
|
||||
toast_xml.Get(), &toast_notification_),
|
||||
"WinAPI: CreateToastNotification failed");
|
||||
|
||||
ComPtr<winui::Notifications::IToastNotification2> toast2;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
toast_notification_->QueryInterface(IID_PPV_ARGS(&toast2)),
|
||||
"WinAPI: Getting Notification interface failed");
|
||||
|
||||
ScopedHString group(kGroup);
|
||||
REPORT_AND_RETURN_IF_FAILED(toast2->put_Group(group),
|
||||
"WinAPI: Setting group failed");
|
||||
|
||||
ScopedHString tag(base::as_wcstr(base::UTF8ToUTF16(notification_id())));
|
||||
REPORT_AND_RETURN_IF_FAILED(toast2->put_Tag(tag),
|
||||
"WinAPI: Setting tag failed");
|
||||
|
||||
REPORT_AND_RETURN_IF_FAILED(SetupCallbacks(toast_notification_.Get()),
|
||||
"WinAPI: SetupCallbacks failed");
|
||||
|
||||
@@ -170,22 +211,20 @@ HRESULT WindowsToastNotification::ShowInternal(
|
||||
}
|
||||
|
||||
HRESULT WindowsToastNotification::GetToastXml(
|
||||
ABI::Windows::UI::Notifications::IToastNotificationManagerStatics*
|
||||
toastManager,
|
||||
winui::Notifications::IToastNotificationManagerStatics* toastManager,
|
||||
const std::u16string& title,
|
||||
const std::u16string& msg,
|
||||
const std::wstring& icon_path,
|
||||
const std::u16string& timeout_type,
|
||||
bool silent,
|
||||
IXmlDocument** toast_xml) {
|
||||
ABI::Windows::UI::Notifications::ToastTemplateType template_type;
|
||||
winui::Notifications::ToastTemplateType template_type;
|
||||
if (title.empty() || msg.empty()) {
|
||||
// Single line toast.
|
||||
template_type =
|
||||
icon_path.empty()
|
||||
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText01
|
||||
: ABI::Windows::UI::Notifications::
|
||||
ToastTemplateType_ToastImageAndText01;
|
||||
? winui::Notifications::ToastTemplateType_ToastText01
|
||||
: winui::Notifications::ToastTemplateType_ToastImageAndText01;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
toast_manager_->GetTemplateContent(template_type, toast_xml),
|
||||
"XML: Fetching XML ToastImageAndText01 template failed");
|
||||
@@ -199,9 +238,8 @@ HRESULT WindowsToastNotification::GetToastXml(
|
||||
// Title and body toast.
|
||||
template_type =
|
||||
icon_path.empty()
|
||||
? ABI::Windows::UI::Notifications::ToastTemplateType_ToastText02
|
||||
: ABI::Windows::UI::Notifications::
|
||||
ToastTemplateType_ToastImageAndText02;
|
||||
? winui::Notifications::ToastTemplateType_ToastText02
|
||||
: winui::Notifications::ToastTemplateType_ToastImageAndText02;
|
||||
REPORT_AND_RETURN_IF_FAILED(
|
||||
toastManager->GetTemplateContent(template_type, toast_xml),
|
||||
"XML: Fetching XML ToastImageAndText02 template failed");
|
||||
@@ -567,7 +605,7 @@ HRESULT WindowsToastNotification::XmlDocumentFromString(
|
||||
}
|
||||
|
||||
HRESULT WindowsToastNotification::SetupCallbacks(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* toast) {
|
||||
winui::Notifications::IToastNotification* toast) {
|
||||
event_handler_ = Make<ToastEventHandler>(this);
|
||||
RETURN_IF_FAILED(
|
||||
toast->add_Activated(event_handler_.Get(), &activated_token_));
|
||||
@@ -578,7 +616,7 @@ HRESULT WindowsToastNotification::SetupCallbacks(
|
||||
}
|
||||
|
||||
bool WindowsToastNotification::RemoveCallbacks(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* toast) {
|
||||
winui::Notifications::IToastNotification* toast) {
|
||||
if (FAILED(toast->remove_Activated(activated_token_)))
|
||||
return false;
|
||||
|
||||
@@ -597,32 +635,29 @@ ToastEventHandler::ToastEventHandler(Notification* notification)
|
||||
ToastEventHandler::~ToastEventHandler() = default;
|
||||
|
||||
IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* sender,
|
||||
winui::Notifications::IToastNotification* sender,
|
||||
IInspectable* args) {
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&Notification::NotificationClicked, notification_));
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Notification clicked";
|
||||
DebugLog("Notification clicked");
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* sender,
|
||||
ABI::Windows::UI::Notifications::IToastDismissedEventArgs* e) {
|
||||
winui::Notifications::IToastNotification* sender,
|
||||
winui::Notifications::IToastDismissedEventArgs* e) {
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&Notification::NotificationDismissed, notification_));
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << "Notification dismissed";
|
||||
|
||||
FROM_HERE, base::BindOnce(&Notification::NotificationDismissed,
|
||||
notification_, false));
|
||||
DebugLog("Notification dismissed");
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
ABI::Windows::UI::Notifications::IToastNotification* sender,
|
||||
ABI::Windows::UI::Notifications::IToastFailedEventArgs* e) {
|
||||
winui::Notifications::IToastNotification* sender,
|
||||
winui::Notifications::IToastFailedEventArgs* e) {
|
||||
HRESULT error;
|
||||
e->get_ErrorCode(&error);
|
||||
std::string errorMessage =
|
||||
@@ -630,8 +665,7 @@ IFACEMETHODIMP ToastEventHandler::Invoke(
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE, base::BindOnce(&Notification::NotificationFailed,
|
||||
notification_, errorMessage));
|
||||
if (IsDebuggingNotifications())
|
||||
LOG(INFO) << errorMessage;
|
||||
DebugLog(errorMessage);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
@@ -52,6 +52,7 @@ class WindowsToastNotification : public Notification {
|
||||
// Notification:
|
||||
void Show(const NotificationOptions& options) override;
|
||||
void Dismiss() override;
|
||||
void Remove() override;
|
||||
|
||||
private:
|
||||
friend class ToastEventHandler;
|
||||
|
||||
@@ -37,6 +37,7 @@ group("extensions_features") {
|
||||
|
||||
generated_json_strings("generated_api_json_strings") {
|
||||
sources = [
|
||||
"action.json",
|
||||
"extension.json",
|
||||
"resources_private.idl",
|
||||
"scripting.idl",
|
||||
|
||||
483
shell/common/extensions/api/action.json
Normal file
483
shell/common/extensions/api/action.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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 () {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user