mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
124 Commits
v11.0.0-be
...
v11.2.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
805e442ff8 | ||
|
|
02a1b42db3 | ||
|
|
5c44dc2a0a | ||
|
|
210c406ab1 | ||
|
|
15a96413b6 | ||
|
|
13e017c861 | ||
|
|
bb1299bbb5 | ||
|
|
42d15f0536 | ||
|
|
70526f8070 | ||
|
|
47640eaa9a | ||
|
|
1f5a8b6894 | ||
|
|
b1f438245f | ||
|
|
a73d563390 | ||
|
|
6a1ef337a7 | ||
|
|
63677086c2 | ||
|
|
8805b996e0 | ||
|
|
47adc0a2b7 | ||
|
|
1559dc74da | ||
|
|
207dbb8d4e | ||
|
|
3bda527e83 | ||
|
|
e3b76f71de | ||
|
|
6be5244111 | ||
|
|
f398ba08a9 | ||
|
|
aa4f3550fd | ||
|
|
095de8114e | ||
|
|
5737bac6f4 | ||
|
|
c95eda8420 | ||
|
|
7fb97f041b | ||
|
|
b4ea9e0fc8 | ||
|
|
19b7336fc3 | ||
|
|
f809e74905 | ||
|
|
f6eb88ac08 | ||
|
|
56e926ce23 | ||
|
|
5e0e91510a | ||
|
|
9bfd5d4a4b | ||
|
|
8afd7877f6 | ||
|
|
bf0b82dc8e | ||
|
|
122d8910d5 | ||
|
|
cb281b7954 | ||
|
|
91cfa96c80 | ||
|
|
a21ed83751 | ||
|
|
c0b48658d8 | ||
|
|
c68f0f04d0 | ||
|
|
a2b61f9b97 | ||
|
|
fc478f4e96 | ||
|
|
b710412e7b | ||
|
|
9a7bda51a3 | ||
|
|
615e467a15 | ||
|
|
f8a900fdde | ||
|
|
c188ed065d | ||
|
|
dcd216984f | ||
|
|
e012f61aee | ||
|
|
50e3a2ae20 | ||
|
|
b4413e9398 | ||
|
|
25df0f5d31 | ||
|
|
6b91371b6e | ||
|
|
8e6d332e96 | ||
|
|
eebd7cfa05 | ||
|
|
a4d73be9c5 | ||
|
|
0e9214a82b | ||
|
|
24c7365bef | ||
|
|
686aefb2cd | ||
|
|
0e0a1421fd | ||
|
|
c4e8c0fd0e | ||
|
|
101a2282ab | ||
|
|
429400040e | ||
|
|
ca7528a2d0 | ||
|
|
d58812b517 | ||
|
|
5942c84288 | ||
|
|
f920463b47 | ||
|
|
296cba563e | ||
|
|
90a96c0b73 | ||
|
|
2c325cd654 | ||
|
|
c26b78bbc0 | ||
|
|
6951c09b61 | ||
|
|
fe21892974 | ||
|
|
0fd6a8ca83 | ||
|
|
de12232df5 | ||
|
|
d12674f9f1 | ||
|
|
75cdd02697 | ||
|
|
08b7f5a569 | ||
|
|
a5077e2586 | ||
|
|
4fa9122151 | ||
|
|
56fa037a46 | ||
|
|
13a9e15a27 | ||
|
|
badc01c2d8 | ||
|
|
b0862a6e63 | ||
|
|
18cde2ef04 | ||
|
|
e6c94854a0 | ||
|
|
94d8b7d4c1 | ||
|
|
c0769d77e5 | ||
|
|
2a6c3bb7d1 | ||
|
|
d892fde98b | ||
|
|
edb6723157 | ||
|
|
9dda9a894a | ||
|
|
90ab868b50 | ||
|
|
4dc98f1347 | ||
|
|
2189ddb14e | ||
|
|
680569e404 | ||
|
|
e119699ae2 | ||
|
|
63e6f08768 | ||
|
|
b14c57e30c | ||
|
|
8a68a304e0 | ||
|
|
60cef385d5 | ||
|
|
4f281e3d31 | ||
|
|
eb2710e483 | ||
|
|
7a15dca09c | ||
|
|
387a15f582 | ||
|
|
38fab63863 | ||
|
|
1b156c53fd | ||
|
|
e4f3f9e1db | ||
|
|
612acc04b0 | ||
|
|
717271095e | ||
|
|
76e54ae0c8 | ||
|
|
0c21ce02c1 | ||
|
|
01fa45fc50 | ||
|
|
832ce14bda | ||
|
|
07318e5146 | ||
|
|
79dff5f782 | ||
|
|
9c9e40294e | ||
|
|
5b617a7f39 | ||
|
|
ff717d4a93 | ||
|
|
51bced4ee3 | ||
|
|
b0fd7cf430 |
@@ -265,16 +265,25 @@ step-gclient-sync: &step-gclient-sync
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
# There are changes to the patches. Make a git commit with the updated patches
|
||||
git add patches
|
||||
GIT_COMMITTER_NAME="Electron Bot" GIT_COMMITTER_EMAIL="anonymous@electronjs.org" git commit -m "update patches" --author="Electron Bot <anonymous@electronjs.org>"
|
||||
GIT_COMMITTER_NAME="Electron Bot" GIT_COMMITTER_EMAIL="electron@github.com" git commit -m "update patches" --author="Electron Bot <electron@github.com>"
|
||||
# Export it
|
||||
mkdir -p ../../patches
|
||||
git format-patch -1 --stdout --keep-subject --no-stat --full-index > ../../patches/update-patches.patch
|
||||
echo
|
||||
echo "======================================================================"
|
||||
echo "There were changes to the patches when applying."
|
||||
echo "Check the CI artifacts for a patch you can apply to fix it."
|
||||
echo "======================================================================"
|
||||
exit 1
|
||||
if (node ./script/push-patch.js 2> /dev/null > /dev/null); then
|
||||
echo
|
||||
echo "======================================================================"
|
||||
echo "Changes to the patches when applying, we have auto-pushed the diff to the current branch"
|
||||
echo "A new CI job will kick off shortly"
|
||||
echo "======================================================================"
|
||||
exit 1
|
||||
else
|
||||
echo
|
||||
echo "======================================================================"
|
||||
echo "There were changes to the patches when applying."
|
||||
echo "Check the CI artifacts for a patch you can apply to fix it."
|
||||
echo "======================================================================"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
@@ -302,10 +311,10 @@ step-setup-goma-for-build: &step-setup-goma-for-build
|
||||
name: Setup Goma
|
||||
command: |
|
||||
echo 'export USE_GOMA=true' >> $BASH_ENV
|
||||
if [ "`uname`" == "Linux" ]; then
|
||||
echo 'export NUMBER_OF_NINJA_PROCESSES=300' >> $BASH_ENV
|
||||
else
|
||||
echo 'export NUMBER_OF_NINJA_PROCESSES=25' >> $BASH_ENV
|
||||
echo 'export NUMBER_OF_NINJA_PROCESSES=300' >> $BASH_ENV
|
||||
if [ "`uname`" == "Darwin" ]; then
|
||||
echo 'ulimit -n 10000' >> $BASH_ENV
|
||||
echo 'sudo launchctl limit maxfiles 65536 200000' >> $BASH_ENV
|
||||
fi
|
||||
if [ ! -z "$RAW_GOMA_AUTH" ]; then
|
||||
echo $RAW_GOMA_AUTH > ~/.goma_oauth2_config
|
||||
@@ -314,7 +323,7 @@ step-setup-goma-for-build: &step-setup-goma-for-build
|
||||
cd build-tools
|
||||
npm install
|
||||
mkdir third_party
|
||||
node -e "require('./src/utils/goma.js').downloadAndPrepare()"
|
||||
node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })"
|
||||
node -e "require('./src/utils/goma.js').ensure()"
|
||||
echo 'export GN_GOMA_FILE='`node -e "console.log(require('./src/utils/goma.js').gnFilePath)"` >> $BASH_ENV
|
||||
echo 'export LOCAL_GOMA_DIR='`node -e "console.log(require('./src/utils/goma.js').dir)"` >> $BASH_ENV
|
||||
@@ -323,15 +332,17 @@ step-setup-goma-for-build: &step-setup-goma-for-build
|
||||
step-restore-brew-cache: &step-restore-brew-cache
|
||||
restore_cache:
|
||||
paths:
|
||||
- /usr/local/Homebrew
|
||||
- /usr/local/Cellar/gnu-tar
|
||||
- /usr/local/bin/gtar
|
||||
keys:
|
||||
- v1-brew-cache-{{ arch }}
|
||||
- v4-brew-cache-{{ arch }}
|
||||
|
||||
step-save-brew-cache: &step-save-brew-cache
|
||||
save_cache:
|
||||
paths:
|
||||
- /usr/local/Homebrew
|
||||
key: v1-brew-cache-{{ arch }}
|
||||
- /usr/local/Cellar/gnu-tar
|
||||
- /usr/local/bin/gtar
|
||||
key: v4-brew-cache-{{ arch }}
|
||||
name: Persisting brew cache
|
||||
|
||||
step-get-more-space-on-mac: &step-get-more-space-on-mac
|
||||
@@ -471,8 +482,10 @@ step-install-gnutar-on-mac: &step-install-gnutar-on-mac
|
||||
name: Install gnu-tar on macos
|
||||
command: |
|
||||
if [ "`uname`" == "Darwin" ]; then
|
||||
brew update
|
||||
brew install gnu-tar
|
||||
if [ ! -d /usr/local/Cellar/gnu-tar/ ]; then
|
||||
brew update
|
||||
brew install gnu-tar
|
||||
fi
|
||||
ln -fs /usr/local/bin/gtar /usr/local/bin/tar
|
||||
fi
|
||||
|
||||
@@ -665,10 +678,10 @@ step-electron-publish: &step-electron-publish
|
||||
cd src/electron
|
||||
if [ "$UPLOAD_TO_S3" == "1" ]; then
|
||||
echo 'Uploading Electron release distribution to S3'
|
||||
script/release/uploaders/upload.py --upload_to_s3
|
||||
script/release/uploaders/upload.py --verbose --upload_to_s3
|
||||
else
|
||||
echo 'Uploading Electron release distribution to Github releases'
|
||||
script/release/uploaders/upload.py
|
||||
script/release/uploaders/upload.py --verbose
|
||||
fi
|
||||
|
||||
step-persist-data-for-tests: &step-persist-data-for-tests
|
||||
@@ -921,6 +934,8 @@ step-ninja-summary: &step-ninja-summary
|
||||
run:
|
||||
name: Print ninja summary
|
||||
command: |
|
||||
set +e
|
||||
set +o pipefail
|
||||
python depot_tools/post_build_ninja_summary.py -C src/out/Default
|
||||
|
||||
step-ninja-report: &step-ninja-report
|
||||
@@ -1147,7 +1162,6 @@ steps-checkout-and-save-cache: &steps-checkout-and-save-cache
|
||||
- *step-maybe-early-exit-doc-only-change
|
||||
- *step-depot-tools-get
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-restore-brew-cache
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-install-gnutar-on-mac
|
||||
|
||||
|
||||
13
BUILD.gn
13
BUILD.gn
@@ -1155,6 +1155,19 @@ if (is_mac) {
|
||||
ldflags += [ "/guard:cf,nolongjmp" ]
|
||||
}
|
||||
|
||||
if (current_cpu == "x86") {
|
||||
# Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by
|
||||
# Chrome's main thread. This saves significant memory on threads (like
|
||||
# those in the Windows thread pool, and others) whose stack size we can
|
||||
# only control through this setting. Because Chrome's main thread needs
|
||||
# a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses
|
||||
# fibers to switch to a 1.5 MiB stack before running any other code.
|
||||
ldflags += [ "/STACK:0x80000" ]
|
||||
} else {
|
||||
# Increase the initial stack size. The default is 1MB, this is 8MB.
|
||||
ldflags += [ "/STACK:0x800000" ]
|
||||
}
|
||||
|
||||
# This is to support renaming of electron.exe. node-gyp has hard-coded
|
||||
# executable names which it will recognise as node. This module definition
|
||||
# file claims that the electron executable is in fact named "node.exe",
|
||||
|
||||
2
DEPS
2
DEPS
@@ -14,7 +14,7 @@ gclient_gn_args = [
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'87.0.4280.47',
|
||||
'87.0.4280.141',
|
||||
'node_version':
|
||||
'v12.18.3',
|
||||
'nan_version':
|
||||
|
||||
@@ -1 +1 @@
|
||||
11.0.0-beta.21
|
||||
11.2.2
|
||||
@@ -28,15 +28,12 @@ The preferred method is to install Electron as a development dependency in your
|
||||
app:
|
||||
|
||||
```sh
|
||||
npm install electron --save-dev [--save-exact]
|
||||
npm install electron --save-dev
|
||||
```
|
||||
|
||||
The `--save-exact` flag is recommended for Electron prior to version 2, as it does not follow semantic
|
||||
versioning. As of version 2.0.0, Electron follows semver, so you don't need `--save-exact` flag. For info on how to manage Electron versions in your apps, see
|
||||
[Electron versioning](docs/tutorial/electron-versioning.md).
|
||||
|
||||
For more installation options and troubleshooting tips, see
|
||||
[installation](docs/tutorial/installation.md).
|
||||
[installation](docs/tutorial/installation.md). For info on how to manage Electron versions in your apps, see
|
||||
[Electron versioning](docs/tutorial/electron-versioning.md).
|
||||
|
||||
## Quick start & Electron Fiddle
|
||||
|
||||
|
||||
@@ -143,7 +143,7 @@ build_script:
|
||||
cd build-tools
|
||||
npm install
|
||||
mkdir third_party
|
||||
node -e "require('./src/utils/goma.js').downloadAndPrepare()"
|
||||
node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })"
|
||||
$env:GN_GOMA_FILE = node -e "console.log(require('./src/utils/goma.js').gnFilePath)"
|
||||
$env:LOCAL_GOMA_DIR = node -e "console.log(require('./src/utils/goma.js').dir)"
|
||||
cd ..
|
||||
@@ -229,10 +229,10 @@ deploy_script:
|
||||
if (Test-Path Env:\ELECTRON_RELEASE) {
|
||||
if (Test-Path Env:\UPLOAD_TO_S3) {
|
||||
Write-Output "Uploading Electron release distribution to s3"
|
||||
& python script\release\uploaders\upload.py --upload_to_s3
|
||||
& python script\release\uploaders\upload.py --verbose --upload_to_s3
|
||||
} else {
|
||||
Write-Output "Uploading Electron release distribution to github releases"
|
||||
& python script\release\uploaders\upload.py
|
||||
& python script\release\uploaders\upload.py --verbose
|
||||
}
|
||||
} elseif (Test-Path Env:\TEST_WOA) {
|
||||
node script/release/ci-release-build.js --job=electron-woa-testing --ci=VSTS --armTest --appveyorJobId=$env:APPVEYOR_JOB_ID $env:APPVEYOR_REPO_BRANCH
|
||||
|
||||
@@ -134,10 +134,17 @@ static_library("chrome") {
|
||||
"//chrome/browser/platform_util.h",
|
||||
"//chrome/browser/ui/browser_dialogs.h",
|
||||
"//chrome/browser/ui/color_chooser.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper.cc",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view.cc",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view.h",
|
||||
]
|
||||
|
||||
if (use_aura) {
|
||||
sources += [ "//chrome/browser/platform_util_aura.cc" ]
|
||||
sources += [
|
||||
"//chrome/browser/platform_util_aura.cc",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_aura.cc",
|
||||
]
|
||||
|
||||
if (!is_win) {
|
||||
sources += [
|
||||
@@ -154,6 +161,8 @@ static_library("chrome") {
|
||||
"//chrome/browser/media/webrtc/window_icon_util_mac.mm",
|
||||
"//chrome/browser/ui/cocoa/color_chooser_mac.h",
|
||||
"//chrome/browser/ui/cocoa/color_chooser_mac.mm",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.mm",
|
||||
]
|
||||
deps += [
|
||||
"//components/remote_cocoa/app_shim",
|
||||
@@ -277,13 +286,15 @@ static_library("chrome") {
|
||||
}
|
||||
}
|
||||
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump.h" ]
|
||||
if (is_mac) {
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump_mac.cc" ]
|
||||
} else if (is_win) {
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump_win.cc" ]
|
||||
} else {
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump.cc" ]
|
||||
if (!is_mas_build) {
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump.h" ]
|
||||
if (is_mac) {
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump_mac.cc" ]
|
||||
} else if (is_win) {
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump_win.cc" ]
|
||||
} else {
|
||||
sources += [ "//chrome/browser/hang_monitor/hang_crash_dump.cc" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -148,6 +148,7 @@ These individual tutorials expand on topics discussed in the guide above.
|
||||
|
||||
### Modules for the Renderer Process (Web Page):
|
||||
|
||||
* [contextBridge](api/context-bridge.md)
|
||||
* [desktopCapturer](api/desktop-capturer.md)
|
||||
* [ipcRenderer](api/ipc-renderer.md)
|
||||
* [remote](api/remote.md)
|
||||
|
||||
@@ -1341,7 +1341,7 @@ systems Application folder. Use in combination with `app.moveToApplicationsFolde
|
||||
### `app.moveToApplicationsFolder([options])` _macOS_
|
||||
|
||||
* `options` Object (optional)
|
||||
* `conflictHandler` Function<Boolean> (optional) - A handler for potential conflict in move failure.
|
||||
* `conflictHandler` Function\<Boolean> (optional) - A handler for potential conflict in move failure.
|
||||
* `conflictType` String - The type of move conflict encountered by the handler; can be `exists` or `existsAndRunning`, where `exists` means that an app of the same name is present in the Applications directory and `existsAndRunning` means both that it exists and that it's presently running.
|
||||
|
||||
Returns `Boolean` - Whether the move was successful. Please note that if
|
||||
@@ -1485,3 +1485,12 @@ which native modules you can use in the renderer process. For more information
|
||||
on the direction Electron is going with renderer process restarts and usage of
|
||||
native modules in the renderer process please check out this
|
||||
[Tracking Issue](https://github.com/electron/electron/issues/18397).
|
||||
|
||||
### `app.runningUnderRosettaTranslation` _macOS_ _Readonly_
|
||||
|
||||
A `Boolean` which when `true` indicates that the app is currently running
|
||||
under the [Rosetta Translator Environment](https://en.wikipedia.org/wiki/Rosetta_(software)).
|
||||
|
||||
You can use this property to prompt users to download the arm64 version of
|
||||
your application when they are running the x64 version under Rosetta
|
||||
incorrectly.
|
||||
|
||||
@@ -541,6 +541,12 @@ Note that this is only emitted when the window is being resized manually. Resizi
|
||||
|
||||
Emitted after the window has been resized.
|
||||
|
||||
#### Event: 'resized' _macOS_ _Windows_
|
||||
|
||||
Emitted once when the window has finished being resized.
|
||||
|
||||
This is usually emitted when the window has been resized manually. On macOS, resizing the window with `setBounds`/`setSize` and setting the `animate` parameter to `true` will also emit this event once resizing has finished.
|
||||
|
||||
#### Event: 'will-move' _macOS_ _Windows_
|
||||
|
||||
Returns:
|
||||
@@ -556,12 +562,12 @@ Note that this is only emitted when the window is being resized manually. Resizi
|
||||
|
||||
Emitted when the window is being moved to a new position.
|
||||
|
||||
__Note__: On macOS this event is an alias of `moved`.
|
||||
|
||||
#### Event: 'moved' _macOS_
|
||||
#### Event: 'moved' _macOS_ _Windows_
|
||||
|
||||
Emitted once when the window is moved to a new position.
|
||||
|
||||
__Note__: On macOS this event is an alias of `move`.
|
||||
|
||||
#### Event: 'enter-full-screen'
|
||||
|
||||
Emitted when the window enters a full-screen state.
|
||||
@@ -1043,7 +1049,7 @@ Returns `Boolean` - Whether the window is in simple (pre-Lion) fullscreen mode.
|
||||
|
||||
Returns `Boolean` - Whether the window is in normal state (not maximized, not minimized, not in fullscreen mode).
|
||||
|
||||
#### `win.setAspectRatio(aspectRatio[, extraSize])` _macOS_ _Linux_
|
||||
#### `win.setAspectRatio(aspectRatio[, extraSize])`
|
||||
|
||||
* `aspectRatio` Float - The aspect ratio to maintain for some portion of the
|
||||
content view.
|
||||
@@ -1064,6 +1070,9 @@ the player itself we would call this function with arguments of 16/9 and
|
||||
are within the content view--only that they exist. Sum any extra width and
|
||||
height areas you have within the overall content view.
|
||||
|
||||
The aspect ratio is not respected when window is resized programmingly with
|
||||
APIs like `win.setSize`.
|
||||
|
||||
#### `win.setBackgroundColor(backgroundColor)`
|
||||
|
||||
* `backgroundColor` String - Window's background color as a hexadecimal value,
|
||||
|
||||
@@ -16,7 +16,7 @@ const { app, contentTracing } = require('electron')
|
||||
app.whenReady().then(() => {
|
||||
(async () => {
|
||||
await contentTracing.startRecording({
|
||||
include_categories: ['*']
|
||||
included_categories: ['*']
|
||||
})
|
||||
console.log('Tracing started')
|
||||
await new Promise(resolve => setTimeout(resolve, 5000))
|
||||
|
||||
@@ -114,3 +114,9 @@ The following methods of `chrome.management` are supported:
|
||||
- `chrome.management.getPermissionWarningsByManifest`
|
||||
- `chrome.management.onEnabled`
|
||||
- `chrome.management.onDisabled`
|
||||
|
||||
### `chrome.webRequest`
|
||||
|
||||
All features of this API are supported.
|
||||
|
||||
> **NOTE:** Electron's [`webRequest`](web-request.md) module takes precedence over `chrome.webRequest` if there are conflicting handlers.
|
||||
|
||||
@@ -9,7 +9,7 @@ with the operating system so that you can customize the operations for various
|
||||
shortcuts.
|
||||
|
||||
**Note:** The shortcut is global; it will work even if the app does
|
||||
not have the keyboard focus. You should not use this module until the `ready`
|
||||
not have the keyboard focus. This module cannot be used before the `ready`
|
||||
event of the app module is emitted.
|
||||
|
||||
```javascript
|
||||
|
||||
@@ -91,7 +91,7 @@ Removes listeners of the specified `channel`.
|
||||
### `ipcMain.handle(channel, listener)`
|
||||
|
||||
* `channel` String
|
||||
* `listener` Function<Promise<void> | any>
|
||||
* `listener` Function<Promise\<void> | any>
|
||||
* `event` IpcMainInvokeEvent
|
||||
* `...args` any[]
|
||||
|
||||
@@ -123,7 +123,7 @@ WebContents is the source of the invoke request.
|
||||
### `ipcMain.handleOnce(channel, listener)`
|
||||
|
||||
* `channel` String
|
||||
* `listener` Function<Promise<void> | any>
|
||||
* `listener` Function<Promise\<void> | any>
|
||||
* `event` IpcMainInvokeEvent
|
||||
* `...args` any[]
|
||||
|
||||
|
||||
@@ -61,9 +61,13 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no
|
||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects will throw an exception.
|
||||
>
|
||||
> Since the main process does not have support for DOM objects such as
|
||||
> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over
|
||||
> Electron's IPC to the main process, as the main process would have no way to decode
|
||||
> them. Attempting to send such objects over IPC will result in an error.
|
||||
|
||||
The main process handles it by listening for `channel` with the
|
||||
[`ipcMain`](ipc-main.md) module.
|
||||
@@ -85,9 +89,13 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no
|
||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects will throw an exception.
|
||||
>
|
||||
> Since the main process does not have support for DOM objects such as
|
||||
> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over
|
||||
> Electron's IPC to the main process, as the main process would have no way to decode
|
||||
> them. Attempting to send such objects over IPC will result in an error.
|
||||
|
||||
The main process should listen for `channel` with
|
||||
[`ipcMain.handle()`](ipc-main.md#ipcmainhandlechannel-listener).
|
||||
@@ -123,9 +131,13 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no
|
||||
included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects will throw an exception.
|
||||
>
|
||||
> Since the main process does not have support for DOM objects such as
|
||||
> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over
|
||||
> Electron's IPC to the main process, as the main process would have no way to decode
|
||||
> them. Attempting to send such objects over IPC will result in an error.
|
||||
|
||||
The main process handles it by listening for `channel` with [`ipcMain`](ipc-main.md) module,
|
||||
and replies by setting `event.returnValue`.
|
||||
|
||||
@@ -385,7 +385,7 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
||||
|
||||
#### `ses.setPermissionCheckHandler(handler)`
|
||||
|
||||
* `handler` Function<Boolean> | null
|
||||
* `handler` Function\<Boolean> | null
|
||||
* `webContents` [WebContents](web-contents.md) - WebContents checking the permission. Please note that if the request comes from a subframe you should use `requestingUrl` to check the request origin.
|
||||
* `permission` String - Enum of 'media'.
|
||||
* `requestingOrigin` String - The origin URL of the permission check
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
# IpcMainEvent Object extends `Event`
|
||||
|
||||
* `processId` Integer - The internal ID of the renderer process that sent this message
|
||||
* `frameId` Integer - The ID of the renderer frame that sent this message
|
||||
* `returnValue` any - Set this to the value to be returned in a synchronous message
|
||||
* `sender` WebContents - Returns the `webContents` that sent the message
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
# IpcMainInvokeEvent Object extends `Event`
|
||||
|
||||
* `processId` Integer - The internal ID of the renderer process that sent this message
|
||||
* `frameId` Integer - The ID of the renderer frame that sent this message
|
||||
* `sender` WebContents - Returns the `webContents` that sent the message
|
||||
|
||||
@@ -42,7 +42,8 @@ returns `null`.
|
||||
|
||||
* `id` Integer
|
||||
|
||||
Returns `WebContents` - A WebContents instance with the given ID.
|
||||
Returns `WebContents` | undefined - A WebContents instance with the given ID, or
|
||||
`undefined` if there is no WebContents associated with the given ID.
|
||||
|
||||
## Class: WebContents
|
||||
|
||||
@@ -1324,9 +1325,9 @@ Returns [`PrinterInfo[]`](structures/printer-info.md)
|
||||
* `pagesPerSheet` Number (optional) - The number of pages to print per page sheet.
|
||||
* `collate` Boolean (optional) - Whether the web page should be collated.
|
||||
* `copies` Number (optional) - The number of copies of the web page to print.
|
||||
* `pageRanges` Record<string, number> (optional) - The page range to print.
|
||||
* `from` Number - the start page.
|
||||
* `to` Number - the end page.
|
||||
* `pageRanges` Object[] (optional) - The page range to print. On macOS, only one range is honored.
|
||||
* `from` Number - Index of the first page to print (0-based).
|
||||
* `to` Number - Index of the last page to print (inclusive) (0-based).
|
||||
* `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
|
||||
* `dpi` Record<string, number> (optional)
|
||||
* `horizontal` Number (optional) - The horizontal dpi.
|
||||
@@ -1352,10 +1353,10 @@ Example usage:
|
||||
const options = {
|
||||
silent: true,
|
||||
deviceName: 'My-Printer',
|
||||
pageRanges: {
|
||||
pageRanges: [{
|
||||
from: 0,
|
||||
to: 1
|
||||
}
|
||||
}]
|
||||
}
|
||||
win.webContents.print(options, (success, errorType) => {
|
||||
if (!success) console.log(errorType)
|
||||
@@ -1373,8 +1374,8 @@ win.webContents.print(options, (success, errorType) => {
|
||||
default margin, 1 for no margin, and 2 for minimum margin.
|
||||
* `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100.
|
||||
* `pageRanges` Record<string, number> (optional) - The page range to print.
|
||||
* `from` Number - zero-based index of the first page to print.
|
||||
* `to` Number - zero-based index of the last page to print (inclusive).
|
||||
* `from` Number - Index of the first page to print (0-based).
|
||||
* `to` Number - Index of the last page to print (inclusive) (0-based).
|
||||
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
|
||||
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` and `width` in microns.
|
||||
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.
|
||||
@@ -1584,8 +1585,7 @@ included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will
|
||||
throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
> special Electron objects will throw an exception.
|
||||
|
||||
The renderer process can handle the message by listening to `channel` with the
|
||||
[`ipcRenderer`](ipc-renderer.md) module.
|
||||
@@ -1621,7 +1621,9 @@ app.whenReady().then(() => {
|
||||
|
||||
#### `contents.sendToFrame(frameId, channel, ...args)`
|
||||
|
||||
* `frameId` Integer
|
||||
* `frameId` Integer | [number, number] - the ID of the frame to send to, or a
|
||||
pair of `[processId, frameId]` if the frame is in a different process to the
|
||||
main frame.
|
||||
* `channel` String
|
||||
* `...args` any[]
|
||||
|
||||
@@ -1631,9 +1633,8 @@ Send an asynchronous message to a specific frame in a renderer process via
|
||||
chains will not be included. Sending Functions, Promises, Symbols, WeakMaps, or
|
||||
WeakSets will throw an exception.
|
||||
|
||||
> **NOTE**: Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects is deprecated, and will begin throwing an exception
|
||||
> starting with Electron 9.
|
||||
> **NOTE:** Sending non-standard JavaScript types such as DOM objects or
|
||||
> special Electron objects will throw an exception.
|
||||
|
||||
The renderer process can handle the message by listening to `channel` with the
|
||||
[`ipcRenderer`](ipc-renderer.md) module.
|
||||
@@ -1795,7 +1796,7 @@ Returns `Boolean` - If *offscreen rendering* is enabled returns whether it is cu
|
||||
* `fps` Integer
|
||||
|
||||
If *offscreen rendering* is enabled sets the frame rate to the specified number.
|
||||
Only values between 1 and 60 are accepted.
|
||||
Only values between 1 and 240 are accepted.
|
||||
|
||||
#### `contents.getFrameRate()`
|
||||
|
||||
@@ -1893,7 +1894,7 @@ The zoom factor is the zoom percent divided by 100, so 300% = 3.0.
|
||||
#### `contents.frameRate`
|
||||
|
||||
An `Integer` property that sets the frame rate of the web contents to the specified number.
|
||||
Only values between 1 and 60 are accepted.
|
||||
Only values between 1 and 240 are accepted.
|
||||
|
||||
Only applicable if *offscreen rendering* is enabled.
|
||||
|
||||
|
||||
@@ -560,9 +560,9 @@ Stops any `findInPage` request for the `webview` with the provided `action`.
|
||||
* `pagesPerSheet` Number (optional) - The number of pages to print per page sheet.
|
||||
* `collate` Boolean (optional) - Whether the web page should be collated.
|
||||
* `copies` Number (optional) - The number of copies of the web page to print.
|
||||
* `pageRanges` Record<string, number> (optional) - The page range to print.
|
||||
* `from` Number - zero-based index of the first page to print.
|
||||
* `to` Number - zero-based index of the last page to print (inclusive).
|
||||
* `pageRanges` Object[] (optional) - The page range to print.
|
||||
* `from` Number - Index of the first page to print (0-based).
|
||||
* `to` Number - Index of the last page to print (inclusive) (0-based).
|
||||
* `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`.
|
||||
* `dpi` Record<string, number> (optional)
|
||||
* `horizontal` Number (optional) - The horizontal dpi.
|
||||
@@ -588,8 +588,8 @@ Prints `webview`'s web page. Same as `webContents.print([options])`.
|
||||
and `width` in microns.
|
||||
* `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100.
|
||||
* `pageRanges` Record<string, number> (optional) - The page range to print.
|
||||
* `from` Number - the first page to print.
|
||||
* `to` Number - the last page to print (inclusive).
|
||||
* `from` Number - Index of the first page to print (0-based).
|
||||
* `to` Number - Index of the last page to print (inclusive) (0-based).
|
||||
* `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`,
|
||||
`A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height`
|
||||
* `printBackground` Boolean (optional) - Whether to print CSS backgrounds.
|
||||
@@ -968,4 +968,4 @@ Emitted when DevTools is closed.
|
||||
Emitted when DevTools is focused / opened.
|
||||
|
||||
[runtime-enabled-features]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/runtime_enabled_features.json5?l=70
|
||||
[chrome-webview]: https://developer.chrome.com/apps/tags/webview
|
||||
[chrome-webview]: https://developer.chrome.com/docs/extensions/reference/webviewTag/
|
||||
|
||||
@@ -195,6 +195,45 @@ you should plan to update your native modules to be context aware.
|
||||
|
||||
For more detailed information see [#18397](https://github.com/electron/electron/issues/18397).
|
||||
|
||||
### Deprecated: `BrowserWindow` extension APIs
|
||||
|
||||
The following extension APIs have been deprecated:
|
||||
* `BrowserWindow.addExtension(path)`
|
||||
* `BrowserWindow.addDevToolsExtension(path)`
|
||||
* `BrowserWindow.removeExtension(name)`
|
||||
* `BrowserWindow.removeDevToolsExtension(name)`
|
||||
* `BrowserWindow.getExtensions()`
|
||||
* `BrowserWindow.getDevToolsExtensions()`
|
||||
|
||||
Use the session APIs instead:
|
||||
* `ses.loadExtension(path)`
|
||||
* `ses.removeExtension(extension_id)`
|
||||
* `ses.getAllExtensions()`
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 9
|
||||
BrowserWindow.addExtension(path)
|
||||
BrowserWindow.addDevToolsExtension(path)
|
||||
// Replace with
|
||||
session.defaultSession.loadExtension(path)
|
||||
```
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 9
|
||||
BrowserWindow.removeExtension(name)
|
||||
BrowserWindow.removeDevToolsExtension(name)
|
||||
// Replace with
|
||||
session.defaultSession.removeExtension(extension_id)
|
||||
```
|
||||
|
||||
```js
|
||||
// Deprecated in Electron 9
|
||||
BrowserWindow.getExtensions()
|
||||
BrowserWindow.getDevToolsExtensions()
|
||||
// Replace with
|
||||
session.defaultSession.getAllExtensions()
|
||||
```
|
||||
|
||||
### Removed: `<webview>.getWebContents()`
|
||||
|
||||
This API, which was deprecated in Electron 8.0, is now removed.
|
||||
@@ -338,6 +377,52 @@ in Electron 8.x, and cease to exist in Electron 9.x. The layout zoom level
|
||||
limits are now fixed at a minimum of 0.25 and a maximum of 5.0, as defined
|
||||
[here](https://chromium.googlesource.com/chromium/src/+/938b37a6d2886bf8335fc7db792f1eb46c65b2ae/third_party/blink/common/page/page_zoom.cc#11).
|
||||
|
||||
### Deprecated events in `systemPreferences`
|
||||
|
||||
The following `systemPreferences` events have been deprecated:
|
||||
* `inverted-color-scheme-changed`
|
||||
* `high-contrast-color-scheme-changed`
|
||||
|
||||
Use the new `updated` event on the `nativeTheme` module instead.
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
systemPreferences.on('inverted-color-scheme-changed', () => { /* ... */ })
|
||||
systemPreferences.on('high-contrast-color-scheme-changed', () => { /* ... */ })
|
||||
|
||||
// Replace with
|
||||
nativeTheme.on('updated', () => { /* ... */ })
|
||||
```
|
||||
|
||||
### Deprecated: methods in `systemPreferences`
|
||||
|
||||
The following `systemPreferences` methods have been deprecated:
|
||||
* `systemPreferences.isDarkMode()`
|
||||
* `systemPreferences.isInvertedColorScheme()`
|
||||
* `systemPreferences.isHighContrastColorScheme()`
|
||||
|
||||
Use the following `nativeTheme` properties instead:
|
||||
* `nativeTheme.shouldUseDarkColors`
|
||||
* `nativeTheme.shouldUseInvertedColorScheme`
|
||||
* `nativeTheme.shouldUseHighContrastColors`
|
||||
|
||||
```js
|
||||
// Deprecated
|
||||
systemPreferences.isDarkMode()
|
||||
// Replace with
|
||||
nativeTheme.shouldUseDarkColors
|
||||
|
||||
// Deprecated
|
||||
systemPreferences.isInvertedColorScheme()
|
||||
// Replace with
|
||||
nativeTheme.shouldUseInvertedColorScheme
|
||||
|
||||
// Deprecated
|
||||
systemPreferences.isHighContrastColorScheme()
|
||||
// Replace with
|
||||
nativeTheme.shouldUseHighContrastColors
|
||||
```
|
||||
|
||||
## Planned Breaking API Changes (7.0)
|
||||
|
||||
### Deprecated: Atom.io Node Headers URL
|
||||
|
||||
@@ -177,12 +177,12 @@ $ gn gen out/Testing-x86 --args='... target_cpu = "x86"'
|
||||
|
||||
Not all combinations of source and target CPU/OS are supported by Chromium.
|
||||
|
||||
<table>
|
||||
<tr><th>Host</th><th>Target</th><th>Status</th></tr>
|
||||
<tr><td>Windows x64</td><td>Windows arm64</td><td>Experimental</td>
|
||||
<tr><td>Windows x64</td><td>Windows x86</td><td>Automatically tested</td></tr>
|
||||
<tr><td>Linux x64</td><td>Linux x86</td><td>Automatically tested</td></tr>
|
||||
</table>
|
||||
| Host | Target | Status |
|
||||
|-------------|---------------|----------------------|
|
||||
| Windows x64 | Windows arm64 | Experimental |
|
||||
| Windows x64 | Windows x86 | Automatically tested |
|
||||
| Linux x64 | Linux x86 | Automatically tested |
|
||||
|
||||
|
||||
If you test other combinations and find them to work, please update this document :)
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ $ pip install pyobjc
|
||||
If you're developing Electron and don't plan to redistribute your
|
||||
custom Electron build, you may skip this section.
|
||||
|
||||
Official Electron builds are built with [Xcode 9.4.1](http://adcdownload.apple.com/Developer_Tools/Xcode_9.4.1/Xcode_9.4.1.xip), and the macOS 10.13 SDK. Building with a newer SDK works too, but the releases currently use the 10.13 SDK.
|
||||
Official Electron builds are built with [Xcode 12.2](https://download.developer.apple.com/Developer_Tools/Xcode_12.2/Xcode_12.2.xip), and the macOS 11.0 SDK. Building with a newer SDK works too, but the releases currently use the 11.0 SDK.
|
||||
|
||||
## Building Electron
|
||||
|
||||
|
||||
@@ -43,8 +43,8 @@ SRV*c:\code\symbols\*https://msdl.microsoft.com/download/symbols;SRV*c:\code\sym
|
||||
|
||||
## Using the symbol server in Visual Studio
|
||||
|
||||
<img src='https://mdn.mozillademos.org/files/733/symbol-server-vc8express-menu.jpg'>
|
||||
<img src='https://mdn.mozillademos.org/files/2497/2005_options.gif'>
|
||||

|
||||

|
||||
|
||||
## Troubleshooting: Symbols will not load
|
||||
|
||||
|
||||
18
docs/fiddles/features/drag-and-drop/index.html
Normal file
18
docs/fiddles/features/drag-and-drop/index.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
<a href="#" id="drag">Drag me</a>
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
41
docs/fiddles/features/drag-and-drop/main.js
Normal file
41
docs/fiddles/features/drag-and-drop/main.js
Normal file
@@ -0,0 +1,41 @@
|
||||
const { app, BrowserWindow, ipcMain, nativeImage, NativeImage } = require('electron')
|
||||
const fs = require('fs');
|
||||
const http = require('http');
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
const iconName = 'iconForDragAndDrop.png';
|
||||
const icon = fs.createWriteStream(`${process.cwd()}/${iconName}`);
|
||||
http.get('http://img.icons8.com/ios/452/drag-and-drop.png', (response) => {
|
||||
response.pipe(icon);
|
||||
});
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
ipcMain.on('ondragstart', (event, filePath) => {
|
||||
event.sender.startDrag({
|
||||
file: filePath,
|
||||
icon: `${process.cwd()}/${iconName}`
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
9
docs/fiddles/features/drag-and-drop/renderer.js
Normal file
9
docs/fiddles/features/drag-and-drop/renderer.js
Normal file
@@ -0,0 +1,9 @@
|
||||
const { ipcRenderer } = require('electron')
|
||||
const fs = require('fs')
|
||||
|
||||
document.getElementById('drag').ondragstart = (event) => {
|
||||
const fileName = 'drag-and-drop.md'
|
||||
fs.writeFileSync(fileName, '# Test drag and drop');
|
||||
event.preventDefault()
|
||||
ipcRenderer.send('ondragstart', process.cwd() + `/${fileName}`)
|
||||
}
|
||||
16
docs/fiddles/features/keyboard-shortcuts/global/index.html
Normal file
16
docs/fiddles/features/keyboard-shortcuts/global/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
31
docs/fiddles/features/keyboard-shortcuts/global/main.js
Normal file
31
docs/fiddles/features/keyboard-shortcuts/global/main.js
Normal file
@@ -0,0 +1,31 @@
|
||||
const { app, BrowserWindow, globalShortcut } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
globalShortcut.register('Alt+CommandOrControl+I', () => {
|
||||
console.log('Electron loves global shortcuts!')
|
||||
})
|
||||
}).then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,14 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,13 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const win = new BrowserWindow({ width: 800, height: 600, webPreferences: { nodeIntegration: true } })
|
||||
|
||||
win.loadFile('index.html')
|
||||
win.webContents.on('before-input-event', (event, input) => {
|
||||
if (input.control && input.key.toLowerCase() === 'i') {
|
||||
console.log('Pressed Control+I')
|
||||
event.preventDefault()
|
||||
}
|
||||
})
|
||||
})
|
||||
16
docs/fiddles/features/keyboard-shortcuts/local/index.html
Normal file
16
docs/fiddles/features/keyboard-shortcuts/local/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
39
docs/fiddles/features/keyboard-shortcuts/local/main.js
Normal file
39
docs/fiddles/features/keyboard-shortcuts/local/main.js
Normal file
@@ -0,0 +1,39 @@
|
||||
const { app, BrowserWindow, Menu, MenuItem } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
const menu = new Menu()
|
||||
menu.append(new MenuItem({
|
||||
label: 'Electron',
|
||||
submenu: [{
|
||||
role: 'help',
|
||||
accelerator: process.platform === 'darwin' ? 'Alt+Cmd+I' : 'Alt+Shift+I',
|
||||
click: () => { console.log('Electron rocks!') }
|
||||
}]
|
||||
}))
|
||||
|
||||
Menu.setApplicationMenu(menu)
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
19
docs/fiddles/features/macos-dark-mode/index.html
Normal file
19
docs/fiddles/features/macos-dark-mode/index.html
Normal file
@@ -0,0 +1,19 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
<link rel="stylesheet" type="text/css" href="./styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>Current theme source: <strong id="theme-source">System</strong></p>
|
||||
|
||||
<button id="toggle-dark-mode">Toggle Dark Mode</button>
|
||||
<button id="reset-to-system">Reset to System Theme</button>
|
||||
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</body>
|
||||
</html>
|
||||
40
docs/fiddles/features/macos-dark-mode/main.js
Normal file
40
docs/fiddles/features/macos-dark-mode/main.js
Normal file
@@ -0,0 +1,40 @@
|
||||
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
|
||||
ipcMain.handle('dark-mode:toggle', () => {
|
||||
if (nativeTheme.shouldUseDarkColors) {
|
||||
nativeTheme.themeSource = 'light'
|
||||
} else {
|
||||
nativeTheme.themeSource = 'dark'
|
||||
}
|
||||
return nativeTheme.shouldUseDarkColors
|
||||
})
|
||||
|
||||
ipcMain.handle('dark-mode:system', () => {
|
||||
nativeTheme.themeSouce = 'system'
|
||||
})
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
11
docs/fiddles/features/macos-dark-mode/renderer.js
Normal file
11
docs/fiddles/features/macos-dark-mode/renderer.js
Normal file
@@ -0,0 +1,11 @@
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
document.getElementById('toggle-dark-mode').addEventListener('click', async () => {
|
||||
const isDarkMode = await ipcRenderer.invoke('dark-mode:toggle')
|
||||
document.getElementById('theme-source').innerHTML = isDarkMode ? 'Dark' : 'Light'
|
||||
})
|
||||
|
||||
document.getElementById('reset-to-system').addEventListener('click', async () => {
|
||||
await ipcRenderer.invoke('dark-mode:system')
|
||||
document.getElementById('theme-source').innerHTML = 'System'
|
||||
})
|
||||
7
docs/fiddles/features/macos-dark-mode/styles.css
Normal file
7
docs/fiddles/features/macos-dark-mode/styles.css
Normal file
@@ -0,0 +1,7 @@
|
||||
@media (prefers-color-scheme: dark) {
|
||||
body { background: #333; color: white; }
|
||||
}
|
||||
|
||||
@media (prefers-color-scheme: light) {
|
||||
body { background: #ddd; color: black; }
|
||||
}
|
||||
16
docs/fiddles/features/macos-dock-menu/index.html
Normal file
16
docs/fiddles/features/macos-dock-menu/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
43
docs/fiddles/features/macos-dock-menu/main.js
Normal file
43
docs/fiddles/features/macos-dock-menu/main.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { app, BrowserWindow, Menu } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
const dockMenu = Menu.buildFromTemplate([
|
||||
{
|
||||
label: 'New Window',
|
||||
click () { console.log('New Window') }
|
||||
}, {
|
||||
label: 'New Window with Settings',
|
||||
submenu: [
|
||||
{ label: 'Basic' },
|
||||
{ label: 'Pro' }
|
||||
]
|
||||
},
|
||||
{ label: 'New Command...' }
|
||||
])
|
||||
|
||||
app.whenReady().then(() => {
|
||||
app.dock.setMenu(dockMenu)
|
||||
}).then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
16
docs/fiddles/features/notifications/main/index.html
Normal file
16
docs/fiddles/features/notifications/main/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
35
docs/fiddles/features/notifications/main/main.js
Normal file
35
docs/fiddles/features/notifications/main/main.js
Normal file
@@ -0,0 +1,35 @@
|
||||
const { app, BrowserWindow, Notification } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
function showNotification () {
|
||||
const notification = {
|
||||
title: 'Basic Notification',
|
||||
body: 'Notification from the Main process'
|
||||
}
|
||||
new Notification(notification).show()
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow).then(showNotification)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
17
docs/fiddles/features/notifications/renderer/index.html
Normal file
17
docs/fiddles/features/notifications/renderer/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
27
docs/fiddles/features/notifications/renderer/main.js
Normal file
27
docs/fiddles/features/notifications/renderer/main.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
7
docs/fiddles/features/notifications/renderer/renderer.js
Normal file
7
docs/fiddles/features/notifications/renderer/renderer.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const myNotification = new Notification('Title', {
|
||||
body: 'Notification from the Renderer process'
|
||||
})
|
||||
|
||||
myNotification.onclick = () => {
|
||||
console.log('Notification clicked')
|
||||
}
|
||||
15
docs/fiddles/features/offscreen-rendering/index.html
Normal file
15
docs/fiddles/features/offscreen-rendering/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
28
docs/fiddles/features/offscreen-rendering/main.js
Normal file
28
docs/fiddles/features/offscreen-rendering/main.js
Normal file
@@ -0,0 +1,28 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
|
||||
app.disableHardwareAcceleration()
|
||||
|
||||
let win
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow({ webPreferences: { offscreen: true } })
|
||||
win.loadURL('https://github.com')
|
||||
win.webContents.on('paint', (event, dirty, image) => {
|
||||
fs.writeFileSync('ex.png', image.toPNG())
|
||||
})
|
||||
win.webContents.setFrameRate(60)
|
||||
console.log(`The screenshot has been successfully saved to ${process.cwd()}/ex.png`)
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
17
docs/fiddles/features/online-detection/main/index.html
Normal file
17
docs/fiddles/features/online-detection/main/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
24
docs/fiddles/features/online-detection/main/main.js
Normal file
24
docs/fiddles/features/online-detection/main/main.js
Normal file
@@ -0,0 +1,24 @@
|
||||
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
|
||||
let onlineStatusWindow
|
||||
|
||||
app.whenReady().then(() => {
|
||||
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false, webPreferences: { nodeIntegration: true } })
|
||||
onlineStatusWindow.loadURL(`file://${__dirname}/index.html`)
|
||||
})
|
||||
|
||||
ipcMain.on('online-status-changed', (event, status) => {
|
||||
console.log(status)
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
7
docs/fiddles/features/online-detection/main/renderer.js
Normal file
7
docs/fiddles/features/online-detection/main/renderer.js
Normal file
@@ -0,0 +1,7 @@
|
||||
const { ipcRenderer } = require('electron')
|
||||
const updateOnlineStatus = () => { ipcRenderer.send('online-status-changed', navigator.onLine ? 'online' : 'offline') }
|
||||
|
||||
window.addEventListener('online', updateOnlineStatus)
|
||||
window.addEventListener('offline', updateOnlineStatus)
|
||||
|
||||
updateOnlineStatus()
|
||||
17
docs/fiddles/features/online-detection/renderer/index.html
Normal file
17
docs/fiddles/features/online-detection/renderer/index.html
Normal file
@@ -0,0 +1,17 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
<script src="renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
20
docs/fiddles/features/online-detection/renderer/main.js
Normal file
20
docs/fiddles/features/online-detection/renderer/main.js
Normal file
@@ -0,0 +1,20 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let onlineStatusWindow
|
||||
|
||||
app.whenReady().then(() => {
|
||||
onlineStatusWindow = new BrowserWindow({ width: 0, height: 0, show: false })
|
||||
onlineStatusWindow.loadURL(`file://${__dirname}/index.html`)
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
@@ -0,0 +1,6 @@
|
||||
const alertOnlineStatus = () => { window.alert(navigator.onLine ? 'online' : 'offline') }
|
||||
|
||||
window.addEventListener('online', alertOnlineStatus)
|
||||
window.addEventListener('offline', alertOnlineStatus)
|
||||
|
||||
alertOnlineStatus()
|
||||
16
docs/fiddles/features/progress-bar/index.html
Normal file
16
docs/fiddles/features/progress-bar/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
30
docs/fiddles/features/progress-bar/main.js
Normal file
30
docs/fiddles/features/progress-bar/main.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
win.setProgressBar(0.5)
|
||||
}
|
||||
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
16
docs/fiddles/features/recent-documents/index.html
Normal file
16
docs/fiddles/features/recent-documents/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
34
docs/fiddles/features/recent-documents/main.js
Normal file
34
docs/fiddles/features/recent-documents/main.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
const fileName = 'recently-used.md'
|
||||
fs.writeFile(fileName, 'Lorem Ipsum', () => {
|
||||
app.addRecentDocument(path.join(process.cwd(), `${fileName}`))
|
||||
})
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.clearRecentDocuments()
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
16
docs/fiddles/features/represented-file/index.html
Normal file
16
docs/fiddles/features/represented-file/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
33
docs/fiddles/features/represented-file/main.js
Normal file
33
docs/fiddles/features/represented-file/main.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const os = require('os');
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.setRepresentedFilename(os.homedir())
|
||||
win.setDocumentEdited(true)
|
||||
})
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
16
docs/fiddles/quick-start/index.html
Normal file
16
docs/fiddles/quick-start/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Hello World!</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="script-src 'self' 'unsafe-inline';" />
|
||||
</head>
|
||||
<body>
|
||||
<h1>Hello World!</h1>
|
||||
<p>
|
||||
We are using node <script>document.write(process.versions.node)</script>,
|
||||
Chrome <script>document.write(process.versions.chrome)</script>,
|
||||
and Electron <script>document.write(process.versions.electron)</script>.
|
||||
</p>
|
||||
</body>
|
||||
</html>
|
||||
27
docs/fiddles/quick-start/main.js
Normal file
27
docs/fiddles/quick-start/main.js
Normal file
@@ -0,0 +1,27 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(createWindow)
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
})
|
||||
|
||||
app.on('activate', () => {
|
||||
if (BrowserWindow.getAllWindows().length === 0) {
|
||||
createWindow()
|
||||
}
|
||||
})
|
||||
@@ -1,25 +1,28 @@
|
||||
# DevTools Extension
|
||||
|
||||
Electron supports the [Chrome DevTools Extension][devtools-extension], which can
|
||||
be used to extend the ability of devtools for debugging popular web frameworks.
|
||||
Electron supports [Chrome DevTools extensions][devtools-extension], which can
|
||||
be used to extend the ability of Chrome's developer tools for debugging
|
||||
popular web frameworks.
|
||||
|
||||
## How to load a DevTools Extension
|
||||
## Loading a DevTools extension with tooling
|
||||
|
||||
This document outlines the process for manually loading an extension.
|
||||
You may also try
|
||||
[electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer),
|
||||
a third-party tool that downloads extensions directly from the Chrome WebStore.
|
||||
The easiest way to load a DevTools extension is to use third-party tooling to automate the
|
||||
process for you. [electron-devtools-installer][electron-devtools-installer] is a popular
|
||||
NPM package that does just that.
|
||||
|
||||
To load an extension in Electron, you need to download it in Chrome browser,
|
||||
locate its filesystem path, and then load it by calling the
|
||||
`BrowserWindow.addDevToolsExtension(extension)` API.
|
||||
## Manually loading a DevTools extension
|
||||
|
||||
Using the [React Developer Tools][react-devtools] as example:
|
||||
If you don't want to use the tooling approach, you can also do all of the necessary
|
||||
operations by hand. To load an extension in Electron, you need to download it via Chrome,
|
||||
locate its filesystem path, and then load it into your [Session][session] by calling the
|
||||
[`ses.loadExtension`] API.
|
||||
|
||||
1. Install it in Chrome browser.
|
||||
Using the [React Developer Tools][react-devtools] as an example:
|
||||
|
||||
1. Install the extension in Google Chrome.
|
||||
1. Navigate to `chrome://extensions`, and find its extension ID, which is a hash
|
||||
string like `fmkadmapgofadopljbjfkapdkoienihi`.
|
||||
1. Find out filesystem location used by Chrome for storing extensions:
|
||||
1. Find out the filesystem location used by Chrome for storing extensions:
|
||||
* on Windows it is `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions`;
|
||||
* on Linux it could be:
|
||||
* `~/.config/google-chrome/Default/Extensions/`
|
||||
@@ -27,36 +30,48 @@ Using the [React Developer Tools][react-devtools] as example:
|
||||
* `~/.config/google-chrome-canary/Default/Extensions/`
|
||||
* `~/.config/chromium/Default/Extensions/`
|
||||
* on macOS it is `~/Library/Application Support/Google/Chrome/Default/Extensions`.
|
||||
1. Pass the location of the extension to `BrowserWindow.addDevToolsExtension`
|
||||
API, for the React Developer Tools, it is something like:
|
||||
1. Pass the location of the extension to the [`ses.loadExtension`][load-extension]
|
||||
API. For React Developer Tools `v4.9.0`, it looks something like:
|
||||
```javascript
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
const { app, session } = require('electron')
|
||||
const path = require('path')
|
||||
const os = require('os')
|
||||
|
||||
BrowserWindow.addDevToolsExtension(
|
||||
path.join(os.homedir(), '/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.3.0_0')
|
||||
)
|
||||
// on macOS
|
||||
const reactDevToolsPath = path.join(
|
||||
os.homedir(),
|
||||
'/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.9.0_0'
|
||||
)
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
await session.defaultSession.loadExtension(reactDevToolsPath)
|
||||
})
|
||||
```
|
||||
|
||||
**Note:** The `BrowserWindow.addDevToolsExtension` API cannot be called before the
|
||||
ready event of the app module is emitted.
|
||||
**Notes:**
|
||||
|
||||
The extension will be remembered so you only need to call this API once per
|
||||
extension. If you try to add an extension that has already been loaded, this method
|
||||
will not return and instead log a warning to the console.
|
||||
* `loadExtension` returns a Promise with an [Extension object][extension-structure],
|
||||
which contains metadata about the extension that was loaded. This promise needs to
|
||||
resolve (e.g. with an `await` expression) before loading a page. Otherwise, the
|
||||
extension won't be guaranteed to load.
|
||||
* `loadExtension` cannot be called before the `ready` event of the `app` module
|
||||
is emitted, nor can it be called on in-memory (non-persistent) sessions.
|
||||
* `loadExtension` must be called on every boot of your app if you want the
|
||||
extension to be loaded.
|
||||
|
||||
### How to remove a DevTools Extension
|
||||
### Removing a DevTools extension
|
||||
|
||||
You can pass the name of the extension to the `BrowserWindow.removeDevToolsExtension`
|
||||
API to remove it. The name of the extension is returned by
|
||||
`BrowserWindow.addDevToolsExtension` and you can get the names of all installed
|
||||
DevTools Extensions using the `BrowserWindow.getDevToolsExtensions` API.
|
||||
You can pass the extension's ID to the [`ses.removeExtension`][remove-extension] API to
|
||||
remove it from your Session. Loaded extensions are not persisted between
|
||||
app launches.
|
||||
|
||||
## Supported DevTools Extensions
|
||||
## DevTools extension support
|
||||
|
||||
Electron only supports a limited set of `chrome.*` APIs, so some extensions
|
||||
using unsupported `chrome.*` APIs for chrome extension features may not work.
|
||||
Following Devtools Extensions are tested and guaranteed to work in Electron:
|
||||
Electron only supports
|
||||
[a limited set of `chrome.*` APIs][supported-extension-apis],
|
||||
so extensions using unsupported `chrome.*` APIs under the hood may not work.
|
||||
|
||||
The following Devtools extensions have been tested to work in Electron:
|
||||
|
||||
* [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi)
|
||||
* [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi)
|
||||
@@ -68,14 +83,22 @@ Following Devtools Extensions are tested and guaranteed to work in Electron:
|
||||
* [Redux DevTools Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd)
|
||||
* [MobX Developer Tools](https://chrome.google.com/webstore/detail/mobx-developer-tools/pfgnfdagidkfgccljigdamigbcnndkod)
|
||||
|
||||
### What should I do if a DevTools Extension is not working?
|
||||
### What should I do if a DevTools extension is not working?
|
||||
|
||||
First please make sure the extension is still being maintained, some extensions
|
||||
can not even work for recent versions of Chrome browser, and we are not able to
|
||||
do anything for them.
|
||||
First, please make sure the extension is still being maintained and is compatible
|
||||
with the latest version of Google Chrome. We cannot provide additional support for
|
||||
unsupported extensions.
|
||||
|
||||
Then file a bug at Electron's issues list, and describe which part of the
|
||||
extension is not working as expected.
|
||||
If the extension works on Chrome but not on Electron, file a bug in Electron's
|
||||
[issue tracker][issue-tracker] and describe which part
|
||||
of the extension is not working as expected.
|
||||
|
||||
[devtools-extension]: https://developer.chrome.com/extensions/devtools
|
||||
[session]: ../api/session.md
|
||||
[react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi
|
||||
[load-extension]: ../api/session.md#sesloadextensionpath
|
||||
[extension-structure]: ../api/structures/extension.md
|
||||
[remove-extension]: ../api/session.md#sesremoveextensionextensionid
|
||||
[electron-devtools-installer]: https://github.com/MarshallOfSound/electron-devtools-installer
|
||||
[supported-extension-apis]: ../api/extensions.md
|
||||
[issue-tracker]: https://github.com/electron/electron/issues
|
||||
|
||||
@@ -9,7 +9,7 @@ Two modes of rendering can be used and only the dirty area is passed in the
|
||||
`'paint'` event to be more efficient. The rendering can be stopped, continued
|
||||
and the frame rate can be set. The specified frame rate is a top limit value,
|
||||
when there is nothing happening on a webpage, no frames are generated. The
|
||||
maximum frame rate is 60, because above that there is no benefit, only
|
||||
maximum frame rate is 240, because above that there is no benefit, only
|
||||
performance loss.
|
||||
|
||||
**Note:** An offscreen window is always created as a [Frameless Window](../api/frameless-window.md).
|
||||
|
||||
@@ -612,9 +612,10 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (options.withFileTypes) {
|
||||
const dirents = [];
|
||||
for (const file of files) {
|
||||
const stats = archive.stat(file);
|
||||
const childPath = path.join(filePath, file);
|
||||
const stats = archive.stat(childPath);
|
||||
if (!stats) {
|
||||
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath: file });
|
||||
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath: childPath });
|
||||
nextTick(callback!, [error]);
|
||||
return;
|
||||
}
|
||||
@@ -654,9 +655,10 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (options && (options as any).withFileTypes) {
|
||||
const dirents = [];
|
||||
for (const file of files) {
|
||||
const stats = archive.stat(file);
|
||||
const childPath = path.join(filePath, file);
|
||||
const stats = archive.stat(childPath);
|
||||
if (!stats) {
|
||||
throw createError(AsarError.NOT_FOUND, { asarPath, filePath: file });
|
||||
throw createError(AsarError.NOT_FOUND, { asarPath, filePath: childPath });
|
||||
}
|
||||
if (stats.isFile) {
|
||||
dirents.push(new fs.Dirent(file, fs.constants.UV_DIRENT_FILE));
|
||||
@@ -686,7 +688,8 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
if (info.size === 0) return ['', false];
|
||||
if (info.unpacked) {
|
||||
const realPath = archive.copyFileOut(filePath);
|
||||
return fs.readFileSync(realPath, { encoding: 'utf8' });
|
||||
const str = fs.readFileSync(realPath, { encoding: 'utf8' });
|
||||
return [str, str.length > 0];
|
||||
}
|
||||
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
|
||||
@@ -17,8 +17,12 @@ Object.setPrototypeOf(protocol, new Proxy({}, {
|
||||
|
||||
ownKeys () {
|
||||
if (!app.isReady()) return [];
|
||||
return Reflect.ownKeys(session.defaultSession!.protocol);
|
||||
},
|
||||
|
||||
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession!.protocol));
|
||||
has: (target, property: string) => {
|
||||
if (!app.isReady()) return false;
|
||||
return Reflect.has(session.defaultSession!.protocol, property);
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor () {
|
||||
|
||||
@@ -1,20 +1,47 @@
|
||||
import { EventEmitter } from 'events';
|
||||
const { createScreen } = process._linkedBinding('electron_common_screen');
|
||||
|
||||
let _screen: Electron.Screen;
|
||||
|
||||
const createScreenIfNeeded = () => {
|
||||
if (_screen === undefined) {
|
||||
_screen = createScreen();
|
||||
}
|
||||
};
|
||||
|
||||
// We can't call createScreen until after app.on('ready'), but this module
|
||||
// exposes an instance created by createScreen. In order to avoid
|
||||
// side-effecting and calling createScreen upon import of this module, instead
|
||||
// we export a proxy which lazily calls createScreen on first access.
|
||||
export default new Proxy({}, {
|
||||
get: (target, prop) => {
|
||||
if (_screen === undefined) {
|
||||
_screen = createScreen();
|
||||
get: (target, property: keyof Electron.Screen) => {
|
||||
createScreenIfNeeded();
|
||||
const value = _screen[property];
|
||||
if (typeof value === 'function') {
|
||||
return value.bind(_screen);
|
||||
}
|
||||
const v = (_screen as any)[prop];
|
||||
if (typeof v === 'function') {
|
||||
return v.bind(_screen);
|
||||
}
|
||||
return v;
|
||||
return value;
|
||||
},
|
||||
set: (target, property: string, value: unknown) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.set(_screen, property, value);
|
||||
},
|
||||
ownKeys: () => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.ownKeys(_screen);
|
||||
},
|
||||
has: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return property in _screen;
|
||||
},
|
||||
getOwnPropertyDescriptor: (target, property: string) => {
|
||||
createScreenIfNeeded();
|
||||
return Reflect.getOwnPropertyDescriptor(_screen, property);
|
||||
},
|
||||
getPrototypeOf: () => {
|
||||
// This is necessary as a result of weirdness with EventEmitterMixin
|
||||
// and FunctionTemplate - we need to explicitly ensure it's returned
|
||||
// in the prototype.
|
||||
return EventEmitter.prototype;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -11,7 +11,7 @@ if ('getAppLevelAppearance' in systemPreferences) {
|
||||
}
|
||||
|
||||
if ('getEffectiveAppearance' in systemPreferences) {
|
||||
const nativeEAGetter = systemPreferences.getAppLevelAppearance;
|
||||
const nativeEAGetter = systemPreferences.getEffectiveAppearance;
|
||||
Object.defineProperty(systemPreferences, 'effectiveAppearance', {
|
||||
get: () => nativeEAGetter.call(systemPreferences)
|
||||
});
|
||||
|
||||
@@ -161,29 +161,29 @@ WebContents.prototype._sendInternalToAll = function (channel, ...args) {
|
||||
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
};
|
||||
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
WebContents.prototype.sendToFrame = function (frame, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
} else if (typeof frameId !== 'number') {
|
||||
throw new Error('Missing required frameId argument');
|
||||
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
|
||||
throw new Error('Missing required frame argument (must be number or array)');
|
||||
}
|
||||
|
||||
const internal = false;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
|
||||
return this._sendToFrame(internal, sendToAll, frame, channel, args);
|
||||
};
|
||||
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
|
||||
WebContents.prototype._sendToFrameInternal = function (frame, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument');
|
||||
} else if (typeof frameId !== 'number') {
|
||||
throw new Error('Missing required frameId argument');
|
||||
} else if (!(typeof frame === 'number' || Array.isArray(frame))) {
|
||||
throw new Error('Missing required frame argument (must be number or array)');
|
||||
}
|
||||
|
||||
const internal = true;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
|
||||
return this._sendToFrame(internal, sendToAll, frame, channel, args);
|
||||
};
|
||||
|
||||
// Following methods are mapped to webFrame.
|
||||
@@ -439,8 +439,9 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
||||
};
|
||||
|
||||
const addReplyToEvent = (event: any) => {
|
||||
const { processId, frameId } = event;
|
||||
event.reply = (...args: any[]) => {
|
||||
event.sender.sendToFrame(event.frameId, ...args);
|
||||
event.sender.sendToFrame([processId, frameId], ...args);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -485,6 +486,13 @@ WebContents.prototype._init = function () {
|
||||
this.goToOffset = navigationController.goToOffset.bind(navigationController);
|
||||
this.getActiveIndex = navigationController.getActiveIndex.bind(navigationController);
|
||||
this.length = navigationController.length.bind(navigationController);
|
||||
// Read off the ID at construction time, so that it's accessible even after
|
||||
// the underlying C++ WebContents is destroyed.
|
||||
const id = this.id;
|
||||
Object.defineProperty(this, 'id', {
|
||||
value: id,
|
||||
writable: false
|
||||
});
|
||||
|
||||
// Every remote callback from renderer process would add a listener to the
|
||||
// render-view-deleted event, so ignore the listeners warning.
|
||||
|
||||
@@ -48,6 +48,9 @@ export const getSourcesImpl = (event: Electron.IpcMainEvent | null, args: Electr
|
||||
}
|
||||
// Remove from currentlyRunning once we resolve or reject
|
||||
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
|
||||
if (event) {
|
||||
event.sender.removeListener('destroyed', stopRunning);
|
||||
}
|
||||
};
|
||||
|
||||
capturer._onerror = (error: string) => {
|
||||
@@ -66,7 +69,7 @@ export const getSourcesImpl = (event: Electron.IpcMainEvent | null, args: Electr
|
||||
// reference to emit and the capturer itself so that it never dispatches
|
||||
// back to the renderer
|
||||
if (event) {
|
||||
event.sender.once('destroyed', () => stopRunning());
|
||||
event.sender.once('destroyed', stopRunning);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ const FUNCTION_PROPERTIES = [
|
||||
];
|
||||
|
||||
type RendererFunctionId = [string, number] // [contextId, funcId]
|
||||
type FinalizerInfo = { id: RendererFunctionId, webContents: electron.WebContents, frameId: number };
|
||||
type FinalizerInfo = { id: RendererFunctionId, webContents: electron.WebContents, frameId: [number, number] };
|
||||
type WeakRef<T> = { deref(): T | undefined }
|
||||
type CallIntoRenderer = (...args: any[]) => void
|
||||
|
||||
@@ -31,7 +31,7 @@ const finalizationRegistry = new (globalThis as any).FinalizationRegistry((fi: F
|
||||
const ref = rendererFunctionCache.get(mapKey);
|
||||
if (ref !== undefined && ref.deref() === undefined) {
|
||||
rendererFunctionCache.delete(mapKey);
|
||||
if (!fi.webContents.isDestroyed()) { fi.webContents.sendToFrame(fi.frameId, 'ELECTRON_RENDERER_RELEASE_CALLBACK', fi.id[0], fi.id[1]); }
|
||||
if (!fi.webContents.isDestroyed()) { fi.webContents._sendToFrameInternal(fi.frameId, 'ELECTRON_RENDERER_RELEASE_CALLBACK', fi.id[0], fi.id[1]); }
|
||||
}
|
||||
});
|
||||
|
||||
@@ -43,7 +43,7 @@ function getCachedRendererFunction (id: RendererFunctionId): CallIntoRenderer |
|
||||
if (deref !== undefined) return deref;
|
||||
}
|
||||
}
|
||||
function setCachedRendererFunction (id: RendererFunctionId, wc: electron.WebContents, frameId: number, value: CallIntoRenderer) {
|
||||
function setCachedRendererFunction (id: RendererFunctionId, wc: electron.WebContents, frameId: [number, number], value: CallIntoRenderer) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const wr = new (globalThis as any).WeakRef(value) as WeakRef<CallIntoRenderer>;
|
||||
const mapKey = id[0] + '~' + id[1];
|
||||
@@ -218,7 +218,7 @@ const fakeConstructor = (constructor: Function, name: string) =>
|
||||
});
|
||||
|
||||
// Convert array of meta data from renderer into array of real values.
|
||||
const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) {
|
||||
const unwrapArgs = function (sender: electron.WebContents, frameId: [number, number], contextId: string, args: any[]) {
|
||||
const metaToValue = function (meta: MetaTypeFromRenderer): any {
|
||||
switch (meta.type) {
|
||||
case 'nativeimage':
|
||||
@@ -263,7 +263,7 @@ const unwrapArgs = function (sender: electron.WebContents, frameId: number, cont
|
||||
const callIntoRenderer = function (this: any, ...args: any[]) {
|
||||
let succeed = false;
|
||||
if (!sender.isDestroyed()) {
|
||||
succeed = (sender as any)._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args));
|
||||
succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args));
|
||||
}
|
||||
if (!succeed) {
|
||||
removeRemoteListenersAndLogWarning(this, callIntoRenderer);
|
||||
@@ -421,7 +421,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, co
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
|
||||
const constructor = objectsRegistry.get(id);
|
||||
|
||||
if (constructor == null) {
|
||||
@@ -432,7 +432,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId,
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
|
||||
const func = objectsRegistry.get(id);
|
||||
|
||||
if (func == null) {
|
||||
@@ -449,7 +449,7 @@ handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
|
||||
const object = objectsRegistry.get(id);
|
||||
|
||||
if (object == null) {
|
||||
@@ -460,7 +460,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, cont
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
|
||||
const object = objectsRegistry.get(id);
|
||||
|
||||
if (object == null) {
|
||||
@@ -477,7 +477,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId,
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args);
|
||||
const obj = objectsRegistry.get(id);
|
||||
|
||||
if (obj == null) {
|
||||
|
||||
@@ -298,7 +298,7 @@ function metaToError (meta: { type: 'error', value: any, members: ObjectMember[]
|
||||
}
|
||||
|
||||
function handleMessage (channel: string, handler: Function) {
|
||||
ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => {
|
||||
ipcRendererInternal.onMessageFromMain(channel, (event, passedContextId, id, ...args) => {
|
||||
if (passedContextId === contextId) {
|
||||
handler(id, ...args);
|
||||
} else {
|
||||
|
||||
@@ -1,119 +0,0 @@
|
||||
import { webFrame } from 'electron';
|
||||
|
||||
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
|
||||
|
||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||
|
||||
const IsolatedWorldIDs = {
|
||||
/**
|
||||
* Start of extension isolated world IDs, as defined in
|
||||
* electron_render_frame_observer.h
|
||||
*/
|
||||
ISOLATED_WORLD_EXTENSIONS: 1 << 20
|
||||
};
|
||||
|
||||
let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS;
|
||||
const extensionWorldId: {[key: string]: number | undefined} = {};
|
||||
|
||||
// https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52
|
||||
const getIsolatedWorldIdForInstance = () => {
|
||||
// TODO(samuelmaddock): allocate and cleanup IDs
|
||||
return isolatedWorldIds++;
|
||||
};
|
||||
|
||||
const escapePattern = function (pattern: string) {
|
||||
return pattern.replace(/[\\^$+?.()|[\]{}]/g, '\\$&');
|
||||
};
|
||||
|
||||
// Check whether pattern matches.
|
||||
// https://developer.chrome.com/extensions/match_patterns
|
||||
const matchesPattern = function (pattern: string) {
|
||||
if (pattern === '<all_urls>') return true;
|
||||
const regexp = new RegExp(`^${pattern.split('*').map(escapePattern).join('.*')}$`);
|
||||
const url = `${location.protocol}//${location.host}${location.pathname}`;
|
||||
return url.match(regexp);
|
||||
};
|
||||
|
||||
// Run the code with chrome API integrated.
|
||||
const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
|
||||
// Assign unique world ID to each extension
|
||||
const worldId = extensionWorldId[extensionId] ||
|
||||
(extensionWorldId[extensionId] = getIsolatedWorldIdForInstance());
|
||||
|
||||
// store extension ID for content script to read in isolated world
|
||||
v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId);
|
||||
|
||||
webFrame.setIsolatedWorldInfo(worldId, {
|
||||
name: `${extensionId} [${worldId}]`
|
||||
// TODO(samuelmaddock): read `content_security_policy` from extension manifest
|
||||
// csp: manifest.content_security_policy,
|
||||
});
|
||||
|
||||
const sources = [{ code, url }];
|
||||
return webFrame.executeJavaScriptInIsolatedWorld(worldId, sources);
|
||||
};
|
||||
|
||||
const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) {
|
||||
for (const { url, code } of scripts) {
|
||||
runContentScript.call(window, extensionId, url, code);
|
||||
}
|
||||
};
|
||||
|
||||
const runStylesheet = function (this: any, url: string, code: string) {
|
||||
webFrame.insertCSS(code);
|
||||
};
|
||||
|
||||
const runAllStylesheet = function (css: Array<Electron.InjectionBase>) {
|
||||
for (const { url, code } of css) {
|
||||
runStylesheet.call(window, url, code);
|
||||
}
|
||||
};
|
||||
|
||||
// Run injected scripts.
|
||||
// https://developer.chrome.com/extensions/content_scripts
|
||||
const injectContentScript = function (extensionId: string, script: Electron.ContentScript) {
|
||||
if (!process.isMainFrame && !script.allFrames) return;
|
||||
if (!script.matches.some(matchesPattern)) return;
|
||||
|
||||
if (script.js) {
|
||||
const fire = runAllContentScript.bind(window, script.js, extensionId);
|
||||
if (script.runAt === 'document_start') {
|
||||
process.once('document-start', fire);
|
||||
} else if (script.runAt === 'document_end') {
|
||||
process.once('document-end', fire);
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fire);
|
||||
}
|
||||
}
|
||||
|
||||
if (script.css) {
|
||||
const fire = runAllStylesheet.bind(window, script.css);
|
||||
if (script.runAt === 'document_start') {
|
||||
process.once('document-start', fire);
|
||||
} else if (script.runAt === 'document_end') {
|
||||
process.once('document-end', fire);
|
||||
} else {
|
||||
document.addEventListener('DOMContentLoaded', fire);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Handle the request of chrome.tabs.executeJavaScript.
|
||||
ipcRendererUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', function (
|
||||
event: Electron.Event,
|
||||
extensionId: string,
|
||||
url: string,
|
||||
code: string
|
||||
) {
|
||||
return runContentScript.call(window, extensionId, url, code);
|
||||
});
|
||||
|
||||
module.exports = (entries: Electron.ContentScriptEntry[]) => {
|
||||
for (const entry of entries) {
|
||||
if (entry.contentScripts) {
|
||||
for (const script of entry.contentScripts) {
|
||||
injectContentScript(entry.extensionId, script);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
export class Event {
|
||||
private listeners: Function[] = []
|
||||
|
||||
addListener (callback: Function) {
|
||||
this.listeners.push(callback);
|
||||
}
|
||||
|
||||
removeListener (callback: Function) {
|
||||
const index = this.listeners.indexOf(callback);
|
||||
if (index !== -1) {
|
||||
this.listeners.splice(index, 1);
|
||||
}
|
||||
}
|
||||
|
||||
emit (...args: any[]) {
|
||||
for (const listener of this.listeners) {
|
||||
listener(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,60 +0,0 @@
|
||||
// Implementation of chrome.i18n.getMessage
|
||||
// https://developer.chrome.com/extensions/i18n#method-getMessage
|
||||
//
|
||||
// Does not implement predefined messages:
|
||||
// https://developer.chrome.com/extensions/i18n#overview-predefined
|
||||
|
||||
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
|
||||
|
||||
interface Placeholder {
|
||||
content: string;
|
||||
example?: string;
|
||||
}
|
||||
|
||||
const getMessages = (extensionId: number) => {
|
||||
try {
|
||||
const data = ipcRendererUtils.invokeSync<string>('CHROME_GET_MESSAGES', extensionId);
|
||||
return JSON.parse(data) || {};
|
||||
} catch {
|
||||
return {};
|
||||
}
|
||||
};
|
||||
|
||||
const replaceNumberedSubstitutions = (message: string, substitutions: string[]) => {
|
||||
return message.replace(/\$(\d+)/, (_, number) => {
|
||||
const index = parseInt(number, 10) - 1;
|
||||
return substitutions[index] || '';
|
||||
});
|
||||
};
|
||||
|
||||
const replacePlaceholders = (message: string, placeholders: Record<string, Placeholder>, substitutions: string[] | string) => {
|
||||
if (typeof substitutions === 'string') substitutions = [substitutions];
|
||||
if (!Array.isArray(substitutions)) substitutions = [];
|
||||
|
||||
if (placeholders) {
|
||||
Object.keys(placeholders).forEach((name: string) => {
|
||||
let { content } = placeholders[name];
|
||||
const substitutionsArray = Array.isArray(substitutions) ? substitutions : [];
|
||||
content = replaceNumberedSubstitutions(content, substitutionsArray);
|
||||
message = message.replace(new RegExp(`\\$${name}\\$`, 'gi'), content);
|
||||
});
|
||||
}
|
||||
|
||||
return replaceNumberedSubstitutions(message, substitutions);
|
||||
};
|
||||
|
||||
const getMessage = (extensionId: number, messageName: string, substitutions: string[]) => {
|
||||
const messages = getMessages(extensionId);
|
||||
if (Object.prototype.hasOwnProperty.call(messages, messageName)) {
|
||||
const { message, placeholders } = messages[messageName];
|
||||
return replacePlaceholders(message, placeholders, substitutions);
|
||||
}
|
||||
};
|
||||
|
||||
exports.setup = (extensionId: number) => {
|
||||
return {
|
||||
getMessage (messageName: string, substitutions: string[]) {
|
||||
return getMessage(extensionId, messageName, substitutions);
|
||||
}
|
||||
};
|
||||
};
|
||||
@@ -1,86 +0,0 @@
|
||||
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
|
||||
|
||||
const getStorage = (storageType: string, extensionId: number, callback: Function) => {
|
||||
if (typeof callback !== 'function') throw new TypeError('No callback provided');
|
||||
|
||||
ipcRendererInternal.invoke<string>('CHROME_STORAGE_READ', storageType, extensionId)
|
||||
.then(data => {
|
||||
if (data !== null) {
|
||||
callback(JSON.parse(data));
|
||||
} else {
|
||||
// Disabled due to false positive in StandardJS
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
callback({});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const setStorage = (storageType: string, extensionId: number, storage: Record<string, any>, callback: Function) => {
|
||||
const json = JSON.stringify(storage);
|
||||
ipcRendererInternal.invoke('CHROME_STORAGE_WRITE', storageType, extensionId, json)
|
||||
.then(() => {
|
||||
if (callback) callback();
|
||||
});
|
||||
};
|
||||
|
||||
const getStorageManager = (storageType: string, extensionId: number) => {
|
||||
return {
|
||||
get (keys: string[], callback: Function) {
|
||||
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
|
||||
if (keys == null) return callback(storage);
|
||||
|
||||
let defaults: Record<string, any> = {};
|
||||
switch (typeof keys) {
|
||||
case 'string':
|
||||
keys = [keys];
|
||||
break;
|
||||
case 'object':
|
||||
if (!Array.isArray(keys)) {
|
||||
defaults = keys;
|
||||
keys = Object.keys(keys);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// Disabled due to false positive in StandardJS
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
if (keys.length === 0) return callback({});
|
||||
|
||||
const items: Record<string, any> = {};
|
||||
keys.forEach((key: string) => {
|
||||
let value = storage[key];
|
||||
if (value == null) value = defaults[key];
|
||||
items[key] = value;
|
||||
});
|
||||
callback(items);
|
||||
});
|
||||
},
|
||||
|
||||
set (items: Record<string, any>, callback: Function) {
|
||||
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
|
||||
Object.keys(items).forEach(name => { storage[name] = items[name]; });
|
||||
setStorage(storageType, extensionId, storage, callback);
|
||||
});
|
||||
},
|
||||
|
||||
remove (keys: string[], callback: Function) {
|
||||
getStorage(storageType, extensionId, (storage: Record<string, any>) => {
|
||||
if (!Array.isArray(keys)) keys = [keys];
|
||||
keys.forEach((key: string) => {
|
||||
delete storage[key];
|
||||
});
|
||||
|
||||
setStorage(storageType, extensionId, storage, callback);
|
||||
});
|
||||
},
|
||||
|
||||
clear (callback: Function) {
|
||||
setStorage(storageType, extensionId, {}, callback);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
export const setup = (extensionId: number) => ({
|
||||
sync: getStorageManager('sync', extensionId),
|
||||
local: getStorageManager('local', extensionId)
|
||||
});
|
||||
@@ -1,19 +0,0 @@
|
||||
import { Event } from '@electron/internal/renderer/extensions/event';
|
||||
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
|
||||
|
||||
class WebNavigation {
|
||||
private onBeforeNavigate = new Event()
|
||||
private onCompleted = new Event()
|
||||
|
||||
constructor () {
|
||||
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event: Electron.IpcRendererEvent, details: any) => {
|
||||
this.onBeforeNavigate.emit(details);
|
||||
});
|
||||
|
||||
ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event: Electron.IpcRendererEvent, details: any) => {
|
||||
this.onCompleted.emit(details);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export const setup = () => new WebNavigation();
|
||||
@@ -3,7 +3,7 @@ import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-in
|
||||
type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any
|
||||
|
||||
export const handle = function <T extends IPCHandler> (channel: string, handler: T) {
|
||||
ipcRendererInternal.on(channel, async (event, requestId, ...args) => {
|
||||
ipcRendererInternal.onMessageFromMain(channel, async (event, requestId, ...args) => {
|
||||
const replyChannel = `${channel}_RESPONSE_${requestId}`;
|
||||
try {
|
||||
event.sender.send(replyChannel, null, await handler(event, ...args));
|
||||
|
||||
@@ -29,4 +29,27 @@ ipcRendererInternal.invoke = async function<T> (channel: string, ...args: any[])
|
||||
return result;
|
||||
};
|
||||
|
||||
ipcRendererInternal.onMessageFromMain = function (channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) {
|
||||
return ipcRendererInternal.on(channel, (event, ...args) => {
|
||||
if (event.senderId !== 0) {
|
||||
console.error(`Message ${channel} sent by unexpected WebContents (${event.senderId})`);
|
||||
return;
|
||||
}
|
||||
|
||||
listener(event, ...args);
|
||||
});
|
||||
};
|
||||
|
||||
ipcRendererInternal.onceMessageFromMain = function (channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) {
|
||||
return ipcRendererInternal.on(channel, function wrapper (event, ...args) {
|
||||
if (event.senderId !== 0) {
|
||||
console.error(`Message ${channel} sent by unexpected WebContents (${event.senderId})`);
|
||||
return;
|
||||
}
|
||||
|
||||
ipcRendererInternal.removeListener(channel, wrapper);
|
||||
listener(event, ...args);
|
||||
});
|
||||
};
|
||||
|
||||
export { ipcRendererInternal };
|
||||
|
||||
@@ -80,7 +80,7 @@ const isUnsafeEvalEnabled: () => Promise<boolean> = function () {
|
||||
// Call _executeJavaScript to bypass the world-safe deprecation warning
|
||||
return (webFrame as any)._executeJavaScript(`(${(() => {
|
||||
try {
|
||||
new Function(''); // eslint-disable-line no-new,no-new-func
|
||||
eval(window.trustedTypes.emptyScript); // eslint-disable-line no-eval
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
@@ -179,7 +179,7 @@ const warnAboutInsecureCSP = function () {
|
||||
|
||||
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
|
||||
'font-weight: bold;', warning);
|
||||
});
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -27,6 +27,7 @@ const WEB_VIEW_EVENTS: Record<string, Array<string>> = {
|
||||
'focus-change': ['focus', 'guestInstanceId'],
|
||||
close: [],
|
||||
crashed: [],
|
||||
'render-process-gone': ['details'],
|
||||
'plugin-crashed': ['name', 'version'],
|
||||
destroyed: [],
|
||||
'page-title-updated': ['title', 'explicitSet'],
|
||||
@@ -66,18 +67,18 @@ const dispatchEvent = function (
|
||||
};
|
||||
|
||||
export function registerEvents (webView: WebViewImpl, viewInstanceId: number) {
|
||||
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () {
|
||||
ipcRendererInternal.onMessageFromMain(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () {
|
||||
webView.guestInstanceId = undefined;
|
||||
webView.reset();
|
||||
const domEvent = new Event('destroyed');
|
||||
webView.dispatchEvent(domEvent);
|
||||
});
|
||||
|
||||
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) {
|
||||
ipcRendererInternal.onMessageFromMain(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) {
|
||||
dispatchEvent(webView, eventName, eventName, ...args);
|
||||
});
|
||||
|
||||
ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) {
|
||||
ipcRendererInternal.onMessageFromMain(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) {
|
||||
const domEvent = new Event('ipc-message') as IpcMessageEvent;
|
||||
domEvent.channel = channel;
|
||||
domEvent.args = args;
|
||||
|
||||
@@ -41,7 +41,9 @@ export class WebViewImpl {
|
||||
// Create internal iframe element.
|
||||
this.internalElement = this.createInternalElement();
|
||||
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' });
|
||||
shadowRoot.innerHTML = '<!DOCTYPE html><style type="text/css">:host { display: flex; }</style>';
|
||||
const style = shadowRoot.ownerDocument.createElement('style');
|
||||
style.textContent = ':host { display: flex; }';
|
||||
shadowRoot.appendChild(style);
|
||||
this.setupWebViewAttributes();
|
||||
this.viewInstanceId = getNextId();
|
||||
shadowRoot.appendChild(this.internalElement);
|
||||
|
||||
@@ -181,7 +181,7 @@ class BrowserWindowProxy {
|
||||
this.guestId = guestId;
|
||||
this._location = new LocationProxy(guestId);
|
||||
|
||||
ipcRendererInternal.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => {
|
||||
ipcRendererInternal.onceMessageFromMain(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => {
|
||||
removeProxy(guestId);
|
||||
this.closed = true;
|
||||
});
|
||||
@@ -281,7 +281,7 @@ export const windowSetup = (
|
||||
if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['prompt'], window.prompt);
|
||||
|
||||
if (!usesNativeWindowOpen || openerId != null) {
|
||||
ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
|
||||
ipcRendererInternal.onMessageFromMain('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
|
||||
_event, sourceId: number, message: any, sourceOrigin: string
|
||||
) {
|
||||
// Manually dispatch event instead of using postMessage because we also need to
|
||||
@@ -336,7 +336,7 @@ export const windowSetup = (
|
||||
let cachedVisibilityState = isHiddenPage ? 'hidden' : 'visible';
|
||||
|
||||
// Subscribe to visibilityState changes.
|
||||
ipcRendererInternal.on('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', function (_event, visibilityState: VisibilityState) {
|
||||
ipcRendererInternal.onMessageFromMain('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', function (_event, visibilityState: VisibilityState) {
|
||||
if (cachedVisibilityState !== visibilityState) {
|
||||
cachedVisibilityState = visibilityState;
|
||||
document.dispatchEvent(new Event('visibilitychange'));
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "11.0.0-beta.21",
|
||||
"version": "11.2.2",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
"@electron/docs-parser": "^0.10.0",
|
||||
"@electron/typescript-definitions": "^8.7.5",
|
||||
"@octokit/auth-app": "^2.10.0",
|
||||
"@octokit/rest": "^18.0.3",
|
||||
"@primer/octicons": "^10.0.0",
|
||||
"@types/basic-auth": "^1.1.3",
|
||||
|
||||
@@ -15,6 +15,7 @@ webview_cross_drag.patch
|
||||
gin_enable_disable_v8_platform.patch
|
||||
blink-worker-enable-csp-in-file-scheme.patch
|
||||
disable-redraw-lock.patch
|
||||
enable_reset_aspect_ratio.patch
|
||||
v8_context_snapshot_generator.patch
|
||||
boringssl_build_gn.patch
|
||||
pepper_plugin_support.patch
|
||||
@@ -100,5 +101,18 @@ worker_feat_add_hook_to_notify_script_ready.patch
|
||||
cherry-pick-2f5b8357dca2.patch
|
||||
fix_use_electron_generated_resources.patch
|
||||
chore_expose_v8_initialization_isolate_callbacks.patch
|
||||
rename_the_v8_context_snapshot_on_arm64_macos_builds.patch
|
||||
crashpad-initialize-logging.patch
|
||||
fix_properly_honor_printing_page_ranges.patch
|
||||
cherry-pick-8f5a08079948.patch
|
||||
cherry-pick-275865e8c237.patch
|
||||
use_public_apis_to_determine_if_a_font_is_a_system_font_in_mas_build.patch
|
||||
cherry-pick-47e21abe349a.patch
|
||||
fix_setparentacessibile_crash_win.patch
|
||||
add_restrictions_to_allowed_extensions_for_file_system_access_api.patch
|
||||
ensure_that_showsavefilepicker_always_shows_the_extension_on_mac.patch
|
||||
sanitize_descriptions_for_file_types.patch
|
||||
mediacapabilities_use_threadsafe_static_wtf_string.patch
|
||||
cherry-pick-06fe641d21bd.patch
|
||||
cherry-pick-3f7b67374a11.patch
|
||||
ots_backport_maxp_sanitization.patch
|
||||
ots_backport_of_glyf_guard_access_to_maxp_version_1_field.patch
|
||||
|
||||
@@ -23,10 +23,10 @@ index f63b17435218d0d67bba044da67c1c80015fc996..d0fe24182f2cb48a1333054ce44b6a7f
|
||||
int32_t world_id) {}
|
||||
virtual void DidClearWindowObject() {}
|
||||
diff --git a/content/renderer/render_frame_impl.cc b/content/renderer/render_frame_impl.cc
|
||||
index e811aec4853c1ba0dacdcad758a5bc834643d7e3..5eb2dcde4d2033015677681ceeba7da9b9fe32df 100644
|
||||
index 72a3bd4837f37bb1bab1f5ca48e90c41a297337f..23ff9cc13aaa97d395983dceb2a27158b2d679a5 100644
|
||||
--- a/content/renderer/render_frame_impl.cc
|
||||
+++ b/content/renderer/render_frame_impl.cc
|
||||
@@ -5034,6 +5034,12 @@ void RenderFrameImpl::DidCreateScriptContext(v8::Local<v8::Context> context,
|
||||
@@ -5036,6 +5036,12 @@ void RenderFrameImpl::DidCreateScriptContext(v8::Local<v8::Context> context,
|
||||
observer.DidCreateScriptContext(context, world_id);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,269 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Austin Sullivan <asully@chromium.org>
|
||||
Date: Mon, 7 Dec 2020 22:09:02 +0000
|
||||
Subject: Add restrictions to allowed extensions for File System Access API
|
||||
|
||||
These restrictions apply to showOpenFilePicker and showSaveFilePicker.
|
||||
|
||||
Existing restriction:
|
||||
- Extension must start with "."
|
||||
|
||||
New restrictions:
|
||||
- Allowed code points: [A-Za-z0-9+.]
|
||||
- Extension length cannot exceed to 16, inclusive of leading "."
|
||||
- Extension cannot end with "."
|
||||
- Extension cannot end with "local" or "lnk"
|
||||
|
||||
(cherry picked from commit c75c5a1e1d72fc923c82ebcaeacc874c88215eff)
|
||||
|
||||
Bug: 1137247, 1140403, 1140410, 1140417, 1140435, 1152327
|
||||
Change-Id: I593f7ca60e05177402885bd3026add16b3a07d0c
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2568534
|
||||
Reviewed-by: Marijn Kruisselbrink <mek@chromium.org>
|
||||
Commit-Queue: Austin Sullivan <asully@chromium.org>
|
||||
Cr-Original-Commit-Position: refs/heads/master@{#833695}
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2576109
|
||||
Commit-Queue: Marijn Kruisselbrink <mek@chromium.org>
|
||||
Auto-Submit: Austin Sullivan <asully@chromium.org>
|
||||
Cr-Commit-Position: refs/branch-heads/4324@{#649}
|
||||
Cr-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102}
|
||||
|
||||
diff --git a/content/browser/file_system_access/file_system_chooser.cc b/content/browser/file_system_access/file_system_chooser.cc
|
||||
index e16cc9bd3bebb0e9460807b5a17a2909dbe0a28c..0a8248badf55121430b4a38b65b570213c2ad3f5 100644
|
||||
--- a/content/browser/file_system_access/file_system_chooser.cc
|
||||
+++ b/content/browser/file_system_access/file_system_chooser.cc
|
||||
@@ -7,6 +7,7 @@
|
||||
#include "base/bind.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
+#include "base/i18n/file_util_icu.h"
|
||||
#include "base/metrics/histogram_functions.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/post_task.h"
|
||||
@@ -47,6 +48,51 @@ void RecordFileSelectionResult(blink::mojom::ChooseFileSystemEntryType type,
|
||||
"NativeFileSystemAPI.FileChooserResult." + TypeToString(type), count);
|
||||
}
|
||||
|
||||
+// Similar to base::FilePath::FinalExtension, but operates with the
|
||||
+// understanding that the StringType passed in is an extension, not a path.
|
||||
+// Returns the last extension without a leading ".".
|
||||
+base::FilePath::StringType GetLastExtension(
|
||||
+ const base::FilePath::StringType& extension) {
|
||||
+ auto last_separator = extension.rfind(base::FilePath::kExtensionSeparator);
|
||||
+ return (last_separator != base::FilePath::StringType::npos)
|
||||
+ ? extension.substr(last_separator + 1)
|
||||
+ : extension;
|
||||
+}
|
||||
+
|
||||
+// Returns whether the specified extension receives special handling by the
|
||||
+// Windows shell.
|
||||
+bool IsShellIntegratedExtension(const base::FilePath::StringType& extension) {
|
||||
+ // TODO(https://crbug.com/1154757): Figure out some way to unify this with
|
||||
+ // net::IsSafePortablePathComponent, with the result probably ending up in
|
||||
+ // base/i18n/file_util_icu.h.
|
||||
+ base::FilePath::StringType extension_lower = base::ToLowerASCII(extension);
|
||||
+
|
||||
+ // .lnk files may be used to execute arbitrary code (see
|
||||
+ // https://nvd.nist.gov/vuln/detail/CVE-2010-2568). .local files are used by
|
||||
+ // Windows to determine which DLLs to load for an application.
|
||||
+ if ((extension_lower == FILE_PATH_LITERAL("local")) ||
|
||||
+ (extension_lower == FILE_PATH_LITERAL("lnk")))
|
||||
+ return true;
|
||||
+
|
||||
+ // Setting a file's extension to a CLSID may conceal its actual file type on
|
||||
+ // some Windows versions (see https://nvd.nist.gov/vuln/detail/CVE-2004-0420).
|
||||
+ if (!extension_lower.empty() &&
|
||||
+ (extension_lower.front() == FILE_PATH_LITERAL('{')) &&
|
||||
+ (extension_lower.back() == FILE_PATH_LITERAL('}')))
|
||||
+ return true;
|
||||
+ return false;
|
||||
+}
|
||||
+
|
||||
+// Extension validation primarily takes place in the renderer. This checks for a
|
||||
+// subset of invalid extensions in the event the renderer is compromised.
|
||||
+bool IsInvalidExtension(base::FilePath::StringType& extension) {
|
||||
+ std::string component8 = base::FilePath(extension).AsUTF8Unsafe();
|
||||
+ auto extension16 = base::UTF8ToUTF16(component8.c_str());
|
||||
+
|
||||
+ return !base::i18n::IsFilenameLegal(extension16) ||
|
||||
+ IsShellIntegratedExtension(GetLastExtension(extension));
|
||||
+}
|
||||
+
|
||||
// Converts the accepted mime types and extensions from |option| into a list
|
||||
// of just extensions to be passed to the file dialog implementation.
|
||||
// The returned list will start with all the explicit website provided
|
||||
@@ -67,7 +113,8 @@ bool GetFileTypesFromAcceptsOption(
|
||||
#else
|
||||
extension = extension_string;
|
||||
#endif
|
||||
- if (extension_set.insert(extension).second) {
|
||||
+ if (extension_set.insert(extension).second &&
|
||||
+ !IsInvalidExtension(extension)) {
|
||||
extensions->push_back(std::move(extension));
|
||||
}
|
||||
}
|
||||
@@ -76,7 +123,8 @@ bool GetFileTypesFromAcceptsOption(
|
||||
base::FilePath::StringType preferred_extension;
|
||||
if (net::GetPreferredExtensionForMimeType(mime_type,
|
||||
&preferred_extension)) {
|
||||
- if (extension_set.insert(preferred_extension).second) {
|
||||
+ if (extension_set.insert(preferred_extension).second &&
|
||||
+ !IsInvalidExtension(preferred_extension)) {
|
||||
extensions->push_back(std::move(preferred_extension));
|
||||
}
|
||||
}
|
||||
@@ -86,7 +134,8 @@ bool GetFileTypesFromAcceptsOption(
|
||||
if (inner.empty())
|
||||
continue;
|
||||
for (auto& extension : inner) {
|
||||
- if (extension_set.insert(extension).second) {
|
||||
+ if (extension_set.insert(extension).second &&
|
||||
+ !IsInvalidExtension(extension)) {
|
||||
extensions->push_back(std::move(extension));
|
||||
}
|
||||
}
|
||||
diff --git a/content/browser/file_system_access/file_system_chooser_unittest.cc b/content/browser/file_system_access/file_system_chooser_unittest.cc
|
||||
index 1f282f037ddc8966c26e5d5d7e9b9eb9fc581861..292671a22e2b5726521fe065dca1d56b8982406d 100644
|
||||
--- a/content/browser/file_system_access/file_system_chooser_unittest.cc
|
||||
+++ b/content/browser/file_system_access/file_system_chooser_unittest.cc
|
||||
@@ -180,6 +180,32 @@ TEST_F(FileSystemChooserTest, AcceptsExtensionsAndMimeTypes) {
|
||||
dialog_params.file_types->extension_description_overrides[0]);
|
||||
}
|
||||
|
||||
+TEST_F(FileSystemChooserTest, IgnoreShellIntegratedExtensions) {
|
||||
+ SelectFileDialogParams dialog_params;
|
||||
+ ui::SelectFileDialog::SetFactory(
|
||||
+ new CancellingSelectFileDialogFactory(&dialog_params));
|
||||
+ std::vector<blink::mojom::ChooseFileSystemEntryAcceptsOptionPtr> accepts;
|
||||
+ accepts.emplace_back(blink::mojom::ChooseFileSystemEntryAcceptsOption::New(
|
||||
+ base::ASCIIToUTF16(""), std::vector<std::string>({}),
|
||||
+ std::vector<std::string>(
|
||||
+ {"lnk", "foo.lnk", "foo.bar.local", "text", "local"})));
|
||||
+ SyncShowDialog(std::move(accepts), /*include_accepts_all=*/false);
|
||||
+
|
||||
+ ASSERT_TRUE(dialog_params.file_types);
|
||||
+ EXPECT_FALSE(dialog_params.file_types->include_all_files);
|
||||
+ ASSERT_EQ(1u, dialog_params.file_types->extensions.size());
|
||||
+ EXPECT_EQ(1, dialog_params.file_type_index);
|
||||
+
|
||||
+ ASSERT_EQ(1u, dialog_params.file_types->extensions[0].size());
|
||||
+ EXPECT_EQ(dialog_params.file_types->extensions[0][0],
|
||||
+ FILE_PATH_LITERAL("text"));
|
||||
+
|
||||
+ ASSERT_EQ(1u,
|
||||
+ dialog_params.file_types->extension_description_overrides.size());
|
||||
+ EXPECT_EQ(base::ASCIIToUTF16(""),
|
||||
+ dialog_params.file_types->extension_description_overrides[0]);
|
||||
+}
|
||||
+
|
||||
TEST_F(FileSystemChooserTest, LocalPath) {
|
||||
const base::FilePath local_path(FILE_PATH_LITERAL("/foo/bar"));
|
||||
ui::SelectedFileInfo selected_file(local_path, local_path);
|
||||
diff --git a/third_party/blink/renderer/modules/file_system_access/global_native_file_system.cc b/third_party/blink/renderer/modules/file_system_access/global_native_file_system.cc
|
||||
index 0622d1d59317754fb0696ab4918740a434e6eaa6..10f759dca7775c189bf692f1743b196a1463d077 100644
|
||||
--- a/third_party/blink/renderer/modules/file_system_access/global_native_file_system.cc
|
||||
+++ b/third_party/blink/renderer/modules/file_system_access/global_native_file_system.cc
|
||||
@@ -29,6 +29,7 @@
|
||||
#include "third_party/blink/renderer/platform/network/http_parsers.h"
|
||||
#include "third_party/blink/renderer/platform/weborigin/security_origin.h"
|
||||
#include "third_party/blink/renderer/platform/wtf/functional.h"
|
||||
+#include "third_party/blink/renderer/platform/wtf/text/ascii_ctype.h"
|
||||
|
||||
namespace blink {
|
||||
|
||||
@@ -38,14 +39,42 @@ constexpr bool IsHTTPWhitespace(UChar chr) {
|
||||
return chr == ' ' || chr == '\n' || chr == '\t' || chr == '\r';
|
||||
}
|
||||
|
||||
-bool AddExtension(const String& extension,
|
||||
- Vector<String>& extensions,
|
||||
- ExceptionState& exception_state) {
|
||||
+bool IsValidSuffixCodePoint(UChar chr) {
|
||||
+ return IsASCIIAlphanumeric(chr) || chr == '+' || chr == '.';
|
||||
+}
|
||||
+
|
||||
+bool VerifyIsValidExtension(const String& extension,
|
||||
+ ExceptionState& exception_state) {
|
||||
if (!extension.StartsWith(".")) {
|
||||
exception_state.ThrowTypeError("Extension '" + extension +
|
||||
"' must start with '.'.");
|
||||
return false;
|
||||
}
|
||||
+ if (!extension.IsAllSpecialCharacters<IsValidSuffixCodePoint>()) {
|
||||
+ exception_state.ThrowTypeError("Extension '" + extension +
|
||||
+ "' contains invalid characters.");
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (extension.EndsWith(".")) {
|
||||
+ exception_state.ThrowTypeError("Extension '" + extension +
|
||||
+ "' must not end with '.'.");
|
||||
+ return false;
|
||||
+ }
|
||||
+ if (extension.length() > 16) {
|
||||
+ exception_state.ThrowTypeError("Extension '" + extension +
|
||||
+ "' cannot be longer than 16 characters.");
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+}
|
||||
+
|
||||
+bool AddExtension(const String& extension,
|
||||
+ Vector<String>& extensions,
|
||||
+ ExceptionState& exception_state) {
|
||||
+ if (!VerifyIsValidExtension(extension, exception_state))
|
||||
+ return false;
|
||||
+
|
||||
extensions.push_back(extension.Substring(1));
|
||||
return true;
|
||||
}
|
||||
diff --git a/third_party/blink/web_tests/external/wpt/native-file-system/showPicker-errors.https.window.js b/third_party/blink/web_tests/external/wpt/native-file-system/showPicker-errors.https.window.js
|
||||
index e8f0d3f540485120cd15d642b1b0d33110797098..d1dabf37da8305094bf7e0bd0fea4e0200d8dd2e 100644
|
||||
--- a/third_party/blink/web_tests/external/wpt/native-file-system/showPicker-errors.https.window.js
|
||||
+++ b/third_party/blink/web_tests/external/wpt/native-file-system/showPicker-errors.https.window.js
|
||||
@@ -80,9 +80,39 @@ function define_file_picker_error_tests(showPickerMethod) {
|
||||
showPickerMethod +
|
||||
': MIME type can\'t have invalid characters in subtype.');
|
||||
|
||||
- promise_test(async t => {
|
||||
- await promise_rejects_js(t, TypeError, self[showPickerMethod]({
|
||||
- types: [{accept: {'text/plain': ['.txt', 'txt']}}]
|
||||
- }));
|
||||
- }, showPickerMethod + ': extension has to start with ".".');
|
||||
+ const invalid_extensions = {
|
||||
+ '.extensiontoolong': 'extension length more than 16.',
|
||||
+ '.txt.': 'extenstion ends with "."',
|
||||
+ 'txt': 'extenstion does not start with "."',
|
||||
+ '.$txt' : 'illegal character "$"',
|
||||
+ '.t<xt': 'illegal character "<"',
|
||||
+ '.t/xt': 'illegal character "\"',
|
||||
+ '.\txt': 'illegal character "/"',
|
||||
+ '.txt\\': 'illegal characters "\\"',
|
||||
+ '.txt?': 'illegal character "?"',
|
||||
+ '.txt*': 'illegal character "*"',
|
||||
+ '.{txt': 'illegal character "{"',
|
||||
+ '.}txt': 'illegal character "}"',
|
||||
+ ' .txt': 'illegal whitespace at front of extension',
|
||||
+ '. txt': 'illegal whitespace in extension',
|
||||
+ '.txt ': 'illegal whitespace at end of extension',
|
||||
+ '.\u202etxt\u202e' : 'illegal RTL character',
|
||||
+ '.t\u00E6xt': 'non-ASCII character "æ"',
|
||||
+ '.קום': 'non-ASCII character "קום"',
|
||||
+ '.txt🙂': 'non-ASCII character "🙂"',
|
||||
+ '.{txt}': 'illegal characters "{" and "}"',
|
||||
+ }
|
||||
+
|
||||
+ for (const [extension, description] of Object.entries(invalid_extensions)) {
|
||||
+ define_file_picker_extension_error_test(showPickerMethod, extension, description)
|
||||
+ }
|
||||
}
|
||||
+
|
||||
+function define_file_picker_extension_error_test(showPickerMethod, extension, description) {
|
||||
+ promise_test(async t => {
|
||||
+ await promise_rejects_js(
|
||||
+ t, TypeError,
|
||||
+ self[showPickerMethod](
|
||||
+ { types: [{ accept: { 'text/plain': ['.txt', extension] } }] }));
|
||||
+ }, showPickerMethod + ': invalid extension "' + extension + '". ' + description + ".");
|
||||
+}
|
||||
\ No newline at end of file
|
||||
@@ -9,10 +9,10 @@ potentially prevent a window from being created.
|
||||
TODO(loc): this patch is currently broken.
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
index 497033d3d5257daffe4aca2ebdd1fd71513db73f..7412bf8f9a9e07975917514e7f4c13253112138a 100644
|
||||
index 8aa3723aa317ccd1591196a4929582b61c0d3b5f..b820655013e58a55698450f703dd7230f1d1d250 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -4945,6 +4945,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
@@ -4959,6 +4959,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,
|
||||
@@ -21,10 +21,10 @@ index 497033d3d5257daffe4aca2ebdd1fd71513db73f..7412bf8f9a9e07975917514e7f4c1325
|
||||
&no_javascript_access);
|
||||
|
||||
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
|
||||
index a39f43b61afc70296470a9ed616d7e8a423780aa..b113c265dcf5b8e3c7533085f1b8409fa13bcfa0 100644
|
||||
index 26bd3968521cc2713d6350a0fa9265124c7efb00..5234e86d8f10533c4f8c6992a328d521c1850cea 100644
|
||||
--- a/content/browser/web_contents/web_contents_impl.cc
|
||||
+++ b/content/browser/web_contents/web_contents_impl.cc
|
||||
@@ -3703,9 +3703,9 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
|
||||
@@ -3712,9 +3712,9 @@ RenderFrameHostDelegate* WebContentsImpl::CreateNewWindow(
|
||||
}
|
||||
|
||||
if (delegate_) {
|
||||
|
||||
188
patches/chromium/cherry-pick-06fe641d21bd.patch
Normal file
188
patches/chromium/cherry-pick-06fe641d21bd.patch
Normal file
@@ -0,0 +1,188 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Ken Rockot <rockot@google.com>
|
||||
Date: Mon, 11 Jan 2021 21:36:13 +0000
|
||||
Subject: Mojo: Fix UAF on NodeChannel
|
||||
|
||||
TBR=rockot@google.com
|
||||
|
||||
(cherry picked from commit 9c8a98b3983dd1c7828ceae2fc8a5a2e9bad1f68)
|
||||
|
||||
Fixed: 1162198
|
||||
Change-Id: Ief850903a7e6ba3d7c5c0129704d1f80aa3467ce
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2612085
|
||||
Commit-Queue: Ken Rockot <rockot@google.com>
|
||||
Reviewed-by: Ken Rockot <rockot@google.com>
|
||||
Reviewed-by: Robert Sesek <rsesek@chromium.org>
|
||||
Cr-Original-Commit-Position: refs/heads/master@{#840942}
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2621996
|
||||
Cr-Commit-Position: refs/branch-heads/4324@{#1637}
|
||||
Cr-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102}
|
||||
|
||||
diff --git a/mojo/core/BUILD.gn b/mojo/core/BUILD.gn
|
||||
index db6af19ea9f35749ca52f0c230425eb783b71265..5c5df364b263dcd89610884d82bfe9d6eb54873b 100644
|
||||
--- a/mojo/core/BUILD.gn
|
||||
+++ b/mojo/core/BUILD.gn
|
||||
@@ -316,6 +316,7 @@ source_set("test_sources") {
|
||||
"data_pipe_unittest.cc",
|
||||
"invitation_unittest.cc",
|
||||
"multiprocess_message_pipe_unittest.cc",
|
||||
+ "node_controller_unittest.cc",
|
||||
"platform_wrapper_unittest.cc",
|
||||
]
|
||||
}
|
||||
diff --git a/mojo/core/core.cc b/mojo/core/core.cc
|
||||
index 9f344cfab31ed3f584c809e4854d57b3ebabb8ab..61444a3dec1548cd22e7bc510568e46a44c1924f 100644
|
||||
--- a/mojo/core/core.cc
|
||||
+++ b/mojo/core/core.cc
|
||||
@@ -137,7 +137,7 @@ void Core::SetIOTaskRunner(
|
||||
NodeController* Core::GetNodeController() {
|
||||
base::AutoLock lock(node_controller_lock_);
|
||||
if (!node_controller_)
|
||||
- node_controller_.reset(new NodeController(this));
|
||||
+ node_controller_ = std::make_unique<NodeController>();
|
||||
return node_controller_.get();
|
||||
}
|
||||
|
||||
diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc
|
||||
index 029bd350b08bdd9b94fa340112db10d50a2f68f9..823a4619efa0886401c0a46283a5782992b0c8a0 100644
|
||||
--- a/mojo/core/node_controller.cc
|
||||
+++ b/mojo/core/node_controller.cc
|
||||
@@ -21,7 +21,6 @@
|
||||
#include "mojo/core/broker.h"
|
||||
#include "mojo/core/broker_host.h"
|
||||
#include "mojo/core/configuration.h"
|
||||
-#include "mojo/core/core.h"
|
||||
#include "mojo/core/request_context.h"
|
||||
#include "mojo/core/user_message_impl.h"
|
||||
#include "mojo/public/cpp/platform/named_platform_channel.h"
|
||||
@@ -146,10 +145,8 @@ class ThreadDestructionObserver
|
||||
|
||||
NodeController::~NodeController() = default;
|
||||
|
||||
-NodeController::NodeController(Core* core)
|
||||
- : core_(core),
|
||||
- name_(GetRandomNodeName()),
|
||||
- node_(new ports::Node(name_, this)) {
|
||||
+NodeController::NodeController()
|
||||
+ : name_(GetRandomNodeName()), node_(new ports::Node(name_, this)) {
|
||||
DVLOG(1) << "Initializing node " << name_;
|
||||
}
|
||||
|
||||
@@ -588,10 +585,17 @@ void NodeController::AddPeer(const ports::NodeName& name,
|
||||
}
|
||||
}
|
||||
|
||||
-void NodeController::DropPeer(const ports::NodeName& name,
|
||||
+void NodeController::DropPeer(const ports::NodeName& node_name,
|
||||
NodeChannel* channel) {
|
||||
DCHECK(io_task_runner_->RunsTasksInCurrentSequence());
|
||||
|
||||
+ // NOTE: Either the `peers_` erasure or the `pending_invitations_` erasure
|
||||
+ // below, if executed, may drop the last reference to the named NodeChannel
|
||||
+ // and thus result in its deletion. The passed `node_name` argument may be
|
||||
+ // owned by that same NodeChannel, so we make a copy of it here to avoid
|
||||
+ // potentially unsafe references further below.
|
||||
+ ports::NodeName name = node_name;
|
||||
+
|
||||
{
|
||||
base::AutoLock lock(peers_lock_);
|
||||
auto it = peers_.find(name);
|
||||
diff --git a/mojo/core/node_controller.h b/mojo/core/node_controller.h
|
||||
index f35237f4eb5176a98cd37375cb53524f1e811f73..b0ddd290973f191b0852b78b01c068a1aa455bfe 100644
|
||||
--- a/mojo/core/node_controller.h
|
||||
+++ b/mojo/core/node_controller.h
|
||||
@@ -52,11 +52,10 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate,
|
||||
};
|
||||
|
||||
// |core| owns and out-lives us.
|
||||
- explicit NodeController(Core* core);
|
||||
+ NodeController();
|
||||
~NodeController() override;
|
||||
|
||||
const ports::NodeName& name() const { return name_; }
|
||||
- Core* core() const { return core_; }
|
||||
ports::Node* node() const { return node_.get(); }
|
||||
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner() const {
|
||||
return io_task_runner_;
|
||||
@@ -135,6 +134,8 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate,
|
||||
base::span<const unsigned char> data);
|
||||
static void DeserializeMessageAsEventForFuzzer(Channel::MessagePtr message);
|
||||
|
||||
+ scoped_refptr<NodeChannel> GetBrokerChannel();
|
||||
+
|
||||
private:
|
||||
friend Core;
|
||||
|
||||
@@ -176,7 +177,6 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate,
|
||||
|
||||
scoped_refptr<NodeChannel> GetPeerChannel(const ports::NodeName& name);
|
||||
scoped_refptr<NodeChannel> GetInviterChannel();
|
||||
- scoped_refptr<NodeChannel> GetBrokerChannel();
|
||||
|
||||
void AddPeer(const ports::NodeName& name,
|
||||
scoped_refptr<NodeChannel> channel,
|
||||
@@ -254,7 +254,6 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate,
|
||||
void ForceDisconnectProcessForTestingOnIOThread(base::ProcessId process_id);
|
||||
|
||||
// These are safe to access from any thread as long as the Node is alive.
|
||||
- Core* const core_;
|
||||
const ports::NodeName name_;
|
||||
const std::unique_ptr<ports::Node> node_;
|
||||
scoped_refptr<base::SingleThreadTaskRunner> io_task_runner_;
|
||||
@@ -319,7 +318,7 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate,
|
||||
AtomicFlag shutdown_callback_flag_;
|
||||
|
||||
// All other fields below must only be accessed on the I/O thread, i.e., the
|
||||
- // thread on which core_->io_task_runner() runs tasks.
|
||||
+ // thread on which `io_task_runner_` runs tasks.
|
||||
|
||||
// Channels to invitees during handshake.
|
||||
NodeMap pending_invitations_;
|
||||
diff --git a/mojo/core/node_controller_unittest.cc b/mojo/core/node_controller_unittest.cc
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..316e162376323f27f83868d6b943c40e395511bb
|
||||
--- /dev/null
|
||||
+++ b/mojo/core/node_controller_unittest.cc
|
||||
@@ -0,0 +1,42 @@
|
||||
+// Copyright 2013 The Chromium Authors. All rights reserved.
|
||||
+// Use of this source code is governed by a BSD-style license that can be
|
||||
+// found in the LICENSE file.
|
||||
+
|
||||
+#include "base/logging.h"
|
||||
+#include "mojo/core/core.h"
|
||||
+#include "mojo/core/test/mojo_test_base.h"
|
||||
+#include "mojo/public/c/system/types.h"
|
||||
+#include "testing/gtest/include/gtest/gtest.h"
|
||||
+
|
||||
+namespace mojo {
|
||||
+namespace core {
|
||||
+namespace {
|
||||
+
|
||||
+using NodeControllerTest = test::MojoTestBase;
|
||||
+
|
||||
+TEST_F(NodeControllerTest, AcceptInvitationFailure) {
|
||||
+ // Spawn a child process that will send an invalid AcceptInvitation
|
||||
+ // NodeChannel message. This is a regression test for
|
||||
+ // https://crbug.com/1162198.
|
||||
+ RunTestClient("SendInvalidAcceptInvitation",
|
||||
+ [&](MojoHandle h) { WriteMessage(h, "hi"); });
|
||||
+}
|
||||
+
|
||||
+DEFINE_TEST_CLIENT_TEST_WITH_PIPE(SendInvalidAcceptInvitation,
|
||||
+ NodeControllerTest,
|
||||
+ h) {
|
||||
+ // A little communication to synchronize against Mojo bringup. By the time
|
||||
+ // this read completes, we must have an internal NodeController with the
|
||||
+ // parent test process connected as its broker.
|
||||
+ EXPECT_EQ("hi", ReadMessage(h));
|
||||
+
|
||||
+ // Send an unexpected AcceptInvitation message to the parent process. This
|
||||
+ // exercises the regression code path in the parent process.
|
||||
+ NodeController* controller = Core::Get()->GetNodeController();
|
||||
+ scoped_refptr<NodeChannel> channel = controller->GetBrokerChannel();
|
||||
+ channel->AcceptInvitation(ports::NodeName{0, 0}, ports::NodeName{0, 0});
|
||||
+}
|
||||
+
|
||||
+} // namespace
|
||||
+} // namespace core
|
||||
+} // namespace mojo
|
||||
413
patches/chromium/cherry-pick-275865e8c237.patch
Normal file
413
patches/chromium/cherry-pick-275865e8c237.patch
Normal file
@@ -0,0 +1,413 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Robinson <mrobinson@igalia.com>
|
||||
Date: Tue, 20 Oct 2020 09:00:52 +0000
|
||||
Subject: Mac: Target a11y actions on certain containers to focused or
|
||||
activedescendant children
|
||||
|
||||
When an action is triggered on a container with selectable children and
|
||||
that container has a focused or activedescendant child, retarget the
|
||||
action to that child. This fixes an issue where when the VoiceOver
|
||||
cursor is on a tree with a focused child, triggered context menus would
|
||||
use the tree as the target element.
|
||||
|
||||
menu on a tree instead of a focused child.
|
||||
|
||||
Bug: 1114892
|
||||
Change-Id: Ibe580beed93aeaac54d1dabaee28443b290fd0e2
|
||||
AX-Relnotes: Fix an issue where VoiceOver would trigger a context
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2466281
|
||||
Commit-Queue: Martin Robinson <mrobinson@igalia.com>
|
||||
Reviewed-by: Scott Violet <sky@chromium.org>
|
||||
Reviewed-by: Dominic Mazzoni <dmazzoni@chromium.org>
|
||||
Reviewed-by: Nektarios Paisios <nektar@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/master@{#818831}
|
||||
|
||||
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.h b/content/browser/accessibility/browser_accessibility_cocoa.h
|
||||
index 770c0cb6a4dad8cbf35900311a23a8296407d81d..0ffc8d768299cc5e7f37878fb656c12b028fead9 100644
|
||||
--- a/content/browser/accessibility/browser_accessibility_cocoa.h
|
||||
+++ b/content/browser/accessibility/browser_accessibility_cocoa.h
|
||||
@@ -117,6 +117,14 @@ id AXTextMarkerRangeFrom(id anchor_textmarker, id focus_textmarker);
|
||||
- (bool)findRowIndex:(BrowserAccessibilityCocoa*)toFind
|
||||
withCurrentIndex:(int*)currentIndex;
|
||||
|
||||
+// Choose the appropriate accessibility object to receive an action depending
|
||||
+// on the characteristics of this accessibility node.
|
||||
+- (content::BrowserAccessibility*)actionTarget;
|
||||
+
|
||||
+// Return the active descendant for this accessibility object or null if there
|
||||
+// is no active descendant defined or in the case of an error.
|
||||
+- (content::BrowserAccessibility*)activeDescendant;
|
||||
+
|
||||
// Internally-used property.
|
||||
@property(nonatomic, readonly) NSPoint origin;
|
||||
|
||||
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
|
||||
index 17aa494d6473866721a8cc5f4c5c646af9307d93..71f927cd35a27dd5f656907dc177fbfe7577d3f0 100644
|
||||
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
|
||||
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
|
||||
@@ -1744,14 +1744,7 @@ - (id)owns {
|
||||
// supported with VoiceOver.
|
||||
//
|
||||
|
||||
- int activeDescendantId;
|
||||
- if (!_owner->GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId,
|
||||
- &activeDescendantId))
|
||||
- return nil;
|
||||
-
|
||||
- BrowserAccessibilityManager* manager = _owner->manager();
|
||||
- BrowserAccessibility* activeDescendant =
|
||||
- manager->GetFromID(activeDescendantId);
|
||||
+ BrowserAccessibility* activeDescendant = [self activeDescendant];
|
||||
if (!activeDescendant)
|
||||
return nil;
|
||||
|
||||
@@ -3729,16 +3722,18 @@ - (void)accessibilityPerformAction:(NSString*)action {
|
||||
return;
|
||||
|
||||
// TODO(dmazzoni): Support more actions.
|
||||
- BrowserAccessibilityManager* manager = _owner->manager();
|
||||
+ BrowserAccessibility* actionTarget = [self actionTarget];
|
||||
+ BrowserAccessibilityManager* manager = actionTarget->manager();
|
||||
if ([action isEqualToString:NSAccessibilityPressAction]) {
|
||||
- manager->DoDefaultAction(*_owner);
|
||||
- if (_owner->GetData().GetRestriction() != ax::mojom::Restriction::kNone ||
|
||||
+ manager->DoDefaultAction(*actionTarget);
|
||||
+ if (actionTarget->GetData().GetRestriction() !=
|
||||
+ ax::mojom::Restriction::kNone ||
|
||||
![self isCheckable])
|
||||
return;
|
||||
// Hack: preemptively set the checked state to what it should become,
|
||||
// otherwise VoiceOver will very likely report the old, incorrect state to
|
||||
// the user as it requests the value too quickly.
|
||||
- ui::AXNode* node = _owner->node();
|
||||
+ ui::AXNode* node = actionTarget->node();
|
||||
if (!node)
|
||||
return;
|
||||
AXNodeData data(node->TakeData()); // Temporarily take data.
|
||||
@@ -3756,13 +3751,13 @@ - (void)accessibilityPerformAction:(NSString*)action {
|
||||
}
|
||||
node->SetData(data); // Set the data back in the node.
|
||||
} else if ([action isEqualToString:NSAccessibilityShowMenuAction]) {
|
||||
- manager->ShowContextMenu(*_owner);
|
||||
+ manager->ShowContextMenu(*actionTarget);
|
||||
} else if ([action isEqualToString:NSAccessibilityScrollToVisibleAction]) {
|
||||
- manager->ScrollToMakeVisible(*_owner, gfx::Rect());
|
||||
+ manager->ScrollToMakeVisible(*actionTarget, gfx::Rect());
|
||||
} else if ([action isEqualToString:NSAccessibilityIncrementAction]) {
|
||||
- manager->Increment(*_owner);
|
||||
+ manager->Increment(*actionTarget);
|
||||
} else if ([action isEqualToString:NSAccessibilityDecrementAction]) {
|
||||
- manager->Decrement(*_owner);
|
||||
+ manager->Decrement(*actionTarget);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3871,4 +3866,32 @@ - (BOOL)accessibilityNotifiesWhenDestroyed {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+// Choose the appropriate accessibility object to receive an action depending
|
||||
+// on the characteristics of this accessibility node.
|
||||
+- (BrowserAccessibility*)actionTarget {
|
||||
+ // When an action is triggered on a container with selectable children and
|
||||
+ // one of those children is an active descendant or focused, retarget the
|
||||
+ // action to that child. See https://crbug.com/1114892.
|
||||
+ if (!ui::IsContainerWithSelectableChildren(_owner->node()->data().role))
|
||||
+ return _owner;
|
||||
+
|
||||
+ if (BrowserAccessibility* activeDescendant = [self activeDescendant])
|
||||
+ return activeDescendant;
|
||||
+
|
||||
+ BrowserAccessibility* focused = _owner->manager()->GetFocus();
|
||||
+ if (focused && focused->IsDescendantOf(_owner))
|
||||
+ return focused;
|
||||
+
|
||||
+ return _owner;
|
||||
+}
|
||||
+
|
||||
+// Return the active descendant for this accessibility object or null if there
|
||||
+// is no active descendant defined or in the case of an error.
|
||||
+- (BrowserAccessibility*)activeDescendant {
|
||||
+ int activeDescendantId;
|
||||
+ if (!_owner->GetIntAttribute(ax::mojom::IntAttribute::kActivedescendantId,
|
||||
+ &activeDescendantId))
|
||||
+ return nullptr;
|
||||
+ return _owner->manager()->GetFromID(activeDescendantId);
|
||||
+}
|
||||
@end
|
||||
diff --git a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
|
||||
index 17801b9774813df79e4b944cb0a73535eb4c07c2..6eb2c484ddc479d9cfa566df0b103782264d3af8 100644
|
||||
--- a/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
|
||||
+++ b/content/browser/accessibility/browser_accessibility_cocoa_browsertest.mm
|
||||
@@ -44,6 +44,35 @@
|
||||
return web_contents->GetRootBrowserAccessibilityManager();
|
||||
}
|
||||
|
||||
+ // Trigger a context menu for the provided element without showing it. Returns
|
||||
+ // the coordinates where the context menu was invoked (calculated based on
|
||||
+ // the provided element). These coordinates are relative to the RenderView
|
||||
+ // origin.
|
||||
+ gfx::Point TriggerContextMenuAndGetMenuLocation(
|
||||
+ NSAccessibilityElement* element,
|
||||
+ ContextMenuFilter* filter) {
|
||||
+ // accessibilityPerformAction is deprecated, but it's still used internally
|
||||
+ // by AppKit.
|
||||
+#pragma clang diagnostic push
|
||||
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
+ [element accessibilityPerformAction:NSAccessibilityShowMenuAction];
|
||||
+ filter->Wait();
|
||||
+
|
||||
+ UntrustworthyContextMenuParams context_menu_params = filter->get_params();
|
||||
+ return gfx::Point(context_menu_params.x, context_menu_params.y);
|
||||
+#pragma clang diagnostic pop
|
||||
+ }
|
||||
+
|
||||
+ void FocusAccessibilityElementAndWaitForFocusChange(
|
||||
+ NSAccessibilityElement* element) {
|
||||
+#pragma clang diagnostic push
|
||||
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
+ [element accessibilitySetValue:@(1)
|
||||
+ forAttribute:NSAccessibilityFocusedAttribute];
|
||||
+#pragma clang diagnostic pop
|
||||
+ WaitForAccessibilityFocusChange();
|
||||
+ }
|
||||
+
|
||||
private:
|
||||
BrowserAccessibility* FindNodeInSubtree(BrowserAccessibility& node,
|
||||
ax::mojom::Role role) {
|
||||
@@ -466,4 +495,165 @@ GURL url(R"HTML(data:text/html,
|
||||
EXPECT_NSEQ(@"AXRow", [row_nodes[1] role]);
|
||||
EXPECT_NSEQ(@"row2", [row_nodes[1] descriptionForAccessibility]);
|
||||
}
|
||||
+
|
||||
+IN_PROC_BROWSER_TEST_F(BrowserAccessibilityCocoaBrowserTest,
|
||||
+ TestTreeContextMenuEvent) {
|
||||
+ AccessibilityNotificationWaiter waiter(shell()->web_contents(),
|
||||
+ ui::kAXModeComplete,
|
||||
+ ax::mojom::Event::kLoadComplete);
|
||||
+
|
||||
+ GURL url(R"HTML(data:text/html,
|
||||
+ <div alt="tree" role="tree">
|
||||
+ <div tabindex="1" role="treeitem">1</div>
|
||||
+ <div tabindex="2" role="treeitem">2</div>
|
||||
+ </div>)HTML");
|
||||
+
|
||||
+ EXPECT_TRUE(NavigateToURL(shell(), url));
|
||||
+ waiter.WaitForNotification();
|
||||
+
|
||||
+ BrowserAccessibility* tree = FindNode(ax::mojom::Role::kTree);
|
||||
+ base::scoped_nsobject<BrowserAccessibilityCocoa> cocoa_tree(
|
||||
+ [ToBrowserAccessibilityCocoa(tree) retain]);
|
||||
+
|
||||
+ NSArray* tree_children = [cocoa_tree children];
|
||||
+ EXPECT_NSEQ(@"AXRow", [tree_children[0] role]);
|
||||
+ EXPECT_NSEQ(@"AXRow", [tree_children[1] role]);
|
||||
+
|
||||
+ content::RenderProcessHost* render_process_host =
|
||||
+ shell()->web_contents()->GetMainFrame()->GetProcess();
|
||||
+ auto menu_filter = base::MakeRefCounted<ContextMenuFilter>(
|
||||
+ ContextMenuFilter::ShowBehavior::kPreventShow);
|
||||
+ render_process_host->AddFilter(menu_filter.get());
|
||||
+
|
||||
+ gfx::Point tree_point =
|
||||
+ TriggerContextMenuAndGetMenuLocation(cocoa_tree, menu_filter.get());
|
||||
+
|
||||
+ menu_filter->Reset();
|
||||
+ gfx::Point item_1_point =
|
||||
+ TriggerContextMenuAndGetMenuLocation(tree_children[1], menu_filter.get());
|
||||
+ ASSERT_NE(tree_point, item_1_point);
|
||||
+
|
||||
+ // Now focus the second child and trigger a context menu on the tree.
|
||||
+ EXPECT_TRUE(
|
||||
+ content::ExecuteScript(shell()->web_contents(),
|
||||
+ "document.body.children[0].children[1].focus();"));
|
||||
+ WaitForAccessibilityFocusChange();
|
||||
+
|
||||
+ // Triggering a context menu on the tree should now trigger the menu
|
||||
+ // on the focused child.
|
||||
+ menu_filter->Reset();
|
||||
+ gfx::Point new_point =
|
||||
+ TriggerContextMenuAndGetMenuLocation(cocoa_tree, menu_filter.get());
|
||||
+ ASSERT_EQ(new_point, item_1_point);
|
||||
+}
|
||||
+
|
||||
+IN_PROC_BROWSER_TEST_F(BrowserAccessibilityCocoaBrowserTest,
|
||||
+ TestEventRetargetingFocus) {
|
||||
+ AccessibilityNotificationWaiter waiter(shell()->web_contents(),
|
||||
+ ui::kAXModeComplete,
|
||||
+ ax::mojom::Event::kLoadComplete);
|
||||
+
|
||||
+ GURL url(R"HTML(data:text/html,
|
||||
+ <div role="tree">
|
||||
+ <div tabindex="1" role="treeitem">1</div>
|
||||
+ <div tabindex="2" role="treeitem">2</div>
|
||||
+ </div>
|
||||
+ <div role="treegrid">
|
||||
+ <div tabindex="1" role="treeitem">1</div>
|
||||
+ <div tabindex="2" role="treeitem">2</div>
|
||||
+ </div>
|
||||
+ <div role="tablist">
|
||||
+ <div tabindex="1" role="tab">1</div>
|
||||
+ <div tabindex="2" role="tab">2</div>
|
||||
+ </div>
|
||||
+ <div role="table">
|
||||
+ <div tabindex="1" role="row">1</div>
|
||||
+ <div tabindex="2" role="row">2</div>
|
||||
+ </div>
|
||||
+ <div role="banner">
|
||||
+ <div tabindex="1" role="link">1</div>
|
||||
+ <div tabindex="2" role="link">2</div>
|
||||
+ </div>)HTML");
|
||||
+
|
||||
+ EXPECT_TRUE(NavigateToURL(shell(), url));
|
||||
+ waiter.WaitForNotification();
|
||||
+
|
||||
+ std::pair<ax::mojom::Role, bool> tests[] = {
|
||||
+ std::make_pair(ax::mojom::Role::kTree, true),
|
||||
+ std::make_pair(ax::mojom::Role::kTreeGrid, true),
|
||||
+ std::make_pair(ax::mojom::Role::kTabList, true),
|
||||
+ std::make_pair(ax::mojom::Role::kTable, false),
|
||||
+ std::make_pair(ax::mojom::Role::kBanner, false),
|
||||
+ };
|
||||
+
|
||||
+ for (auto& test : tests) {
|
||||
+ base::scoped_nsobject<BrowserAccessibilityCocoa> parent(
|
||||
+ [ToBrowserAccessibilityCocoa(FindNode(test.first)) retain]);
|
||||
+ BrowserAccessibilityCocoa* child = [parent children][1];
|
||||
+
|
||||
+ EXPECT_NE(nullptr, parent.get());
|
||||
+ EXPECT_EQ([child owner], [child actionTarget]);
|
||||
+ EXPECT_EQ([parent owner], [parent actionTarget]);
|
||||
+
|
||||
+ FocusAccessibilityElementAndWaitForFocusChange(child);
|
||||
+ ASSERT_EQ(test.second, [parent actionTarget] == [child owner]);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
+IN_PROC_BROWSER_TEST_F(BrowserAccessibilityCocoaBrowserTest,
|
||||
+ TestEventRetargetingActiveDescendant) {
|
||||
+ AccessibilityNotificationWaiter waiter(shell()->web_contents(),
|
||||
+ ui::kAXModeComplete,
|
||||
+ ax::mojom::Event::kLoadComplete);
|
||||
+
|
||||
+ GURL url(R"HTML(data:text/html,
|
||||
+ <div role="tree" aria-activedescendant="tree-child">
|
||||
+ <div tabindex="1" role="treeitem">1</div>
|
||||
+ <div id="tree-child" tabindex="2" role="treeitem">2</div>
|
||||
+ </div>
|
||||
+ <div role="treegrid" aria-activedescendant="treegrid-child">
|
||||
+ <div tabindex="1" role="treeitem">1</div>
|
||||
+ <div id="treegrid-child" tabindex="2" role="treeitem">2</div>
|
||||
+ </div>
|
||||
+ <div role="tablist" aria-activedescendant="tablist-child">
|
||||
+ <div tabindex="1" role="tab">1</div>
|
||||
+ <div id="tablist-child" tabindex="2" role="tab">2</div>
|
||||
+ </div>
|
||||
+ <div role="table" aria-activedescendant="table-child">
|
||||
+ <div tabindex="1" role="row">1</div>
|
||||
+ <div id="table-child" tabindex="2" role="row">2</div>
|
||||
+ </div>
|
||||
+ <div role="banner" aria-activedescendant="banner-child">
|
||||
+ <div tabindex="1" role="link">1</div>
|
||||
+ <div id="banner-child" tabindex="2" role="link">2</div>
|
||||
+ </div>)HTML");
|
||||
+
|
||||
+ EXPECT_TRUE(NavigateToURL(shell(), url));
|
||||
+ waiter.WaitForNotification();
|
||||
+
|
||||
+ std::pair<ax::mojom::Role, bool> tests[] = {
|
||||
+ std::make_pair(ax::mojom::Role::kTree, true),
|
||||
+ std::make_pair(ax::mojom::Role::kTreeGrid, true),
|
||||
+ std::make_pair(ax::mojom::Role::kTabList, true),
|
||||
+ std::make_pair(ax::mojom::Role::kTable, false),
|
||||
+ std::make_pair(ax::mojom::Role::kBanner, false),
|
||||
+ };
|
||||
+
|
||||
+ for (auto& test : tests) {
|
||||
+ base::scoped_nsobject<BrowserAccessibilityCocoa> parent(
|
||||
+ [ToBrowserAccessibilityCocoa(FindNode(test.first)) retain]);
|
||||
+ BrowserAccessibilityCocoa* first_child = [parent children][0];
|
||||
+ BrowserAccessibilityCocoa* second_child = [parent children][1];
|
||||
+
|
||||
+ EXPECT_NE(nullptr, parent.get());
|
||||
+ EXPECT_EQ([second_child owner], [second_child actionTarget]);
|
||||
+ EXPECT_EQ(test.second, [second_child owner] == [parent actionTarget]);
|
||||
+
|
||||
+ // aria-activedescendant should take priority of focus for determining
|
||||
+ // if an object is the action target.
|
||||
+ FocusAccessibilityElementAndWaitForFocusChange(first_child);
|
||||
+ EXPECT_EQ(test.second, [second_child owner] == [parent actionTarget]);
|
||||
+ }
|
||||
+}
|
||||
+
|
||||
} // namespace content
|
||||
diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc
|
||||
index 47161f98b2fcdacece578cde50141860d2ab0a40..a8f988b8465c420224979d805baf454483e49134 100644
|
||||
--- a/content/public/test/browser_test_utils.cc
|
||||
+++ b/content/public/test/browser_test_utils.cc
|
||||
@@ -3220,10 +3220,11 @@ void VerifyStaleContentOnFrameEviction(
|
||||
|
||||
#endif // defined(USE_AURA)
|
||||
|
||||
-ContextMenuFilter::ContextMenuFilter()
|
||||
+ContextMenuFilter::ContextMenuFilter(ShowBehavior behavior)
|
||||
: BrowserMessageFilter(FrameMsgStart),
|
||||
run_loop_(std::make_unique<base::RunLoop>()),
|
||||
- quit_closure_(run_loop_->QuitClosure()) {}
|
||||
+ quit_closure_(run_loop_->QuitClosure()),
|
||||
+ show_behavior_(behavior) {}
|
||||
|
||||
bool ContextMenuFilter::OnMessageReceived(const IPC::Message& message) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
||||
@@ -3234,6 +3235,9 @@ bool ContextMenuFilter::OnMessageReceived(const IPC::Message& message) {
|
||||
GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&ContextMenuFilter::OnContextMenu, this, menu_params));
|
||||
+ // Returning true here blocks the default action for this message, which
|
||||
+ // means that the menu will not be shown.
|
||||
+ return show_behavior_ == ShowBehavior::kPreventShow;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -3244,6 +3248,12 @@ void ContextMenuFilter::Wait() {
|
||||
run_loop_ = nullptr;
|
||||
}
|
||||
|
||||
+void ContextMenuFilter::Reset() {
|
||||
+ ASSERT_EQ(run_loop_, nullptr);
|
||||
+ run_loop_ = std::make_unique<base::RunLoop>();
|
||||
+ quit_closure_ = run_loop_->QuitClosure();
|
||||
+}
|
||||
+
|
||||
ContextMenuFilter::~ContextMenuFilter() = default;
|
||||
|
||||
void ContextMenuFilter::OnContextMenu(
|
||||
diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h
|
||||
index f68d423999ff711b46b7fe9d718163158e1d5223..3568bcceee9867db634306d36e3281d32450ea0a 100644
|
||||
--- a/content/public/test/browser_test_utils.h
|
||||
+++ b/content/public/test/browser_test_utils.h
|
||||
@@ -1688,10 +1688,15 @@ void VerifyStaleContentOnFrameEviction(
|
||||
// sent by the renderer.
|
||||
class ContextMenuFilter : public content::BrowserMessageFilter {
|
||||
public:
|
||||
- ContextMenuFilter();
|
||||
+ // Whether or not the ContextMenu should be prevented from performing
|
||||
+ // its default action, preventing the context menu from showing.
|
||||
+ enum ShowBehavior { kShow, kPreventShow };
|
||||
+
|
||||
+ explicit ContextMenuFilter(ShowBehavior behavior = ShowBehavior::kShow);
|
||||
|
||||
bool OnMessageReceived(const IPC::Message& message) override;
|
||||
void Wait();
|
||||
+ void Reset();
|
||||
|
||||
content::UntrustworthyContextMenuParams get_params() { return last_params_; }
|
||||
|
||||
@@ -1703,6 +1708,7 @@ class ContextMenuFilter : public content::BrowserMessageFilter {
|
||||
std::unique_ptr<base::RunLoop> run_loop_;
|
||||
base::OnceClosure quit_closure_;
|
||||
content::UntrustworthyContextMenuParams last_params_;
|
||||
+ const ShowBehavior show_behavior_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ContextMenuFilter);
|
||||
};
|
||||
102
patches/chromium/cherry-pick-3f7b67374a11.patch
Normal file
102
patches/chromium/cherry-pick-3f7b67374a11.patch
Normal file
@@ -0,0 +1,102 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Vladimir Levin <vmpstr@chromium.org>
|
||||
Date: Sat, 9 Jan 2021 01:26:39 +0000
|
||||
Subject: content-visibility: Don't adjust position of a locked hittest result
|
||||
node.
|
||||
|
||||
This patch ensures that if we have a hittest result that has a locked
|
||||
node, we don't try to recurse into its subtree. This can happen when we
|
||||
do a PositionWithAffinity check.
|
||||
|
||||
R=chrishtr@chromium.org
|
||||
|
||||
(cherry picked from commit 8483cf6944e38203c3b247163c54cfa105e89c56)
|
||||
|
||||
Bug: 1162131
|
||||
Change-Id: I357bd7032c6c2b6c9405bf26c49a36bda22d6a0d
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2611453
|
||||
Reviewed-by: Chris Harrelson <chrishtr@chromium.org>
|
||||
Commit-Queue: vmpstr <vmpstr@chromium.org>
|
||||
Cr-Original-Commit-Position: refs/heads/master@{#840727}
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2618603
|
||||
Reviewed-by: Xianzhu Wang <wangxianzhu@chromium.org>
|
||||
Reviewed-by: Krishna Govind <govind@chromium.org>
|
||||
Cr-Commit-Position: refs/branch-heads/4324@{#1566}
|
||||
Cr-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102}
|
||||
|
||||
diff --git a/third_party/blink/renderer/core/layout/hit_test_result.cc b/third_party/blink/renderer/core/layout/hit_test_result.cc
|
||||
index 1359aa6bdd532028d8280b093a4381a70e4a7577..156f768e1f522f9b01c6633de2abb670b32c5c7d 100644
|
||||
--- a/third_party/blink/renderer/core/layout/hit_test_result.cc
|
||||
+++ b/third_party/blink/renderer/core/layout/hit_test_result.cc
|
||||
@@ -21,12 +21,14 @@
|
||||
|
||||
#include "third_party/blink/renderer/core/layout/hit_test_result.h"
|
||||
|
||||
+#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h"
|
||||
#include "third_party/blink/renderer/core/dom/flat_tree_traversal.h"
|
||||
#include "third_party/blink/renderer/core/dom/pseudo_element.h"
|
||||
#include "third_party/blink/renderer/core/dom/shadow_root.h"
|
||||
#include "third_party/blink/renderer/core/editing/editing_utilities.h"
|
||||
#include "third_party/blink/renderer/core/editing/frame_selection.h"
|
||||
#include "third_party/blink/renderer/core/editing/position_with_affinity.h"
|
||||
+#include "third_party/blink/renderer/core/editing/text_affinity.h"
|
||||
#include "third_party/blink/renderer/core/editing/visible_units.h"
|
||||
#include "third_party/blink/renderer/core/frame/local_frame.h"
|
||||
#include "third_party/blink/renderer/core/frame/visual_viewport.h"
|
||||
@@ -144,6 +146,19 @@ PositionWithAffinity HitTestResult::GetPosition() const {
|
||||
LayoutObject* layout_object = GetLayoutObject();
|
||||
if (!layout_object)
|
||||
return PositionWithAffinity();
|
||||
+
|
||||
+ // We should never have a layout object that is within a locked subtree.
|
||||
+ CHECK(!DisplayLockUtilities::NearestLockedExclusiveAncestor(*layout_object));
|
||||
+
|
||||
+ // If the layout object is blocked by display lock, we return the beginning of
|
||||
+ // the node as the position. This is because we don't paint contents of the
|
||||
+ // element. Furthermore, any caret adjustments below can access layout-dirty
|
||||
+ // state in the subtree of this object.
|
||||
+ if (layout_object->ChildPaintBlockedByDisplayLock()) {
|
||||
+ return PositionWithAffinity(Position(*inner_node_, 0),
|
||||
+ TextAffinity::kDefault);
|
||||
+ }
|
||||
+
|
||||
if (inner_possibly_pseudo_node_->IsPseudoElement() &&
|
||||
inner_possibly_pseudo_node_->GetPseudoId() == kPseudoIdBefore) {
|
||||
return PositionWithAffinity(MostForwardCaretPosition(
|
||||
diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-080.html b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-080.html
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..d3cea5fb83767ddfc236850097387644e0f74c8e
|
||||
--- /dev/null
|
||||
+++ b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-080.html
|
||||
@@ -0,0 +1,31 @@
|
||||
+<!doctype HTML>
|
||||
+<html id=html>
|
||||
+<meta charset="utf8">
|
||||
+<title>Content Visibility: caret position with html hidden</title>
|
||||
+<link rel="author" title="Vladimir Levin" href="mailto:vmpstr@chromium.org">
|
||||
+<link rel="help" href="https://drafts.csswg.org/css-contain/#content-visibility">
|
||||
+<meta name="assert" content="caretRangeFromPoint works even if html has content-visibility hidden">
|
||||
+
|
||||
+<script src="/resources/testharness.js"></script>
|
||||
+<script src="/resources/testharnessreport.js"></script>
|
||||
+
|
||||
+<meter></meter>
|
||||
+<iframe></iframe>
|
||||
+<style>
|
||||
+* {
|
||||
+ all: initial;
|
||||
+ content-visibility: hidden;
|
||||
+}
|
||||
+</style>
|
||||
+
|
||||
+<script>
|
||||
+test(() => {
|
||||
+ const range = document.caretRangeFromPoint();
|
||||
+ assert_not_equals(range, null, "range exists");
|
||||
+ assert_equals(range.startContainer, html, "startContainer is html");
|
||||
+ assert_equals(range.startOffset, 0, "startOffset is zero");
|
||||
+ assert_equals(range.endContainer, html, "endContainer is html");
|
||||
+ assert_equals(range.endOffset, 0, "endOffset is zero");
|
||||
+}, "Caret range from point");
|
||||
+</script>
|
||||
+</html>
|
||||
723
patches/chromium/cherry-pick-47e21abe349a.patch
Normal file
723
patches/chromium/cherry-pick-47e21abe349a.patch
Normal file
@@ -0,0 +1,723 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Robinson <mrobinson@igalia.com>
|
||||
Date: Thu, 19 Nov 2020 21:28:45 +0000
|
||||
Subject: Improve AXTextStateChangeType in
|
||||
NSAccessibilitySelectedTextChangedNotification
|
||||
|
||||
When setting the AXTextStateChangeType key in the user info data
|
||||
structure for the notification, use AXTextStateChangeTypeSelectionMove
|
||||
when we detect a focus change. This causes VoiceOver to properly
|
||||
announce the label and type of form entries when their contents are
|
||||
selected due to focus changes.
|
||||
|
||||
type are now announced.
|
||||
|
||||
Bug: 1127421
|
||||
Change-Id: I42d66ad60fbcba7c8c34396fdbc3f6e0c739d1a2
|
||||
AX-Relnotes: When focus changes moves to form inputs, the input label and
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2512913
|
||||
Commit-Queue: Martin Robinson <mrobinson@igalia.com>
|
||||
Reviewed-by: Nektarios Paisios <nektar@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/master@{#829380}
|
||||
|
||||
diff --git a/content/browser/accessibility/accessibility_event_recorder_mac.mm b/content/browser/accessibility/accessibility_event_recorder_mac.mm
|
||||
index fcb93bab1c94f792dc9868e895a0e562d1fd8449..1e415ee8f849b67c098e49572c9d4e890ed44d04 100644
|
||||
--- a/content/browser/accessibility/accessibility_event_recorder_mac.mm
|
||||
+++ b/content/browser/accessibility/accessibility_event_recorder_mac.mm
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#import <Cocoa/Cocoa.h>
|
||||
|
||||
+#include <algorithm>
|
||||
#include <string>
|
||||
|
||||
#include "base/logging.h"
|
||||
@@ -15,6 +16,7 @@
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "content/browser/accessibility/accessibility_tools_utils_mac.h"
|
||||
#include "content/browser/accessibility/browser_accessibility_manager.h"
|
||||
+#include "ui/accessibility/platform/ax_private_webkit_constants_mac.h"
|
||||
|
||||
namespace content {
|
||||
|
||||
@@ -28,7 +30,11 @@
|
||||
~AccessibilityEventRecorderMac() override;
|
||||
|
||||
// Callback executed every time we receive an event notification.
|
||||
- void EventReceived(AXUIElementRef element, CFStringRef notification);
|
||||
+ void EventReceived(AXUIElementRef element,
|
||||
+ CFStringRef notification,
|
||||
+ CFDictionaryRef user_info);
|
||||
+ static std::string SerializeTextSelectionChangedProperties(
|
||||
+ CFDictionaryRef user_info);
|
||||
|
||||
private:
|
||||
// Add one notification to the list of notifications monitored by our
|
||||
@@ -54,10 +60,11 @@
|
||||
static void EventReceivedThunk(AXObserverRef observer_ref,
|
||||
AXUIElementRef element,
|
||||
CFStringRef notification,
|
||||
+ CFDictionaryRef user_info,
|
||||
void* refcon) {
|
||||
AccessibilityEventRecorderMac* this_ptr =
|
||||
static_cast<AccessibilityEventRecorderMac*>(refcon);
|
||||
- this_ptr->EventReceived(element, notification);
|
||||
+ this_ptr->EventReceived(element, notification, user_info);
|
||||
}
|
||||
|
||||
// static
|
||||
@@ -95,8 +102,9 @@ static void EventReceivedThunk(AXObserverRef observer_ref,
|
||||
base::ProcessId pid,
|
||||
AXUIElementRef node)
|
||||
: AccessibilityEventRecorder(manager), observer_run_loop_source_(NULL) {
|
||||
- if (kAXErrorSuccess != AXObserverCreate(pid, EventReceivedThunk,
|
||||
- observer_ref_.InitializeInto())) {
|
||||
+ if (kAXErrorSuccess !=
|
||||
+ AXObserverCreateWithInfoCallback(pid, EventReceivedThunk,
|
||||
+ observer_ref_.InitializeInto())) {
|
||||
LOG(FATAL) << "Failed to create AXObserverRef";
|
||||
}
|
||||
|
||||
@@ -157,7 +165,8 @@ static void EventReceivedThunk(AXObserverRef observer_ref,
|
||||
}
|
||||
|
||||
void AccessibilityEventRecorderMac::EventReceived(AXUIElementRef element,
|
||||
- CFStringRef notification) {
|
||||
+ CFStringRef notification,
|
||||
+ CFDictionaryRef user_info) {
|
||||
std::string notification_str = base::SysCFStringRefToUTF8(notification);
|
||||
std::string role = GetAXAttributeValue(element, NSAccessibilityRoleAttribute);
|
||||
if (role.empty())
|
||||
@@ -180,7 +189,49 @@ static void EventReceivedThunk(AXObserverRef observer_ref,
|
||||
if (!value.empty())
|
||||
log += base::StringPrintf(" AXValue=\"%s\"", value.c_str());
|
||||
|
||||
+ if (notification_str ==
|
||||
+ base::SysNSStringToUTF8(NSAccessibilitySelectedTextChangedNotification))
|
||||
+ log += " " + SerializeTextSelectionChangedProperties(user_info);
|
||||
+
|
||||
OnEvent(log);
|
||||
}
|
||||
|
||||
+std::string
|
||||
+AccessibilityEventRecorderMac::SerializeTextSelectionChangedProperties(
|
||||
+ CFDictionaryRef user_info) {
|
||||
+ std::vector<std::string> serialized_info;
|
||||
+ CFDictionaryApplyFunction(
|
||||
+ user_info,
|
||||
+ [](const void* raw_key, const void* raw_value, void* context) {
|
||||
+ auto* key = static_cast<NSString*>(raw_key);
|
||||
+ auto* value = static_cast<NSObject*>(raw_value);
|
||||
+ auto* serialized_info = static_cast<std::vector<std::string>*>(context);
|
||||
+ std::string value_string;
|
||||
+ if ([key isEqual:ui::NSAccessibilityTextStateChangeTypeKey]) {
|
||||
+ value_string = ToString(static_cast<ui::AXTextStateChangeType>(
|
||||
+ [static_cast<NSNumber*>(value) intValue]));
|
||||
+ } else if ([key isEqual:ui::NSAccessibilityTextSelectionDirection]) {
|
||||
+ value_string = ToString(static_cast<ui::AXTextSelectionDirection>(
|
||||
+ [static_cast<NSNumber*>(value) intValue]));
|
||||
+ } else if ([key isEqual:ui::NSAccessibilityTextSelectionGranularity]) {
|
||||
+ value_string = ToString(static_cast<ui::AXTextSelectionGranularity>(
|
||||
+ [static_cast<NSNumber*>(value) intValue]));
|
||||
+ } else if ([key isEqual:ui::NSAccessibilityTextEditType]) {
|
||||
+ value_string = ToString(static_cast<ui::AXTextEditType>(
|
||||
+ [static_cast<NSNumber*>(value) intValue]));
|
||||
+ } else {
|
||||
+ return;
|
||||
+ }
|
||||
+ serialized_info->push_back(base::SysNSStringToUTF8(key) + "=" +
|
||||
+ value_string);
|
||||
+ },
|
||||
+ &serialized_info);
|
||||
+
|
||||
+ // Always sort the info so that we don't depend on CFDictionary for
|
||||
+ // consistent output ordering.
|
||||
+ std::sort(serialized_info.begin(), serialized_info.end());
|
||||
+
|
||||
+ return base::JoinString(serialized_info, " ");
|
||||
+}
|
||||
+
|
||||
} // namespace content
|
||||
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.h b/content/browser/accessibility/browser_accessibility_manager_mac.h
|
||||
index b4043f56341e72bcf75ef71c56cb8cf9fc442579..925bfe1ea191ab846805fdbff1b0314e779b1c9e 100644
|
||||
--- a/content/browser/accessibility/browser_accessibility_manager_mac.h
|
||||
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.h
|
||||
@@ -54,7 +54,8 @@ class CONTENT_EXPORT BrowserAccessibilityManagerMac
|
||||
const std::vector<Change>& changes) override;
|
||||
|
||||
// Returns an autoreleased object.
|
||||
- NSDictionary* GetUserInfoForSelectedTextChangedNotification();
|
||||
+ NSDictionary* GetUserInfoForSelectedTextChangedNotification(
|
||||
+ bool focus_changed);
|
||||
|
||||
// Returns an autoreleased object.
|
||||
NSDictionary* GetUserInfoForValueChangedNotification(
|
||||
diff --git a/content/browser/accessibility/browser_accessibility_manager_mac.mm b/content/browser/accessibility/browser_accessibility_manager_mac.mm
|
||||
index 68b8ccd88fb4ad6e4793a54869d5424f2a860d8a..4232959e30ab758bff58aa8e4457b6cdd3c7745b 100644
|
||||
--- a/content/browser/accessibility/browser_accessibility_manager_mac.mm
|
||||
+++ b/content/browser/accessibility/browser_accessibility_manager_mac.mm
|
||||
@@ -19,91 +19,13 @@
|
||||
#include "content/public/browser/web_contents.h"
|
||||
#include "ui/accelerated_widget_mac/accelerated_widget_mac.h"
|
||||
#include "ui/accessibility/ax_role_properties.h"
|
||||
+#include "ui/accessibility/platform/ax_private_webkit_constants_mac.h"
|
||||
|
||||
namespace {
|
||||
|
||||
// Use same value as in Safari's WebKit.
|
||||
const int kLiveRegionChangeIntervalMS = 20;
|
||||
|
||||
-// Declare undocumented accessibility constants and enums only present in
|
||||
-// WebKit.
|
||||
-
|
||||
-enum AXTextStateChangeType {
|
||||
- AXTextStateChangeTypeUnknown,
|
||||
- AXTextStateChangeTypeEdit,
|
||||
- AXTextStateChangeTypeSelectionMove,
|
||||
- AXTextStateChangeTypeSelectionExtend
|
||||
-};
|
||||
-
|
||||
-enum AXTextSelectionDirection {
|
||||
- AXTextSelectionDirectionUnknown,
|
||||
- AXTextSelectionDirectionBeginning,
|
||||
- AXTextSelectionDirectionEnd,
|
||||
- AXTextSelectionDirectionPrevious,
|
||||
- AXTextSelectionDirectionNext,
|
||||
- AXTextSelectionDirectionDiscontiguous
|
||||
-};
|
||||
-
|
||||
-enum AXTextSelectionGranularity {
|
||||
- AXTextSelectionGranularityUnknown,
|
||||
- AXTextSelectionGranularityCharacter,
|
||||
- AXTextSelectionGranularityWord,
|
||||
- AXTextSelectionGranularityLine,
|
||||
- AXTextSelectionGranularitySentence,
|
||||
- AXTextSelectionGranularityParagraph,
|
||||
- AXTextSelectionGranularityPage,
|
||||
- AXTextSelectionGranularityDocument,
|
||||
- AXTextSelectionGranularityAll
|
||||
-};
|
||||
-
|
||||
-enum AXTextEditType {
|
||||
- AXTextEditTypeUnknown,
|
||||
- AXTextEditTypeDelete,
|
||||
- AXTextEditTypeInsert,
|
||||
- AXTextEditTypeTyping,
|
||||
- AXTextEditTypeDictation,
|
||||
- AXTextEditTypeCut,
|
||||
- AXTextEditTypePaste,
|
||||
- AXTextEditTypeAttributesChange
|
||||
-};
|
||||
-
|
||||
-// Native mac notifications fired.
|
||||
-NSString* const NSAccessibilityAutocorrectionOccurredNotification =
|
||||
- @"AXAutocorrectionOccurred";
|
||||
-NSString* const NSAccessibilityLoadCompleteNotification = @"AXLoadComplete";
|
||||
-NSString* const NSAccessibilityInvalidStatusChangedNotification =
|
||||
- @"AXInvalidStatusChanged";
|
||||
-NSString* const NSAccessibilityLiveRegionCreatedNotification =
|
||||
- @"AXLiveRegionCreated";
|
||||
-NSString* const NSAccessibilityLiveRegionChangedNotification =
|
||||
- @"AXLiveRegionChanged";
|
||||
-NSString* const NSAccessibilityExpandedChanged = @"AXExpandedChanged";
|
||||
-NSString* const NSAccessibilityMenuItemSelectedNotification =
|
||||
- @"AXMenuItemSelected";
|
||||
-
|
||||
-// The following native mac notifications are not fired:
|
||||
-// AXLayoutComplete: Voiceover does not use this, it is considered too spammy.
|
||||
-
|
||||
-// Attributes used for NSAccessibilitySelectedTextChangedNotification and
|
||||
-// NSAccessibilityValueChangedNotification.
|
||||
-NSString* const NSAccessibilityTextStateChangeTypeKey =
|
||||
- @"AXTextStateChangeType";
|
||||
-NSString* const NSAccessibilityTextStateSyncKey = @"AXTextStateSync";
|
||||
-NSString* const NSAccessibilityTextSelectionDirection =
|
||||
- @"AXTextSelectionDirection";
|
||||
-NSString* const NSAccessibilityTextSelectionGranularity =
|
||||
- @"AXTextSelectionGranularity";
|
||||
-NSString* const NSAccessibilityTextSelectionChangedFocus =
|
||||
- @"AXTextSelectionChangedFocus";
|
||||
-NSString* const NSAccessibilityTextChangeElement = @"AXTextChangeElement";
|
||||
-NSString* const NSAccessibilityTextEditType = @"AXTextEditType";
|
||||
-NSString* const NSAccessibilityTextChangeValue = @"AXTextChangeValue";
|
||||
-NSString* const NSAccessibilityChangeValueStartMarker =
|
||||
- @"AXTextChangeValueStartMarker";
|
||||
-NSString* const NSAccessibilityTextChangeValueLength =
|
||||
- @"AXTextChangeValueLength";
|
||||
-NSString* const NSAccessibilityTextChangeValues = @"AXTextChangeValues";
|
||||
-
|
||||
} // namespace
|
||||
|
||||
namespace content {
|
||||
@@ -164,7 +86,7 @@
|
||||
NSString* mac_notification = nullptr;
|
||||
switch (event_type) {
|
||||
case ax::mojom::Event::kAutocorrectionOccured:
|
||||
- mac_notification = NSAccessibilityAutocorrectionOccurredNotification;
|
||||
+ mac_notification = ui::NSAccessibilityAutocorrectionOccurredNotification;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
@@ -203,6 +125,8 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
auto native_node = ToBrowserAccessibilityCocoa(node);
|
||||
DCHECK(native_node);
|
||||
|
||||
+ bool focus_changed = GetFocus() != GetLastFocusedNode();
|
||||
+
|
||||
// Refer to |AXObjectCache::postPlatformNotification| in WebKit source code.
|
||||
NSString* mac_notification = nullptr;
|
||||
switch (event_type) {
|
||||
@@ -232,7 +156,7 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
// |NSAccessibilityLoadCompleteNotification| should only be fired on the
|
||||
// top document and when the document is not Chrome's new tab page.
|
||||
if (IsRootTree() && !IsChromeNewTabPage()) {
|
||||
- mac_notification = NSAccessibilityLoadCompleteNotification;
|
||||
+ mac_notification = ui::NSAccessibilityLoadCompleteNotification;
|
||||
} else {
|
||||
// Voiceover moves focus to the web content when it receives an
|
||||
// AXLoadComplete event. On Chrome's new tab page, focus should stay
|
||||
@@ -242,7 +166,7 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
}
|
||||
break;
|
||||
case ui::AXEventGenerator::Event::INVALID_STATUS_CHANGED:
|
||||
- mac_notification = NSAccessibilityInvalidStatusChangedNotification;
|
||||
+ mac_notification = ui::NSAccessibilityInvalidStatusChangedNotification;
|
||||
break;
|
||||
case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
|
||||
if (ui::IsTableLike(node->GetRole())) {
|
||||
@@ -289,7 +213,7 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
// API has been present on versions of OS X since 10.7 but doesn't
|
||||
// appear to be needed by Voiceover before version 10.11.
|
||||
NSDictionary* user_info =
|
||||
- GetUserInfoForSelectedTextChangedNotification();
|
||||
+ GetUserInfoForSelectedTextChangedNotification(focus_changed);
|
||||
|
||||
BrowserAccessibilityManager* root_manager = GetRootManager();
|
||||
if (!root_manager)
|
||||
@@ -342,11 +266,11 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
}
|
||||
break;
|
||||
case ui::AXEventGenerator::Event::LIVE_REGION_CREATED:
|
||||
- mac_notification = NSAccessibilityLiveRegionCreatedNotification;
|
||||
+ mac_notification = ui::NSAccessibilityLiveRegionCreatedNotification;
|
||||
break;
|
||||
case ui::AXEventGenerator::Event::ALERT:
|
||||
NSAccessibilityPostNotification(
|
||||
- native_node, NSAccessibilityLiveRegionCreatedNotification);
|
||||
+ native_node, ui::NSAccessibilityLiveRegionCreatedNotification);
|
||||
// Voiceover requires a live region changed notification to actually
|
||||
// announce the live region.
|
||||
FireGeneratedEvent(ui::AXEventGenerator::Event::LIVE_REGION_CHANGED,
|
||||
@@ -359,7 +283,7 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
|
||||
if (never_suppress_or_delay_events_for_testing_) {
|
||||
NSAccessibilityPostNotification(
|
||||
- native_node, NSAccessibilityLiveRegionChangedNotification);
|
||||
+ native_node, ui::NSAccessibilityLiveRegionChangedNotification);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -383,7 +307,7 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
[](base::scoped_nsobject<BrowserAccessibilityCocoa> node) {
|
||||
if (node && [node instanceActive]) {
|
||||
NSAccessibilityPostNotification(
|
||||
- node, NSAccessibilityLiveRegionChangedNotification);
|
||||
+ node, ui::NSAccessibilityLiveRegionChangedNotification);
|
||||
}
|
||||
},
|
||||
std::move(retained_node)),
|
||||
@@ -398,7 +322,7 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
node->GetRole() == ax::mojom::Role::kTreeItem) {
|
||||
mac_notification = NSAccessibilityRowExpandedNotification;
|
||||
} else {
|
||||
- mac_notification = NSAccessibilityExpandedChanged;
|
||||
+ mac_notification = ui::NSAccessibilityExpandedChanged;
|
||||
}
|
||||
break;
|
||||
case ui::AXEventGenerator::Event::COLLAPSED:
|
||||
@@ -406,11 +330,11 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
node->GetRole() == ax::mojom::Role::kTreeItem) {
|
||||
mac_notification = NSAccessibilityRowCollapsedNotification;
|
||||
} else {
|
||||
- mac_notification = NSAccessibilityExpandedChanged;
|
||||
+ mac_notification = ui::NSAccessibilityExpandedChanged;
|
||||
}
|
||||
break;
|
||||
case ui::AXEventGenerator::Event::MENU_ITEM_SELECTED:
|
||||
- mac_notification = NSAccessibilityMenuItemSelectedNotification;
|
||||
+ mac_notification = ui::NSAccessibilityMenuItemSelectedNotification;
|
||||
break;
|
||||
case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED:
|
||||
case ui::AXEventGenerator::Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED:
|
||||
@@ -514,18 +438,32 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
}
|
||||
}
|
||||
|
||||
-NSDictionary* BrowserAccessibilityManagerMac::
|
||||
- GetUserInfoForSelectedTextChangedNotification() {
|
||||
+NSDictionary*
|
||||
+BrowserAccessibilityManagerMac::GetUserInfoForSelectedTextChangedNotification(
|
||||
+ bool focus_changed) {
|
||||
NSMutableDictionary* user_info =
|
||||
[[[NSMutableDictionary alloc] init] autorelease];
|
||||
- [user_info setObject:@YES forKey:NSAccessibilityTextStateSyncKey];
|
||||
- [user_info setObject:@(AXTextStateChangeTypeUnknown)
|
||||
- forKey:NSAccessibilityTextStateChangeTypeKey];
|
||||
- [user_info setObject:@(AXTextSelectionDirectionUnknown)
|
||||
- forKey:NSAccessibilityTextSelectionDirection];
|
||||
- [user_info setObject:@(AXTextSelectionGranularityUnknown)
|
||||
- forKey:NSAccessibilityTextSelectionGranularity];
|
||||
- [user_info setObject:@YES forKey:NSAccessibilityTextSelectionChangedFocus];
|
||||
+ [user_info setObject:@YES forKey:ui::NSAccessibilityTextStateSyncKey];
|
||||
+ [user_info setObject:@(ui::AXTextSelectionDirectionUnknown)
|
||||
+ forKey:ui::NSAccessibilityTextSelectionDirection];
|
||||
+ [user_info setObject:@(ui::AXTextSelectionGranularityUnknown)
|
||||
+ forKey:ui::NSAccessibilityTextSelectionGranularity];
|
||||
+ [user_info setObject:@YES
|
||||
+ forKey:ui::NSAccessibilityTextSelectionChangedFocus];
|
||||
+
|
||||
+ // Try to detect when the text selection changes due to a focus change.
|
||||
+ // This is necessary so that VoiceOver also anounces information about the
|
||||
+ // element that contains this selection.
|
||||
+ // TODO(mrobinson): Determine definitively what the type of this text
|
||||
+ // selection change is. This requires passing this information here from
|
||||
+ // blink.
|
||||
+ if (focus_changed) {
|
||||
+ [user_info setObject:@(ui::AXTextStateChangeTypeSelectionMove)
|
||||
+ forKey:ui::NSAccessibilityTextStateChangeTypeKey];
|
||||
+ } else {
|
||||
+ [user_info setObject:@(ui::AXTextStateChangeTypeUnknown)
|
||||
+ forKey:ui::NSAccessibilityTextStateChangeTypeKey];
|
||||
+ }
|
||||
|
||||
int32_t focus_id = ax_tree()->GetUnignoredSelection().focus_object_id;
|
||||
BrowserAccessibility* focus_object = GetFromID(focus_id);
|
||||
@@ -534,7 +472,7 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
auto native_focus_object = ToBrowserAccessibilityCocoa(focus_object);
|
||||
if (native_focus_object && [native_focus_object instanceActive]) {
|
||||
[user_info setObject:native_focus_object
|
||||
- forKey:NSAccessibilityTextChangeElement];
|
||||
+ forKey:ui::NSAccessibilityTextChangeElement];
|
||||
|
||||
#ifndef MAS_BUILD
|
||||
id selected_text = [native_focus_object selectedTextMarkerRange];
|
||||
@@ -565,38 +503,39 @@ void PostAnnouncementNotification(NSString* announcement) {
|
||||
if (!deleted_text.empty()) {
|
||||
NSMutableDictionary* change =
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
- NSAccessibilityTextEditType : @(AXTextEditTypeDelete),
|
||||
- NSAccessibilityTextChangeValueLength : @(deleted_text.length()),
|
||||
- NSAccessibilityTextChangeValue :
|
||||
+ ui::NSAccessibilityTextEditType : @(ui::AXTextEditTypeDelete),
|
||||
+ ui::NSAccessibilityTextChangeValueLength : @(deleted_text.length()),
|
||||
+ ui::NSAccessibilityTextChangeValue :
|
||||
base::SysUTF16ToNSString(deleted_text)
|
||||
}];
|
||||
if (edit_text_marker) {
|
||||
- change[NSAccessibilityChangeValueStartMarker] = edit_text_marker;
|
||||
+ change[ui::NSAccessibilityChangeValueStartMarker] = edit_text_marker;
|
||||
}
|
||||
[changes addObject:change];
|
||||
}
|
||||
if (!inserted_text.empty()) {
|
||||
// TODO(nektar): Figure out if this is a paste, insertion or typing.
|
||||
// Changes to Blink would be required. A heuristic is currently used.
|
||||
- auto edit_type = inserted_text.length() > 1 ? @(AXTextEditTypeInsert)
|
||||
- : @(AXTextEditTypeTyping);
|
||||
+ auto edit_type = inserted_text.length() > 1 ? @(ui::AXTextEditTypeInsert)
|
||||
+ : @(ui::AXTextEditTypeTyping);
|
||||
NSMutableDictionary* change =
|
||||
[NSMutableDictionary dictionaryWithDictionary:@{
|
||||
- NSAccessibilityTextEditType : edit_type,
|
||||
- NSAccessibilityTextChangeValueLength : @(inserted_text.length()),
|
||||
- NSAccessibilityTextChangeValue :
|
||||
+ ui::NSAccessibilityTextEditType : edit_type,
|
||||
+ ui::NSAccessibilityTextChangeValueLength : @(inserted_text.length()),
|
||||
+ ui::NSAccessibilityTextChangeValue :
|
||||
base::SysUTF16ToNSString(inserted_text)
|
||||
}];
|
||||
if (edit_text_marker) {
|
||||
- change[NSAccessibilityChangeValueStartMarker] = edit_text_marker;
|
||||
+ change[ui::NSAccessibilityChangeValueStartMarker] = edit_text_marker;
|
||||
}
|
||||
[changes addObject:change];
|
||||
}
|
||||
|
||||
return @{
|
||||
- NSAccessibilityTextStateChangeTypeKey : @(AXTextStateChangeTypeEdit),
|
||||
- NSAccessibilityTextChangeValues : changes,
|
||||
- NSAccessibilityTextChangeElement : native_node
|
||||
+ ui::
|
||||
+ NSAccessibilityTextStateChangeTypeKey : @(ui::AXTextStateChangeTypeEdit),
|
||||
+ ui::NSAccessibilityTextChangeValues : changes,
|
||||
+ ui::NSAccessibilityTextChangeElement : native_node
|
||||
};
|
||||
}
|
||||
|
||||
diff --git a/content/test/data/accessibility/event/aria-combo-box-focus-expected-mac.txt b/content/test/data/accessibility/event/aria-combo-box-focus-expected-mac.txt
|
||||
index ad5e2bf2c8029185c51eecc94cac1dbe7608c99e..67e07a89f77f7c61a57eb51cecea211df5227341 100644
|
||||
--- a/content/test/data/accessibility/event/aria-combo-box-focus-expected-mac.txt
|
||||
+++ b/content/test/data/accessibility/event/aria-combo-box-focus-expected-mac.txt
|
||||
@@ -1,3 +1,3 @@
|
||||
AXFocusedUIElementChanged on AXStaticText AXValue="Apple not selected"
|
||||
-AXSelectedTextChanged on AXStaticText AXValue="Apple not selected"
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
\ No newline at end of file
|
||||
+AXSelectedTextChanged on AXStaticText AXValue="Apple not selected" AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeSelectionMove
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeSelectionMove
|
||||
diff --git a/content/test/data/accessibility/event/caret-browsing-disabled-expected-mac.txt b/content/test/data/accessibility/event/caret-browsing-disabled-expected-mac.txt
|
||||
index ec0b74d984ade9928705242f9b0682a743e20fb2..87447c5a4b14efde5b64b1d340a3fa2fb6574b80 100644
|
||||
--- a/content/test/data/accessibility/event/caret-browsing-disabled-expected-mac.txt
|
||||
+++ b/content/test/data/accessibility/event/caret-browsing-disabled-expected-mac.txt
|
||||
@@ -1,5 +1,5 @@
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
=== Start Continuation ===
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
diff --git a/content/test/data/accessibility/event/caret-browsing-enabled-expected-mac.txt b/content/test/data/accessibility/event/caret-browsing-enabled-expected-mac.txt
|
||||
index ec0b74d984ade9928705242f9b0682a743e20fb2..87447c5a4b14efde5b64b1d340a3fa2fb6574b80 100644
|
||||
--- a/content/test/data/accessibility/event/caret-browsing-enabled-expected-mac.txt
|
||||
+++ b/content/test/data/accessibility/event/caret-browsing-enabled-expected-mac.txt
|
||||
@@ -1,5 +1,5 @@
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
=== Start Continuation ===
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
-AXSelectedTextChanged on AXWebArea
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
diff --git a/content/test/data/accessibility/event/text-selection-changed-expected-mac.txt b/content/test/data/accessibility/event/text-selection-changed-expected-mac.txt
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..9213c873393d595bba8796cdb0e2325d3ee37ee9
|
||||
--- /dev/null
|
||||
+++ b/content/test/data/accessibility/event/text-selection-changed-expected-mac.txt
|
||||
@@ -0,0 +1,10 @@
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeUnknown
|
||||
+=== Start Continuation ===
|
||||
+AXFocusedUIElementChanged on AXTextField AXDescription="input" AXValue="input"
|
||||
+AXSelectedTextChanged on AXTextField AXDescription="input" AXValue="input" AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeSelectionMove
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeSelectionMove
|
||||
+=== Start Continuation ===
|
||||
+AXFocusedUIElementChanged on AXTextArea AXDescription="textarea"
|
||||
+AXSelectedTextChanged on AXTextArea AXDescription="textarea" AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeSelectionMove
|
||||
+AXSelectedTextChanged on AXWebArea AXTextSelectionDirection=AXTextSelectionDirectionUnknown AXTextSelectionGranularity=AXTextSelectionGranularityUnknown AXTextStateChangeType=AXTextStateChangeTypeSelectionMove
|
||||
diff --git a/ui/accessibility/platform/BUILD.gn b/ui/accessibility/platform/BUILD.gn
|
||||
index 2f015b939bb6835f2ff817ce6c960157eaf32342..eafddade0f7fa8ab8d008bf4888fc9ecef85463d 100644
|
||||
--- a/ui/accessibility/platform/BUILD.gn
|
||||
+++ b/ui/accessibility/platform/BUILD.gn
|
||||
@@ -110,6 +110,8 @@ source_set("platform") {
|
||||
"ax_event_intent_mac.mm",
|
||||
"ax_platform_node_mac.h",
|
||||
"ax_platform_node_mac.mm",
|
||||
+ "ax_private_webkit_constants_mac.h",
|
||||
+ "ax_private_webkit_constants_mac.mm",
|
||||
]
|
||||
|
||||
frameworks = [
|
||||
diff --git a/ui/accessibility/platform/ax_private_webkit_constants_mac.h b/ui/accessibility/platform/ax_private_webkit_constants_mac.h
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..5f1776a3aeac89b70d6d852b8e5209a1208271ae
|
||||
--- /dev/null
|
||||
+++ b/ui/accessibility/platform/ax_private_webkit_constants_mac.h
|
||||
@@ -0,0 +1,96 @@
|
||||
+// Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
+// Use of this source code is governed by a BSD-style license that can be
|
||||
+// found in the LICENSE file.
|
||||
+
|
||||
+#ifndef UI_ACCESSIBILITY_PLATFORM_AX_PRIVATE_WEBKIT_CONSTANTS_MAC_H_
|
||||
+#define UI_ACCESSIBILITY_PLATFORM_AX_PRIVATE_WEBKIT_CONSTANTS_MAC_H_
|
||||
+
|
||||
+#import <Cocoa/Cocoa.h>
|
||||
+#include "ui/accessibility/ax_export.h"
|
||||
+
|
||||
+namespace ui {
|
||||
+
|
||||
+enum AXTextStateChangeType {
|
||||
+ AXTextStateChangeTypeUnknown,
|
||||
+ AXTextStateChangeTypeEdit,
|
||||
+ AXTextStateChangeTypeSelectionMove,
|
||||
+ AXTextStateChangeTypeSelectionExtend
|
||||
+};
|
||||
+
|
||||
+enum AXTextSelectionDirection {
|
||||
+ AXTextSelectionDirectionUnknown,
|
||||
+ AXTextSelectionDirectionBeginning,
|
||||
+ AXTextSelectionDirectionEnd,
|
||||
+ AXTextSelectionDirectionPrevious,
|
||||
+ AXTextSelectionDirectionNext,
|
||||
+ AXTextSelectionDirectionDiscontiguous
|
||||
+};
|
||||
+
|
||||
+enum AXTextSelectionGranularity {
|
||||
+ AXTextSelectionGranularityUnknown,
|
||||
+ AXTextSelectionGranularityCharacter,
|
||||
+ AXTextSelectionGranularityWord,
|
||||
+ AXTextSelectionGranularityLine,
|
||||
+ AXTextSelectionGranularitySentence,
|
||||
+ AXTextSelectionGranularityParagraph,
|
||||
+ AXTextSelectionGranularityPage,
|
||||
+ AXTextSelectionGranularityDocument,
|
||||
+ AXTextSelectionGranularityAll
|
||||
+};
|
||||
+
|
||||
+enum AXTextEditType {
|
||||
+ AXTextEditTypeUnknown,
|
||||
+ AXTextEditTypeDelete,
|
||||
+ AXTextEditTypeInsert,
|
||||
+ AXTextEditTypeTyping,
|
||||
+ AXTextEditTypeDictation,
|
||||
+ AXTextEditTypeCut,
|
||||
+ AXTextEditTypePaste,
|
||||
+ AXTextEditTypeAttributesChange
|
||||
+};
|
||||
+
|
||||
+// Native mac notifications fired.
|
||||
+NSString* const NSAccessibilityAutocorrectionOccurredNotification =
|
||||
+ @"AXAutocorrectionOccurred";
|
||||
+NSString* const NSAccessibilityLoadCompleteNotification = @"AXLoadComplete";
|
||||
+NSString* const NSAccessibilityInvalidStatusChangedNotification =
|
||||
+ @"AXInvalidStatusChanged";
|
||||
+NSString* const NSAccessibilityLiveRegionCreatedNotification =
|
||||
+ @"AXLiveRegionCreated";
|
||||
+NSString* const NSAccessibilityLiveRegionChangedNotification =
|
||||
+ @"AXLiveRegionChanged";
|
||||
+NSString* const NSAccessibilityExpandedChanged = @"AXExpandedChanged";
|
||||
+NSString* const NSAccessibilityMenuItemSelectedNotification =
|
||||
+ @"AXMenuItemSelected";
|
||||
+
|
||||
+// The following native mac notifications are not fired:
|
||||
+// AXLayoutComplete: Voiceover does not use this, it is considered too spammy.
|
||||
+
|
||||
+// Attributes used for NSAccessibilitySelectedTextChangedNotification and
|
||||
+// NSAccessibilityValueChangedNotification.
|
||||
+NSString* const NSAccessibilityTextStateChangeTypeKey =
|
||||
+ @"AXTextStateChangeType";
|
||||
+NSString* const NSAccessibilityTextStateSyncKey = @"AXTextStateSync";
|
||||
+NSString* const NSAccessibilityTextSelectionDirection =
|
||||
+ @"AXTextSelectionDirection";
|
||||
+NSString* const NSAccessibilityTextSelectionGranularity =
|
||||
+ @"AXTextSelectionGranularity";
|
||||
+NSString* const NSAccessibilityTextSelectionChangedFocus =
|
||||
+ @"AXTextSelectionChangedFocus";
|
||||
+NSString* const NSAccessibilityTextChangeElement = @"AXTextChangeElement";
|
||||
+NSString* const NSAccessibilityTextEditType = @"AXTextEditType";
|
||||
+NSString* const NSAccessibilityTextChangeValue = @"AXTextChangeValue";
|
||||
+NSString* const NSAccessibilityChangeValueStartMarker =
|
||||
+ @"AXTextChangeValueStartMarker";
|
||||
+NSString* const NSAccessibilityTextChangeValueLength =
|
||||
+ @"AXTextChangeValueLength";
|
||||
+NSString* const NSAccessibilityTextChangeValues = @"AXTextChangeValues";
|
||||
+
|
||||
+AX_EXPORT const char* ToString(AXTextStateChangeType);
|
||||
+AX_EXPORT const char* ToString(AXTextSelectionDirection);
|
||||
+AX_EXPORT const char* ToString(AXTextSelectionGranularity);
|
||||
+AX_EXPORT const char* ToString(AXTextEditType);
|
||||
+
|
||||
+} // namespace ui
|
||||
+
|
||||
+#endif // UI_ACCESSIBILITY_PLATFORM_AX_PRIVATE_WEBKIT_CONSTANTS_MAC_H_
|
||||
diff --git a/ui/accessibility/platform/ax_private_webkit_constants_mac.mm b/ui/accessibility/platform/ax_private_webkit_constants_mac.mm
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..7de31a487dd6e0e6a4af2f4fa62e463f41a0d96a
|
||||
--- /dev/null
|
||||
+++ b/ui/accessibility/platform/ax_private_webkit_constants_mac.mm
|
||||
@@ -0,0 +1,91 @@
|
||||
+// Copyright 2020 The Chromium Authors. All rights reserved.
|
||||
+// Use of this source code is governed by a BSD-style license that can be
|
||||
+// found in the LICENSE file.
|
||||
+
|
||||
+#include "ui/accessibility/platform/ax_private_webkit_constants_mac.h"
|
||||
+
|
||||
+namespace ui {
|
||||
+
|
||||
+const char* ToString(AXTextStateChangeType type) {
|
||||
+ switch (type) {
|
||||
+ case AXTextStateChangeTypeUnknown:
|
||||
+ return "AXTextStateChangeTypeUnknown";
|
||||
+ case AXTextStateChangeTypeEdit:
|
||||
+ return "AXTextStateChangeTypeEdit";
|
||||
+ case AXTextStateChangeTypeSelectionMove:
|
||||
+ return "AXTextStateChangeTypeSelectionMove";
|
||||
+ case AXTextStateChangeTypeSelectionExtend:
|
||||
+ return "AXTextStateChangeTypeSelectionExtend";
|
||||
+ }
|
||||
+
|
||||
+ return "";
|
||||
+}
|
||||
+
|
||||
+const char* ToString(AXTextSelectionDirection direction) {
|
||||
+ switch (direction) {
|
||||
+ case AXTextSelectionDirectionUnknown:
|
||||
+ return "AXTextSelectionDirectionUnknown";
|
||||
+ case AXTextSelectionDirectionBeginning:
|
||||
+ return "AXTextSelectionDirectionBeginning";
|
||||
+ case AXTextSelectionDirectionEnd:
|
||||
+ return "AXTextSelectionDirectionEnd";
|
||||
+ case AXTextSelectionDirectionPrevious:
|
||||
+ return "AXTextSelectionDirectionPrevious";
|
||||
+ case AXTextSelectionDirectionNext:
|
||||
+ return "AXTextSelectionDirectionNext";
|
||||
+ case AXTextSelectionDirectionDiscontiguous:
|
||||
+ return "AXTextSelectionDirectionDiscontiguous";
|
||||
+ }
|
||||
+
|
||||
+ return "";
|
||||
+}
|
||||
+
|
||||
+const char* ToString(AXTextSelectionGranularity granularity) {
|
||||
+ switch (granularity) {
|
||||
+ case AXTextSelectionGranularityUnknown:
|
||||
+ return "AXTextSelectionGranularityUnknown";
|
||||
+ case AXTextSelectionGranularityCharacter:
|
||||
+ return "AXTextSelectionGranularityCharacter";
|
||||
+ case AXTextSelectionGranularityWord:
|
||||
+ return "AXTextSelectionGranularityWord";
|
||||
+ case AXTextSelectionGranularityLine:
|
||||
+ return "AXTextSelectionGranularityLine";
|
||||
+ case AXTextSelectionGranularitySentence:
|
||||
+ return "AXTextSelectionGranularitySentence";
|
||||
+ case AXTextSelectionGranularityParagraph:
|
||||
+ return "AXTextSelectionGranularityParagraph";
|
||||
+ case AXTextSelectionGranularityPage:
|
||||
+ return "AXTextSelectionGranularityPage";
|
||||
+ case AXTextSelectionGranularityDocument:
|
||||
+ return "AXTextSelectionGranularityDocument";
|
||||
+ case AXTextSelectionGranularityAll:
|
||||
+ return "AXTextSelectionGranularityAll";
|
||||
+ }
|
||||
+
|
||||
+ return "";
|
||||
+}
|
||||
+
|
||||
+const char* ToString(AXTextEditType type) {
|
||||
+ switch (type) {
|
||||
+ case AXTextEditTypeUnknown:
|
||||
+ return "AXTextEditTypeUnknown";
|
||||
+ case AXTextEditTypeDelete:
|
||||
+ return "AXTextEditTypeDelete";
|
||||
+ case AXTextEditTypeInsert:
|
||||
+ return "AXTextEditTypeInsert";
|
||||
+ case AXTextEditTypeTyping:
|
||||
+ return "AXTextEditTypeTyping";
|
||||
+ case AXTextEditTypeDictation:
|
||||
+ return "AXTextEditTypeDictation";
|
||||
+ case AXTextEditTypeCut:
|
||||
+ return "AXTextEditTypeCut";
|
||||
+ case AXTextEditTypePaste:
|
||||
+ return "AXTextEditTypePaste";
|
||||
+ case AXTextEditTypeAttributesChange:
|
||||
+ return "AXTextEditTypeAttributesChange";
|
||||
+ }
|
||||
+
|
||||
+ return "";
|
||||
+}
|
||||
+
|
||||
+} // namespace ui
|
||||
115
patches/chromium/cherry-pick-8f5a08079948.patch
Normal file
115
patches/chromium/cherry-pick-8f5a08079948.patch
Normal file
@@ -0,0 +1,115 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Martin Robinson <mrobinson@igalia.com>
|
||||
Date: Mon, 26 Oct 2020 23:12:55 +0000
|
||||
Subject: Expose a11y popup menu type via the name "AXPopupValue" on Mac
|
||||
|
||||
This value was exposed via the name "AXHasPopupValue", while WebKit
|
||||
exposes this value via "AXPopupValue." Fix the attribute name, which in
|
||||
turn fixes an issue with how popup buttons are announced in VoiceOver.
|
||||
|
||||
Bug: 1129678
|
||||
Change-Id: Iede26b2fd6ddb9d7717fcb47fd1dd1cce5b74075
|
||||
AX-Relnotes: Fix an issue with the announcement of popup buttons in VoiceOver.
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2489985
|
||||
Commit-Queue: Aaron Leventhal <aleventhal@chromium.org>
|
||||
Reviewed-by: Aaron Leventhal <aleventhal@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/master@{#820964}
|
||||
|
||||
diff --git a/content/browser/accessibility/browser_accessibility_cocoa.mm b/content/browser/accessibility/browser_accessibility_cocoa.mm
|
||||
index 3abd9933ab325bda6cb486a41fd6c8b246aa8e71..17aa494d6473866721a8cc5f4c5c646af9307d93 100644
|
||||
--- a/content/browser/accessibility/browser_accessibility_cocoa.mm
|
||||
+++ b/content/browser/accessibility/browser_accessibility_cocoa.mm
|
||||
@@ -92,7 +92,7 @@
|
||||
@"AXFocusableAncestor";
|
||||
NSString* const NSAccessibilityGrabbedAttribute = @"AXGrabbed";
|
||||
NSString* const NSAccessibilityHasPopupAttribute = @"AXHasPopup";
|
||||
-NSString* const NSAccessibilityHasPopupValueAttribute = @"AXHasPopupValue";
|
||||
+NSString* const NSAccessibilityPopupValueAttribute = @"AXPopupValue";
|
||||
NSString* const NSAccessibilityHighestEditableAncestorAttribute =
|
||||
@"AXHighestEditableAncestor";
|
||||
NSString* const NSAccessibilityInvalidAttribute = @"AXInvalid";
|
||||
@@ -836,7 +836,7 @@ + (void)initialize {
|
||||
{NSAccessibilityGrabbedAttribute, @"grabbed"},
|
||||
{NSAccessibilityHeaderAttribute, @"header"},
|
||||
{NSAccessibilityHasPopupAttribute, @"hasPopup"},
|
||||
- {NSAccessibilityHasPopupValueAttribute, @"hasPopupValue"},
|
||||
+ {NSAccessibilityPopupValueAttribute, @"popupValue"},
|
||||
{NSAccessibilityHelpAttribute, @"help"},
|
||||
{NSAccessibilityHighestEditableAncestorAttribute,
|
||||
@"highestEditableAncestor"},
|
||||
@@ -1411,7 +1411,7 @@ - (NSNumber*)hasPopup {
|
||||
return @(_owner->HasIntAttribute(ax::mojom::IntAttribute::kHasPopup));
|
||||
}
|
||||
|
||||
-- (NSString*)hasPopupValue {
|
||||
+- (NSString*)popupValue {
|
||||
if (![self instanceActive])
|
||||
return nil;
|
||||
int hasPopup = _owner->GetIntAttribute(ax::mojom::IntAttribute::kHasPopup);
|
||||
@@ -3586,7 +3586,7 @@ - (NSArray*)accessibilityAttributeNames {
|
||||
|
||||
if (_owner->HasIntAttribute(ax::mojom::IntAttribute::kHasPopup)) {
|
||||
[ret addObjectsFromArray:@[
|
||||
- NSAccessibilityHasPopupAttribute, NSAccessibilityHasPopupValueAttribute
|
||||
+ NSAccessibilityHasPopupAttribute, NSAccessibilityPopupValueAttribute
|
||||
]];
|
||||
}
|
||||
|
||||
diff --git a/content/test/data/accessibility/aria/aria-haspopup-expected-mac.txt b/content/test/data/accessibility/aria/aria-haspopup-expected-mac.txt
|
||||
index e988b9fe7d061fa3aa6930ababa4f32c095bd8a3..de0f31f2065490d6085027e2800706a3926bcf5b 100644
|
||||
--- a/content/test/data/accessibility/aria/aria-haspopup-expected-mac.txt
|
||||
+++ b/content/test/data/accessibility/aria/aria-haspopup-expected-mac.txt
|
||||
@@ -1,10 +1,10 @@
|
||||
AXWebArea
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='menu'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='menu'
|
||||
++AXPopUpButton
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='menu'
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='listbox'
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='grid'
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='dialog'
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='menu'
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='listbox'
|
||||
-++AXPopUpButton AXHasPopup=1 AXHasPopupValue='listbox'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='menu'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='listbox'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='grid'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='dialog'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='menu'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='listbox'
|
||||
+++AXPopUpButton AXHasPopup=1 AXPopupValue='listbox'
|
||||
diff --git a/content/test/data/accessibility/aria/aria-haspopup.html b/content/test/data/accessibility/aria/aria-haspopup.html
|
||||
index 026397c4e9ad805f5facfc499cc986cd0763aef3..7d982745e89b8346bf71eee90110fe6a96648629 100644
|
||||
--- a/content/test/data/accessibility/aria/aria-haspopup.html
|
||||
+++ b/content/test/data/accessibility/aria/aria-haspopup.html
|
||||
@@ -3,7 +3,7 @@
|
||||
@MAC-ALLOW:AXShowMenu*
|
||||
@MAC-ALLOW:AXPress*
|
||||
@MAC-ALLOW:AXHasPopup
|
||||
-@MAC-ALLOW:AXHasPopupValue
|
||||
+@MAC-ALLOW:AXPopupValue
|
||||
@WIN-ALLOW:EXPANDED*
|
||||
@WIN-ALLOW:HASPOPUP*
|
||||
@WIN-ALLOW:haspopup*
|
||||
diff --git a/content/test/data/accessibility/html/input-suggestions-source-element-expected-mac.txt b/content/test/data/accessibility/html/input-suggestions-source-element-expected-mac.txt
|
||||
index 4b0bde650deed34edead37ffabd3eda2d7a82b49..38ca751991f564f9a24ba131652014f161d4b996 100644
|
||||
--- a/content/test/data/accessibility/html/input-suggestions-source-element-expected-mac.txt
|
||||
+++ b/content/test/data/accessibility/html/input-suggestions-source-element-expected-mac.txt
|
||||
@@ -1,3 +1,3 @@
|
||||
AXWebArea AXRoleDescription='HTML content'
|
||||
++AXGroup AXRoleDescription='group'
|
||||
-++++AXComboBox AXAutocompleteValue='list' AXHasPopup=1 AXHasPopupValue='listbox' AXRoleDescription='combo box'
|
||||
+++++AXComboBox AXAutocompleteValue='list' AXHasPopup=1 AXPopupValue='listbox' AXRoleDescription='combo box'
|
||||
diff --git a/content/test/data/accessibility/html/input-suggestions-source-element.html b/content/test/data/accessibility/html/input-suggestions-source-element.html
|
||||
index 039ac5855e1122b34dad0c1fae42f798791b7890..d024f17de3d3b04029c532e616140e20afa87549 100644
|
||||
--- a/content/test/data/accessibility/html/input-suggestions-source-element.html
|
||||
+++ b/content/test/data/accessibility/html/input-suggestions-source-element.html
|
||||
@@ -1,7 +1,7 @@
|
||||
<!--
|
||||
@MAC-ALLOW:AXRoleDescription
|
||||
@MAC-ALLOW:AXHasPopup
|
||||
-@MAC-ALLOW:AXHasPopupValue
|
||||
+@MAC-ALLOW:AXPopupValue
|
||||
@WIN-ALLOW:haspopup*
|
||||
@AURALINUX-ALLOW:haspopup*
|
||||
@AURALINUX-ALLOW:supports-autocompletion
|
||||
@@ -7,7 +7,7 @@ spellchecker uses a few IDS_ resources. We need to load these from
|
||||
Electrons grit header instead of Chromes
|
||||
|
||||
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
|
||||
index 4dd4cabe46e19061e944174a64ef7b6ee5b49737..d4272b70b2c6861ff6003bd000ab7639caf41160 100644
|
||||
index 62ddc2d8548bcac4d55bbc7283426c426fecf254..b0eb803cd132390d492b82600d8629e2c43cc72e 100644
|
||||
--- a/chrome/browser/BUILD.gn
|
||||
+++ b/chrome/browser/BUILD.gn
|
||||
@@ -5960,6 +5960,7 @@ static_library("browser") {
|
||||
|
||||
@@ -75,10 +75,10 @@ index aaa9ad1d58d7f584cbf391e0cb0a61a0d25d5c69..5b02834b5ecaf4bcb7e051d9ee98cd5e
|
||||
// Used by WebView to sample crashes without generating the unwanted dumps. If
|
||||
// the returned value is less than 100, crash dumping will be sampled to that
|
||||
diff --git a/components/crash/core/app/crashpad_mac.mm b/components/crash/core/app/crashpad_mac.mm
|
||||
index aa7c3302f2e3fc7129381b355ed4fa1bc386f8cb..d7dd05118f3c90f5ab570f59685c43bd180885e5 100644
|
||||
index eb675321436a53e82432144d43f258bed6e938e2..ae2032a12eac7c789d790e53857d11ba3e03d510 100644
|
||||
--- a/components/crash/core/app/crashpad_mac.mm
|
||||
+++ b/components/crash/core/app/crashpad_mac.mm
|
||||
@@ -71,6 +71,8 @@
|
||||
@@ -77,6 +77,8 @@
|
||||
} // @autoreleasepool
|
||||
return process_annotations;
|
||||
}();
|
||||
@@ -87,7 +87,7 @@ index aa7c3302f2e3fc7129381b355ed4fa1bc386f8cb..d7dd05118f3c90f5ab570f59685c43bd
|
||||
return annotations;
|
||||
}
|
||||
|
||||
@@ -141,6 +143,13 @@ void DumpProcessWithoutCrashing(task_t task_port) {
|
||||
@@ -147,6 +149,13 @@ void DumpProcessWithoutCrashing(task_t task_port) {
|
||||
|
||||
std::vector<std::string> arguments;
|
||||
|
||||
|
||||
@@ -46,10 +46,10 @@ index 29fed88f36ecaf0c022d2a5a2f3ed7db66e2c96c..e1aadf680851d23cc2ce6023b20f496e
|
||||
// navigation. Now we know that the renderer has updated its state accordingly
|
||||
// and it is safe to also clear the browser side history.
|
||||
diff --git a/ui/base/clipboard/clipboard_win.cc b/ui/base/clipboard/clipboard_win.cc
|
||||
index 8d0e981720bb4a0ec4c42f389275fd375c1b1e43..9473f4f7819667d7cc4bfa514955b4e65d69c1c9 100644
|
||||
index 260858079f2f511cb6eb513c37eec8a0d38b55a0..39f2cef515c4a1724aeb6f3660730221f251e51e 100644
|
||||
--- a/ui/base/clipboard/clipboard_win.cc
|
||||
+++ b/ui/base/clipboard/clipboard_win.cc
|
||||
@@ -850,10 +850,10 @@ SkBitmap ClipboardWin::ReadImageInternal(ClipboardBuffer buffer) const {
|
||||
@@ -817,10 +817,10 @@ SkBitmap ClipboardWin::ReadImageInternal(ClipboardBuffer buffer) const {
|
||||
|
||||
void ClipboardWin::WriteToClipboard(ClipboardFormatType format, HANDLE handle) {
|
||||
UINT cf_format = format.ToFormatEtc().cfFormat;
|
||||
|
||||
@@ -35,10 +35,10 @@ index bb7ff971823db702991a3167b5b0ac1b3dd4a6c9..e5afad68300bdd6ac109b3c9b94d3c20
|
||||
|
||||
// If we are likely to software composite the resource, we use sRGB because
|
||||
diff --git a/cc/trees/layer_tree_settings.h b/cc/trees/layer_tree_settings.h
|
||||
index 64fe3c1c3f60758c25f9bc88daedf35633508aa7..a2e9ebb3dc31b4e8665376ffb1f805c6093bce5c 100644
|
||||
index e5a58655788be3bd1c86e238e34bbadf8012bc48..2a2de85e89a8a0f803b7c987620a259b26281d8d 100644
|
||||
--- a/cc/trees/layer_tree_settings.h
|
||||
+++ b/cc/trees/layer_tree_settings.h
|
||||
@@ -98,6 +98,8 @@ class CC_EXPORT LayerTreeSettings {
|
||||
@@ -99,6 +99,8 @@ class CC_EXPORT LayerTreeSettings {
|
||||
bool use_rgba_4444 = false;
|
||||
bool unpremultiply_and_dither_low_bit_depth_tiles = false;
|
||||
|
||||
@@ -81,7 +81,7 @@ index 5575c90cf05ec43bc787711f1d44b8dd58ead99c..a544d836159522751c1262370d8c1562
|
||||
!command_line->HasSwitch(switches::kUIDisablePartialSwap);
|
||||
#if defined(OS_APPLE)
|
||||
diff --git a/components/viz/service/display/gl_renderer.cc b/components/viz/service/display/gl_renderer.cc
|
||||
index e5179c0e4971bc593860b1e5b606fec651e6756b..fc7c09d19c60d498885d3e4f1a4a4ac995060e3d 100644
|
||||
index e5179c0e4971bc593860b1e5b606fec651e6756b..1f3343600c6203c84586f6e4dcdc5976d9cf44f3 100644
|
||||
--- a/components/viz/service/display/gl_renderer.cc
|
||||
+++ b/components/viz/service/display/gl_renderer.cc
|
||||
@@ -86,6 +86,9 @@
|
||||
@@ -121,7 +121,7 @@ index e5179c0e4971bc593860b1e5b606fec651e6756b..fc7c09d19c60d498885d3e4f1a4a4ac9
|
||||
tint_gl_composited_content_,
|
||||
ShouldApplyRoundedCorner(quad)),
|
||||
- CurrentRenderPassColorSpace(), CurrentRenderPassColorSpace());
|
||||
+ PATCH_CS(CurrentRenderPassColorSpace()),
|
||||
+ PATCH_CS(CurrentRenderPassColorSpace()),
|
||||
+ PATCH_CS(CurrentRenderPassColorSpace()));
|
||||
|
||||
gfx::ColorSpace quad_color_space = gfx::ColorSpace::CreateSRGB();
|
||||
@@ -229,7 +229,7 @@ index e5179c0e4971bc593860b1e5b606fec651e6756b..fc7c09d19c60d498885d3e4f1a4a4ac9
|
||||
+
|
||||
+#undef PATCH_CS
|
||||
diff --git a/content/browser/gpu/gpu_process_host.cc b/content/browser/gpu/gpu_process_host.cc
|
||||
index de4eadaf7be8be95fc0ad454d66c8ec397cfc20a..d4c451ea992490befb34b3690a98d968bb50b899 100644
|
||||
index d7439d851d6c027a20bf09a258603939b5c19f1e..72162a468f63543c508a3d34e357be1dfcdeba51 100644
|
||||
--- a/content/browser/gpu/gpu_process_host.cc
|
||||
+++ b/content/browser/gpu/gpu_process_host.cc
|
||||
@@ -221,6 +221,7 @@ GpuTerminationStatus ConvertToGpuTerminationStatus(
|
||||
@@ -327,7 +327,7 @@ index 95e818f1406eb1a73a746b73a608137ab5c6e894..6e1d3b3b1add4eda90560856b6915d46
|
||||
// is what the renderer uses if its not threaded.
|
||||
settings.enable_checker_imaging =
|
||||
diff --git a/ui/gfx/mac/io_surface.cc b/ui/gfx/mac/io_surface.cc
|
||||
index 3e80dc40c83bd4eb96f9029643c4a0856253494e..8e19a78f106daf4185ee69970832ea1237d3d03b 100644
|
||||
index ec83bd814a0d5d6802b349d08b22013a0d99e015..043e769e7db9773de2054dcc66f13f062823f58f 100644
|
||||
--- a/ui/gfx/mac/io_surface.cc
|
||||
+++ b/ui/gfx/mac/io_surface.cc
|
||||
@@ -18,6 +18,7 @@
|
||||
@@ -353,7 +353,7 @@ index 3e80dc40c83bd4eb96f9029643c4a0856253494e..8e19a78f106daf4185ee69970832ea12
|
||||
// Allow but ignore invalid color spaces.
|
||||
if (!color_space.IsValid())
|
||||
return true;
|
||||
@@ -265,6 +274,15 @@ IOSurfaceRef CreateIOSurface(const gfx::Size& size,
|
||||
@@ -286,6 +295,15 @@ IOSurfaceRef CreateIOSurface(const gfx::Size& size,
|
||||
DCHECK_EQ(kIOReturnSuccess, r);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 529a5b71b9987be21bfb1494b420ef88bbd17f7a..19d7421e1de9680a8b1364f010e19cc95ef7325a 100644
|
||||
index 1ff402be7362bd1dd64f3ffb88081f6cfac328d8..aa00ff974485d3093f05b0cdaeb6c8ed865015e3 100644
|
||||
--- a/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
|
||||
@@ -692,6 +692,9 @@ void RenderWidgetHostImpl::WasHidden() {
|
||||
@@ -693,6 +693,9 @@ void RenderWidgetHostImpl::WasHidden() {
|
||||
if (is_hidden_)
|
||||
return;
|
||||
|
||||
@@ -20,7 +20,7 @@ index 529a5b71b9987be21bfb1494b420ef88bbd17f7a..19d7421e1de9680a8b1364f010e19cc9
|
||||
blink::mojom::PointerLockResult::kWrongDocument);
|
||||
|
||||
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
|
||||
index 9d7d10136a683620d6fb67246aae51df982930d0..c119dd8916fe2fbde97ce91cce6f788fc62f3534 100644
|
||||
index 9f02961f7ef63d672b918439bb84121ea22e2931..66fd1cbec90c1ecc1f0f1a9722367a58d66c64fd 100644
|
||||
--- a/content/browser/renderer_host/render_widget_host_impl.h
|
||||
+++ b/content/browser/renderer_host/render_widget_host_impl.h
|
||||
@@ -173,6 +173,9 @@ class CONTENT_EXPORT RenderWidgetHostImpl
|
||||
|
||||
38
patches/chromium/enable_reset_aspect_ratio.patch
Normal file
38
patches/chromium/enable_reset_aspect_ratio.patch
Normal file
@@ -0,0 +1,38 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Cheng Zhao <zcbenz@gmail.com>
|
||||
Date: Thu, 4 Oct 2018 14:57:02 -0700
|
||||
Subject: feat: enable setting aspect ratio to 0
|
||||
|
||||
Make SetAspectRatio accept 0 as valid input, which would reset to null.
|
||||
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
index 3b132d4d7618e11b851b315e82fa67442c852fb3..af2c2f2bbc1f52f4455fb973ab2fc0d0dd013ca5 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
@@ -484,7 +484,7 @@ void DesktopWindowTreeHostWin::SetOpacity(float opacity) {
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::SetAspectRatio(const gfx::SizeF& aspect_ratio) {
|
||||
- DCHECK(!aspect_ratio.IsEmpty());
|
||||
+ DCHECK_NE(aspect_ratio.height(), 0);
|
||||
message_handler_->SetAspectRatio(aspect_ratio.width() /
|
||||
aspect_ratio.height());
|
||||
}
|
||||
diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc
|
||||
index 42eeb16a3d59de7e37eb858a982cc3534ab7cc51..ce3672e4d5cee51651bc6f8c294b27f0a9e01682 100644
|
||||
--- a/ui/views/win/hwnd_message_handler.cc
|
||||
+++ b/ui/views/win/hwnd_message_handler.cc
|
||||
@@ -920,8 +920,11 @@ void HWNDMessageHandler::SetFullscreen(bool fullscreen) {
|
||||
}
|
||||
|
||||
void HWNDMessageHandler::SetAspectRatio(float aspect_ratio) {
|
||||
- // If the aspect ratio is not in the valid range, do nothing.
|
||||
- DCHECK_GT(aspect_ratio, 0.0f);
|
||||
+ // If the aspect ratio is 0, reset it to null.
|
||||
+ if (aspect_ratio == 0.0f) {
|
||||
+ aspect_ratio_.reset();
|
||||
+ return;
|
||||
+ }
|
||||
|
||||
aspect_ratio_ = aspect_ratio;
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Marijn Kruisselbrink <mek@google.com>
|
||||
Date: Fri, 18 Dec 2020 00:49:47 +0000
|
||||
Subject: Ensure that showSaveFilePicker always shows the extension on Mac.
|
||||
|
||||
While not a very strong security boundary, making it possible for
|
||||
users to know what extension a file will be saved with is a good idea.
|
||||
|
||||
This also fixes support for compound extensions with the File System
|
||||
Access API (i.e. ".tar.gz"). The mac file dialog already had a
|
||||
workaround if the default path ended in such an extension, but the
|
||||
same problem occurs if the file type filters include a type with a
|
||||
compound extension.
|
||||
|
||||
(cherry picked from commit 5bf1c6c22201394711a9067d2c970ab9f55555d0)
|
||||
|
||||
Bug: 1137247
|
||||
Change-Id: I492bf36baced3de044b8fed5d57fc7b9b5b64400
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2582842
|
||||
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
|
||||
Reviewed-by: Avi Drissman <avi@chromium.org>
|
||||
Commit-Queue: Marijn Kruisselbrink <mek@chromium.org>
|
||||
Cr-Original-Commit-Position: refs/heads/master@{#835803}
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2597785
|
||||
Reviewed-by: Marijn Kruisselbrink <mek@chromium.org>
|
||||
Cr-Commit-Position: refs/branch-heads/4324@{#1044}
|
||||
Cr-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102}
|
||||
|
||||
diff --git a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm b/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm
|
||||
index 7d89bebe61156f266805f0abc81bfafde2c70653..a621a83f29461f8490fc8c0a859ce841be1e0e6f 100644
|
||||
--- a/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm
|
||||
+++ b/components/remote_cocoa/app_shim/select_file_dialog_bridge.mm
|
||||
@@ -206,6 +206,8 @@ - (void)popupAction:(id)sender {
|
||||
}
|
||||
}
|
||||
|
||||
+ const bool keep_extension_visible =
|
||||
+ file_types ? file_types->keep_extension_visible : false;
|
||||
if (type_ != SelectFileDialogType::kFolder &&
|
||||
type_ != SelectFileDialogType::kUploadFolder &&
|
||||
type_ != SelectFileDialogType::kExistingFolder) {
|
||||
@@ -227,7 +229,7 @@ - (void)popupAction:(id)sender {
|
||||
// this by never hiding extensions in that case.
|
||||
base::FilePath::StringType penultimate_extension =
|
||||
default_path.RemoveFinalExtension().FinalExtension();
|
||||
- if (!penultimate_extension.empty()) {
|
||||
+ if (!penultimate_extension.empty() || keep_extension_visible) {
|
||||
[dialog setExtensionHidden:NO];
|
||||
} else {
|
||||
[dialog setExtensionHidden:YES];
|
||||
diff --git a/components/remote_cocoa/common/select_file_dialog.mojom b/components/remote_cocoa/common/select_file_dialog.mojom
|
||||
index 31e91923ada59f308d4132dd269e65c2d342fa37..916eb21df6438997e660de758d8c7966dd5040f0 100644
|
||||
--- a/components/remote_cocoa/common/select_file_dialog.mojom
|
||||
+++ b/components/remote_cocoa/common/select_file_dialog.mojom
|
||||
@@ -28,6 +28,9 @@ struct SelectFileTypeInfo {
|
||||
|
||||
// Specifies whether or not there is be a filter added for all files.
|
||||
bool include_all_files;
|
||||
+
|
||||
+ // Specifies whether the (save) file dialog should keep the extension visible.
|
||||
+ bool keep_extension_visible;
|
||||
};
|
||||
|
||||
// The interface to a file selection (Save As, Upload, etc) dialog.
|
||||
diff --git a/content/browser/file_system_access/file_system_chooser.cc b/content/browser/file_system_access/file_system_chooser.cc
|
||||
index 0a8248badf55121430b4a38b65b570213c2ad3f5..0d3936538e60a26e620e7794b055221c782e062d 100644
|
||||
--- a/content/browser/file_system_access/file_system_chooser.cc
|
||||
+++ b/content/browser/file_system_access/file_system_chooser.cc
|
||||
@@ -173,6 +173,7 @@ ui::SelectFileDialog::FileTypeInfo ConvertAcceptsToFileTypeInfo(
|
||||
file_types.include_all_files = true;
|
||||
|
||||
file_types.allowed_paths = ui::SelectFileDialog::FileTypeInfo::ANY_PATH;
|
||||
+ file_types.keep_extension_visible = true;
|
||||
|
||||
return file_types;
|
||||
}
|
||||
diff --git a/ui/shell_dialogs/select_file_dialog.cc b/ui/shell_dialogs/select_file_dialog.cc
|
||||
index 96e589f89803bf9c5c7e0bd3e72f33b44791c975..6e76013970887a6de4e6ea1136f0e98840aaed73 100644
|
||||
--- a/ui/shell_dialogs/select_file_dialog.cc
|
||||
+++ b/ui/shell_dialogs/select_file_dialog.cc
|
||||
@@ -26,8 +26,7 @@ ui::SelectFileDialogFactory* dialog_factory_ = NULL;
|
||||
|
||||
namespace ui {
|
||||
|
||||
-SelectFileDialog::FileTypeInfo::FileTypeInfo()
|
||||
- : include_all_files(false), allowed_paths(NATIVE_PATH) {}
|
||||
+SelectFileDialog::FileTypeInfo::FileTypeInfo() = default;
|
||||
|
||||
SelectFileDialog::FileTypeInfo::FileTypeInfo(const FileTypeInfo& other) =
|
||||
default;
|
||||
diff --git a/ui/shell_dialogs/select_file_dialog.h b/ui/shell_dialogs/select_file_dialog.h
|
||||
index 832c12fb4a687fb1473f636745e383fd659e3341..0bcc440b21c1729dac4bfba03cabf87ec2aa6d83 100644
|
||||
--- a/ui/shell_dialogs/select_file_dialog.h
|
||||
+++ b/ui/shell_dialogs/select_file_dialog.h
|
||||
@@ -135,7 +135,13 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
||||
std::vector<base::string16> extension_description_overrides;
|
||||
|
||||
// Specifies whether there will be a filter added for all files (i.e. *.*).
|
||||
- bool include_all_files;
|
||||
+ bool include_all_files = false;
|
||||
+
|
||||
+ // Some implementations by default hide the extension of a file, in
|
||||
+ // particular in a save file dialog. If this is set to true, where
|
||||
+ // supported, the save file dialog will instead keep the file extension
|
||||
+ // visible.
|
||||
+ bool keep_extension_visible = false;
|
||||
|
||||
// Specifies which type of paths the caller can handle.
|
||||
enum AllowedPaths {
|
||||
@@ -153,7 +159,7 @@ class SHELL_DIALOGS_EXPORT SelectFileDialog
|
||||
// docs.google.com URL.
|
||||
ANY_PATH_OR_URL
|
||||
};
|
||||
- AllowedPaths allowed_paths;
|
||||
+ AllowedPaths allowed_paths = NATIVE_PATH;
|
||||
};
|
||||
|
||||
// Returns a file path with a base name at most 255 characters long. This
|
||||
diff --git a/ui/shell_dialogs/select_file_dialog_mac.mm b/ui/shell_dialogs/select_file_dialog_mac.mm
|
||||
index 9b16bab3b3b1889f182cb61903f8003310a503be..2fcf6f46aa8d44084b55569e431f669a700236c7 100644
|
||||
--- a/ui/shell_dialogs/select_file_dialog_mac.mm
|
||||
+++ b/ui/shell_dialogs/select_file_dialog_mac.mm
|
||||
@@ -135,6 +135,8 @@
|
||||
mojo_file_types->extension_description_overrides =
|
||||
file_types->extension_description_overrides;
|
||||
mojo_file_types->include_all_files = file_types->include_all_files;
|
||||
+ mojo_file_types->keep_extension_visible =
|
||||
+ file_types->keep_extension_visible;
|
||||
}
|
||||
|
||||
auto callback = base::BindOnce(&SelectFileDialogImpl::FileWasSelected,
|
||||
diff --git a/ui/shell_dialogs/select_file_dialog_mac_unittest.mm b/ui/shell_dialogs/select_file_dialog_mac_unittest.mm
|
||||
index 8d983f5050a8de5a8fe469b37bab0c2dc007a0f2..8b65d667414c1bdfb7a4eace0892b9b3f4b7d372 100644
|
||||
--- a/ui/shell_dialogs/select_file_dialog_mac_unittest.mm
|
||||
+++ b/ui/shell_dialogs/select_file_dialog_mac_unittest.mm
|
||||
@@ -501,8 +501,29 @@ void ResetDialog() {
|
||||
EXPECT_FALSE([panel isExtensionHidden]);
|
||||
}
|
||||
|
||||
-// Test to ensure lifetime is sound if a reference to the panel outlives the
|
||||
-// delegate.
|
||||
+// Verify that the file dialog does not hide extension when the
|
||||
+// `keep_extension_visible` flag is set to true.
|
||||
+TEST_F(SelectFileDialogMacTest, KeepExtensionVisible) {
|
||||
+ const std::string extensions_arr[][2] = {{"html", "htm"}, {"jpeg", "jpg"}};
|
||||
+
|
||||
+ SelectFileDialog::FileTypeInfo file_type_info;
|
||||
+ file_type_info.extensions.push_back(
|
||||
+ GetVectorFromArray<std::string>(extensions_arr[0]));
|
||||
+ file_type_info.extensions.push_back(
|
||||
+ GetVectorFromArray<std::string>(extensions_arr[1]));
|
||||
+ file_type_info.keep_extension_visible = true;
|
||||
+
|
||||
+ FileDialogArguments args(GetDefaultArguments());
|
||||
+ args.file_types = &file_type_info;
|
||||
+
|
||||
+ SelectFileWithParams(args);
|
||||
+ NSSavePanel* panel = GetPanel();
|
||||
+ EXPECT_FALSE([panel canSelectHiddenExtension]);
|
||||
+ EXPECT_FALSE([panel isExtensionHidden]);
|
||||
+}
|
||||
+
|
||||
+// Test to ensure lifetime is sound if a reference to
|
||||
+// the panel outlives the delegate.
|
||||
TEST_F(SelectFileDialogMacTest, Lifetime) {
|
||||
base::scoped_nsobject<NSSavePanel> panel;
|
||||
@autoreleasepool {
|
||||
@@ -62,7 +62,7 @@ index e2ca7dd198390ebc923facd690904faeb8300e62..f2f53775abb0ba2954cbb3ec431d84c3
|
||||
};
|
||||
|
||||
diff --git a/ui/native_theme/native_theme_win.cc b/ui/native_theme/native_theme_win.cc
|
||||
index eaf817b4b5400fa4d2b6af7c86ac69aabf030a38..0f702be38f8e015c4e61677db5cf566259650dc6 100644
|
||||
index fd075a6f8a1357e7456a97d37af339848635a102..54fa642f614acd221ceaeee0938506e6fea41b1a 100644
|
||||
--- a/ui/native_theme/native_theme_win.cc
|
||||
+++ b/ui/native_theme/native_theme_win.cc
|
||||
@@ -737,6 +737,8 @@ bool NativeThemeWin::ShouldUseDarkColors() const {
|
||||
|
||||
@@ -7,10 +7,10 @@ Subject:
|
||||
Disable usage of pthread_fchdir_np and pthread_chdir_np in MAS builds.
|
||||
|
||||
diff --git a/base/process/launch_mac.cc b/base/process/launch_mac.cc
|
||||
index 2841b7735430ca3fb03fff7a737b393a8d168747..e356bfe7fdfc11b3b1df429337cf460037788dd8 100644
|
||||
index c074f0d73eab1993b580f317c42480688e6669e6..2a86b0c6897efd10c483d0816b3f7a54f1e13d2a 100644
|
||||
--- a/base/process/launch_mac.cc
|
||||
+++ b/base/process/launch_mac.cc
|
||||
@@ -26,8 +26,10 @@ extern "C" {
|
||||
@@ -27,8 +27,10 @@ extern "C" {
|
||||
// descriptor. libpthread only exposes a syscall wrapper starting in
|
||||
// macOS 10.12, but the system call dates back to macOS 10.5. On older OSes,
|
||||
// the syscall is issued directly.
|
||||
@@ -21,7 +21,7 @@ index 2841b7735430ca3fb03fff7a737b393a8d168747..e356bfe7fdfc11b3b1df429337cf4600
|
||||
|
||||
int responsibility_spawnattrs_setdisclaim(posix_spawnattr_t attrs, int disclaim)
|
||||
API_AVAILABLE(macosx(10.14));
|
||||
@@ -102,21 +104,29 @@ class PosixSpawnFileActions {
|
||||
@@ -103,21 +105,29 @@ class PosixSpawnFileActions {
|
||||
};
|
||||
|
||||
int ChangeCurrentThreadDirectory(const char* path) {
|
||||
@@ -51,7 +51,7 @@ index 2841b7735430ca3fb03fff7a737b393a8d168747..e356bfe7fdfc11b3b1df429337cf4600
|
||||
}
|
||||
|
||||
struct GetAppOutputOptions {
|
||||
@@ -232,11 +242,13 @@ Process LaunchProcess(const std::vector<std::string>& argv,
|
||||
@@ -233,11 +243,13 @@ Process LaunchProcess(const std::vector<std::string>& argv,
|
||||
file_actions.Inherit(STDERR_FILENO);
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user