mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
330 Commits
v16.0.0-al
...
16-x-y
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
10ce3725c2 | ||
|
|
83f6303394 | ||
|
|
d1b75716ad | ||
|
|
1c6d5ea377 | ||
|
|
547d144706 | ||
|
|
38dd2a7c91 | ||
|
|
f464c58fdc | ||
|
|
4396c8782e | ||
|
|
9590295102 | ||
|
|
10036238e0 | ||
|
|
1cd89aabc8 | ||
|
|
1f15e8319c | ||
|
|
ce93e5fadb | ||
|
|
62124241c4 | ||
|
|
8f0526f9c0 | ||
|
|
b1ddee468d | ||
|
|
3d82701377 | ||
|
|
a6cee3acd5 | ||
|
|
086d40d566 | ||
|
|
1234a27369 | ||
|
|
93c0c9da82 | ||
|
|
c955f9347a | ||
|
|
67729c767b | ||
|
|
6f467871f2 | ||
|
|
a0c4f8fdf2 | ||
|
|
c7006b5740 | ||
|
|
6d561f464d | ||
|
|
3de21c4909 | ||
|
|
8221ab7a9b | ||
|
|
2e737734d5 | ||
|
|
a3680b0119 | ||
|
|
0bdca55bd6 | ||
|
|
e29c0ce313 | ||
|
|
4de4d8c23e | ||
|
|
ab1df46536 | ||
|
|
6b04cce91a | ||
|
|
32f91f19e0 | ||
|
|
d71d648669 | ||
|
|
ae5367293c | ||
|
|
730c7d3b10 | ||
|
|
db4fdae287 | ||
|
|
05828cd284 | ||
|
|
6e59d52787 | ||
|
|
e20fe5a17b | ||
|
|
c59fd3fb83 | ||
|
|
62f7c777b7 | ||
|
|
44de2c9596 | ||
|
|
26fdde4dc8 | ||
|
|
916acaa4f7 | ||
|
|
66fa9f6066 | ||
|
|
5c28bdc70f | ||
|
|
4cd6426bae | ||
|
|
1e949f1cac | ||
|
|
faec32b419 | ||
|
|
9554722913 | ||
|
|
f7d172932f | ||
|
|
b12d8a358d | ||
|
|
cbe137e60b | ||
|
|
01a49e799f | ||
|
|
504daa7b8d | ||
|
|
1603cd4ccf | ||
|
|
41e460c44f | ||
|
|
1d780d716e | ||
|
|
9a564930a1 | ||
|
|
a6c71cc914 | ||
|
|
7d979882fb | ||
|
|
66b255d1f5 | ||
|
|
3d7bea1f53 | ||
|
|
50fb4e245d | ||
|
|
b76b7baab8 | ||
|
|
a4fd0d3351 | ||
|
|
40fef39431 | ||
|
|
3da3edd613 | ||
|
|
ecc35a43e8 | ||
|
|
c01ca5edaa | ||
|
|
4f16275d16 | ||
|
|
8f2d8e0336 | ||
|
|
9ca00c6afe | ||
|
|
c163154cc9 | ||
|
|
7be0d42658 | ||
|
|
0b06b686d6 | ||
|
|
0dd20a20a0 | ||
|
|
f1d7143310 | ||
|
|
0817274bf9 | ||
|
|
75e48b99a8 | ||
|
|
5dae280b79 | ||
|
|
3f5e040652 | ||
|
|
43d242f4dc | ||
|
|
ba8a1588bf | ||
|
|
9bdb2d9d94 | ||
|
|
53450220c2 | ||
|
|
7761ea6713 | ||
|
|
b31f855ea4 | ||
|
|
7ef2a0d8ed | ||
|
|
ccce1e642b | ||
|
|
2d1398bd44 | ||
|
|
06a3bdadd7 | ||
|
|
8873984bd0 | ||
|
|
9a32bf98dd | ||
|
|
0315f070f4 | ||
|
|
d0131444c1 | ||
|
|
7d8c387666 | ||
|
|
e500406f47 | ||
|
|
dc3232e690 | ||
|
|
6bca1d6622 | ||
|
|
5d5708cf48 | ||
|
|
f8c521d795 | ||
|
|
45e14a5ef5 | ||
|
|
f0fa9b5f56 | ||
|
|
d6ef6c368a | ||
|
|
b5de806b42 | ||
|
|
e6859b8ef3 | ||
|
|
4a28bb2010 | ||
|
|
1c6b450f1e | ||
|
|
d76743fd1c | ||
|
|
df6f78950d | ||
|
|
75191f812f | ||
|
|
679c979f40 | ||
|
|
2b5c403e48 | ||
|
|
a3d4c4e84d | ||
|
|
f75bd998fa | ||
|
|
e0fe73521d | ||
|
|
5a4bdde636 | ||
|
|
cbf5a75572 | ||
|
|
7205c97421 | ||
|
|
f9fb8abe59 | ||
|
|
013b38c964 | ||
|
|
b11b40e640 | ||
|
|
079ec9d9c0 | ||
|
|
8608b785db | ||
|
|
e08c317065 | ||
|
|
b9f4112209 | ||
|
|
32d5cc03ef | ||
|
|
a29a04afaf | ||
|
|
f92f97972a | ||
|
|
23d22d7a5b | ||
|
|
a9fe62cc8f | ||
|
|
8391e5226d | ||
|
|
1082e6c764 | ||
|
|
5bf4483f3b | ||
|
|
d4942d0760 | ||
|
|
88074b0ef7 | ||
|
|
a8a200f4c9 | ||
|
|
d58765681c | ||
|
|
def17fe6f9 | ||
|
|
fc1c566613 | ||
|
|
3c57345f82 | ||
|
|
c2e6903a33 | ||
|
|
1587fbd4c3 | ||
|
|
c190c2120e | ||
|
|
26e4da36e4 | ||
|
|
187dc7b8c0 | ||
|
|
48e78496ce | ||
|
|
984e24ee16 | ||
|
|
33055eb1e0 | ||
|
|
4a7b5aa45b | ||
|
|
77451d2cf3 | ||
|
|
e8f90e57d8 | ||
|
|
0b5c5d97bd | ||
|
|
70c0f06890 | ||
|
|
1413e815ec | ||
|
|
c899294a12 | ||
|
|
736f9f21f1 | ||
|
|
2045d85d54 | ||
|
|
fe842f52b6 | ||
|
|
c5f16f36f0 | ||
|
|
7d5d1a1e1b | ||
|
|
476447c6ea | ||
|
|
0e71144a8f | ||
|
|
d62b02af05 | ||
|
|
f998093fad | ||
|
|
b1ffac5969 | ||
|
|
0bb5f136f4 | ||
|
|
0027bd5979 | ||
|
|
41926c1773 | ||
|
|
bce34419b6 | ||
|
|
702645d84b | ||
|
|
facee77287 | ||
|
|
37ca29a27e | ||
|
|
03381c0281 | ||
|
|
71c8dd7028 | ||
|
|
0294075751 | ||
|
|
39bb2a4cbf | ||
|
|
d8ad2afcad | ||
|
|
9ef4825b77 | ||
|
|
6b0479b556 | ||
|
|
891374e794 | ||
|
|
de8623db8a | ||
|
|
ada1e9137b | ||
|
|
b6492686e4 | ||
|
|
0b57386492 | ||
|
|
ad1101e610 | ||
|
|
86cf473d81 | ||
|
|
26e37db25c | ||
|
|
3ddb61fc3a | ||
|
|
68a512a541 | ||
|
|
adb571bcde | ||
|
|
9183cf6a66 | ||
|
|
c5d5c28a79 | ||
|
|
59aba70e8f | ||
|
|
e640ab47fd | ||
|
|
fff7e18c70 | ||
|
|
569440962d | ||
|
|
bee9ff83dd | ||
|
|
41b7a0c9c2 | ||
|
|
cefa1bb82f | ||
|
|
eb7a897849 | ||
|
|
ac0143af8e | ||
|
|
6dc9d0629a | ||
|
|
187b009a70 | ||
|
|
040910b1d0 | ||
|
|
27a2f89b2b | ||
|
|
a3f736e655 | ||
|
|
8091d72019 | ||
|
|
8a2f487605 | ||
|
|
66dec80e48 | ||
|
|
ca946ac23e | ||
|
|
73ba0cb46b | ||
|
|
cf0b1e1b68 | ||
|
|
371a61dc59 | ||
|
|
4aef2d7cc3 | ||
|
|
ebd3360601 | ||
|
|
f1017d553d | ||
|
|
f6c16b4966 | ||
|
|
e029f65fe1 | ||
|
|
a2b79a2450 | ||
|
|
51d448c22d | ||
|
|
28230978e8 | ||
|
|
93ae6e3f73 | ||
|
|
b088673a5d | ||
|
|
a66d541471 | ||
|
|
beac90fba4 | ||
|
|
b4a6e0ad34 | ||
|
|
483d4fa603 | ||
|
|
358bffbcb0 | ||
|
|
dae224619f | ||
|
|
9110380ef6 | ||
|
|
00dff390ac | ||
|
|
16d3917334 | ||
|
|
2cc843737b | ||
|
|
fe248d310d | ||
|
|
b2ea9fd381 | ||
|
|
55a293cfaf | ||
|
|
e8ba44c632 | ||
|
|
ddab5ac3fc | ||
|
|
347f18a634 | ||
|
|
a2332646fa | ||
|
|
cdf312e16f | ||
|
|
445099073a | ||
|
|
05eb145d2b | ||
|
|
c16e94ebbf | ||
|
|
48be5c3387 | ||
|
|
9fd781073f | ||
|
|
c25edb5fdd | ||
|
|
cf6df4ae36 | ||
|
|
6729edf590 | ||
|
|
2415813313 | ||
|
|
136c539836 | ||
|
|
a76f2a07a9 | ||
|
|
8b6aecc6f0 | ||
|
|
3186a246a5 | ||
|
|
15ff515437 | ||
|
|
d3a24c24af | ||
|
|
f4a51e2adc | ||
|
|
cdc897c745 | ||
|
|
8c842552b0 | ||
|
|
e918fe6030 | ||
|
|
d9e0025429 | ||
|
|
5fc4a4768e | ||
|
|
cf54123ec6 | ||
|
|
948aa12af0 | ||
|
|
5fdf37eac9 | ||
|
|
1dcf237c8d | ||
|
|
8285172412 | ||
|
|
5735cb3cb0 | ||
|
|
1adea9e34e | ||
|
|
0cb6a2ea8a | ||
|
|
48e9e34acd | ||
|
|
1433180472 | ||
|
|
48a1117cbf | ||
|
|
b213c345bf | ||
|
|
46b8dcfb03 | ||
|
|
5c48a94b48 | ||
|
|
22df8eadbf | ||
|
|
8520fa8e6f | ||
|
|
517493a1f3 | ||
|
|
c10c449258 | ||
|
|
5026e7823e | ||
|
|
c063a2d9fe | ||
|
|
c46620d4ba | ||
|
|
92f20955bd | ||
|
|
762ecfef24 | ||
|
|
ea96f4c176 | ||
|
|
d085de4787 | ||
|
|
dd7a8da6be | ||
|
|
a4ded39e61 | ||
|
|
d3ff2033ed | ||
|
|
005b193307 | ||
|
|
369269c475 | ||
|
|
339eccfc42 | ||
|
|
65c8415bac | ||
|
|
30be3150ff | ||
|
|
7d9ce1379f | ||
|
|
27d1f6a360 | ||
|
|
be46387f22 | ||
|
|
fe160165b6 | ||
|
|
be1c18096b | ||
|
|
2e3b05bcad | ||
|
|
54657fb667 | ||
|
|
c83d65eab0 | ||
|
|
3daad4fd40 | ||
|
|
b4a79672cf | ||
|
|
a71c610747 | ||
|
|
b1366421d1 | ||
|
|
f2696fb431 | ||
|
|
25c536d355 | ||
|
|
6530067b85 | ||
|
|
8626aa29d1 | ||
|
|
9e0a182821 | ||
|
|
2d8fa4d225 | ||
|
|
5a7ba8ba8c | ||
|
|
ede6b45bc1 | ||
|
|
1000ddfcab | ||
|
|
63110a7159 | ||
|
|
308ef699e6 | ||
|
|
764b2a6041 | ||
|
|
5009694dc9 | ||
|
|
c07016e1d1 | ||
|
|
7c5a346464 | ||
|
|
4f54cc0d18 |
1
.circleci/.gitignore
vendored
Normal file
1
.circleci/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
config-staging
|
||||
2603
.circleci/config.yml
2603
.circleci/config.yml
File diff suppressed because it is too large
Load Diff
2469
.circleci/config/base.yml
Normal file
2469
.circleci/config/base.yml
Normal file
File diff suppressed because it is too large
Load Diff
34
.circleci/config/build.js
Normal file
34
.circleci/config/build.js
Normal file
@@ -0,0 +1,34 @@
|
||||
const cp = require('child_process');
|
||||
const fs = require('fs-extra');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const STAGING_DIR = path.resolve(__dirname, '..', 'config-staging');
|
||||
|
||||
function copyAndExpand(dir = './') {
|
||||
const absDir = path.resolve(__dirname, dir);
|
||||
const targetDir = path.resolve(STAGING_DIR, dir);
|
||||
|
||||
if (!fs.existsSync(targetDir)) {
|
||||
fs.mkdirSync(targetDir);
|
||||
}
|
||||
|
||||
for (const file of fs.readdirSync(absDir)) {
|
||||
if (!file.endsWith('.yml')) {
|
||||
if (fs.statSync(path.resolve(absDir, file)).isDirectory()) {
|
||||
copyAndExpand(path.join(dir, file));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve(targetDir, file), yaml.dump(yaml.load(fs.readFileSync(path.resolve(absDir, file), 'utf8')), {
|
||||
noRefs: true,
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (fs.pathExists(STAGING_DIR)) fs.removeSync(STAGING_DIR);
|
||||
copyAndExpand();
|
||||
|
||||
const output = cp.spawnSync(process.env.CIRCLECI_BINARY || 'circleci', ['config', 'pack', STAGING_DIR]);
|
||||
fs.writeFileSync(path.resolve(STAGING_DIR, 'built.yml'), output.stdout.toString());
|
||||
51
.circleci/config/jobs/lint.yml
Normal file
51
.circleci/config/jobs/lint.yml
Normal file
@@ -0,0 +1,51 @@
|
||||
executor:
|
||||
name: linux-docker
|
||||
size: medium
|
||||
steps:
|
||||
- checkout:
|
||||
path: src/electron
|
||||
- run:
|
||||
name: Setup third_party Depot Tools
|
||||
command: |
|
||||
# "depot_tools" has to be checkout into "//third_party/depot_tools" so pylint.py can a "pylintrc" file.
|
||||
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git src/third_party/depot_tools
|
||||
echo 'export PATH="$PATH:'"$PWD"'/src/third_party/depot_tools"' >> $BASH_ENV
|
||||
- run:
|
||||
name: Download GN Binary
|
||||
command: |
|
||||
chromium_revision="$(grep -A1 chromium_version src/electron/DEPS | tr -d '\n' | cut -d\' -f4)"
|
||||
gn_version="$(curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/DEPS?format=TEXT" | base64 -d | grep gn_version | head -n1 | cut -d\' -f4)"
|
||||
|
||||
cipd ensure -ensure-file - -root . \<<-CIPD
|
||||
\$ServiceURL https://chrome-infra-packages.appspot.com/
|
||||
@Subdir src/buildtools/linux64
|
||||
gn/gn/linux-amd64 $gn_version
|
||||
CIPD
|
||||
|
||||
echo 'export CHROMIUM_BUILDTOOLS_PATH="'"$PWD"'/src/buildtools"' >> $BASH_ENV
|
||||
- run:
|
||||
name: Download clang-format Binary
|
||||
command: |
|
||||
chromium_revision="$(grep -A1 chromium_version src/electron/DEPS | tr -d '\n' | cut -d\' -f4)"
|
||||
|
||||
sha1_path='buildtools/linux64/clang-format.sha1'
|
||||
curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/${sha1_path}?format=TEXT" | base64 -d > "src/${sha1_path}"
|
||||
|
||||
download_from_google_storage.py --no_resume --no_auth --bucket chromium-clang-format -s "src/${sha1_path}"
|
||||
- run:
|
||||
name: Run Lint
|
||||
command: |
|
||||
# gn.py tries to find a gclient root folder starting from the current dir.
|
||||
# When it fails and returns "None" path, the whole script fails. Let's "fix" it.
|
||||
touch .gclient
|
||||
# Another option would be to checkout "buildtools" inside the Electron checkout,
|
||||
# but then we would lint its contents (at least gn format), and it doesn't pass it.
|
||||
|
||||
cd src/electron
|
||||
node script/yarn install --frozen-lockfile
|
||||
node script/yarn lint
|
||||
- run:
|
||||
name: Run Script Typechecker
|
||||
command: |
|
||||
cd src/electron
|
||||
node script/yarn tsc -p tsconfig.script.json
|
||||
10
.circleci/config/package.json
Normal file
10
.circleci/config/package.json
Normal file
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"name": "@electron/circleci-config",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fs-extra": "^10.1.0",
|
||||
"js-yaml": "^4.1.0"
|
||||
}
|
||||
}
|
||||
43
.circleci/config/yarn.lock
Normal file
43
.circleci/config/yarn.lock
Normal file
@@ -0,0 +1,43 @@
|
||||
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
|
||||
# yarn lockfile v1
|
||||
|
||||
|
||||
argparse@^2.0.1:
|
||||
version "2.0.1"
|
||||
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
|
||||
integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
|
||||
|
||||
fs-extra@^10.1.0:
|
||||
version "10.1.0"
|
||||
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-10.1.0.tgz#02873cfbc4084dde127eaa5f9905eef2325d1abf"
|
||||
integrity sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==
|
||||
dependencies:
|
||||
graceful-fs "^4.2.0"
|
||||
jsonfile "^6.0.1"
|
||||
universalify "^2.0.0"
|
||||
|
||||
graceful-fs@^4.1.6, graceful-fs@^4.2.0:
|
||||
version "4.2.10"
|
||||
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
|
||||
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
|
||||
|
||||
js-yaml@^4.1.0:
|
||||
version "4.1.0"
|
||||
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
|
||||
integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
|
||||
dependencies:
|
||||
argparse "^2.0.1"
|
||||
|
||||
jsonfile@^6.0.1:
|
||||
version "6.1.0"
|
||||
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-6.1.0.tgz#bc55b2634793c679ec6403094eb13698a6ec0aae"
|
||||
integrity sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==
|
||||
dependencies:
|
||||
universalify "^2.0.0"
|
||||
optionalDependencies:
|
||||
graceful-fs "^4.1.6"
|
||||
|
||||
universalify@^2.0.0:
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-2.0.0.tgz#75a4984efedc4b08975c5aeb73f530d02df25717"
|
||||
integrity sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==
|
||||
2
.github/CODEOWNERS
vendored
2
.github/CODEOWNERS
vendored
@@ -4,7 +4,7 @@
|
||||
# https://git-scm.com/docs/gitignore
|
||||
|
||||
# Upgrades WG
|
||||
/patches/ @electron/wg-upgrades
|
||||
/patches/ @electron/wg-upgrades @electron/wg-security
|
||||
DEPS @electron/wg-upgrades
|
||||
|
||||
# Releases WG
|
||||
|
||||
20
.github/workflows/semantic.yml
vendored
Normal file
20
.github/workflows/semantic.yml
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
name: "Check Semantic Commit"
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types:
|
||||
- opened
|
||||
- edited
|
||||
- synchronize
|
||||
|
||||
jobs:
|
||||
main:
|
||||
name: Validate PR Title
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: semantic-pull-request
|
||||
uses: amannn/action-semantic-pull-request@v4
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
validateSingleCommit: false
|
||||
22
BUILD.gn
22
BUILD.gn
@@ -187,6 +187,12 @@ action("electron_js2c") {
|
||||
rebase_path(sources, root_build_dir)
|
||||
}
|
||||
|
||||
action("generate_config_gypi") {
|
||||
outputs = [ "$root_gen_dir/config.gypi" ]
|
||||
script = "script/generate-config-gypi.py"
|
||||
args = rebase_path(outputs) + [ target_cpu ]
|
||||
}
|
||||
|
||||
target_gen_default_app_js = "$target_gen_dir/js/default_app"
|
||||
|
||||
typescript_build("default_app_js") {
|
||||
@@ -347,6 +353,7 @@ source_set("electron_lib") {
|
||||
"//base/allocator:buildflags",
|
||||
"//chrome/app:command_ids",
|
||||
"//chrome/app/resources:platform_locale_settings",
|
||||
"//components/autofill/core/common:features",
|
||||
"//components/certificate_transparency",
|
||||
"//components/language/core/browser",
|
||||
"//components/net_log",
|
||||
@@ -413,7 +420,6 @@ source_set("electron_lib") {
|
||||
]
|
||||
|
||||
include_dirs = [
|
||||
"chromium_src",
|
||||
".",
|
||||
"$target_gen_dir",
|
||||
|
||||
@@ -547,8 +553,9 @@ source_set("electron_lib") {
|
||||
"GLIB_DISABLE_DEPRECATION_WARNINGS",
|
||||
]
|
||||
|
||||
sources += filenames.lib_sources_nss
|
||||
sources += [
|
||||
"shell/browser/certificate_manager_model.cc",
|
||||
"shell/browser/certificate_manager_model.h",
|
||||
"shell/browser/ui/gtk/app_indicator_icon.cc",
|
||||
"shell/browser/ui/gtk/app_indicator_icon.h",
|
||||
"shell/browser/ui/gtk/app_indicator_icon_menu.cc",
|
||||
@@ -1243,6 +1250,10 @@ if (is_mac) {
|
||||
if (!is_component_build && is_component_ffmpeg) {
|
||||
configs += [ "//build/config/gcc:rpath_for_built_shared_libraries" ]
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
deps += [ "//sandbox/linux:chrome_sandbox" ]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1379,11 +1390,13 @@ dist_zip("electron_dist_zip") {
|
||||
if (is_linux) {
|
||||
data_deps += [ "//sandbox/linux:chrome_sandbox" ]
|
||||
}
|
||||
deps = data_deps
|
||||
outputs = [ "$root_build_dir/dist.zip" ]
|
||||
}
|
||||
|
||||
dist_zip("electron_ffmpeg_zip") {
|
||||
data_deps = [ "//third_party/ffmpeg" ]
|
||||
deps = data_deps
|
||||
outputs = [ "$root_build_dir/ffmpeg.zip" ]
|
||||
}
|
||||
|
||||
@@ -1401,6 +1414,7 @@ group("electron_chromedriver") {
|
||||
dist_zip("electron_chromedriver_zip") {
|
||||
testonly = true
|
||||
data_deps = electron_chromedriver_deps
|
||||
deps = data_deps
|
||||
outputs = [ "$root_build_dir/chromedriver.zip" ]
|
||||
}
|
||||
|
||||
@@ -1419,6 +1433,7 @@ group("electron_mksnapshot") {
|
||||
|
||||
dist_zip("electron_mksnapshot_zip") {
|
||||
data_deps = mksnapshot_deps
|
||||
deps = data_deps
|
||||
outputs = [ "$root_build_dir/mksnapshot.zip" ]
|
||||
}
|
||||
|
||||
@@ -1429,6 +1444,7 @@ copy("hunspell_dictionaries") {
|
||||
|
||||
dist_zip("hunspell_dictionaries_zip") {
|
||||
data_deps = [ ":hunspell_dictionaries" ]
|
||||
deps = data_deps
|
||||
flatten = true
|
||||
|
||||
outputs = [ "$root_build_dir/hunspell_dictionaries.zip" ]
|
||||
@@ -1442,6 +1458,7 @@ copy("libcxx_headers") {
|
||||
|
||||
dist_zip("libcxx_headers_zip") {
|
||||
data_deps = [ ":libcxx_headers" ]
|
||||
deps = data_deps
|
||||
flatten = true
|
||||
flatten_relative_to = rebase_path(
|
||||
"$target_gen_dir/electron_libcxx_include/buildtools/third_party/libc++/trunk",
|
||||
@@ -1457,6 +1474,7 @@ copy("libcxxabi_headers") {
|
||||
|
||||
dist_zip("libcxxabi_headers_zip") {
|
||||
data_deps = [ ":libcxxabi_headers" ]
|
||||
deps = data_deps
|
||||
flatten = true
|
||||
flatten_relative_to = rebase_path(
|
||||
"$target_gen_dir/electron_libcxxabi_include/buildtools/third_party/libc++abi/trunk",
|
||||
|
||||
2
DEPS
2
DEPS
@@ -15,7 +15,7 @@ gclient_gn_args = [
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'95.0.4629.0',
|
||||
'96.0.4664.174',
|
||||
'node_version':
|
||||
'v16.9.1',
|
||||
'nan_version':
|
||||
|
||||
@@ -1 +1 @@
|
||||
16.0.0-alpha.4
|
||||
16.2.8
|
||||
48
appveyor.yml
48
appveyor.yml
@@ -11,7 +11,7 @@
|
||||
# - "TARGET_ARCH" Choose from {'ia32', 'x64', 'arm', 'arm64', 'mips64el'}.
|
||||
# Is used in some publishing scripts, but does NOT affect the Electron binary.
|
||||
# Must match 'target_cpu' passed to "GN_EXTRA_ARGS" and "NPM_CONFIG_ARCH" value.
|
||||
# - "UPLOAD_TO_S3" Set it to '1' upload a release to the S3 bucket.
|
||||
# - "UPLOAD_TO_STORAGE" Set it to '1' upload a release to the Azure bucket.
|
||||
# Otherwise the release will be uploaded to the Github Releases.
|
||||
# (The value is only checked if "ELECTRON_RELEASE" is defined.)
|
||||
#
|
||||
@@ -66,6 +66,31 @@ build_script:
|
||||
- mkdir src
|
||||
- update_depot_tools.bat
|
||||
- ps: Move-Item $env:APPVEYOR_BUILD_FOLDER -Destination src\electron
|
||||
- ps: >-
|
||||
if (Test-Path 'env:RAW_GOMA_AUTH') {
|
||||
$env:GOMA_OAUTH2_CONFIG_FILE = "$pwd\.goma_oauth2_config"
|
||||
$env:RAW_GOMA_AUTH | Set-Content $env:GOMA_OAUTH2_CONFIG_FILE
|
||||
}
|
||||
- git clone https://github.com/electron/build-tools.git
|
||||
- cd build-tools
|
||||
- npm install
|
||||
- mkdir third_party
|
||||
- ps: >-
|
||||
node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })"
|
||||
- ps: $env:GN_GOMA_FILE = node -e "console.log(require('./src/utils/goma.js').gnFilePath)"
|
||||
- ps: $env:LOCAL_GOMA_DIR = node -e "console.log(require('./src/utils/goma.js').dir)"
|
||||
- cd ..
|
||||
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
|
||||
- ps: >-
|
||||
if (Test-Path 'env:RAW_GOMA_AUTH') {
|
||||
$goma_login = python $env:LOCAL_GOMA_DIR\goma_auth.py info
|
||||
if ($goma_login -eq 'Login as Fermi Planck') {
|
||||
Write-warning "Goma authentication is correct";
|
||||
} else {
|
||||
Write-warning "WARNING!!!!!! Goma authentication is incorrect; please update Goma auth token.";
|
||||
$host.SetShouldExit(1)
|
||||
}
|
||||
}
|
||||
- ps: $env:CHROMIUM_BUILDTOOLS_PATH="$pwd\src\buildtools"
|
||||
- ps: >-
|
||||
if ($env:GN_CONFIG -ne 'release') {
|
||||
@@ -129,21 +154,6 @@ build_script:
|
||||
Write-warning "Failed to add third_party\angle\.git; continuing anyway"
|
||||
}
|
||||
}
|
||||
- ps: >-
|
||||
if (Test-Path 'env:RAW_GOMA_AUTH') {
|
||||
$env:GOMA_OAUTH2_CONFIG_FILE = "$pwd\.goma_oauth2_config"
|
||||
$env:RAW_GOMA_AUTH | Set-Content $env:GOMA_OAUTH2_CONFIG_FILE
|
||||
}
|
||||
- git clone https://github.com/electron/build-tools.git
|
||||
- cd build-tools
|
||||
- npm install
|
||||
- mkdir third_party
|
||||
- ps: >-
|
||||
node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })"
|
||||
- ps: $env:GN_GOMA_FILE = node -e "console.log(require('./src/utils/goma.js').gnFilePath)"
|
||||
- ps: $env:LOCAL_GOMA_DIR = node -e "console.log(require('./src/utils/goma.js').dir)"
|
||||
- cd ..
|
||||
- ps: .\src\electron\script\start-goma.ps1 -gomaDir $env:LOCAL_GOMA_DIR
|
||||
- cd src
|
||||
- set BUILD_CONFIG_PATH=//electron/build/args/%GN_CONFIG%.gn
|
||||
- gn gen out/Default "--args=import(\"%BUILD_CONFIG_PATH%\") import(\"%GN_GOMA_FILE%\") %GN_EXTRA_ARGS% "
|
||||
@@ -221,9 +231,9 @@ deploy_script:
|
||||
- cd electron
|
||||
- ps: >-
|
||||
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 --verbose --upload_to_s3
|
||||
if (Test-Path Env:\UPLOAD_TO_STORAGE) {
|
||||
Write-Output "Uploading Electron release distribution to azure"
|
||||
& python script\release\uploaders\upload.py --verbose --upload_to_storage
|
||||
} else {
|
||||
Write-Output "Uploading Electron release distribution to github releases"
|
||||
& python script\release\uploaders\upload.py --verbose
|
||||
|
||||
@@ -16,6 +16,9 @@ proprietary_codecs = true
|
||||
ffmpeg_branding = "Chrome"
|
||||
|
||||
enable_basic_printing = true
|
||||
|
||||
# Removes DLLs from the build, which are only meant to be used for Chromium development.
|
||||
# See https://github.com/electron/electron/pull/17985
|
||||
angle_enable_vulkan_validation_layers = false
|
||||
dawn_enable_vulkan_validation_layers = false
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
from __future__ import with_statement
|
||||
from __future__ import unicode_literals
|
||||
|
||||
import contextlib
|
||||
import sys
|
||||
import os
|
||||
import optparse
|
||||
import json
|
||||
import re
|
||||
import subprocess
|
||||
|
||||
sys.path.append("%s/../../build" % os.path.dirname(os.path.realpath(__file__)))
|
||||
|
||||
@@ -33,36 +36,56 @@ def calculate_hash(root):
|
||||
return CalculateHash('.', None)
|
||||
|
||||
def windows_installed_software():
|
||||
import win32com.client
|
||||
strComputer = "."
|
||||
objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator")
|
||||
objSWbemServices = objWMIService.ConnectServer(strComputer, "root\cimv2")
|
||||
colItems = objSWbemServices.ExecQuery("Select * from Win32_Product")
|
||||
items = []
|
||||
powershell_command = [
|
||||
"Get-CimInstance",
|
||||
"-Namespace",
|
||||
"root\cimv2",
|
||||
"-Class",
|
||||
"Win32_product",
|
||||
"|",
|
||||
"Select",
|
||||
"vendor,",
|
||||
"description,",
|
||||
"@{l='install_location';e='InstallLocation'},",
|
||||
"@{l='install_date';e='InstallDate'},",
|
||||
"@{l='install_date_2';e='InstallDate2'},",
|
||||
"caption,",
|
||||
"version,",
|
||||
"name,",
|
||||
"@{l='sku_number';e='SKUNumber'}",
|
||||
"|",
|
||||
"ConvertTo-Json",
|
||||
]
|
||||
|
||||
for objItem in colItems:
|
||||
item = {}
|
||||
if objItem.Caption:
|
||||
item['caption'] = objItem.Caption
|
||||
if objItem.Caption:
|
||||
item['description'] = objItem.Description
|
||||
if objItem.InstallDate:
|
||||
item['install_date'] = objItem.InstallDate
|
||||
if objItem.InstallDate2:
|
||||
item['install_date_2'] = objItem.InstallDate2
|
||||
if objItem.InstallLocation:
|
||||
item['install_location'] = objItem.InstallLocation
|
||||
if objItem.Name:
|
||||
item['name'] = objItem.Name
|
||||
if objItem.SKUNumber:
|
||||
item['sku_number'] = objItem.SKUNumber
|
||||
if objItem.Vendor:
|
||||
item['vendor'] = objItem.Vendor
|
||||
if objItem.Version:
|
||||
item['version'] = objItem.Version
|
||||
items.append(item)
|
||||
proc = subprocess.Popen(
|
||||
["powershell.exe", "-Command", "-"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
|
||||
return items
|
||||
stdout, _ = proc.communicate(" ".join(powershell_command).encode("utf-8"))
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError("Failed to get list of installed software")
|
||||
|
||||
# On AppVeyor there's other output related to PSReadline,
|
||||
# so grab only the JSON output and ignore everything else
|
||||
json_match = re.match(
|
||||
r".*(\[.*{.*}.*\]).*", stdout.decode("utf-8"), re.DOTALL
|
||||
)
|
||||
|
||||
if not json_match:
|
||||
raise RuntimeError(
|
||||
"Couldn't find JSON output for list of installed software"
|
||||
)
|
||||
|
||||
# Filter out missing keys
|
||||
return list(
|
||||
map(
|
||||
lambda info: {k: info[k] for k in info if info[k]},
|
||||
json.loads(json_match.group(1)),
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def windows_profile():
|
||||
@@ -89,7 +112,7 @@ def windows_profile():
|
||||
|
||||
def main(options):
|
||||
if sys.platform == 'win32':
|
||||
with open(options.output_json, 'wb') as f:
|
||||
with open(options.output_json, 'w') as f:
|
||||
json.dump(windows_profile(), f)
|
||||
else:
|
||||
raise OSError("Unsupported OS")
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
from __future__ import print_function
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
@@ -15,6 +15,10 @@ static_library("chrome") {
|
||||
sources = [
|
||||
"//chrome/browser/accessibility/accessibility_ui.cc",
|
||||
"//chrome/browser/accessibility/accessibility_ui.h",
|
||||
"//chrome/browser/app_mode/app_mode_utils.cc",
|
||||
"//chrome/browser/app_mode/app_mode_utils.h",
|
||||
"//chrome/browser/browser_features.cc",
|
||||
"//chrome/browser/browser_features.h",
|
||||
"//chrome/browser/browser_process.cc",
|
||||
"//chrome/browser/browser_process.h",
|
||||
"//chrome/browser/devtools/devtools_contents_resizing_strategy.cc",
|
||||
@@ -25,6 +29,7 @@ static_library("chrome") {
|
||||
"//chrome/browser/devtools/devtools_eye_dropper.h",
|
||||
"//chrome/browser/devtools/devtools_file_system_indexer.cc",
|
||||
"//chrome/browser/devtools/devtools_file_system_indexer.h",
|
||||
"//chrome/browser/devtools/devtools_settings.h",
|
||||
"//chrome/browser/extensions/global_shortcut_listener.cc",
|
||||
"//chrome/browser/extensions/global_shortcut_listener.h",
|
||||
"//chrome/browser/icon_loader.cc",
|
||||
@@ -50,6 +55,20 @@ static_library("chrome") {
|
||||
"//chrome/browser/process_singleton.h",
|
||||
"//chrome/browser/ui/browser_dialogs.cc",
|
||||
"//chrome/browser/ui/browser_dialogs.h",
|
||||
"//chrome/browser/ui/exclusive_access/exclusive_access_bubble_type.cc",
|
||||
"//chrome/browser/ui/exclusive_access/exclusive_access_bubble_type.h",
|
||||
"//chrome/browser/ui/exclusive_access/exclusive_access_controller_base.cc",
|
||||
"//chrome/browser/ui/exclusive_access/exclusive_access_controller_base.h",
|
||||
"//chrome/browser/ui/exclusive_access/exclusive_access_manager.cc",
|
||||
"//chrome/browser/ui/exclusive_access/exclusive_access_manager.h",
|
||||
"//chrome/browser/ui/exclusive_access/fullscreen_controller.cc",
|
||||
"//chrome/browser/ui/exclusive_access/fullscreen_controller.h",
|
||||
"//chrome/browser/ui/exclusive_access/fullscreen_within_tab_helper.cc",
|
||||
"//chrome/browser/ui/exclusive_access/fullscreen_within_tab_helper.h",
|
||||
"//chrome/browser/ui/exclusive_access/keyboard_lock_controller.cc",
|
||||
"//chrome/browser/ui/exclusive_access/keyboard_lock_controller.h",
|
||||
"//chrome/browser/ui/exclusive_access/mouse_lock_controller.cc",
|
||||
"//chrome/browser/ui/exclusive_access/mouse_lock_controller.h",
|
||||
"//chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc",
|
||||
"//chrome/browser/ui/views/autofill/autofill_popup_view_utils.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper.cc",
|
||||
@@ -96,7 +115,7 @@ static_library("chrome") {
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
sources += [ "//chrome/browser/media/webrtc/window_icon_util_linux.cc" ]
|
||||
sources += [ "//chrome/browser/media/webrtc/window_icon_util_ozone.cc" ]
|
||||
}
|
||||
|
||||
if (use_aura) {
|
||||
@@ -113,13 +132,13 @@ static_library("chrome") {
|
||||
"//components/keyed_service/content",
|
||||
"//components/paint_preview/buildflags",
|
||||
"//components/proxy_config",
|
||||
"//components/services/language_detection/public/mojom",
|
||||
"//content/public/browser",
|
||||
"//services/strings",
|
||||
]
|
||||
|
||||
deps = [
|
||||
"//chrome/browser:resource_prefetch_predictor_proto",
|
||||
"//chrome/services/speech:buildflags",
|
||||
"//components/optimization_guide/proto:optimization_guide_proto",
|
||||
]
|
||||
|
||||
@@ -129,16 +148,6 @@ static_library("chrome") {
|
||||
|
||||
if (is_linux) {
|
||||
sources += [ "//chrome/browser/icon_loader_auralinux.cc" ]
|
||||
if (use_x11 || use_ozone) {
|
||||
sources +=
|
||||
[ "//chrome/browser/extensions/global_shortcut_listener_linux.cc" ]
|
||||
}
|
||||
if (use_x11) {
|
||||
sources += [
|
||||
"//chrome/browser/extensions/global_shortcut_listener_x11.cc",
|
||||
"//chrome/browser/extensions/global_shortcut_listener_x11.h",
|
||||
]
|
||||
}
|
||||
if (use_ozone) {
|
||||
deps += [ "//ui/ozone" ]
|
||||
sources += [
|
||||
@@ -168,6 +177,7 @@ static_library("chrome") {
|
||||
|
||||
if (enable_desktop_capturer) {
|
||||
sources += [
|
||||
"//chrome/browser/media/webrtc/desktop_media_list.cc",
|
||||
"//chrome/browser/media/webrtc/desktop_media_list.h",
|
||||
"//chrome/browser/media/webrtc/desktop_media_list_base.cc",
|
||||
"//chrome/browser/media/webrtc/desktop_media_list_base.h",
|
||||
@@ -205,6 +215,13 @@ static_library("chrome") {
|
||||
"//chrome/browser/printing/printing_service.h",
|
||||
]
|
||||
|
||||
if (enable_oop_printing) {
|
||||
sources += [
|
||||
"//chrome/browser/printing/print_backend_service_manager.cc",
|
||||
"//chrome/browser/printing/print_backend_service_manager.h",
|
||||
]
|
||||
}
|
||||
|
||||
public_deps += [
|
||||
"//chrome/services/printing:lib",
|
||||
"//components/printing/browser",
|
||||
@@ -212,6 +229,7 @@ static_library("chrome") {
|
||||
"//components/services/print_compositor",
|
||||
"//components/services/print_compositor/public/cpp",
|
||||
"//components/services/print_compositor/public/mojom",
|
||||
"//printing/backend",
|
||||
]
|
||||
|
||||
deps += [
|
||||
|
||||
@@ -59,10 +59,9 @@ an issue:
|
||||
* [Testing and Debugging](tutorial/application-debugging.md)
|
||||
* [Debugging the Main Process](tutorial/debugging-main-process.md)
|
||||
* [Debugging with Visual Studio Code](tutorial/debugging-vscode.md)
|
||||
* [Using Selenium and WebDriver](tutorial/using-selenium-and-webdriver.md)
|
||||
* [Testing on Headless CI Systems (Travis, Jenkins)](tutorial/testing-on-headless-ci.md)
|
||||
* [DevTools Extension](tutorial/devtools-extension.md)
|
||||
* [Automated Testing with a Custom Driver](tutorial/automated-testing-with-a-custom-driver.md)
|
||||
* [Automated Testing](tutorial/automated-testing.md)
|
||||
* [REPL](tutorial/repl.md)
|
||||
* [Distribution](tutorial/application-distribution.md)
|
||||
* [Supported Platforms](tutorial/support.md#supported-platforms)
|
||||
|
||||
@@ -36,10 +36,10 @@ Returns:
|
||||
* `launchInfo` Record<string, any> | [NotificationResponse](structures/notification-response.md) _macOS_
|
||||
|
||||
Emitted once, when Electron has finished initializing. On macOS, `launchInfo`
|
||||
holds the `userInfo` of the `NSUserNotification` or information from
|
||||
[`UNNotificationResponse`](structures/notification-response.md) that was used to open the
|
||||
application, if it was launched from Notification Center. You can also call
|
||||
`app.isReady()` to check if this event has already fired and `app.whenReady()`
|
||||
holds the `userInfo` of the [`NSUserNotification`](https://developer.apple.com/documentation/foundation/nsusernotification)
|
||||
or information from [`UNNotificationResponse`](https://developer.apple.com/documentation/usernotifications/unnotificationresponse)
|
||||
that was used to open the application, if it was launched from Notification Center.
|
||||
You can also call `app.isReady()` to check if this event has already fired and `app.whenReady()`
|
||||
to get a Promise that is fulfilled when Electron is initialized.
|
||||
|
||||
### Event: 'window-all-closed'
|
||||
@@ -483,6 +483,7 @@ Returns:
|
||||
* `event` Event
|
||||
* `argv` String[] - An array of the second instance's command line arguments
|
||||
* `workingDirectory` String - The second instance's working directory
|
||||
* `additionalData` unknown - A JSON object of additional data passed from the second instance
|
||||
|
||||
This event will be emitted inside the primary instance of your application
|
||||
when a second instance has been executed and calls `app.requestSingleInstanceLock()`.
|
||||
@@ -939,7 +940,9 @@ app.setJumpList([
|
||||
])
|
||||
```
|
||||
|
||||
### `app.requestSingleInstanceLock()`
|
||||
### `app.requestSingleInstanceLock([additionalData])`
|
||||
|
||||
* `additionalData` Record<any, any> (optional) - A JSON object containing additional data to send to the first instance.
|
||||
|
||||
Returns `Boolean`
|
||||
|
||||
@@ -966,12 +969,16 @@ starts:
|
||||
const { app } = require('electron')
|
||||
let myWindow = null
|
||||
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
const additionalData = { myKey: 'myValue' }
|
||||
const gotTheLock = app.requestSingleInstanceLock(additionalData)
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => {
|
||||
// Print out data received from the second instance.
|
||||
console.log(additionalData)
|
||||
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (myWindow) {
|
||||
if (myWindow.isMinimized()) myWindow.restore()
|
||||
|
||||
@@ -15,14 +15,16 @@ Process: [Main](../glossary.md#main-process)
|
||||
|
||||
```javascript
|
||||
// In the main process.
|
||||
const { BrowserView, BrowserWindow } = require('electron')
|
||||
const { app, BrowserView, BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({ width: 800, height: 600 })
|
||||
app.whenReady().then(() => {
|
||||
const win = new BrowserWindow({ width: 800, height: 600 })
|
||||
|
||||
const view = new BrowserView()
|
||||
win.setBrowserView(view)
|
||||
view.setBounds({ x: 0, y: 0, width: 300, height: 300 })
|
||||
view.webContents.loadURL('https://electronjs.org')
|
||||
const view = new BrowserView()
|
||||
win.setBrowserView(view)
|
||||
view.setBounds({ x: 0, y: 0, width: 300, height: 300 })
|
||||
view.webContents.loadURL('https://electronjs.org')
|
||||
})
|
||||
```
|
||||
|
||||
### `new BrowserView([options])` _Experimental_
|
||||
|
||||
@@ -17,10 +17,11 @@ win.loadURL('https://github.com')
|
||||
win.loadFile('index.html')
|
||||
```
|
||||
|
||||
## Frameless window
|
||||
## Window customization
|
||||
|
||||
To create a window without chrome, or a transparent window in arbitrary shape,
|
||||
you can use the [Frameless Window](frameless-window.md) API.
|
||||
The `BrowserWindow` class exposes various ways to modify the look and behavior of
|
||||
your app's windows. For more details, see the [Window Customization](../tutorial/window-customization.md)
|
||||
tutorial.
|
||||
|
||||
## Showing the window gracefully
|
||||
|
||||
@@ -184,7 +185,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
`true`.
|
||||
* `paintWhenInitiallyHidden` Boolean (optional) - Whether the renderer should be active when `show` is `false` and it has just been created. In order for `document.visibilityState` to work correctly on first load with `show: false` you should set this to `false`. Setting this to `false` will cause the `ready-to-show` event to not fire. Default is `true`.
|
||||
* `frame` Boolean (optional) - Specify `false` to create a
|
||||
[Frameless Window](frameless-window.md). Default is `true`.
|
||||
[frameless window](../tutorial/window-customization.md#create-frameless-windows). Default is `true`.
|
||||
* `parent` BrowserWindow (optional) - Specify parent window. Default is `null`.
|
||||
* `modal` Boolean (optional) - Whether this is a modal window. This only works when the
|
||||
window is a child window. Default is `false`.
|
||||
@@ -206,7 +207,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
transparent) and 1.0 (fully opaque). This is only implemented on Windows and macOS.
|
||||
* `darkTheme` Boolean (optional) - Forces using dark theme for the window, only works on
|
||||
some GTK+3 desktop environments. Default is `false`.
|
||||
* `transparent` Boolean (optional) - Makes the window [transparent](frameless-window.md#transparent-window).
|
||||
* `transparent` Boolean (optional) - Makes the window [transparent](../tutorial/window-customization.md#create-transparent-windows).
|
||||
Default is `false`. On Windows, does not work unless the window is frameless.
|
||||
* `type` String (optional) - The type of window, default is normal window. See more about
|
||||
this below.
|
||||
@@ -393,6 +394,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
* `titleBarOverlay` Object | Boolean (optional) - When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS or using a `titleBarStyle` so that the standard window controls ("traffic lights" on macOS) are visible, this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and [CSS Environment Variables][overlay-css-env-vars]. Specifying `true` will result in an overlay with default system colors. Default is `false`.
|
||||
* `color` String (optional) _Windows_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color.
|
||||
* `symbolColor` String (optional) _Windows_ - The CSS color of the symbols on the Window Controls Overlay when enabled. Default is the system color.
|
||||
* `height` Integer (optional) _macOS_ _Windows_ - The height of the title bar and Window Controls Overlay in pixels. Default is system height.
|
||||
|
||||
When setting minimum or maximum window size with `minWidth`/`maxWidth`/
|
||||
`minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from
|
||||
@@ -558,7 +560,7 @@ Returns:
|
||||
|
||||
Emitted before the window is moved. On Windows, calling `event.preventDefault()` will prevent the window from being moved.
|
||||
|
||||
Note that this is only emitted when the window is being resized manually. Resizing the window with `setBounds`/`setSize` will not emit this event.
|
||||
Note that this is only emitted when the window is being moved manually. Moving the window with `setPosition`/`setBounds`/`center` will not emit this event.
|
||||
|
||||
#### Event: 'move'
|
||||
|
||||
@@ -998,7 +1000,7 @@ APIs like `win.setSize`.
|
||||
is `true`). Default is `#FFF` (white).
|
||||
|
||||
Sets the background color of the window. See [Setting
|
||||
`backgroundColor`](#setting-backgroundcolor).
|
||||
`backgroundColor`](#setting-the-backgroundcolor-property).
|
||||
|
||||
#### `win.previewFile(path[, displayName])` _macOS_
|
||||
|
||||
@@ -1043,7 +1045,7 @@ Returns [`Rectangle`](structures/rectangle.md) - The `bounds` of the window as `
|
||||
#### `win.getBackgroundColor()`
|
||||
|
||||
Returns `String` - Gets the background color of the window. See [Setting
|
||||
`backgroundColor`](#setting-backgroundcolor).
|
||||
`backgroundColor`](#setting-the-backgroundcolor-property).
|
||||
|
||||
#### `win.setContentBounds(bounds[, animate])`
|
||||
|
||||
@@ -1696,7 +1698,7 @@ current window into a top-level window.
|
||||
|
||||
#### `win.getParentWindow()`
|
||||
|
||||
Returns `BrowserWindow` - The parent window.
|
||||
Returns `BrowserWindow | null` - The parent window or `null` if there is no parent.
|
||||
|
||||
#### `win.getChildWindows()`
|
||||
|
||||
|
||||
@@ -76,7 +76,7 @@ Writes `markup` to the clipboard.
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.writeHTML('<b>Hi</b')
|
||||
clipboard.writeHTML('<b>Hi</b>')
|
||||
```
|
||||
|
||||
### `clipboard.readImage([type])`
|
||||
@@ -197,7 +197,7 @@ Returns `Boolean` - Whether the clipboard supports the specified `format`.
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
const hasFormat = clipboard.has('<p>selection</p>')
|
||||
const hasFormat = clipboard.has('public/utf8-plain-text')
|
||||
console.log(hasFormat)
|
||||
// 'true' or 'false'
|
||||
```
|
||||
|
||||
@@ -53,3 +53,12 @@ Returns `Boolean` - Whether the command-line switch is present.
|
||||
Returns `String` - The command-line switch value.
|
||||
|
||||
**Note:** When the switch is not present or has no value, it returns empty string.
|
||||
|
||||
#### `commandLine.removeSwitch(switch)`
|
||||
|
||||
* `switch` String - A command-line switch
|
||||
|
||||
Removes the specified switch from Chromium's command line.
|
||||
|
||||
**Note:** This will not affect `process.argv`. The intended usage of this function is to
|
||||
control Chromium's behavior.
|
||||
|
||||
@@ -102,8 +102,8 @@ has been included below for completeness:
|
||||
| `Boolean` | Simple | ✅ | ✅ | N/A |
|
||||
| `Object` | Complex | ✅ | ✅ | Keys must be supported using only "Simple" types in this table. Values must be supported in this table. Prototype modifications are dropped. Sending custom classes will copy values but not the prototype. |
|
||||
| `Array` | Complex | ✅ | ✅ | Same limitations as the `Object` type |
|
||||
| `Error` | Complex | ✅ | ✅ | Errors that are thrown are also copied, this can result in the message and stack trace of the error changing slightly due to being thrown in a different context |
|
||||
| `Promise` | Complex | ✅ | ✅ | Promises are only proxied if they are the return value or exact parameter. Promises nested in arrays or objects will be dropped. |
|
||||
| `Error` | Complex | ✅ | ✅ | Errors that are thrown are also copied, this can result in the message and stack trace of the error changing slightly due to being thrown in a different context, and any custom properties on the Error object [will be lost](https://github.com/electron/electron/issues/25596) |
|
||||
| `Promise` | Complex | ✅ | ✅ | N/A
|
||||
| `Function` | Complex | ✅ | ✅ | Prototype modifications are dropped. Sending classes or constructors will not work. |
|
||||
| [Cloneable Types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) | Simple | ✅ | ✅ | See the linked document on cloneable types |
|
||||
| `Element` | Complex | ✅ | ✅ | Prototype modifications are dropped. Sending custom elements will not work. |
|
||||
|
||||
@@ -99,7 +99,7 @@ the response.
|
||||
* `expirationDate` Double (optional) - The expiration date of the cookie as the number of
|
||||
seconds since the UNIX epoch. If omitted then the cookie becomes a session
|
||||
cookie and will not be retained between sessions.
|
||||
* `sameSite` String (optional) - The [Same Site](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies) policy to apply to this cookie. Can be `unspecified`, `no_restriction`, `lax` or `strict`. Default is `no_restriction`.
|
||||
* `sameSite` String (optional) - The [Same Site](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#SameSite_cookies) policy to apply to this cookie. Can be `unspecified`, `no_restriction`, `lax` or `strict`. Default is `lax`.
|
||||
|
||||
Returns `Promise<void>` - A promise which resolves when the cookie has been set
|
||||
|
||||
|
||||
@@ -122,7 +122,7 @@ Prints Chromium's internal logging to the console.
|
||||
|
||||
Setting this variable is the same as passing `--enable-logging`
|
||||
on the command line. For more info, see `--enable-logging` in [command-line
|
||||
switches](./command-line-switches.md#enable-loggingfile).
|
||||
switches](./command-line-switches.md#--enable-loggingfile).
|
||||
|
||||
### `ELECTRON_LOG_FILE`
|
||||
|
||||
@@ -130,7 +130,7 @@ Sets the file destination for Chromium's internal logging.
|
||||
|
||||
Setting this variable is the same as passing `--log-file`
|
||||
on the command line. For more info, see `--log-file` in [command-line
|
||||
switches](./command-line-switches.md#log-filepath).
|
||||
switches](./command-line-switches.md#--log-filepath).
|
||||
|
||||
### `ELECTRON_DEBUG_DRAG_REGIONS`
|
||||
|
||||
|
||||
@@ -1,221 +0,0 @@
|
||||
# Frameless Window
|
||||
|
||||
> Open a window without toolbars, borders, or other graphical "chrome".
|
||||
|
||||
A frameless window is a window that has no
|
||||
[chrome](https://developer.mozilla.org/en-US/docs/Glossary/Chrome), the parts of
|
||||
the window, like toolbars, that are not a part of the web page. These are
|
||||
options on the [`BrowserWindow`](browser-window.md) class.
|
||||
|
||||
## Create a frameless window
|
||||
|
||||
To create a frameless window, you need to set `frame` to `false` in
|
||||
[BrowserWindow](browser-window.md)'s `options`:
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ width: 800, height: 600, frame: false })
|
||||
win.show()
|
||||
```
|
||||
|
||||
### Alternatives
|
||||
|
||||
There's an alternative way to specify a chromeless window on macOS and Windows.
|
||||
Instead of setting `frame` to `false` which disables both the titlebar and window controls,
|
||||
you may want to have the title bar hidden and your content extend to the full window size,
|
||||
yet still preserve the window controls ("traffic lights" on macOS) for standard window actions.
|
||||
You can do so by specifying the `titleBarStyle` option:
|
||||
|
||||
#### `hidden`
|
||||
|
||||
Results in a hidden title bar and a full size content window. On macOS, the title bar still has the standard window controls (“traffic lights”) in the top left.
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ titleBarStyle: 'hidden' })
|
||||
win.show()
|
||||
```
|
||||
|
||||
### Alternatives on macOS
|
||||
|
||||
#### `hiddenInset`
|
||||
|
||||
Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge.
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
|
||||
win.show()
|
||||
```
|
||||
|
||||
#### `customButtonsOnHover`
|
||||
|
||||
Uses custom drawn close, and miniaturize buttons that display
|
||||
when hovering in the top left of the window. The fullscreen button
|
||||
is not available due to restrictions of frameless windows as they
|
||||
interface with Apple's macOS window masks. These custom buttons prevent
|
||||
issues with mouse events that occur with the standard window toolbar buttons.
|
||||
This option is only applicable for frameless windows.
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ titleBarStyle: 'customButtonsOnHover', frame: false })
|
||||
win.show()
|
||||
```
|
||||
|
||||
## Windows Control Overlay
|
||||
|
||||
When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS, using one of the `titleBarStyle`s as described above so
|
||||
that the traffic lights are visible, or using `titleBarStyle: hidden` on Windows, you can access the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and
|
||||
[CSS Environment Variables][overlay-css-env-vars] by setting the `titleBarOverlay` option to true. Specifying `true` will result in an overlay with default system colors.
|
||||
|
||||
On Windows, you can also specify the color of the overlay and its symbols by setting `titleBarOverlay` to an object with the options `color` and `symbolColor`. If an option is not specified, the color will default to its system color for the window control buttons:
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: true
|
||||
})
|
||||
win.show()
|
||||
```
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
color: '#2f3241',
|
||||
symbolColor: '#74b1be'
|
||||
}
|
||||
})
|
||||
win.show()
|
||||
```
|
||||
|
||||
## Transparent window
|
||||
|
||||
By setting the `transparent` option to `true`, you can also make the frameless
|
||||
window transparent:
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ transparent: true, frame: false })
|
||||
win.show()
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
* You can not click through the transparent area. We are going to introduce an
|
||||
API to set window shape to solve this, see
|
||||
[our issue](https://github.com/electron/electron/issues/1335) for details.
|
||||
* Transparent windows are not resizable. Setting `resizable` to `true` may make
|
||||
a transparent window stop working on some platforms.
|
||||
* The `blur` filter only applies to the web page, so there is no way to apply
|
||||
blur effect to the content below the window (i.e. other applications open on
|
||||
the user's system).
|
||||
* The window will not be transparent when DevTools is opened.
|
||||
* On Windows operating systems,
|
||||
* transparent windows will not work when DWM is
|
||||
disabled.
|
||||
* transparent windows can not be maximized using the Windows system menu or by double clicking the title bar. The reasoning behind this can be seen on [this pull request](https://github.com/electron/electron/pull/28207).
|
||||
* On Linux, users have to put `--enable-transparent-visuals --disable-gpu` in
|
||||
the command line to disable GPU and allow ARGB to make transparent window,
|
||||
this is caused by an upstream bug that [alpha channel doesn't work on some
|
||||
NVidia drivers](https://bugs.chromium.org/p/chromium/issues/detail?id=369209) on
|
||||
Linux.
|
||||
* On Mac, the native window shadow will not be shown on a transparent window.
|
||||
|
||||
## Click-through window
|
||||
|
||||
To create a click-through window, i.e. making the window ignore all mouse
|
||||
events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events]
|
||||
API:
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow()
|
||||
win.setIgnoreMouseEvents(true)
|
||||
```
|
||||
|
||||
### Forwarding
|
||||
|
||||
Ignoring mouse messages makes the web page oblivious to mouse movement, meaning
|
||||
that mouse movement events will not be emitted. On Windows operating systems an
|
||||
optional parameter can be used to forward mouse move messages to the web page,
|
||||
allowing events such as `mouseleave` to be emitted:
|
||||
|
||||
```javascript
|
||||
const { ipcRenderer } = require('electron')
|
||||
const el = document.getElementById('clickThroughElement')
|
||||
el.addEventListener('mouseenter', () => {
|
||||
ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
|
||||
})
|
||||
el.addEventListener('mouseleave', () => {
|
||||
ipcRenderer.send('set-ignore-mouse-events', false)
|
||||
})
|
||||
|
||||
// Main process
|
||||
const { ipcMain } = require('electron')
|
||||
ipcMain.on('set-ignore-mouse-events', (event, ...args) => {
|
||||
BrowserWindow.fromWebContents(event.sender).setIgnoreMouseEvents(...args)
|
||||
})
|
||||
```
|
||||
|
||||
This makes the web page click-through when over `el`, and returns to normal
|
||||
outside it.
|
||||
|
||||
## Draggable region
|
||||
|
||||
By default, the frameless window is non-draggable. Apps need to specify
|
||||
`-webkit-app-region: drag` in CSS to tell Electron which regions are draggable
|
||||
(like the OS's standard titlebar), and apps can also use
|
||||
`-webkit-app-region: no-drag` to exclude the non-draggable area from the
|
||||
draggable region. Note that only rectangular shapes are currently supported.
|
||||
|
||||
Note: `-webkit-app-region: drag` is known to have problems while the developer tools are open. See this [GitHub issue](https://github.com/electron/electron/issues/3647) for more information including a workaround.
|
||||
|
||||
To make the whole window draggable, you can add `-webkit-app-region: drag` as
|
||||
`body`'s style:
|
||||
|
||||
```html
|
||||
<body style="-webkit-app-region: drag">
|
||||
</body>
|
||||
```
|
||||
|
||||
And note that if you have made the whole window draggable, you must also mark
|
||||
buttons as non-draggable, otherwise it would be impossible for users to click on
|
||||
them:
|
||||
|
||||
```css
|
||||
button {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
```
|
||||
|
||||
If you're only setting a custom titlebar as draggable, you also need to make all
|
||||
buttons in titlebar non-draggable.
|
||||
|
||||
## Text selection
|
||||
|
||||
In a frameless window the dragging behavior may conflict with selecting text.
|
||||
For example, when you drag the titlebar you may accidentally select the text on
|
||||
the titlebar. To prevent this, you need to disable text selection within a
|
||||
draggable area like this:
|
||||
|
||||
```css
|
||||
.titlebar {
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
```
|
||||
|
||||
## Context menu
|
||||
|
||||
On some platforms, the draggable area will be treated as a non-client frame, so
|
||||
when you right click on it a system menu will pop up. To make the context menu
|
||||
behave correctly on all platforms you should never use a custom context menu on
|
||||
draggable areas.
|
||||
|
||||
[ignore-mouse-events]: browser-window.md#winsetignoremouseeventsignore-options
|
||||
[overlay-javascript-apis]: https://github.com/WICG/window-controls-overlay/blob/main/explainer.md#javascript-apis
|
||||
[overlay-css-env-vars]: https://github.com/WICG/window-controls-overlay/blob/main/explainer.md#css-environment-variables
|
||||
@@ -67,3 +67,8 @@ or is being instructed to show a high-contrast UI.
|
||||
|
||||
A `Boolean` for if the OS / Chromium currently has an inverted color scheme
|
||||
or is being instructed to use an inverted color scheme.
|
||||
|
||||
### `nativeTheme.inForcedColorsMode` _Windows_ _Readonly_
|
||||
|
||||
A `boolean` indicating whether Chromium is in forced colors mode, controlled by system accessibility settings.
|
||||
Currently, Windows high contrast is the only system setting that triggers forced colors mode.
|
||||
|
||||
@@ -178,7 +178,6 @@ Returns an object with V8 heap statistics. Note that all statistics are reported
|
||||
Returns `Object`:
|
||||
|
||||
* `allocated` Integer - Size of all allocated objects in Kilobytes.
|
||||
* `marked` Integer - Size of all marked objects in Kilobytes.
|
||||
* `total` Integer - Total allocated space in Kilobytes.
|
||||
|
||||
Returns an object with Blink memory information.
|
||||
|
||||
@@ -18,9 +18,9 @@ The `safeStorage` module has the following methods:
|
||||
|
||||
Returns `Boolean` - Whether encryption is available.
|
||||
|
||||
On Linux, returns true if the secret key is
|
||||
available. On MacOS, returns true if Keychain is available.
|
||||
On Windows, returns true with no other preconditions.
|
||||
On Linux, returns true if the app has emitted the `ready` event and the secret key is available.
|
||||
On MacOS, returns true if Keychain is available.
|
||||
On Windows, returns true once the app has emitted the `ready` event.
|
||||
|
||||
### `safeStorage.encryptString(plainText)`
|
||||
|
||||
|
||||
@@ -297,6 +297,35 @@ app.whenReady().then(() => {
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
win.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'serial') {
|
||||
// Add logic here to determine if permission should be given to allow serial selection
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Optionally, retrieve previously persisted devices from a persistent store
|
||||
const grantedDevices = fetchGrantedDevices()
|
||||
|
||||
win.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (new URL(details.origin).hostname === 'some-host' && details.deviceType === 'serial') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.serial.requestPort` first)
|
||||
return true
|
||||
}
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
win.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
|
||||
event.preventDefault()
|
||||
const selectedPort = portList.find((device) => {
|
||||
@@ -589,6 +618,7 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
||||
* `permissionGranted` Boolean - Allow or deny the permission.
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
* `externalURL` String (optional) - The url of the `openExternal` request.
|
||||
* `securityOrigin` String (optional) - The security origin of the `media` request.
|
||||
* `mediaTypes` String[] (optional) - The types of media access being requested, elements can be `video`
|
||||
or `audio`
|
||||
* `requestingUrl` String - The last URL the requesting frame loaded
|
||||
@@ -647,9 +677,9 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents,
|
||||
|
||||
* `handler` Function\<Boolean> | null
|
||||
* `details` Object
|
||||
* `deviceType` String - The type of device that permission is being requested on, can be `hid`.
|
||||
* `deviceType` String - The type of device that permission is being requested on, can be `hid` or `serial`.
|
||||
* `origin` String - The origin URL of the device permission check.
|
||||
* `device` [HIDDevice](structures/hid-device.md) - the device that permission is being requested for.
|
||||
* `device` [HIDDevice](structures/hid-device.md) | [SerialPort](structures/serial-port.md)- the device that permission is being requested for.
|
||||
* `frame` [WebFrameMain](web-frame-main.md) - WebFrameMain checking the device permission.
|
||||
|
||||
Sets the handler which can be used to respond to device permission checks for the `session`.
|
||||
@@ -674,6 +704,8 @@ app.whenReady().then(() => {
|
||||
if (permission === 'hid') {
|
||||
// Add logic here to determine if permission should be given to allow HID selection
|
||||
return true
|
||||
} else if (permission === 'serial') {
|
||||
// Add logic here to determine if permission should be given to allow serial port selection
|
||||
}
|
||||
return false
|
||||
})
|
||||
@@ -694,6 +726,11 @@ app.whenReady().then(() => {
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
})
|
||||
} else if (details.deviceType === 'serial') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.hid.requestDevice` first)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
@@ -866,8 +903,10 @@ setting with the current OS locale. This setting is persisted across restarts.
|
||||
By default Electron will download hunspell dictionaries from the Chromium CDN. If you want to override this
|
||||
behavior you can use this API to point the dictionary downloader at your own hosted version of the hunspell
|
||||
dictionaries. We publish a `hunspell_dictionaries.zip` file with each release which contains the files you need
|
||||
to host here, the file server must be **case insensitive** you must upload each file twice, once with the case it
|
||||
has in the ZIP file and once with the filename as all lower case.
|
||||
to host here.
|
||||
|
||||
The file server must be **case insensitive**. If you cannot do this, you must upload each file twice: once with
|
||||
the case it has in the ZIP file and once with the filename as all lowercase.
|
||||
|
||||
If the files present in `hunspell_dictionaries.zip` are available at `https://example.com/dictionaries/language-code.bdic`
|
||||
then you should call this api with `ses.setSpellCheckerDictionaryDownloadURL('https://example.com/dictionaries/')`. Please
|
||||
|
||||
@@ -736,6 +736,8 @@ first available device will be selected. `callback` should be called with
|
||||
`deviceId` to be selected, passing empty string to `callback` will
|
||||
cancel the request.
|
||||
|
||||
If no event listener is added for this event, all bluetooth requests will be cancelled.
|
||||
|
||||
```javascript
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
@@ -1494,8 +1496,8 @@ win.loadURL('http://github.com')
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
// Use default printing options
|
||||
const pdfPath = path.join(os.homedir(), 'Desktop', 'temp.pdf')
|
||||
win.webContents.printToPDF({}).then(data => {
|
||||
const pdfPath = path.join(os.homedir(), 'Desktop', 'temp.pdf')
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
@@ -1610,7 +1612,7 @@ app.whenReady().then(() => {
|
||||
|
||||
* `options` Object (optional)
|
||||
* `mode` String - Opens the devtools with specified dock state, can be
|
||||
`right`, `bottom`, `undocked`, `detach`. Defaults to last used dock state.
|
||||
`left`, `right`, `bottom`, `undocked`, `detach`. Defaults to last used dock state.
|
||||
In `undocked` mode it's possible to dock back. In `detach` mode it's not.
|
||||
* `activate` Boolean (optional) - Whether to bring the opened devtools window
|
||||
to the foreground. The default is `true`.
|
||||
@@ -1838,7 +1840,7 @@ the cursor when dragging.
|
||||
|
||||
#### `contents.savePage(fullPath, saveType)`
|
||||
|
||||
* `fullPath` String - The full file path.
|
||||
* `fullPath` String - The absolute file path.
|
||||
* `saveType` String - Specify the save type.
|
||||
* `HTMLOnly` - Save only the HTML of the page.
|
||||
* `HTMLComplete` - Save complete-html page.
|
||||
|
||||
@@ -5,8 +5,8 @@
|
||||
Process: [Renderer](../glossary.md#renderer-process)
|
||||
|
||||
`webFrame` export of the Electron module is an instance of the `WebFrame`
|
||||
class representing the top frame of the current `BrowserWindow`. Sub-frames can
|
||||
be retrieved by certain properties and methods (e.g. `webFrame.firstChild`).
|
||||
class representing the current frame. Sub-frames can be retrieved by
|
||||
certain properties and methods (e.g. `webFrame.firstChild`).
|
||||
|
||||
An example of zooming current page to 200%.
|
||||
|
||||
|
||||
@@ -1025,3 +1025,78 @@ 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/docs/extensions/reference/webviewTag/
|
||||
|
||||
### Event: 'context-menu'
|
||||
|
||||
Returns:
|
||||
|
||||
* `params` Object
|
||||
* `x` Integer - x coordinate.
|
||||
* `y` Integer - y coordinate.
|
||||
* `linkURL` String - URL of the link that encloses the node the context menu
|
||||
was invoked on.
|
||||
* `linkText` String - Text associated with the link. May be an empty
|
||||
string if the contents of the link are an image.
|
||||
* `pageURL` String - URL of the top level page that the context menu was
|
||||
invoked on.
|
||||
* `frameURL` String - URL of the subframe that the context menu was invoked
|
||||
on.
|
||||
* `srcURL` String - Source URL for the element that the context menu
|
||||
was invoked on. Elements with source URLs are images, audio and video.
|
||||
* `mediaType` String - Type of the node the context menu was invoked on. Can
|
||||
be `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`.
|
||||
* `hasImageContents` Boolean - Whether the context menu was invoked on an image
|
||||
which has non-empty contents.
|
||||
* `isEditable` Boolean - Whether the context is editable.
|
||||
* `selectionText` String - Text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `titleText` String - Title text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `altText` String - Alt text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `suggestedFilename` String - Suggested filename to be used when saving file through 'Save
|
||||
Link As' option of context menu.
|
||||
* `selectionRect` [Rectangle](structures/rectangle.md) - Rect representing the coordinates in the document space of the selection.
|
||||
* `selectionStartOffset` Number - Start position of the selection text.
|
||||
* `referrerPolicy` [Referrer](structures/referrer.md) - The referrer policy of the frame on which the menu is invoked.
|
||||
* `misspelledWord` String - The misspelled word under the cursor, if any.
|
||||
* `dictionarySuggestions` String[] - An array of suggested words to show the
|
||||
user to replace the `misspelledWord`. Only available if there is a misspelled
|
||||
word and spellchecker is enabled.
|
||||
* `frameCharset` String - The character encoding of the frame on which the
|
||||
menu was invoked.
|
||||
* `inputFieldType` String - If the context menu was invoked on an input
|
||||
field, the type of that field. Possible values are `none`, `plainText`,
|
||||
`password`, `other`.
|
||||
* `spellcheckEnabled` Boolean - If the context is editable, whether or not spellchecking is enabled.
|
||||
* `menuSourceType` String - Input source that invoked the context menu.
|
||||
Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`, `longPress`, `longTap`, `touchHandle`, `stylus`, `adjustSelection`, or `adjustSelectionReset`.
|
||||
* `mediaFlags` Object - The flags for the media element the context menu was
|
||||
invoked on.
|
||||
* `inError` Boolean - Whether the media element has crashed.
|
||||
* `isPaused` Boolean - Whether the media element is paused.
|
||||
* `isMuted` Boolean - Whether the media element is muted.
|
||||
* `hasAudio` Boolean - Whether the media element has audio.
|
||||
* `isLooping` Boolean - Whether the media element is looping.
|
||||
* `isControlsVisible` Boolean - Whether the media element's controls are
|
||||
visible.
|
||||
* `canToggleControls` Boolean - Whether the media element's controls are
|
||||
toggleable.
|
||||
* `canPrint` Boolean - Whether the media element can be printed.
|
||||
* `canSave` Boolean - Whether or not the media element can be downloaded.
|
||||
* `canShowPictureInPicture` Boolean - Whether the media element can show picture-in-picture.
|
||||
* `isShowingPictureInPicture` Boolean - Whether the media element is currently showing picture-in-picture.
|
||||
* `canRotate` Boolean - Whether the media element can be rotated.
|
||||
* `canLoop` Boolean - Whether the media element can be looped.
|
||||
* `editFlags` Object - These flags indicate whether the renderer believes it
|
||||
is able to perform the corresponding action.
|
||||
* `canUndo` Boolean - Whether the renderer believes it can undo.
|
||||
* `canRedo` Boolean - Whether the renderer believes it can redo.
|
||||
* `canCut` Boolean - Whether the renderer believes it can cut.
|
||||
* `canCopy` Boolean - Whether the renderer believes it can copy.
|
||||
* `canPaste` Boolean - Whether the renderer believes it can paste.
|
||||
* `canDelete` Boolean - Whether the renderer believes it can delete.
|
||||
* `canSelectAll` Boolean - Whether the renderer believes it can select all.
|
||||
* `canEditRichly` Boolean - Whether the renderer believes it can edit text richly.
|
||||
|
||||
Emitted when there is a new context menu that needs to be handled.
|
||||
|
||||
@@ -64,6 +64,9 @@ window.open('https://github.com', '_blank', 'top=500,left=200,frame=false,nodeIn
|
||||
`features` will be passed to any registered `webContents`'s
|
||||
`did-create-window` event handler in the `options` argument.
|
||||
* `frameName` follows the specification of `windowName` located in the [native documentation](https://developer.mozilla.org/en-US/docs/Web/API/Window/open#parameters).
|
||||
* When opening `about:blank`, the child window's `WebPreferences` will be copied
|
||||
from the parent window, and there is no way to override it because Chromium
|
||||
skips browser side navigation in this case.
|
||||
|
||||
To customize or cancel the creation of the window, you can optionally set an
|
||||
override handler with `webContents.setWindowOpenHandler()` from the main
|
||||
|
||||
@@ -69,6 +69,18 @@ Electron apps.
|
||||
See [here](#removed-desktopcapturergetsources-in-the-renderer) for details on
|
||||
how to replace this API in your app.
|
||||
|
||||
## Planned Breaking API Changes (15.0)
|
||||
|
||||
### Default Changed: `nativeWindowOpen` defaults to `true`
|
||||
|
||||
Prior to Electron 15, `window.open` was by default shimmed to use
|
||||
`BrowserWindowProxy`. This meant that `window.open('about:blank')` did not work
|
||||
to open synchronously scriptable child windows, among other incompatibilities.
|
||||
`nativeWindowOpen` is no longer experimental, and is now the default.
|
||||
|
||||
See the documentation for [window.open in Electron](api/window-open.md)
|
||||
for more details.
|
||||
|
||||
## Planned Breaking API Changes (14.0)
|
||||
|
||||
### Removed: `remote` module
|
||||
@@ -119,16 +131,6 @@ ensure your code works with this property enabled. It has been enabled by defau
|
||||
|
||||
You will be affected by this change if you use either `webFrame.executeJavaScript` or `webFrame.executeJavaScriptInIsolatedWorld`. You will need to ensure that values returned by either of those methods are supported by the [Context Bridge API](api/context-bridge.md#parameter--error--return-type-support) as these methods use the same value passing semantics.
|
||||
|
||||
### Default Changed: `nativeWindowOpen` defaults to `true`
|
||||
|
||||
Prior to Electron 14, `window.open` was by default shimmed to use
|
||||
`BrowserWindowProxy`. This meant that `window.open('about:blank')` did not work
|
||||
to open synchronously scriptable child windows, among other incompatibilities.
|
||||
`nativeWindowOpen` is no longer experimental, and is now the default.
|
||||
|
||||
See the documentation for [window.open in Electron](api/window-open.md)
|
||||
for more details.
|
||||
|
||||
### Removed: BrowserWindowConstructorOptions inheriting from parent windows
|
||||
|
||||
Prior to Electron 14, windows opened with `window.open` would inherit
|
||||
|
||||
@@ -85,42 +85,37 @@ $ gclient sync -f
|
||||
|
||||
## Building
|
||||
|
||||
**Set the environment variable for chromium build tools**
|
||||
|
||||
On Linux & MacOS
|
||||
|
||||
```sh
|
||||
$ cd src
|
||||
$ export CHROMIUM_BUILDTOOLS_PATH=`pwd`/buildtools
|
||||
$ gn gen out/Testing --args="import(\"//electron/build/args/testing.gn\") $GN_EXTRA_ARGS"
|
||||
```
|
||||
|
||||
Or on Windows (without the optional argument):
|
||||
On Windows:
|
||||
|
||||
```sh
|
||||
$ cd src
|
||||
$ set CHROMIUM_BUILDTOOLS_PATH=%cd%\buildtools
|
||||
```
|
||||
|
||||
**To generate Testing build config of Electron:**
|
||||
|
||||
```sh
|
||||
$ gn gen out/Testing --args="import(\"//electron/build/args/testing.gn\")"
|
||||
```
|
||||
|
||||
This will generate a build directory `out/Testing` under `src/` with
|
||||
the testing build configuration. You can replace `Testing` with another name,
|
||||
but it should be a subdirectory of `out`.
|
||||
Also you shouldn't have to run `gn gen` again—if you want to change the
|
||||
build arguments, you can run `gn args out/Testing` to bring up an editor.
|
||||
|
||||
To see the list of available build configuration options, run `gn args
|
||||
out/Testing --list`.
|
||||
|
||||
**For generating Testing build config of
|
||||
Electron:**
|
||||
**To generate Release build config of Electron:**
|
||||
|
||||
```sh
|
||||
$ gn gen out/Testing --args="import(\"//electron/build/args/testing.gn\") $GN_EXTRA_ARGS"
|
||||
$ gn gen out/Release --args="import(\"//electron/build/args/release.gn\")"
|
||||
```
|
||||
|
||||
**For generating Release (aka "non-component" or "static") build config of
|
||||
Electron:**
|
||||
**Note:** This will generate a `out/Testing` or `out/Release` build directory under `src/` with the testing or release build depending upon the configuration passed above. You can replace `Testing|Release` with another names, but it should be a subdirectory of `out`.
|
||||
|
||||
```sh
|
||||
$ gn gen out/Release --args="import(\"//electron/build/args/release.gn\") $GN_EXTRA_ARGS"
|
||||
```
|
||||
Also you shouldn't have to run `gn gen` again—if you want to change the build arguments, you can run `gn args out/Testing` to bring up an editor. To see the list of available build configuration options, run `gn args out/Testing --list`.
|
||||
|
||||
**To build, run `ninja` with the `electron` target:**
|
||||
Nota Bene: This will also take a while and probably heat up your lap.
|
||||
@@ -156,13 +151,13 @@ $ ./out/Testing/electron
|
||||
On linux, first strip the debugging and symbol information:
|
||||
|
||||
```sh
|
||||
electron/script/strip-binaries.py -d out/Release
|
||||
$ electron/script/strip-binaries.py -d out/Release
|
||||
```
|
||||
|
||||
To package the electron build as a distributable zip file:
|
||||
|
||||
```sh
|
||||
ninja -C out/Release electron:electron_dist_zip
|
||||
$ ninja -C out/Release electron:electron_dist_zip
|
||||
```
|
||||
|
||||
### Cross-compiling
|
||||
|
||||
@@ -7,21 +7,7 @@ Follow the guidelines below for building **Electron itself** on Linux, for the p
|
||||
## Prerequisites
|
||||
|
||||
* At least 25GB disk space and 8GB RAM.
|
||||
* Python 2.7.x. Some distributions like CentOS 6.x still use Python 2.6.x
|
||||
so you may need to check your Python version with `python -V`.
|
||||
|
||||
Please also ensure that your system and Python version support at least TLS 1.2.
|
||||
For a quick test, run the following script:
|
||||
|
||||
```sh
|
||||
$ npx @electron/check-python-tls
|
||||
```
|
||||
|
||||
If the script returns that your configuration is using an outdated security
|
||||
protocol, use your system's package manager to update Python to the latest
|
||||
version in the 2.7.x branch. Alternatively, visit https://www.python.org/downloads/
|
||||
for detailed instructions.
|
||||
|
||||
* Python >= 3.7.
|
||||
* Node.js. There are various ways to install Node. You can download
|
||||
source code from [nodejs.org](https://nodejs.org) and compile it.
|
||||
Doing so permits installing Node on your own home directory as a standard user.
|
||||
|
||||
@@ -6,45 +6,12 @@ Follow the guidelines below for building **Electron itself** on macOS, for the p
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* macOS >= 10.11.6
|
||||
* [Xcode](https://developer.apple.com/technologies/tools/) >= 9.0.0
|
||||
* macOS >= 11.6.0
|
||||
* [Xcode](https://developer.apple.com/technologies/tools/). The exact version
|
||||
needed depends on what branch you are building, but the latest version of
|
||||
Xcode is generally a good bet for building `main`.
|
||||
* [node.js](https://nodejs.org) (external)
|
||||
* Python 2.7 with support for TLS 1.2
|
||||
|
||||
## Python
|
||||
|
||||
Please also ensure that your system and Python version support at least TLS 1.2.
|
||||
This depends on both your version of macOS and Python. For a quick test, run:
|
||||
|
||||
```sh
|
||||
$ npx @electron/check-python-tls
|
||||
```
|
||||
|
||||
If the script returns that your configuration is using an outdated security
|
||||
protocol, you can either update macOS to High Sierra or install a new version
|
||||
of Python 2.7.x. To upgrade Python, use [Homebrew](https://brew.sh/):
|
||||
|
||||
```sh
|
||||
$ brew install python@2 && brew link python@2 --force
|
||||
```
|
||||
|
||||
If you are using Python as provided by Homebrew, you also need to install
|
||||
the following Python modules:
|
||||
|
||||
* [pyobjc](https://pypi.org/project/pyobjc/#description)
|
||||
|
||||
You can use `pip` to install it:
|
||||
|
||||
```sh
|
||||
$ pip install pyobjc
|
||||
```
|
||||
|
||||
## macOS SDK
|
||||
|
||||
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 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.
|
||||
* Python >= 3.7
|
||||
|
||||
## Building Electron
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ Style](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/stylegui
|
||||
[clang-format](clang-format.md) to format the C++ code automatically. There is
|
||||
also a script `script/cpplint.py` to check whether all files conform.
|
||||
|
||||
The Python version we are using now is Python 2.7.
|
||||
The Python version we are using now is Python 3.9.
|
||||
|
||||
The C++ code uses a lot of Chromium's abstractions and types, so it's
|
||||
recommended to get acquainted with them. A good place to start is
|
||||
|
||||
@@ -43,8 +43,9 @@ SRV*c:\code\symbols\*https://msdl.microsoft.com/download/symbols;SRV*c:\code\sym
|
||||
|
||||
## Using the symbol server in Visual Studio
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||

|
||||
|
||||
## Troubleshooting: Symbols will not load
|
||||
|
||||
|
||||
@@ -59,8 +59,9 @@
|
||||
|
||||
<p>
|
||||
For more details, see the
|
||||
<a href="https://electronjs.org/docs/api/frameless-window/">
|
||||
Frameless Window
|
||||
<a href="https://electronjs.org/docs/tutorial/window-customization/">
|
||||
Window Customization
|
||||
|
||||
</a>
|
||||
documentation.
|
||||
</p>
|
||||
|
||||
BIN
docs/images/vs-options-debugging-symbols.png
Normal file
BIN
docs/images/vs-options-debugging-symbols.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 32 KiB |
BIN
docs/images/vs-tools-options.png
Normal file
BIN
docs/images/vs-tools-options.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
docs/images/windows-progress-bar.png
Normal file
BIN
docs/images/windows-progress-bar.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 19 KiB |
@@ -1,48 +1,7 @@
|
||||
# Accessibility
|
||||
|
||||
Making accessible applications is important and we're happy to provide
|
||||
functionality to [Devtron][devtron] and [Spectron][spectron] that gives
|
||||
developers the opportunity to make their apps better for everyone.
|
||||
|
||||
---
|
||||
|
||||
Accessibility concerns in Electron applications are similar to those of
|
||||
websites because they're both ultimately HTML. With Electron apps, however,
|
||||
you can't use the online resources for accessibility audits because your app
|
||||
doesn't have a URL to point the auditor to.
|
||||
|
||||
These features bring those auditing tools to your Electron app. You can
|
||||
choose to add audits to your tests with Spectron or use them within DevTools
|
||||
with Devtron. Read on for a summary of the tools.
|
||||
|
||||
## Spectron
|
||||
|
||||
In the testing framework Spectron, you can now audit each window and `<webview>`
|
||||
tag in your application. For example:
|
||||
|
||||
```javascript
|
||||
app.client.auditAccessibility().then((audit) => {
|
||||
if (audit.failed) {
|
||||
console.error(audit.message)
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
You can read more about this feature in [Spectron's documentation][spectron-a11y].
|
||||
|
||||
## Devtron
|
||||
|
||||
In Devtron, there is an accessibility tab which will allow you to audit a
|
||||
page in your app, sort and filter the results.
|
||||
|
||||
![devtron screenshot][devtron-screenshot]
|
||||
|
||||
Both of these tools are using the [Accessibility Developer Tools][a11y-devtools]
|
||||
library built by Google for Chrome. You can learn more about the accessibility
|
||||
audit rules this library uses on that [repository's wiki][a11y-devtools-wiki].
|
||||
|
||||
If you know of other great accessibility tools for Electron, add them to the
|
||||
accessibility documentation with a pull request.
|
||||
websites because they're both ultimately HTML.
|
||||
|
||||
## Manually enabling accessibility features
|
||||
|
||||
@@ -84,10 +43,6 @@ CFStringRef kAXManualAccessibility = CFSTR("AXManualAccessibility");
|
||||
}
|
||||
```
|
||||
|
||||
[devtron]: https://electronjs.org/devtron
|
||||
[devtron-screenshot]: https://cloud.githubusercontent.com/assets/1305617/17156618/9f9bcd72-533f-11e6-880d-389115f40a2a.png
|
||||
[spectron]: https://electronjs.org/spectron
|
||||
[spectron-a11y]: https://github.com/electron/spectron#accessibility-testing
|
||||
[a11y-docs]: https://www.chromium.org/developers/design-documents/accessibility#TOC-How-Chrome-detects-the-presence-of-Assistive-Technology
|
||||
[a11y-devtools]: https://github.com/GoogleChrome/accessibility-developer-tools
|
||||
[a11y-devtools-wiki]: https://github.com/GoogleChrome/accessibility-developer-tools/wiki/Audit-Rules
|
||||
|
||||
@@ -56,7 +56,7 @@ will then be your distribution to deliver to users.
|
||||
|
||||
### With an app source code archive
|
||||
|
||||
Instead of from shipping your app by copying all of its source files, you can
|
||||
Instead of shipping your app by copying all of its source files, you can
|
||||
package your app into an [asar] archive to improve the performance of reading
|
||||
files on platforms like Windows, if you are not already using a bundler such
|
||||
as Parcel or Webpack.
|
||||
|
||||
@@ -1,135 +0,0 @@
|
||||
# Automated Testing with a Custom Driver
|
||||
|
||||
To write automated tests for your Electron app, you will need a way to "drive" your application. [Spectron](https://electronjs.org/spectron) is a commonly-used solution which lets you emulate user actions via [WebDriver](https://webdriver.io/). However, it's also possible to write your own custom driver using node's builtin IPC-over-STDIO. The benefit of a custom driver is that it tends to require less overhead than Spectron, and lets you expose custom methods to your test suite.
|
||||
|
||||
To create a custom driver, we'll use Node.js' [child_process](https://nodejs.org/api/child_process.html) API. The test suite will spawn the Electron process, then establish a simple messaging protocol:
|
||||
|
||||
```js
|
||||
const childProcess = require('child_process')
|
||||
const electronPath = require('electron')
|
||||
|
||||
// spawn the process
|
||||
const env = { /* ... */ }
|
||||
const stdio = ['inherit', 'inherit', 'inherit', 'ipc']
|
||||
const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
|
||||
|
||||
// listen for IPC messages from the app
|
||||
appProcess.on('message', (msg) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
// send an IPC message to the app
|
||||
appProcess.send({ my: 'message' })
|
||||
```
|
||||
|
||||
From within the Electron app, you can listen for messages and send replies using the Node.js [process](https://nodejs.org/api/process.html) API:
|
||||
|
||||
```js
|
||||
// listen for IPC messages from the test suite
|
||||
process.on('message', (msg) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
// send an IPC message to the test suite
|
||||
process.send({ my: 'message' })
|
||||
```
|
||||
|
||||
We can now communicate from the test suite to the Electron app using the `appProcess` object.
|
||||
|
||||
For convenience, you may want to wrap `appProcess` in a driver object that provides more high-level functions. Here is an example of how you can do this:
|
||||
|
||||
```js
|
||||
class TestDriver {
|
||||
constructor ({ path, args, env }) {
|
||||
this.rpcCalls = []
|
||||
|
||||
// start child process
|
||||
env.APP_TEST_DRIVER = 1 // let the app know it should listen for messages
|
||||
this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })
|
||||
|
||||
// handle rpc responses
|
||||
this.process.on('message', (message) => {
|
||||
// pop the handler
|
||||
const rpcCall = this.rpcCalls[message.msgId]
|
||||
if (!rpcCall) return
|
||||
this.rpcCalls[message.msgId] = null
|
||||
// reject/resolve
|
||||
if (message.reject) rpcCall.reject(message.reject)
|
||||
else rpcCall.resolve(message.resolve)
|
||||
})
|
||||
|
||||
// wait for ready
|
||||
this.isReady = this.rpc('isReady').catch((err) => {
|
||||
console.error('Application failed to start', err)
|
||||
this.stop()
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
// simple RPC call
|
||||
// to use: driver.rpc('method', 1, 2, 3).then(...)
|
||||
async rpc (cmd, ...args) {
|
||||
// send rpc request
|
||||
const msgId = this.rpcCalls.length
|
||||
this.process.send({ msgId, cmd, args })
|
||||
return new Promise((resolve, reject) => this.rpcCalls.push({ resolve, reject }))
|
||||
}
|
||||
|
||||
stop () {
|
||||
this.process.kill()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
In the app, you'd need to write a simple handler for the RPC calls:
|
||||
|
||||
```js
|
||||
const METHODS = {
|
||||
isReady () {
|
||||
// do any setup needed
|
||||
return true
|
||||
}
|
||||
// define your RPC-able methods here
|
||||
}
|
||||
|
||||
const onMessage = async ({ msgId, cmd, args }) => {
|
||||
let method = METHODS[cmd]
|
||||
if (!method) method = () => new Error('Invalid method: ' + cmd)
|
||||
try {
|
||||
const resolve = await method(...args)
|
||||
process.send({ msgId, resolve })
|
||||
} catch (err) {
|
||||
const reject = {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
name: err.name
|
||||
}
|
||||
process.send({ msgId, reject })
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.APP_TEST_DRIVER) {
|
||||
process.on('message', onMessage)
|
||||
}
|
||||
```
|
||||
|
||||
Then, in your test suite, you can use your test-driver as follows:
|
||||
|
||||
```js
|
||||
const test = require('ava')
|
||||
const electronPath = require('electron')
|
||||
|
||||
const app = new TestDriver({
|
||||
path: electronPath,
|
||||
args: ['./app'],
|
||||
env: {
|
||||
NODE_ENV: 'test'
|
||||
}
|
||||
})
|
||||
test.before(async t => {
|
||||
await app.isReady
|
||||
})
|
||||
test.after.always('cleanup', async t => {
|
||||
await app.stop()
|
||||
})
|
||||
```
|
||||
409
docs/tutorial/automated-testing.md
Normal file
409
docs/tutorial/automated-testing.md
Normal file
@@ -0,0 +1,409 @@
|
||||
# Automated Testing
|
||||
|
||||
Test automation is an efficient way of validating that your application code works as intended.
|
||||
While Electron doesn't actively maintain its own testing solution, this guide will go over a couple
|
||||
ways you can run end-to-end automated tests on your Electron app.
|
||||
|
||||
## Using the WebDriver interface
|
||||
|
||||
From [ChromeDriver - WebDriver for Chrome][chrome-driver]:
|
||||
|
||||
> WebDriver is an open source tool for automated testing of web apps across many
|
||||
> browsers. It provides capabilities for navigating to web pages, user input,
|
||||
> JavaScript execution, and more. ChromeDriver is a standalone server which
|
||||
> implements WebDriver's wire protocol for Chromium. It is being developed by
|
||||
> members of the Chromium and WebDriver teams.
|
||||
|
||||
There are a few ways that you can set up testing using WebDriver.
|
||||
|
||||
### With WebdriverIO
|
||||
|
||||
[WebdriverIO](https://webdriver.io/) (WDIO) is a test automation framework that provides a
|
||||
Node.js package for testing with WebDriver. Its ecosystem also includes various plugins
|
||||
(e.g. reporter and services) that can help you put together your test setup.
|
||||
|
||||
#### Install the testrunner
|
||||
|
||||
First you need to run the WebdriverIO starter toolkit in your project root directory:
|
||||
|
||||
```sh npm2yarn
|
||||
npx wdio . --yes
|
||||
```
|
||||
|
||||
This installs all necessary packages for you and generates a `wdio.conf.js` configuration file.
|
||||
|
||||
#### Connect WDIO to your Electron app
|
||||
|
||||
Update the capabilities in your configuration file to point to your Electron app binary:
|
||||
|
||||
```javascript title='wdio.conf.js'
|
||||
export.config = {
|
||||
// ...
|
||||
capabilities: [{
|
||||
browserName: 'chrome',
|
||||
'goog:chromeOptions': {
|
||||
binary: '/path/to/your/electron/binary', // Path to your Electron binary.
|
||||
args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/
|
||||
}
|
||||
}]
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
#### Run your tests
|
||||
|
||||
To run your tests:
|
||||
|
||||
```sh
|
||||
$ npx wdio run wdio.conf.js
|
||||
```
|
||||
|
||||
### With Selenium
|
||||
|
||||
[Selenium](https://www.selenium.dev/) is a web automation framework that
|
||||
exposes bindings to WebDriver APIs in many languages. Their Node.js bindings
|
||||
are available under the `selenium-webdriver` package on NPM.
|
||||
|
||||
#### Run a ChromeDriver server
|
||||
|
||||
In order to use Selenium with Electron, you need to download the `electron-chromedriver`
|
||||
binary, and run it:
|
||||
|
||||
```sh npm2yarn
|
||||
npm install --save-dev electron-chromedriver
|
||||
./node_modules/.bin/chromedriver
|
||||
Starting ChromeDriver (v2.10.291558) on port 9515
|
||||
Only local connections are allowed.
|
||||
```
|
||||
|
||||
Remember the port number `9515`, which will be used later.
|
||||
|
||||
#### Connect Selenium to ChromeDriver
|
||||
|
||||
Next, install Selenium into your project:
|
||||
|
||||
```sh npm2yarn
|
||||
npm install --save-dev selenium-webdriver
|
||||
```
|
||||
|
||||
Usage of `selenium-webdriver` with Electron is the same as with
|
||||
normal websites, except that you have to manually specify how to connect
|
||||
ChromeDriver and where to find the binary of your Electron app:
|
||||
|
||||
```js title='test.js'
|
||||
const webdriver = require('selenium-webdriver')
|
||||
const driver = new webdriver.Builder()
|
||||
// The "9515" is the port opened by ChromeDriver.
|
||||
.usingServer('http://localhost:9515')
|
||||
.withCapabilities({
|
||||
'goog:chromeOptions': {
|
||||
// Here is the path to your Electron binary.
|
||||
binary: '/Path-to-Your-App.app/Contents/MacOS/Electron'
|
||||
}
|
||||
})
|
||||
.forBrowser('chrome') // note: use .forBrowser('electron') for selenium-webdriver <= 3.6.0
|
||||
.build()
|
||||
driver.get('http://www.google.com')
|
||||
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver')
|
||||
driver.findElement(webdriver.By.name('btnG')).click()
|
||||
driver.wait(() => {
|
||||
return driver.getTitle().then((title) => {
|
||||
return title === 'webdriver - Google Search'
|
||||
})
|
||||
}, 1000)
|
||||
driver.quit()
|
||||
```
|
||||
|
||||
## Using Playwright
|
||||
|
||||
[Microsoft Playwright](https://playwright.dev) is an end-to-end testing framework built
|
||||
using browser-specific remote debugging protocols, similar to the [Puppeteer] headless
|
||||
Node.js API but geared towards end-to-end testing. Playwright has experimental Electron
|
||||
support via Electron's support for the [Chrome DevTools Protocol] (CDP).
|
||||
|
||||
### Install dependencies
|
||||
|
||||
You can install Playwright through your preferred Node.js package manager. The Playwright team
|
||||
recommends using the `PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD` environment variable to avoid
|
||||
unnecessary browser downloads when testing an Electron app.
|
||||
|
||||
```sh npm2yarn
|
||||
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD=1 npm install --save-dev playwright
|
||||
```
|
||||
|
||||
Playwright also comes with its own test runner, Playwright Test, which is built for end-to-end
|
||||
testing. You can also install it as a dev dependency in your project:
|
||||
|
||||
```sh npm2yarn
|
||||
npm install --save-dev @playwright/test
|
||||
```
|
||||
|
||||
:::caution Dependencies
|
||||
This tutorial was written `playwright@1.16.3` and `@playwright/test@1.16.3`. Check out
|
||||
[Playwright's releases][playwright-releases] page to learn about
|
||||
changes that might affect the code below.
|
||||
:::
|
||||
|
||||
:::info Using third-party test runners
|
||||
If you're interested in using an alternative test runner (e.g. Jest or Mocha), check out
|
||||
Playwright's [Third-Party Test Runner][playwright-test-runners] guide.
|
||||
:::
|
||||
|
||||
### Write your tests
|
||||
|
||||
Playwright launches your app in development mode through the `_electron.launch` API.
|
||||
To point this API to your Electron app, you can pass the path to your main process
|
||||
entry point (here, it is `main.js`).
|
||||
|
||||
```js {5}
|
||||
const { _electron: electron } = require('playwright')
|
||||
const { test } = require('@playwright/test')
|
||||
|
||||
test('launch app', async () => {
|
||||
const electronApp = await electron.launch({ args: ['main.js'] })
|
||||
// close app
|
||||
await electronApp.close()
|
||||
})
|
||||
```
|
||||
|
||||
After that, you will access to an instance of Playwright's `ElectronApp` class. This
|
||||
is a powerful class that has access to main process modules for example:
|
||||
|
||||
```js {6-11}
|
||||
const { _electron: electron } = require('playwright')
|
||||
const { test } = require('@playwright/test')
|
||||
|
||||
test('get isPackaged', async () => {
|
||||
const electronApp = await electron.launch({ args: ['main.js'] })
|
||||
const isPackaged = await electronApp.evaluate(async ({ app }) => {
|
||||
// This runs in Electron's main process, parameter here is always
|
||||
// the result of the require('electron') in the main app script.
|
||||
return app.isPackaged
|
||||
})
|
||||
console.log(isPackaged) // false (because we're in development mode)
|
||||
// close app
|
||||
await electronApp.close()
|
||||
})
|
||||
```
|
||||
|
||||
It can also create individual [Page][playwright-page] objects from Electron BrowserWindow instances.
|
||||
For example, to grab the first BrowserWindow and save a screenshot:
|
||||
|
||||
```js {6-7}
|
||||
const { _electron: electron } = require('playwright')
|
||||
const { test } = require('@playwright/test')
|
||||
|
||||
test('save screenshot', async () => {
|
||||
const electronApp = await electron.launch({ args: ['main.js'] })
|
||||
const window = await electronApp.firstWindow()
|
||||
await window.screenshot({ path: 'intro.png' })
|
||||
// close app
|
||||
await electronApp.close()
|
||||
})
|
||||
```
|
||||
|
||||
Putting all this together using the PlayWright Test runner, let's create a `example.spec.js`
|
||||
test file with a single test and assertion:
|
||||
|
||||
```js title='example.spec.js'
|
||||
const { _electron: electron } = require('playwright')
|
||||
const { test, expect } = require('@playwright/test')
|
||||
|
||||
test('example test', async () => {
|
||||
const electronApp = await electron.launch({ args: ['.'] })
|
||||
const isPackaged = await electronApp.evaluate(async ({ app }) => {
|
||||
// This runs in Electron's main process, parameter here is always
|
||||
// the result of the require('electron') in the main app script.
|
||||
return app.isPackaged;
|
||||
});
|
||||
|
||||
expect(isPackaged).toBe(false);
|
||||
|
||||
// Wait for the first BrowserWindow to open
|
||||
// and return its Page object
|
||||
const window = await electronApp.firstWindow()
|
||||
await window.screenshot({ path: 'intro.png' })
|
||||
|
||||
// close app
|
||||
await electronApp.close()
|
||||
});
|
||||
```
|
||||
|
||||
Then, run Playwright Test using `npx playwright test`. You should see the test pass in your
|
||||
console, and have an `intro.png` screenshot on your filesystem.
|
||||
|
||||
```console
|
||||
☁ $ npx playwright test
|
||||
|
||||
Running 1 test using 1 worker
|
||||
|
||||
✓ example.spec.js:4:1 › example test (1s)
|
||||
```
|
||||
|
||||
:::info
|
||||
Playwright Test will automatically run any files matching the `.*(test|spec)\.(js|ts|mjs)` regex.
|
||||
You can customize this match in the [Playwright Test configuration options][playwright-test-config].
|
||||
:::
|
||||
|
||||
:::tip Further reading
|
||||
Check out Playwright's documentation for the full [Electron][playwright-electron]
|
||||
and [ElectronApplication][playwright-electronapplication] class APIs.
|
||||
:::
|
||||
|
||||
## Using a custom test driver
|
||||
|
||||
It's also possible to write your own custom driver using Node.js' built-in IPC-over-STDIO.
|
||||
Custom test drivers require you to write additional app code, but have lower overhead and let you
|
||||
expose custom methods to your test suite.
|
||||
|
||||
To create a custom driver, we'll use Node.js' [`child_process`](https://nodejs.org/api/child_process.html) API.
|
||||
The test suite will spawn the Electron process, then establish a simple messaging protocol:
|
||||
|
||||
```js title='testDriver.js'
|
||||
const childProcess = require('child_process')
|
||||
const electronPath = require('electron')
|
||||
|
||||
// spawn the process
|
||||
const env = { /* ... */ }
|
||||
const stdio = ['inherit', 'inherit', 'inherit', 'ipc']
|
||||
const appProcess = childProcess.spawn(electronPath, ['./app'], { stdio, env })
|
||||
|
||||
// listen for IPC messages from the app
|
||||
appProcess.on('message', (msg) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
// send an IPC message to the app
|
||||
appProcess.send({ my: 'message' })
|
||||
```
|
||||
|
||||
From within the Electron app, you can listen for messages and send replies using the Node.js
|
||||
[`process`](https://nodejs.org/api/process.html) API:
|
||||
|
||||
```js title='main.js'
|
||||
// listen for messages from the test suite
|
||||
process.on('message', (msg) => {
|
||||
// ...
|
||||
})
|
||||
|
||||
// send a message to the test suite
|
||||
process.send({ my: 'message' })
|
||||
```
|
||||
|
||||
We can now communicate from the test suite to the Electron app using the `appProcess` object.
|
||||
|
||||
For convenience, you may want to wrap `appProcess` in a driver object that provides more
|
||||
high-level functions. Here is an example of how you can do this. Let's start by creating
|
||||
a `TestDriver` class:
|
||||
|
||||
```js title='testDriver.js'
|
||||
class TestDriver {
|
||||
constructor ({ path, args, env }) {
|
||||
this.rpcCalls = []
|
||||
|
||||
// start child process
|
||||
env.APP_TEST_DRIVER = 1 // let the app know it should listen for messages
|
||||
this.process = childProcess.spawn(path, args, { stdio: ['inherit', 'inherit', 'inherit', 'ipc'], env })
|
||||
|
||||
// handle rpc responses
|
||||
this.process.on('message', (message) => {
|
||||
// pop the handler
|
||||
const rpcCall = this.rpcCalls[message.msgId]
|
||||
if (!rpcCall) return
|
||||
this.rpcCalls[message.msgId] = null
|
||||
// reject/resolve
|
||||
if (message.reject) rpcCall.reject(message.reject)
|
||||
else rpcCall.resolve(message.resolve)
|
||||
})
|
||||
|
||||
// wait for ready
|
||||
this.isReady = this.rpc('isReady').catch((err) => {
|
||||
console.error('Application failed to start', err)
|
||||
this.stop()
|
||||
process.exit(1)
|
||||
})
|
||||
}
|
||||
|
||||
// simple RPC call
|
||||
// to use: driver.rpc('method', 1, 2, 3).then(...)
|
||||
async rpc (cmd, ...args) {
|
||||
// send rpc request
|
||||
const msgId = this.rpcCalls.length
|
||||
this.process.send({ msgId, cmd, args })
|
||||
return new Promise((resolve, reject) => this.rpcCalls.push({ resolve, reject }))
|
||||
}
|
||||
|
||||
stop () {
|
||||
this.process.kill()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { TestDriver };
|
||||
```
|
||||
|
||||
In your app code, can then write a simple handler to receive RPC calls:
|
||||
|
||||
```js title='main.js'
|
||||
const METHODS = {
|
||||
isReady () {
|
||||
// do any setup needed
|
||||
return true
|
||||
}
|
||||
// define your RPC-able methods here
|
||||
}
|
||||
|
||||
const onMessage = async ({ msgId, cmd, args }) => {
|
||||
let method = METHODS[cmd]
|
||||
if (!method) method = () => new Error('Invalid method: ' + cmd)
|
||||
try {
|
||||
const resolve = await method(...args)
|
||||
process.send({ msgId, resolve })
|
||||
} catch (err) {
|
||||
const reject = {
|
||||
message: err.message,
|
||||
stack: err.stack,
|
||||
name: err.name
|
||||
}
|
||||
process.send({ msgId, reject })
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.APP_TEST_DRIVER) {
|
||||
process.on('message', onMessage)
|
||||
}
|
||||
```
|
||||
|
||||
Then, in your test suite, you can use your `TestDriver` class with the test automation
|
||||
framework of your choosing. The following example uses
|
||||
[`ava`](https://www.npmjs.com/package/ava), but other popular choices like Jest
|
||||
or Mocha would work as well:
|
||||
|
||||
```js title='test.js'
|
||||
const test = require('ava')
|
||||
const electronPath = require('electron')
|
||||
const { TestDriver } = require('./testDriver')
|
||||
|
||||
const app = new TestDriver({
|
||||
path: electronPath,
|
||||
args: ['./app'],
|
||||
env: {
|
||||
NODE_ENV: 'test'
|
||||
}
|
||||
})
|
||||
test.before(async t => {
|
||||
await app.isReady
|
||||
})
|
||||
test.after.always('cleanup', async t => {
|
||||
await app.stop()
|
||||
})
|
||||
```
|
||||
|
||||
[chrome-driver]: https://sites.google.com/chromium.org/driver/
|
||||
[Puppeteer]: https://github.com/puppeteer/puppeteer
|
||||
[playwright-electron]: https://playwright.dev/docs/api/class-electron/
|
||||
[playwright-electronapplication]: https://playwright.dev/docs/api/class-electronapplication
|
||||
[playwright-page]: https://playwright.dev/docs/api/class-page
|
||||
[playwright-releases]: https://github.com/microsoft/playwright/releases
|
||||
[playwright-test-config]: https://playwright.dev/docs/api/class-testconfig#test-config-test-match
|
||||
[playwright-test-runners]: https://playwright.dev/docs/test-runners/
|
||||
[Chrome DevTools Protocol]: https://chromedevtools.github.io/devtools-protocol/
|
||||
@@ -84,13 +84,22 @@ There are several additional APIs for working with the Web Serial API:
|
||||
and [`serial-port-removed`](../api/session.md#event-serial-port-removed) events
|
||||
on the Session can be used to handle devices being plugged in or unplugged during the
|
||||
`navigator.serial.requestPort` process.
|
||||
* [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
can be used to provide default permissioning to devices without first calling
|
||||
for permission to devices via `navigator.serial.requestPort`. Additionally,
|
||||
the default behavior of Electron is to store granted device permision through
|
||||
the lifetime of the corresponding WebContents. If longer term storage is
|
||||
needed, a developer can store granted device permissions (eg when handling
|
||||
the `select-serial-port` event) and then read from that storage with
|
||||
`setDevicePermissionHandler`.
|
||||
* [`ses.setPermissionCheckHandler(handler)`](../api/session.md#sessetpermissioncheckhandlerhandler)
|
||||
can be used to disable serial access for specific origins.
|
||||
|
||||
### Example
|
||||
|
||||
This example demonstrates an Electron application that automatically selects
|
||||
the first available Arduino Uno serial device (if connected) through
|
||||
serial devices through [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
as well as demonstrating selecting the first available Arduino Uno serial device (if connected) through
|
||||
[`select-serial-port` event on the Session](../api/session.md#event-select-serial-port)
|
||||
when the `Test Web Serial` button is clicked.
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ Special notes:
|
||||
* All dates are our goals but there may be reasons for adjusting the stable deadline, such as security bugs.
|
||||
* Take a look at the [5.0.0 Timeline blog post](https://electronjs.org/blog/electron-5-0-timeline) for info about publicizing our release dates.
|
||||
* Since Electron 6.0, we've been targeting every other Chromium version and releasing our stable on the same day as Chrome stable. You can reference Chromium's release schedule [here](https://chromiumdash.appspot.com/schedule). See [Electron's new release cadence blog post](https://www.electronjs.org/blog/12-week-cadence) for more details on our release schedule.
|
||||
* Electron 15.0 only will include a special Alpha release. Starting in Electron 16.0, we will release on an 8-week cadence. See [Electron's new 8-week cadence blog post](https://www.electronjs.org/blog/8-week-cadence) for more details.
|
||||
* Starting in Electron 16.0, we will release on an 8-week cadence. See [Electron's new 8-week cadence blog post](https://www.electronjs.org/blog/8-week-cadence) for more details.
|
||||
|
||||
| Electron | Alpha | Beta | Stable | Chrome | Node |
|
||||
| ------- | ----- | ------- | ------ | ------ | ---- |
|
||||
@@ -25,4 +25,5 @@ Special notes:
|
||||
| 13.0.0 | -- | 2021-Mar-04 | 2021-May-25 | M91 | v14.16 |
|
||||
| 14.0.0 | -- | 2021-May-27 | 2021-Aug-31 | M93 | v14.17 |
|
||||
| 15.0.0 | 2021-Jul-20 | 2021-Sep-01 | 2021-Sep-21 | M94 | v16.5 |
|
||||
| 16.0.0 | -- | 2021-Sep-23 | 2021-Nov-16 | M96 | TBD |
|
||||
| 16.0.0 | 2021-Sep-23 | 2021-Oct-20 | 2021-Nov-16 | M96 | v16.9 |
|
||||
| 17.0.0 | 2021-Nov-18 | 2022-Jan-06 | 2022-Feb-01 | M98 | TBD |
|
||||
|
||||
@@ -2,43 +2,31 @@
|
||||
|
||||
> A detailed look at our versioning policy and implementation.
|
||||
|
||||
As of version 2.0.0, Electron follows [SemVer](#semver). The following command will install the most recent stable build of Electron:
|
||||
As of version 2.0.0, Electron follows the [SemVer](#semver) spec. The following command will install the most recent stable build of Electron:
|
||||
|
||||
```sh
|
||||
```sh npm2yarn
|
||||
npm install --save-dev electron
|
||||
```
|
||||
|
||||
To update an existing project to use the latest stable version:
|
||||
|
||||
```sh
|
||||
```sh npm2yarn
|
||||
npm install --save-dev electron@latest
|
||||
```
|
||||
|
||||
## Version 1.x
|
||||
|
||||
Electron versions *< 2.0* did not conform to the [SemVer](https://semver.org) spec: major versions corresponded to end-user API changes, minor versions corresponded to Chromium major releases, and patch versions corresponded to new features and bug fixes. While convenient for developers merging features, it creates problems for developers of client-facing applications. The QA testing cycles of major apps like Slack, Stride, Teams, Skype, VS Code, Atom, and Desktop can be lengthy and stability is a highly desired outcome. There is a high risk in adopting new features while trying to absorb bug fixes.
|
||||
|
||||
Here is an example of the 1.x strategy:
|
||||
|
||||

|
||||
|
||||
An app developed with `1.8.1` cannot take the `1.8.3` bug fix without either absorbing the `1.8.2` feature, or by backporting the fix and maintaining a new release line.
|
||||
|
||||
## Version 2.0 and Beyond
|
||||
## Versioning scheme
|
||||
|
||||
There are several major changes from our 1.x strategy outlined below. Each change is intended to satisfy the needs and priorities of developers/maintainers and app developers.
|
||||
|
||||
1. Strict use of SemVer
|
||||
1. Strict use of the the [SemVer](#semver) spec
|
||||
2. Introduction of semver-compliant `-beta` tags
|
||||
3. Introduction of [conventional commit messages](https://conventionalcommits.org/)
|
||||
4. Well-defined stabilization branches
|
||||
5. The `master` branch is versionless; only stabilization branches contain version information
|
||||
5. The `main` branch is versionless; only stabilization branches contain version information
|
||||
|
||||
We will cover in detail how git branching works, how npm tagging works, what developers should expect to see, and how one can backport changes.
|
||||
|
||||
# SemVer
|
||||
|
||||
From 2.0 onward, Electron will follow SemVer.
|
||||
## SemVer
|
||||
|
||||
Below is a table explicitly mapping types of changes to their corresponding category of SemVer (e.g. Major, Minor, Patch).
|
||||
|
||||
@@ -48,22 +36,25 @@ Below is a table explicitly mapping types of changes to their corresponding cate
|
||||
| Node.js major version updates | Node.js minor version updates | Node.js patch version updates |
|
||||
| Chromium version updates | | fix-related chromium patches |
|
||||
|
||||
For more information, see the [Semantic Versioning 2.0.0](https://semver.org/) spec.
|
||||
|
||||
Note that most Chromium updates will be considered breaking. Fixes that can be backported will likely be cherry-picked as patches.
|
||||
|
||||
# Stabilization Branches
|
||||
## Stabilization branches
|
||||
|
||||
Stabilization branches are branches that run parallel to master, taking in only cherry-picked commits that are related to security or stability. These branches are never merged back to master.
|
||||
Stabilization branches are branches that run parallel to `main`, taking in only cherry-picked commits that are related to security or stability. These branches are never merged back to `main`.
|
||||
|
||||

|
||||
|
||||
Since Electron 8, stabilization branches are always **major** version lines, and named against the following template `$MAJOR-x-y` e.g. `8-x-y`. Prior to that we used **minor** version lines and named them as `$MAJOR-$MINOR-x` e.g. `2-0-x`
|
||||
Since Electron 8, stabilization branches are always **major** version lines, and named against the following template `$MAJOR-x-y` e.g. `8-x-y`. Prior to that we used **minor** version lines and named them as `$MAJOR-$MINOR-x` e.g. `2-0-x`.
|
||||
|
||||
We allow for multiple stabilization branches to exist simultaneously, one for each supported version. For more details on which versions are supported, see our [Electron Release Timelines](./electron-timelines.md) doc.
|
||||
|
||||
We allow for multiple stabilization branches to exist simultaneously, and intend to support at least two in parallel at all times, backporting security fixes as necessary.
|
||||

|
||||
|
||||
Older lines will not be supported by GitHub, but other groups can take ownership and backport stability and security fixes on their own. We discourage this, but recognize that it makes life easier for many app developers.
|
||||
Older lines will not be supported by the Electron project, but other groups can take ownership and backport stability and security fixes on their own. We discourage this, but recognize that it makes life easier for many app developers.
|
||||
|
||||
# Beta Releases and Bug Fixes
|
||||
## Beta releases and bug fixes
|
||||
|
||||
Developers want to know which releases are _safe_ to use. Even seemingly innocent features can introduce regressions in complex applications. At the same time, locking to a fixed version is dangerous because you’re ignoring security patches and bug fixes that may have come out since your version. Our goal is to allow the following standard semver ranges in `package.json` :
|
||||
|
||||
@@ -116,15 +107,7 @@ A few examples of how various SemVer ranges will pick up new releases:
|
||||
|
||||

|
||||
|
||||
# Missing Features: Alphas
|
||||
|
||||
Our strategy has a few tradeoffs, which for now we feel are appropriate. Most importantly that new features in master may take a while before reaching a stable release line. If you want to try a new feature immediately, you will have to build Electron yourself.
|
||||
|
||||
As a future consideration, we may introduce one or both of the following:
|
||||
|
||||
* alpha releases that have looser stability constraints to betas; for example it would be allowable to admit new features while a stability channel is in _alpha_
|
||||
|
||||
# Feature Flags
|
||||
## Feature flags
|
||||
|
||||
Feature flags are a common practice in Chromium, and are well-established in the web-development ecosystem. In the context of Electron, a feature flag or **soft branch** must have the following properties:
|
||||
|
||||
@@ -132,20 +115,29 @@ Feature flags are a common practice in Chromium, and are well-established in the
|
||||
* it completely segments new and old code paths; refactoring old code to support a new feature _violates_ the feature-flag contract
|
||||
* feature flags are eventually removed after the feature is released
|
||||
|
||||
# Semantic Commits
|
||||
## Semantic commits
|
||||
|
||||
We seek to increase clarity at all levels of the update and releases process. Starting with `2.0.0` we will require pull requests adhere to the [Conventional Commits](https://conventionalcommits.org/) spec, which can be summarized as follows:
|
||||
All pull requests must adhere to the [Conventional Commits](https://conventionalcommits.org/) spec, which can be summarized as follows:
|
||||
|
||||
* Commits that would result in a SemVer **major** bump must start their body with `BREAKING CHANGE:`.
|
||||
* Commits that would result in a SemVer **minor** bump must start with `feat:`.
|
||||
* Commits that would result in a SemVer **patch** bump must start with `fix:`.
|
||||
|
||||
* We allow squashing of commits, provided that the squashed message adheres to the above message format.
|
||||
* It is acceptable for some commits in a pull request to not include a semantic prefix, as long as the pull request title contains a meaningful encompassing semantic message.
|
||||
The `electron/electron` repository also enforces squash merging, so you only need to make sure that your pull request has the correct title prefix.
|
||||
|
||||
# Versioned `master`
|
||||
## Versioned `main` branch
|
||||
|
||||
* The `master` branch will always contain the next major version `X.0.0-nightly.DATE` in its `package.json`
|
||||
* Release branches are never merged back to master
|
||||
* Release branches _do_ contain the correct version in their `package.json`
|
||||
* As soon as a release branch is cut for a major, master must be bumped to the next major. I.e. `master` is always versioned as the next theoretical release branch
|
||||
* The `main` branch will always contain the next major version `X.0.0-nightly.DATE` in its `package.json`.
|
||||
* Release branches are never merged back to `main`.
|
||||
* Release branches _do_ contain the correct version in their `package.json`.
|
||||
* As soon as a release branch is cut for a major, `main` must be bumped to the next major (i.e. `main` is always versioned as the next theoretical release branch).
|
||||
|
||||
## Historical versioning (Electron 1.X)
|
||||
|
||||
Electron versions *< 2.0* did not conform to the [SemVer](https://semver.org) spec: major versions corresponded to end-user API changes, minor versions corresponded to Chromium major releases, and patch versions corresponded to new features and bug fixes. While convenient for developers merging features, it creates problems for developers of client-facing applications. The QA testing cycles of major apps like Slack, Teams, Skype, VS Code, and GitHub Desktop can be lengthy and stability is a highly desired outcome. There is a high risk in adopting new features while trying to absorb bug fixes.
|
||||
|
||||
Here is an example of the 1.x strategy:
|
||||
|
||||

|
||||
|
||||
An app developed with `1.8.1` cannot take the `1.8.3` bug fix without either absorbing the `1.8.2` feature, or by backporting the fix and maintaining a new release line.
|
||||
|
||||
@@ -91,7 +91,7 @@ The above configuration will download from URLs such as
|
||||
`https://npm.taobao.org/mirrors/electron/8.0.0/electron-v8.0.0-linux-x64.zip`.
|
||||
|
||||
If your mirror serves artifacts with different checksums to the official
|
||||
Electron release you may have to set `ELECTRON_USE_REMOTE_CHECKSUMS=1` to
|
||||
Electron release you may have to set `electron_use_remote_checksums=1` to
|
||||
force Electron to use the remote `SHASUMS256.txt` file to verify the checksum
|
||||
instead of the embedded checksums.
|
||||
|
||||
|
||||
@@ -56,4 +56,4 @@ problem. If not, feel free to fill out our bug report template and submit a new
|
||||
[comic]: https://www.google.com/googlebooks/chrome/
|
||||
[fiddle]: https://electronjs.org/fiddle
|
||||
[issue-tracker]: https://github.com/electron/electron/issues
|
||||
[discord]: https://discord.gg/electron
|
||||
[discord]: https://discord.gg/electronjs
|
||||
|
||||
@@ -146,7 +146,7 @@ desktop environment that follows [Desktop Notifications
|
||||
Specification][notification-spec], including Cinnamon, Enlightenment, Unity,
|
||||
GNOME, KDE.
|
||||
|
||||
[notification-spec]: https://developer.gnome.org/notification-spec/
|
||||
[notification-spec]: https://developer-old.gnome.org/notification-spec/
|
||||
[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx
|
||||
[set-app-user-model-id]: ../api/app.md#appsetappusermodelidid-windows
|
||||
[squirrel-events]: https://github.com/electron/windows-installer/blob/master/README.md#handling-squirrel-events
|
||||
|
||||
@@ -17,7 +17,7 @@ the dirty area is passed to the `paint` event to be more efficient.
|
||||
losses with no benefits.
|
||||
* When nothing is happening on a webpage, no frames are generated.
|
||||
* An offscreen window is always created as a
|
||||
[Frameless Window](../api/frameless-window.md).
|
||||
[Frameless Window](../tutorial/window-customization.md)..
|
||||
|
||||
### Rendering Modes
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ without the need of switching to the window itself.
|
||||
|
||||
On Windows, you can use a taskbar button to display a progress bar.
|
||||
|
||||
![Windows Progress Bar][https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png]
|
||||

|
||||
|
||||
On macOS, the progress bar will be displayed as a part of the dock icon.
|
||||
|
||||
|
||||
@@ -119,7 +119,7 @@ of your project.
|
||||
|
||||
Before we can create a window for our application, we need to create the content that
|
||||
will be loaded into it. In Electron, each window displays web contents that can be loaded
|
||||
from either from a local HTML file or a remote URL.
|
||||
from either a local HTML file or a remote URL.
|
||||
|
||||
For this tutorial, you will be doing the former. Create an `index.html` file in the root
|
||||
folder of your project:
|
||||
|
||||
@@ -73,7 +73,7 @@ until the maintainers feel the maintenance burden is too high to continue doing
|
||||
* 16.x.y
|
||||
* 15.x.y
|
||||
* 14.x.y
|
||||
* 13
|
||||
* 13.x.y
|
||||
|
||||
### End-of-life
|
||||
|
||||
|
||||
@@ -1,173 +0,0 @@
|
||||
# Selenium and WebDriver
|
||||
|
||||
From [ChromeDriver - WebDriver for Chrome][chrome-driver]:
|
||||
|
||||
> WebDriver is an open source tool for automated testing of web apps across many
|
||||
> browsers. It provides capabilities for navigating to web pages, user input,
|
||||
> JavaScript execution, and more. ChromeDriver is a standalone server which
|
||||
> implements WebDriver's wire protocol for Chromium. It is being developed by
|
||||
> members of the Chromium and WebDriver teams.
|
||||
|
||||
## Setting up Spectron
|
||||
|
||||
[Spectron][spectron] is the officially supported ChromeDriver testing framework
|
||||
for Electron. It is built on top of [WebdriverIO](https://webdriver.io/) and
|
||||
has helpers to access Electron APIs in your tests and bundles ChromeDriver.
|
||||
|
||||
```sh
|
||||
$ npm install --save-dev spectron
|
||||
```
|
||||
|
||||
```javascript
|
||||
// A simple test to verify a visible window is opened with a title
|
||||
const Application = require('spectron').Application
|
||||
const assert = require('assert')
|
||||
|
||||
const myApp = new Application({
|
||||
path: '/Applications/MyApp.app/Contents/MacOS/MyApp'
|
||||
})
|
||||
|
||||
const verifyWindowIsVisibleWithTitle = async (app) => {
|
||||
await app.start()
|
||||
try {
|
||||
// Check if the window is visible
|
||||
const isVisible = await app.browserWindow.isVisible()
|
||||
// Verify the window is visible
|
||||
assert.strictEqual(isVisible, true)
|
||||
// Get the window's title
|
||||
const title = await app.client.getTitle()
|
||||
// Verify the window's title
|
||||
assert.strictEqual(title, 'My App')
|
||||
} catch (error) {
|
||||
// Log any failures
|
||||
console.error('Test failed', error.message)
|
||||
}
|
||||
// Stop the application
|
||||
await app.stop()
|
||||
}
|
||||
|
||||
verifyWindowIsVisibleWithTitle(myApp)
|
||||
```
|
||||
|
||||
## Setting up with WebDriverJs
|
||||
|
||||
[WebDriverJs](https://www.selenium.dev/selenium/docs/api/javascript/index.html) provides
|
||||
a Node package for testing with web driver, we will use it as an example.
|
||||
|
||||
### 1. Start ChromeDriver
|
||||
|
||||
First you need to download the `chromedriver` binary, and run it:
|
||||
|
||||
```sh
|
||||
$ npm install electron-chromedriver
|
||||
$ ./node_modules/.bin/chromedriver
|
||||
Starting ChromeDriver (v2.10.291558) on port 9515
|
||||
Only local connections are allowed.
|
||||
```
|
||||
|
||||
Remember the port number `9515`, which will be used later
|
||||
|
||||
### 2. Install WebDriverJS
|
||||
|
||||
```sh
|
||||
$ npm install selenium-webdriver
|
||||
```
|
||||
|
||||
### 3. Connect to ChromeDriver
|
||||
|
||||
The usage of `selenium-webdriver` with Electron is the same with
|
||||
upstream, except that you have to manually specify how to connect
|
||||
chrome driver and where to find Electron's binary:
|
||||
|
||||
```javascript
|
||||
const webdriver = require('selenium-webdriver')
|
||||
|
||||
const driver = new webdriver.Builder()
|
||||
// The "9515" is the port opened by chrome driver.
|
||||
.usingServer('http://localhost:9515')
|
||||
.withCapabilities({
|
||||
'goog:chromeOptions': {
|
||||
// Here is the path to your Electron binary.
|
||||
binary: '/Path-to-Your-App.app/Contents/MacOS/Electron'
|
||||
}
|
||||
})
|
||||
.forBrowser('chrome') // note: use .forBrowser('electron') for selenium-webdriver <= 3.6.0
|
||||
.build()
|
||||
|
||||
driver.get('http://www.google.com')
|
||||
driver.findElement(webdriver.By.name('q')).sendKeys('webdriver')
|
||||
driver.findElement(webdriver.By.name('btnG')).click()
|
||||
driver.wait(() => {
|
||||
return driver.getTitle().then((title) => {
|
||||
return title === 'webdriver - Google Search'
|
||||
})
|
||||
}, 1000)
|
||||
|
||||
driver.quit()
|
||||
```
|
||||
|
||||
## Setting up with WebdriverIO
|
||||
|
||||
[WebdriverIO](https://webdriver.io/) provides a Node package for testing with web
|
||||
driver.
|
||||
|
||||
### 1. Start ChromeDriver
|
||||
|
||||
First you need to download the `chromedriver` binary, and run it:
|
||||
|
||||
```sh
|
||||
$ npm install electron-chromedriver
|
||||
$ ./node_modules/.bin/chromedriver --url-base=wd/hub --port=9515
|
||||
Starting ChromeDriver (v2.10.291558) on port 9515
|
||||
Only local connections are allowed.
|
||||
```
|
||||
|
||||
Remember the port number `9515`, which will be used later
|
||||
|
||||
### 2. Install WebdriverIO
|
||||
|
||||
```sh
|
||||
$ npm install webdriverio
|
||||
```
|
||||
|
||||
### 3. Connect to chrome driver
|
||||
|
||||
```javascript
|
||||
const webdriverio = require('webdriverio')
|
||||
const options = {
|
||||
host: 'localhost', // Use localhost as chrome driver server
|
||||
port: 9515, // "9515" is the port opened by chrome driver.
|
||||
desiredCapabilities: {
|
||||
browserName: 'chrome',
|
||||
'goog:chromeOptions': {
|
||||
binary: '/Path-to-Your-App/electron', // Path to your Electron binary.
|
||||
args: [/* cli arguments */] // Optional, perhaps 'app=' + /path/to/your/app/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const client = webdriverio.remote(options)
|
||||
|
||||
client
|
||||
.init()
|
||||
.url('http://google.com')
|
||||
.setValue('#q', 'webdriverio')
|
||||
.click('#btnG')
|
||||
.getTitle().then((title) => {
|
||||
console.log('Title was: ' + title)
|
||||
})
|
||||
.end()
|
||||
```
|
||||
|
||||
## Workflow
|
||||
|
||||
To test your application without rebuilding Electron,
|
||||
[place](application-distribution.md)
|
||||
your app source into Electron's resource directory.
|
||||
|
||||
Alternatively, pass an argument to run with your Electron binary that points to
|
||||
your app's folder. This eliminates the need to copy-paste your app into
|
||||
Electron's resource directory.
|
||||
|
||||
[chrome-driver]: https://sites.google.com/a/chromium.org/chromedriver/
|
||||
[spectron]: https://electronjs.org/spectron
|
||||
271
docs/tutorial/window-customization.md
Normal file
271
docs/tutorial/window-customization.md
Normal file
@@ -0,0 +1,271 @@
|
||||
# Window Customization
|
||||
|
||||
The `BrowserWindow` module is the foundation of your Electron application, and it exposes
|
||||
many APIs that can change the look and behavior of your browser windows. In this
|
||||
tutorial, we will be going over the various use-cases for window customization on
|
||||
macOS, Windows, and Linux.
|
||||
|
||||
## Create frameless windows
|
||||
|
||||
A frameless window is a window that has no [chrome]. Not to be confused with the Google
|
||||
Chrome browser, window _chrome_ refers to the parts of the window (e.g. toolbars, controls)
|
||||
that are not a part of the web page.
|
||||
|
||||
To create a frameless window, you need to set `frame` to `false` in the `BrowserWindow`
|
||||
constructor.
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ frame: false })
|
||||
```
|
||||
|
||||
## Apply custom title bar styles _macOS_ _Windows_
|
||||
|
||||
Title bar styles allow you to hide most of a BrowserWindow's chrome while keeping the
|
||||
system's native window controls intact and can be configured with the `titleBarStyle`
|
||||
option in the `BrowserWindow` constructor.
|
||||
|
||||
Applying the `hidden` title bar style results in a hidden title bar and a full-size
|
||||
content window.
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ titleBarStyle: 'hidden' })
|
||||
```
|
||||
|
||||
### Control the traffic lights _macOS_
|
||||
|
||||
On macOS, applying the `hidden` title bar style will still expose the standard window
|
||||
controls (“traffic lights”) in the top left.
|
||||
|
||||
#### Customize the look of your traffic lights _macOS_
|
||||
|
||||
The `customButtonsOnHover` title bar style will hide the traffic lights until you hover
|
||||
over them. This is useful if you want to create custom traffic lights in your HTML but still
|
||||
use the native UI to control the window.
|
||||
|
||||
```javascript
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ titleBarStyle: 'customButtonsOnHover' })
|
||||
```
|
||||
|
||||
#### Customize the traffic light position _macOS_
|
||||
|
||||
To modify the position of the traffic light window controls, there are two configuration
|
||||
options available.
|
||||
|
||||
Applying `hiddenInset` title bar style will shift the vertical inset of the traffic lights
|
||||
by a fixed amount.
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
|
||||
```
|
||||
|
||||
If you need more granular control over the positioning of the traffic lights, you can pass
|
||||
a set of coordinates to the `trafficLightPosition` option in the `BrowserWindow`
|
||||
constructor.
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({
|
||||
titleBarStyle: 'hidden',
|
||||
trafficLightPosition: { x: 10, y: 10 }
|
||||
})
|
||||
```
|
||||
|
||||
#### Show and hide the traffic lights programmatically _macOS_
|
||||
|
||||
You can also show and hide the traffic lights programmatically from the main process.
|
||||
The `win.setWindowButtonVisibility` forces traffic lights to be show or hidden depending
|
||||
on the value of its boolean parameter.
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow()
|
||||
// hides the traffic lights
|
||||
win.setWindowButtonVisibility(false)
|
||||
```
|
||||
|
||||
> Note: Given the number of APIs available, there are many ways of achieving this. For instance,
|
||||
> combining `frame: false` with `win.setWindowButtonVisibility(true)` will yield the same
|
||||
> layout outcome as setting `titleBarStyle: 'hidden'`.
|
||||
|
||||
## Window Controls Overlay _macOS_ _Windows_
|
||||
|
||||
The [Window Controls Overlay API] is a web standard that gives web apps the ability to
|
||||
customize their title bar region when installed on desktop. Electron exposes this API
|
||||
through the `BrowserWindow` constructor option `titleBarOverlay`.
|
||||
|
||||
This option only works whenever a custom `titlebarStyle` is applied on macOS or Windows.
|
||||
When `titleBarOverlay` is enabled, the window controls become exposed in their default
|
||||
position, and DOM elements cannot use the area underneath this region.
|
||||
|
||||
The `titleBarOverlay` option accepts two different value formats.
|
||||
|
||||
Specifying `true` on either platform will result in an overlay region with default
|
||||
system colors:
|
||||
|
||||
```javascript title='main.js'
|
||||
// on macOS or Windows
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: true
|
||||
})
|
||||
```
|
||||
|
||||
On Windows, you can also specify the color of the overlay and its symbols by setting
|
||||
`titleBarOverlay` to an object with the `color` and `symbolColor` properties. If an option
|
||||
is not specified, the color will default to its system color for the window control buttons:
|
||||
|
||||
```javascript title='main.js'
|
||||
// on Windows
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
color: '#2f3241',
|
||||
symbolColor: '#74b1be'
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
> Note: Once your title bar overlay is enabled from the main process, you can access the overlay's
|
||||
> color and dimension values from a renderer using a set of readonly
|
||||
> [JavaScript APIs][overlay-javascript-apis] and [CSS Environment Variables][overlay-css-env-vars].
|
||||
|
||||
## Create transparent windows
|
||||
|
||||
By setting the `transparent` option to `true`, you can make a fully transparent window.
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow({ transparent: true })
|
||||
```
|
||||
|
||||
### Limitations
|
||||
|
||||
* You cannot click through the transparent area. See
|
||||
[#1335](https://github.com/electron/electron/issues/1335) for details.
|
||||
* Transparent windows are not resizable. Setting `resizable` to `true` may make
|
||||
a transparent window stop working on some platforms.
|
||||
* The CSS [`blur()`] filter only applies to the window's web contents, so there is no way to apply
|
||||
blur effect to the content below the window (i.e. other applications open on
|
||||
the user's system).
|
||||
* The window will not be transparent when DevTools is opened.
|
||||
* On _Windows_:
|
||||
* Transparent windows will not work when DWM is disabled.
|
||||
* Transparent windows can not be maximized using the Windows system menu or by double
|
||||
clicking the title bar. The reasoning behind this can be seen on
|
||||
PR [#28207](https://github.com/electron/electron/pull/28207).
|
||||
* On _macOS_:
|
||||
* The native window shadow will not be shown on a transparent window.
|
||||
|
||||
## Create click-through windows
|
||||
|
||||
To create a click-through window, i.e. making the window ignore all mouse
|
||||
events, you can call the [win.setIgnoreMouseEvents(ignore)][ignore-mouse-events]
|
||||
API:
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
const win = new BrowserWindow()
|
||||
win.setIgnoreMouseEvents(true)
|
||||
```
|
||||
|
||||
### Forward mouse events _macOS_ _Windows_
|
||||
|
||||
Ignoring mouse messages makes the web contents oblivious to mouse movement,
|
||||
meaning that mouse movement events will not be emitted. On Windows and macOS, an
|
||||
optional parameter can be used to forward mouse move messages to the web page,
|
||||
allowing events such as `mouseleave` to be emitted:
|
||||
|
||||
```javascript title='main.js'
|
||||
const { BrowserWindow, ipcMain } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
const win = new BrowserWindow({
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'preload.js')
|
||||
}
|
||||
})
|
||||
|
||||
ipcMain.on('set-ignore-mouse-events', (event, ...args) => {
|
||||
const win = BrowserWindow.fromWebContents(event.sender)
|
||||
win.setIgnoreMouseEvents(...args)
|
||||
})
|
||||
```
|
||||
|
||||
```javascript title='preload.js'
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
const el = document.getElementById('clickThroughElement')
|
||||
el.addEventListener('mouseenter', () => {
|
||||
ipcRenderer.send('set-ignore-mouse-events', true, { forward: true })
|
||||
})
|
||||
el.addEventListener('mouseleave', () => {
|
||||
ipcRenderer.send('set-ignore-mouse-events', false)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
This makes the web page click-through when over the `#clickThroughElement` element,
|
||||
and returns to normal outside it.
|
||||
|
||||
## Set custom draggable region
|
||||
|
||||
By default, the frameless window is non-draggable. Apps need to specify
|
||||
`-webkit-app-region: drag` in CSS to tell Electron which regions are draggable
|
||||
(like the OS's standard titlebar), and apps can also use
|
||||
`-webkit-app-region: no-drag` to exclude the non-draggable area from the
|
||||
draggable region. Note that only rectangular shapes are currently supported.
|
||||
|
||||
To make the whole window draggable, you can add `-webkit-app-region: drag` as
|
||||
`body`'s style:
|
||||
|
||||
```css title='styles.css'
|
||||
body {
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
```
|
||||
|
||||
And note that if you have made the whole window draggable, you must also mark
|
||||
buttons as non-draggable, otherwise it would be impossible for users to click on
|
||||
them:
|
||||
|
||||
```css title='styles.css'
|
||||
button {
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
```
|
||||
|
||||
If you're only setting a custom titlebar as draggable, you also need to make all
|
||||
buttons in titlebar non-draggable.
|
||||
|
||||
### Tip: disable text selection
|
||||
|
||||
When creating a draggable region, the dragging behavior may conflict with text selection.
|
||||
For example, when you drag the titlebar, you may accidentally select its text contents.
|
||||
To prevent this, you need to disable text selection within a draggable area like this:
|
||||
|
||||
```css
|
||||
.titlebar {
|
||||
-webkit-user-select: none;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
```
|
||||
|
||||
### Tip: disable context menus
|
||||
|
||||
On some platforms, the draggable area will be treated as a non-client frame, so
|
||||
when you right click on it, a system menu will pop up. To make the context menu
|
||||
behave correctly on all platforms, you should never use a custom context menu on
|
||||
draggable areas.
|
||||
|
||||
[`blur()`]: https://developer.mozilla.org/en-US/docs/Web/CSS/filter-function/blur()
|
||||
[`BrowserWindow`]: ../api/browser-window.md
|
||||
[chrome]: https://developer.mozilla.org/en-US/docs/Glossary/Chrome
|
||||
[ignore-mouse-events]: ../api/browser-window.md#winsetignoremouseeventsignore-options
|
||||
[overlay-css-env-vars]: https://github.com/WICG/window-controls-overlay/blob/main/explainer.md#css-environment-variables
|
||||
[overlay-javascript-apis]: https://github.com/WICG/window-controls-overlay/blob/main/explainer.md#javascript-apis
|
||||
[Window Controls Overlay API]: https://github.com/WICG/window-controls-overlay/blob/main/explainer.md
|
||||
@@ -177,6 +177,7 @@ template("electron_paks") {
|
||||
"${root_gen_dir}/components/strings/components_strings_",
|
||||
"${root_gen_dir}/third_party/blink/public/strings/blink_strings_",
|
||||
"${root_gen_dir}/device/bluetooth/strings/bluetooth_strings_",
|
||||
"${root_gen_dir}/extensions/strings/extensions_strings_",
|
||||
"${root_gen_dir}/services/strings/services_strings_",
|
||||
"${root_gen_dir}/ui/strings/app_locale_settings_",
|
||||
"${root_gen_dir}/ui/strings/ui_strings_",
|
||||
@@ -185,6 +186,7 @@ template("electron_paks") {
|
||||
"//chrome/app/resources:platform_locale_settings",
|
||||
"//components/strings:components_strings",
|
||||
"//device/bluetooth/strings",
|
||||
"//extensions/strings",
|
||||
"//services/strings",
|
||||
"//third_party/blink/public/strings",
|
||||
"//ui/strings:app_locale_settings",
|
||||
|
||||
@@ -33,6 +33,23 @@
|
||||
{SCREEN_INDEX, plural, =1{Screen #} other{Screen #}}
|
||||
</message>
|
||||
|
||||
<!-- File Select Helper-->
|
||||
<message name="IDS_IMAGE_FILES" desc="The description of the image file extensions in the select file dialog.">
|
||||
Image Files
|
||||
</message>
|
||||
<message name="IDS_AUDIO_FILES" desc="The description of the audio file extensions in the select file dialog.">
|
||||
Audio Files
|
||||
</message>
|
||||
<message name="IDS_VIDEO_FILES" desc="The description of the video file extensions in the select file dialog.">
|
||||
Video Files
|
||||
</message>
|
||||
<message name="IDS_CUSTOM_FILES" desc="The description of the custom file extensions in the select file dialog.">
|
||||
Custom Files
|
||||
</message>
|
||||
<message name="IDS_DEFAULT_DOWNLOAD_FILENAME" desc="Default name for downloaded files when we have no idea what they could be.">
|
||||
download
|
||||
</message>
|
||||
|
||||
<!-- Picture-in-Picture -->
|
||||
<if expr="is_macosx">
|
||||
<message name="IDS_PICTURE_IN_PICTURE_TITLE_TEXT" desc="Title of the Picture-in-Picture window. This appears in the system tray and window header.">
|
||||
|
||||
@@ -23,7 +23,6 @@ auto_filenames = {
|
||||
"docs/api/environment-variables.md",
|
||||
"docs/api/extensions.md",
|
||||
"docs/api/file-object.md",
|
||||
"docs/api/frameless-window.md",
|
||||
"docs/api/global-shortcut.md",
|
||||
"docs/api/in-app-purchase.md",
|
||||
"docs/api/incoming-message.md",
|
||||
|
||||
@@ -347,6 +347,8 @@ filenames = {
|
||||
"shell/browser/child_web_contents_tracker.h",
|
||||
"shell/browser/cookie_change_notifier.cc",
|
||||
"shell/browser/cookie_change_notifier.h",
|
||||
"shell/browser/electron_api_ipc_handler_impl.cc",
|
||||
"shell/browser/electron_api_ipc_handler_impl.h",
|
||||
"shell/browser/electron_autofill_driver.cc",
|
||||
"shell/browser/electron_autofill_driver.h",
|
||||
"shell/browser/electron_autofill_driver_factory.cc",
|
||||
@@ -355,8 +357,6 @@ filenames = {
|
||||
"shell/browser/electron_browser_client.h",
|
||||
"shell/browser/electron_browser_context.cc",
|
||||
"shell/browser/electron_browser_context.h",
|
||||
"shell/browser/electron_browser_handler_impl.cc",
|
||||
"shell/browser/electron_browser_handler_impl.h",
|
||||
"shell/browser/electron_browser_main_parts.cc",
|
||||
"shell/browser/electron_browser_main_parts.h",
|
||||
"shell/browser/electron_download_manager_delegate.cc",
|
||||
@@ -373,6 +373,8 @@ filenames = {
|
||||
"shell/browser/electron_quota_permission_context.h",
|
||||
"shell/browser/electron_speech_recognition_manager_delegate.cc",
|
||||
"shell/browser/electron_speech_recognition_manager_delegate.h",
|
||||
"shell/browser/electron_web_contents_utility_handler_impl.cc",
|
||||
"shell/browser/electron_web_contents_utility_handler_impl.h",
|
||||
"shell/browser/electron_web_ui_controller_factory.cc",
|
||||
"shell/browser/electron_web_ui_controller_factory.h",
|
||||
"shell/browser/event_emitter_mixin.cc",
|
||||
@@ -675,11 +677,6 @@ filenames = {
|
||||
"shell/utility/electron_content_utility_client.h",
|
||||
]
|
||||
|
||||
lib_sources_nss = [
|
||||
"chromium_src/chrome/browser/certificate_manager_model.cc",
|
||||
"chromium_src/chrome/browser/certificate_manager_model.h",
|
||||
]
|
||||
|
||||
lib_sources_extensions = [
|
||||
"shell/browser/extensions/api/i18n/i18n_api.cc",
|
||||
"shell/browser/extensions/api/i18n/i18n_api.h",
|
||||
|
||||
@@ -39,7 +39,8 @@ Object.assign(app, {
|
||||
hasSwitch: (theSwitch: string) => commandLine.hasSwitch(String(theSwitch)),
|
||||
getSwitchValue: (theSwitch: string) => commandLine.getSwitchValue(String(theSwitch)),
|
||||
appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
|
||||
appendArgument: (arg: string) => commandLine.appendArgument(String(arg))
|
||||
appendArgument: (arg: string) => commandLine.appendArgument(String(arg)),
|
||||
removeSwitch: (theSwitch: string) => commandLine.removeSwitch(String(theSwitch))
|
||||
} as Electron.CommandLine
|
||||
});
|
||||
|
||||
|
||||
@@ -72,7 +72,10 @@ BrowserWindow.getAllWindows = () => {
|
||||
|
||||
BrowserWindow.getFocusedWindow = () => {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (window.isFocused() || window.isDevToolsFocused()) return window;
|
||||
const hasWC = window.webContents && !window.webContents.isDestroyed();
|
||||
if (!window.isDestroyed() && hasWC) {
|
||||
if (window.isFocused() || window.isDevToolsFocused()) return window;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
@@ -593,12 +593,16 @@ WebContents.prototype._init = function () {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event);
|
||||
if (this.listenerCount('ipc-message-sync') === 0 && ipcMain.listenerCount(channel) === 0) {
|
||||
console.warn(`WebContents #${this.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
|
||||
}
|
||||
this.emit('ipc-message-sync', event, channel, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
});
|
||||
|
||||
this.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent, internal: boolean, channel: string, message: any, ports: any[]) {
|
||||
addSenderFrameToEvent(event);
|
||||
event.ports = ports.map(p => new MessagePortMain(p));
|
||||
ipcMain.emit(channel, event, message);
|
||||
});
|
||||
@@ -666,16 +670,6 @@ WebContents.prototype._init = function () {
|
||||
postBody
|
||||
};
|
||||
windowOpenOverriddenOptions = this._callWindowOpenHandler(event, details);
|
||||
// if attempting to use this API with the deprecated new-window event,
|
||||
// windowOpenOverriddenOptions will always return null. This ensures
|
||||
// short-term backwards compatibility until new-window is removed.
|
||||
const parsedFeatures = parseFeatures(rawFeatures);
|
||||
const overriddenFeatures: BrowserWindowConstructorOptions = {
|
||||
...parsedFeatures.options,
|
||||
webPreferences: parsedFeatures.webPreferences
|
||||
};
|
||||
windowOpenOverriddenOptions = windowOpenOverriddenOptions || overriddenFeatures;
|
||||
|
||||
if (!event.defaultPrevented) {
|
||||
const secureOverrideWebPreferences = windowOpenOverriddenOptions ? {
|
||||
// Allow setting of backgroundColor as a webPreference even though
|
||||
@@ -685,9 +679,19 @@ WebContents.prototype._init = function () {
|
||||
transparent: windowOpenOverriddenOptions.transparent,
|
||||
...windowOpenOverriddenOptions.webPreferences
|
||||
} : undefined;
|
||||
this._setNextChildWebPreferences(
|
||||
makeWebPreferences({ embedder: event.sender, secureOverrideWebPreferences })
|
||||
);
|
||||
// TODO(zcbenz): The features string is parsed twice: here where it is
|
||||
// passed to C++, and in |makeBrowserWindowOptions| later where it is
|
||||
// not actually used since the WebContents is created here.
|
||||
// We should be able to remove the latter once the |nativeWindowOpen|
|
||||
// option is removed.
|
||||
const { webPreferences: parsedWebPreferences } = parseFeatures(rawFeatures);
|
||||
// Parameters should keep same with |makeBrowserWindowOptions|.
|
||||
const webPreferences = makeWebPreferences({
|
||||
embedder: event.sender,
|
||||
insecureParsedWebPreferences: parsedWebPreferences,
|
||||
secureOverrideWebPreferences
|
||||
});
|
||||
this._setNextChildWebPreferences(webPreferences);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -735,6 +739,14 @@ WebContents.prototype._init = function () {
|
||||
}
|
||||
});
|
||||
|
||||
this.on('select-bluetooth-device', (event, devices, callback) => {
|
||||
if (this.listenerCount('select-bluetooth-device') === 1) {
|
||||
// Cancel it if there are no handlers
|
||||
event.preventDefault();
|
||||
callback('');
|
||||
}
|
||||
});
|
||||
|
||||
const event = process._linkedBinding('electron_browser_event').createEmpty();
|
||||
app.emit('web-contents-created', event, this);
|
||||
|
||||
|
||||
@@ -7,7 +7,11 @@ WebFrameMain.prototype.send = function (channel, ...args) {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
return this._send(false /* internal */, channel, args);
|
||||
try {
|
||||
return this._send(false /* internal */, channel, args);
|
||||
} catch (e) {
|
||||
console.error('Error sending from webFrameMain: ', e);
|
||||
}
|
||||
};
|
||||
|
||||
WebFrameMain.prototype._sendInternal = function (channel, ...args) {
|
||||
@@ -15,7 +19,11 @@ WebFrameMain.prototype._sendInternal = function (channel, ...args) {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
return this._send(true /* internal */, channel, args);
|
||||
try {
|
||||
return this._send(true /* internal */, channel, args);
|
||||
} catch (e) {
|
||||
console.error('Error sending from webFrameMain: ', e);
|
||||
}
|
||||
};
|
||||
|
||||
WebFrameMain.prototype.postMessage = function (...args) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { webViewEvents } from '@electron/internal/common/web-view-events';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
|
||||
interface GuestInstance {
|
||||
elementInstanceId: number;
|
||||
elementInstanceId?: number;
|
||||
visibilityState?: VisibilityState;
|
||||
embedder: Electron.WebContents;
|
||||
guest: Electron.WebContents;
|
||||
@@ -45,7 +45,6 @@ function makeWebPreferences (embedder: Electron.WebContents, params: Record<stri
|
||||
webSecurity: !params.disablewebsecurity,
|
||||
enableBlinkFeatures: params.blinkfeatures,
|
||||
disableBlinkFeatures: params.disableblinkfeatures,
|
||||
partition: params.partition,
|
||||
...parsedWebPreferences
|
||||
};
|
||||
|
||||
@@ -75,26 +74,27 @@ function makeWebPreferences (embedder: Electron.WebContents, params: Record<stri
|
||||
return webPreferences;
|
||||
}
|
||||
|
||||
function makeLoadURLOptions (params: Record<string, any>) {
|
||||
const opts: Electron.LoadURLOptions = {};
|
||||
if (params.httpreferrer) {
|
||||
opts.httpReferrer = params.httpreferrer;
|
||||
}
|
||||
if (params.useragent) {
|
||||
opts.userAgent = params.useragent;
|
||||
}
|
||||
return opts;
|
||||
}
|
||||
|
||||
// Create a new guest instance.
|
||||
const createGuest = function (embedder: Electron.WebContents, embedderFrameId: number, elementInstanceId: number, params: Record<string, any>) {
|
||||
const webPreferences = makeWebPreferences(embedder, params);
|
||||
const event = eventBinding.createWithSender(embedder);
|
||||
|
||||
embedder.emit('will-attach-webview', event, webPreferences, params);
|
||||
if (event.defaultPrevented) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-undef
|
||||
const guest = (webContents as typeof ElectronInternal.WebContents).create({
|
||||
...webPreferences,
|
||||
type: 'webview',
|
||||
partition: params.partition,
|
||||
embedder
|
||||
});
|
||||
|
||||
const guestInstanceId = guest.id;
|
||||
guestInstances.set(guestInstanceId, {
|
||||
elementInstanceId,
|
||||
guest,
|
||||
embedder
|
||||
});
|
||||
@@ -108,6 +108,9 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
|
||||
// Init guest web view after attached.
|
||||
guest.once('did-attach' as any, function (this: Electron.WebContents, event: Electron.Event) {
|
||||
const params = this.attachParams!;
|
||||
delete this.attachParams;
|
||||
|
||||
const previouslyAttached = this.viewInstanceId != null;
|
||||
this.viewInstanceId = params.instanceId;
|
||||
|
||||
@@ -117,14 +120,7 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
}
|
||||
|
||||
if (params.src) {
|
||||
const opts: Electron.LoadURLOptions = {};
|
||||
if (params.httpreferrer) {
|
||||
opts.httpReferrer = params.httpreferrer;
|
||||
}
|
||||
if (params.useragent) {
|
||||
opts.userAgent = params.useragent;
|
||||
}
|
||||
this.loadURL(params.src, opts);
|
||||
this.loadURL(params.src, params.opts);
|
||||
}
|
||||
embedder.emit('did-attach-webview', event, guest);
|
||||
});
|
||||
@@ -177,25 +173,78 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
}
|
||||
});
|
||||
|
||||
if (attachGuest(embedder, embedderFrameId, elementInstanceId, guestInstanceId, params)) {
|
||||
return guestInstanceId;
|
||||
}
|
||||
|
||||
return -1;
|
||||
};
|
||||
|
||||
// Attach the guest to an element of embedder.
|
||||
const attachGuest = function (embedder: Electron.WebContents, embedderFrameId: number, elementInstanceId: number, guestInstanceId: number, params: Record<string, any>) {
|
||||
// Destroy the old guest when attaching.
|
||||
const key = `${embedder.id}-${elementInstanceId}`;
|
||||
const oldGuestInstanceId = embedderElementsMap.get(key);
|
||||
if (oldGuestInstanceId != null) {
|
||||
// Reattachment to the same guest is just a no-op.
|
||||
if (oldGuestInstanceId === guestInstanceId) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const oldGuestInstance = guestInstances.get(oldGuestInstanceId);
|
||||
if (oldGuestInstance) {
|
||||
oldGuestInstance.guest.detachFromOuterFrame();
|
||||
}
|
||||
}
|
||||
|
||||
const guestInstance = guestInstances.get(guestInstanceId);
|
||||
// If this isn't a valid guest instance then do nothing.
|
||||
if (!guestInstance) {
|
||||
console.error(new Error(`Guest attach failed: Invalid guestInstanceId ${guestInstanceId}`));
|
||||
return false;
|
||||
}
|
||||
const { guest } = guestInstance;
|
||||
if (guest.hostWebContents !== embedder) {
|
||||
console.error(new Error(`Guest attach failed: Access denied to guestInstanceId ${guestInstanceId}`));
|
||||
return false;
|
||||
}
|
||||
|
||||
const { instanceId } = params;
|
||||
|
||||
// If this guest is already attached to an element then remove it
|
||||
if (guestInstance.elementInstanceId) {
|
||||
const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`;
|
||||
embedderElementsMap.delete(oldKey);
|
||||
|
||||
// Remove guest from embedder if moving across web views
|
||||
if (guest.viewInstanceId !== instanceId) {
|
||||
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId);
|
||||
guestInstance.embedder._sendInternal(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${guest.viewInstanceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
const webPreferences = makeWebPreferences(embedder, params);
|
||||
|
||||
const event = eventBinding.createWithSender(embedder);
|
||||
embedder.emit('will-attach-webview', event, webPreferences, params);
|
||||
if (event.defaultPrevented) {
|
||||
if (guest.viewInstanceId == null) guest.viewInstanceId = instanceId;
|
||||
guest.destroy();
|
||||
return false;
|
||||
}
|
||||
|
||||
guest.attachParams = { instanceId, src: params.src, opts: makeLoadURLOptions(params) };
|
||||
embedderElementsMap.set(key, guestInstanceId);
|
||||
|
||||
guest.setEmbedder(embedder);
|
||||
guestInstance.embedder = embedder;
|
||||
guestInstance.elementInstanceId = elementInstanceId;
|
||||
|
||||
watchEmbedder(embedder);
|
||||
|
||||
webViewManager.addGuest(guestInstanceId, embedder, guest, webPreferences);
|
||||
guest.attachToIframe(embedder, embedderFrameId);
|
||||
|
||||
return guestInstanceId;
|
||||
return true;
|
||||
};
|
||||
|
||||
// Remove an guest-embedder relationship.
|
||||
|
||||
@@ -65,8 +65,13 @@ export function openGuestWindow ({ event, embedder, guest, referrer, disposition
|
||||
// https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name
|
||||
const existingWindow = getGuestWindowByFrameName(frameName);
|
||||
if (existingWindow) {
|
||||
existingWindow.loadURL(url);
|
||||
return existingWindow;
|
||||
if (existingWindow.isDestroyed() || existingWindow.webContents.isDestroyed()) {
|
||||
// FIXME(t57ser): The webContents is destroyed for some reason, unregister the frame name
|
||||
unregisterFrameName(frameName);
|
||||
} else {
|
||||
existingWindow.loadURL(url);
|
||||
return existingWindow;
|
||||
}
|
||||
}
|
||||
|
||||
const window = new BrowserWindow({
|
||||
@@ -212,6 +217,10 @@ function makeBrowserWindowOptions ({ embedder, features, overrideOptions }: {
|
||||
height: 600,
|
||||
...parsedOptions,
|
||||
...overrideOptions,
|
||||
// Note that for |nativeWindowOpen: true| the WebContents is created in
|
||||
// |api::WebContents::WebContentsCreatedWithFullParams|, with prefs
|
||||
// parsed in the |-will-add-new-contents| event.
|
||||
// The |webPreferences| here is only used by |nativeWindowOpen: false|.
|
||||
webPreferences: makeWebPreferences({
|
||||
embedder,
|
||||
insecureParsedWebPreferences: parsedWebPreferences,
|
||||
|
||||
@@ -38,6 +38,10 @@ ipcMainInternal.handle(IPC_MESSAGES.BROWSER_GET_LAST_WEB_PREFERENCES, function (
|
||||
return event.sender.getLastWebPreferences();
|
||||
});
|
||||
|
||||
ipcMainInternal.handle(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO, function (event) {
|
||||
return event.sender._getProcessMemoryInfo();
|
||||
});
|
||||
|
||||
// Methods not listed in this set are called directly in the renderer process.
|
||||
const allowedClipboardMethods = (() => {
|
||||
switch (process.platform) {
|
||||
|
||||
@@ -4,9 +4,11 @@ export const enum IPC_MESSAGES {
|
||||
BROWSER_PRELOAD_ERROR = 'BROWSER_PRELOAD_ERROR',
|
||||
BROWSER_SANDBOX_LOAD = 'BROWSER_SANDBOX_LOAD',
|
||||
BROWSER_WINDOW_CLOSE = 'BROWSER_WINDOW_CLOSE',
|
||||
BROWSER_GET_PROCESS_MEMORY_INFO = 'BROWSER_GET_PROCESS_MEMORY_INFO',
|
||||
|
||||
GUEST_INSTANCE_VISIBILITY_CHANGE = 'GUEST_INSTANCE_VISIBILITY_CHANGE',
|
||||
|
||||
GUEST_VIEW_INTERNAL_DESTROY_GUEST = 'GUEST_VIEW_INTERNAL_DESTROY_GUEST',
|
||||
GUEST_VIEW_INTERNAL_DISPATCH_EVENT = 'GUEST_VIEW_INTERNAL_DISPATCH_EVENT',
|
||||
|
||||
GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST = 'GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST',
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
export const enum IPC_MESSAGES {
|
||||
BROWSER_REQUIRE = 'REMOTE_BROWSER_REQUIRE',
|
||||
BROWSER_GET_BUILTIN = 'REMOTE_BROWSER_GET_BUILTIN',
|
||||
BROWSER_GET_GLOBAL = 'REMOTE_BROWSER_GET_GLOBAL',
|
||||
BROWSER_GET_CURRENT_WINDOW = 'REMOTE_BROWSER_GET_CURRENT_WINDOW',
|
||||
BROWSER_GET_CURRENT_WEB_CONTENTS = 'REMOTE_BROWSER_GET_CURRENT_WEB_CONTENTS',
|
||||
BROWSER_CONSTRUCTOR = 'REMOTE_BROWSER_CONSTRUCTOR',
|
||||
BROWSER_FUNCTION_CALL = 'REMOTE_BROWSER_FUNCTION_CALL',
|
||||
BROWSER_MEMBER_CONSTRUCTOR = 'REMOTE_BROWSER_MEMBER_CONSTRUCTOR',
|
||||
BROWSER_MEMBER_CALL = 'REMOTE_BROWSER_MEMBER_CALL',
|
||||
BROWSER_MEMBER_GET = 'REMOTE_BROWSER_MEMBER_GET',
|
||||
BROWSER_MEMBER_SET = 'REMOTE_BROWSER_MEMBER_SET',
|
||||
BROWSER_DEREFERENCE = 'REMOTE_BROWSER_DEREFERENCE',
|
||||
BROWSER_CONTEXT_RELEASE = 'REMOTE_BROWSER_CONTEXT_RELEASE',
|
||||
BROWSER_WRONG_CONTEXT_ERROR = 'REMOTE_BROWSER_WRONG_CONTEXT_ERROR',
|
||||
|
||||
RENDERER_CALLBACK = 'REMOTE_RENDERER_CALLBACK',
|
||||
RENDERER_RELEASE_CALLBACK = 'REMOTE_RENDERER_RELEASE_CALLBACK',
|
||||
}
|
||||
@@ -59,6 +59,10 @@ v8Util.setHiddenValue(global, 'ipcNative', {
|
||||
}
|
||||
});
|
||||
|
||||
process.getProcessMemoryInfo = () => {
|
||||
return ipcRendererInternal.invoke<Electron.ProcessMemoryInfo>(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO);
|
||||
};
|
||||
|
||||
// Use electron module after everything is ready.
|
||||
const { webFrameInit } = require('@electron/internal/renderer/web-frame-init') as typeof webFrameInitModule;
|
||||
webFrameInit();
|
||||
|
||||
@@ -77,15 +77,8 @@ const isLocalhost = function () {
|
||||
*
|
||||
* @returns {boolean} Is a CSP with `unsafe-eval` set?
|
||||
*/
|
||||
const isUnsafeEvalEnabled: () => Promise<boolean> = function () {
|
||||
return webFrame.executeJavaScript(`(${(() => {
|
||||
try {
|
||||
eval(window.trustedTypes.emptyScript); // eslint-disable-line no-eval
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}).toString()})()`, false);
|
||||
const isUnsafeEvalEnabled = () => {
|
||||
return webFrame._isEvalAllowed();
|
||||
};
|
||||
|
||||
const moreInformation = `\nFor more information and help, consult
|
||||
@@ -174,16 +167,14 @@ const warnAboutDisabledWebSecurity = function (webPreferences?: Electron.WebPref
|
||||
* Logs a warning message about unset or insecure CSP
|
||||
*/
|
||||
const warnAboutInsecureCSP = function () {
|
||||
isUnsafeEvalEnabled().then((enabled) => {
|
||||
if (!enabled) return;
|
||||
if (!isUnsafeEvalEnabled()) return;
|
||||
|
||||
const warning = `This renderer process has either no Content Security
|
||||
Policy set or a policy with "unsafe-eval" enabled. This exposes users of
|
||||
this app to unnecessary security risks.\n${moreInformation}`;
|
||||
const warning = `This renderer process has either no Content Security
|
||||
Policy set or a policy with "unsafe-eval" enabled. This exposes users of
|
||||
this app to unnecessary security risks.\n${moreInformation}`;
|
||||
|
||||
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
|
||||
'font-weight: bold;', warning);
|
||||
}).catch(() => {});
|
||||
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
|
||||
'font-weight: bold;', warning);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -6,6 +6,7 @@ const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_fr
|
||||
|
||||
export interface GuestViewDelegate {
|
||||
dispatchEvent (eventName: string, props: Record<string, any>): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
const DEPRECATED_EVENTS: Record<string, string> = {
|
||||
@@ -13,6 +14,11 @@ const DEPRECATED_EVENTS: Record<string, string> = {
|
||||
} as const;
|
||||
|
||||
export function registerEvents (viewInstanceId: number, delegate: GuestViewDelegate) {
|
||||
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${viewInstanceId}`, function () {
|
||||
delegate.reset();
|
||||
delegate.dispatchEvent('destroyed', {});
|
||||
});
|
||||
|
||||
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`, function (event, eventName, props) {
|
||||
if (DEPRECATED_EVENTS[eventName] != null) {
|
||||
delegate.dispatchEvent(DEPRECATED_EVENTS[eventName], props);
|
||||
@@ -23,6 +29,7 @@ export function registerEvents (viewInstanceId: number, delegate: GuestViewDeleg
|
||||
}
|
||||
|
||||
export function deregisterEvents (viewInstanceId: number) {
|
||||
ipcRendererInternal.removeAllListeners(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${viewInstanceId}`);
|
||||
ipcRendererInternal.removeAllListeners(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,8 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => {
|
||||
}
|
||||
if (!internal.elementAttached) {
|
||||
hooks.guestViewInternal.registerEvents(internal.viewInstanceId, {
|
||||
dispatchEvent: internal.dispatchEvent.bind(internal)
|
||||
dispatchEvent: internal.dispatchEvent.bind(internal),
|
||||
reset: internal.reset.bind(internal)
|
||||
});
|
||||
internal.elementAttached = true;
|
||||
(internal.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC) as SrcAttribute).parse();
|
||||
|
||||
@@ -25,7 +25,6 @@ export class WebViewImpl {
|
||||
public hasFocus = false
|
||||
public internalInstanceId?: number;
|
||||
public resizeObserver?: ResizeObserver;
|
||||
public userAgentOverride?: string;
|
||||
public viewInstanceId: number
|
||||
|
||||
// on* Event handlers.
|
||||
@@ -180,8 +179,7 @@ export class WebViewImpl {
|
||||
|
||||
buildParams () {
|
||||
const params: Record<string, any> = {
|
||||
instanceId: this.viewInstanceId,
|
||||
userAgentOverride: this.userAgentOverride
|
||||
instanceId: this.viewInstanceId
|
||||
};
|
||||
|
||||
for (const [attributeName, attribute] of this.attributes) {
|
||||
@@ -193,7 +191,7 @@ export class WebViewImpl {
|
||||
|
||||
attachGuestInstance (guestInstanceId: number) {
|
||||
if (guestInstanceId === -1) {
|
||||
this.dispatchEvent('destroyed');
|
||||
// Do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -86,6 +86,10 @@ Object.assign(preloadProcess, processProps);
|
||||
Object.assign(process, binding.process);
|
||||
Object.assign(process, processProps);
|
||||
|
||||
process.getProcessMemoryInfo = preloadProcess.getProcessMemoryInfo = () => {
|
||||
return ipcRendererInternal.invoke<Electron.ProcessMemoryInfo>(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO);
|
||||
};
|
||||
|
||||
Object.defineProperty(preloadProcess, 'noDeprecation', {
|
||||
get () {
|
||||
return process.noDeprecation;
|
||||
|
||||
@@ -22,7 +22,8 @@ if (isInstalled()) {
|
||||
const platform = process.env.npm_config_platform || process.platform;
|
||||
let arch = process.env.npm_config_arch || process.arch;
|
||||
|
||||
if (platform === 'darwin' && process.platform === 'darwin' && arch === 'x64') {
|
||||
if (platform === 'darwin' && process.platform === 'darwin' && arch === 'x64' &&
|
||||
process.env.npm_config_arch === undefined) {
|
||||
// When downloading for macOS ON macOS and we think we need x64 we should
|
||||
// check if we're running under rosetta and download the arm64 version if appropriate
|
||||
try {
|
||||
|
||||
13
package.json
13
package.json
@@ -1,9 +1,10 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "16.0.0-alpha.4",
|
||||
"version": "16.2.8",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
"@azure/storage-blob": "^12.9.0",
|
||||
"@electron/docs-parser": "^0.12.2",
|
||||
"@electron/typescript-definitions": "^8.9.5",
|
||||
"@octokit/auth-app": "^2.10.0",
|
||||
@@ -33,7 +34,7 @@
|
||||
"asar": "^3.1.0",
|
||||
"aws-sdk": "^2.727.1",
|
||||
"check-for-leaks": "^1.2.1",
|
||||
"colors": "^1.4.0",
|
||||
"colors": "1.4.0",
|
||||
"dotenv-safe": "^4.0.4",
|
||||
"dugite": "^1.103.0",
|
||||
"eslint": "^7.4.0",
|
||||
@@ -78,14 +79,14 @@
|
||||
"generate-version-json": "node script/generate-version-json.js",
|
||||
"lint": "node ./script/lint.js && npm run lint:clang-format && npm run lint:docs",
|
||||
"lint:js": "node ./script/lint.js --js",
|
||||
"lint:clang-format": "python script/run-clang-format.py -r -c chromium_src/ shell/ || (echo \"\\nCode not formatted correctly.\" && exit 1)",
|
||||
"lint:clang-format": "python3 script/run-clang-format.py -r -c shell/ || (echo \"\\nCode not formatted correctly.\" && exit 1)",
|
||||
"lint:clang-tidy": "ts-node ./script/run-clang-tidy.ts",
|
||||
"lint:cpp": "node ./script/lint.js --cc",
|
||||
"lint:objc": "node ./script/lint.js --objc",
|
||||
"lint:py": "node ./script/lint.js --py",
|
||||
"lint:gn": "node ./script/lint.js --gn",
|
||||
"lint:docs": "remark docs -qf && npm run lint:js-in-markdown && npm run create-typescript-definitions && npm run lint:docs-relative-links && npm run lint:markdownlint",
|
||||
"lint:docs-relative-links": "python ./script/check-relative-doc-links.py",
|
||||
"lint:docs-relative-links": "python3 ./script/check-relative-doc-links.py",
|
||||
"lint:markdownlint": "markdownlint \"*.md\" \"docs/**/*.md\"",
|
||||
"lint:js-in-markdown": "standard-markdown docs",
|
||||
"create-api-json": "electron-docs-parser --dir=./",
|
||||
@@ -116,14 +117,14 @@
|
||||
"ts-node script/gen-filenames.ts"
|
||||
],
|
||||
"*.{cc,mm,c,h}": [
|
||||
"python script/run-clang-format.py -r -c --fix"
|
||||
"python3 script/run-clang-format.py -r -c --fix"
|
||||
],
|
||||
"*.md": [
|
||||
"npm run lint:docs"
|
||||
],
|
||||
"*.{gn,gni}": [
|
||||
"npm run gn-check",
|
||||
"python script/run-gn-format.py"
|
||||
"python3 script/run-gn-format.py"
|
||||
],
|
||||
"*.py": [
|
||||
"node script/lint.js --py --fix --only --"
|
||||
|
||||
14
patches/angle/.patches
Normal file
14
patches/angle/.patches
Normal file
@@ -0,0 +1,14 @@
|
||||
vangle_change_the_default_vulkan_device_choose_logic.patch
|
||||
m98_vulkan_fix_vkcmdresolveimage_extents.patch
|
||||
m98_vulkan_fix_vkcmdresolveimage_offsets.patch
|
||||
cherry-pick-49e8ff16f1fe.patch
|
||||
m98_protect_against_deleting_a_current_xfb_buffer.patch
|
||||
m99_vulkan_prevent_out_of_bounds_read_in_divisor_emulation_path.patch
|
||||
m99_vulkan_streamvertexdatawithdivisor_write_beyond_buffer_boundary.patch
|
||||
cherry-pick-2b75a29bf241.patch
|
||||
m96-lts_fix_base_level_changes_not_updating_fbo_completeness_check.patch
|
||||
m100_fix_crash_when_pausing_xfb_then_deleting_a_buffer.patch
|
||||
cherry-pick-d27d9d059b51.patch
|
||||
cherry-pick-d49484c21e3c.patch
|
||||
cherry-pick-a602a068e022.patch
|
||||
cherry-pick-a4f71e40e571.patch
|
||||
94
patches/angle/cherry-pick-2b75a29bf241.patch
Normal file
94
patches/angle/cherry-pick-2b75a29bf241.patch
Normal file
@@ -0,0 +1,94 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jamie Madill <jmadill@chromium.org>
|
||||
Date: Tue, 1 Mar 2022 15:40:38 -0500
|
||||
Subject: Vulkan: Fix issue with redefining a layered attachment.
|
||||
|
||||
The fix ensures we complete level redefinition before we get the
|
||||
layer render target in TextureVk::getAttachmentRenderTarget.
|
||||
|
||||
Bug: chromium:1296866
|
||||
Change-Id: Id7fa8e9fed5e766c30580b09336713c675c4e4f0
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3498283
|
||||
Commit-Queue: Jamie Madill <jmadill@chromium.org>
|
||||
(cherry picked from commit 348ece42552a99cff88f79c80652b9dd3155ab22)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3513754
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
index 75cd69adc7839fc05650fc7cc30680ca92102a7f..27902ccdec3c1674110f03df41006558b211576c 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
@@ -2325,6 +2325,15 @@ angle::Result TextureVk::getAttachmentRenderTarget(const gl::Context *context,
|
||||
ASSERT(mState.hasBeenBoundAsAttachment());
|
||||
ANGLE_TRY(ensureRenderable(contextVk));
|
||||
|
||||
+ if (mRedefinedLevels.any())
|
||||
+ {
|
||||
+ // If we have redefined levels, we must flush those out to fix the render targets.
|
||||
+ ANGLE_TRY(respecifyImageStorage(contextVk));
|
||||
+ }
|
||||
+
|
||||
+ // Otherwise, don't flush staged updates here. We'll handle that in FramebufferVk so we can
|
||||
+ // defer clears.
|
||||
+
|
||||
if (!mImage->valid())
|
||||
{
|
||||
// Immutable texture must already have a valid image
|
||||
@@ -2364,8 +2373,6 @@ angle::Result TextureVk::getAttachmentRenderTarget(const gl::Context *context,
|
||||
mState.getType(), samples, *mImage, useRobustInit));
|
||||
}
|
||||
|
||||
- // Don't flush staged updates here. We'll handle that in FramebufferVk so it can defer clears.
|
||||
-
|
||||
GLuint layerIndex = 0, layerCount = 0, imageLayerCount = 0;
|
||||
GetRenderTargetLayerCountAndIndex(mImage, imageIndex, &layerIndex, &layerCount,
|
||||
&imageLayerCount);
|
||||
@@ -2376,10 +2383,14 @@ angle::Result TextureVk::getAttachmentRenderTarget(const gl::Context *context,
|
||||
gl::LevelIndex(imageIndex.getLevelIndex()),
|
||||
renderToTextureIndex);
|
||||
|
||||
- ASSERT(imageIndex.getLevelIndex() <
|
||||
- static_cast<int32_t>(mSingleLayerRenderTargets[renderToTextureIndex].size()));
|
||||
- *rtOut = &mSingleLayerRenderTargets[renderToTextureIndex][imageIndex.getLevelIndex()]
|
||||
- [layerIndex];
|
||||
+ std::vector<RenderTargetVector> &levelRenderTargets =
|
||||
+ mSingleLayerRenderTargets[renderToTextureIndex];
|
||||
+ ASSERT(imageIndex.getLevelIndex() < static_cast<int32_t>(levelRenderTargets.size()));
|
||||
+
|
||||
+ RenderTargetVector &layerRenderTargets = levelRenderTargets[imageIndex.getLevelIndex()];
|
||||
+ ASSERT(imageIndex.getLayerIndex() < static_cast<int32_t>(layerRenderTargets.size()));
|
||||
+
|
||||
+ *rtOut = &layerRenderTargets[layerIndex];
|
||||
}
|
||||
else
|
||||
{
|
||||
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
index d95f7b585f23af246cc65b1d68a1524c62f4024e..46740cae77df7b6e9509093c02b79a332b4f106d 100644
|
||||
--- a/src/tests/gl_tests/FramebufferTest.cpp
|
||||
+++ b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
@@ -4000,6 +4000,25 @@ TEST_P(FramebufferTest_ES3, BindRenderbufferThenModifySize)
|
||||
ASSERT_GL_NO_ERROR();
|
||||
}
|
||||
|
||||
+// Tests redefining a layered framebuffer attachment.
|
||||
+TEST_P(FramebufferTest_ES3, RedefineLayerAttachment)
|
||||
+{
|
||||
+ GLTexture texture;
|
||||
+ glBindTexture(GL_TEXTURE_3D, texture);
|
||||
+ std::vector<uint8_t> imgData(20480, 0);
|
||||
+ glTexImage3D(GL_TEXTURE_3D, 0, GL_R8, 8, 8, 8, 0, GL_RED, GL_UNSIGNED_BYTE, imgData.data());
|
||||
+
|
||||
+ GLFramebuffer fbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
+ glFramebufferTextureLayer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, texture, 0, 8);
|
||||
+ glGenerateMipmap(GL_TEXTURE_3D);
|
||||
+
|
||||
+ glTexImage3D(GL_TEXTURE_3D, 0, GL_R8UI, 16, 16, 16, 0, GL_RED_INTEGER, GL_UNSIGNED_BYTE,
|
||||
+ imgData.data());
|
||||
+ glCopyTexSubImage3D(GL_TEXTURE_3D, 0, 0, 0, 2, 2, 15, 16, 16);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
ANGLE_INSTANTIATE_TEST_ES2(AddMockTextureNoRenderTargetTest);
|
||||
ANGLE_INSTANTIATE_TEST_ES2(FramebufferTest);
|
||||
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(FramebufferFormatsTest);
|
||||
381
patches/angle/cherry-pick-49e8ff16f1fe.patch
Normal file
381
patches/angle/cherry-pick-49e8ff16f1fe.patch
Normal file
@@ -0,0 +1,381 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Date: Tue, 25 Jan 2022 12:15:16 -0500
|
||||
Subject: M99: Vulkan: Fix texture array level redefinition
|
||||
|
||||
When a level of a texture is redefined, all staged updates to that level
|
||||
should be removed, not the ones specific to the new layers. The bug
|
||||
fixed was that if the texture was redefined to have its number of layers
|
||||
changed, the staged higher-layer-count update to the image was not
|
||||
removed.
|
||||
|
||||
Bug: chromium:1289383
|
||||
Change-Id: Iab79c38d846d1abbdd92e11b1b60a3adf0fbde4c
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3441309
|
||||
Reviewed-by: Lingfeng Yang <lfy@google.com>
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
index 4c28358fad590350577177d9997f7731c3ea398f..75cd69adc7839fc05650fc7cc30680ca92102a7f 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
@@ -1602,12 +1602,25 @@ angle::Result TextureVk::redefineLevel(const gl::Context *context,
|
||||
|
||||
if (mImage != nullptr)
|
||||
{
|
||||
- // If there is any staged changes for this index, we can remove them since we're going to
|
||||
+ // If there are any staged changes for this index, we can remove them since we're going to
|
||||
// override them with this call.
|
||||
gl::LevelIndex levelIndexGL(index.getLevelIndex());
|
||||
uint32_t layerIndex = index.hasLayer() ? index.getLayerIndex() : 0;
|
||||
- mImage->removeSingleSubresourceStagedUpdates(contextVk, levelIndexGL, layerIndex,
|
||||
- index.getLayerCount());
|
||||
+ if (gl::IsArrayTextureType(index.getType()))
|
||||
+ {
|
||||
+ // A multi-layer texture is being redefined, remove all updates to this level; the
|
||||
+ // number of layers may have changed.
|
||||
+ mImage->removeStagedUpdates(contextVk, levelIndexGL, levelIndexGL);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ // Otherwise remove only updates to this layer. For example, cube map updates can be
|
||||
+ // done through glTexImage2D, one per cube face (i.e. layer) and so should not remove
|
||||
+ // updates to the other layers.
|
||||
+ ASSERT(index.getLayerCount() == 1);
|
||||
+ mImage->removeSingleSubresourceStagedUpdates(contextVk, levelIndexGL, layerIndex,
|
||||
+ index.getLayerCount());
|
||||
+ }
|
||||
|
||||
if (mImage->valid())
|
||||
{
|
||||
diff --git a/src/tests/gl_tests/MipmapTest.cpp b/src/tests/gl_tests/MipmapTest.cpp
|
||||
index 8a6d01ca36a84a9e294de3f6f0114ee7a54e1d9a..957a52304edc9aa245f9f21e5557cc105cbad789 100644
|
||||
--- a/src/tests/gl_tests/MipmapTest.cpp
|
||||
+++ b/src/tests/gl_tests/MipmapTest.cpp
|
||||
@@ -1686,6 +1686,106 @@ TEST_P(MipmapTestES3, MipmapsForTexture3D)
|
||||
EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
|
||||
}
|
||||
|
||||
+// Create a 2D array, then immediately redefine it to have fewer layers. Regression test for a bug
|
||||
+// in the Vulkan backend where the old higher-layer-count data upload was not removed.
|
||||
+TEST_P(MipmapTestES3, TextureArrayRedefineThenGenerateMipmap)
|
||||
+{
|
||||
+ int px = getWindowWidth() / 2;
|
||||
+ int py = getWindowHeight() / 2;
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture);
|
||||
+
|
||||
+ // Fill the whole texture with red, then redefine it and fill with green
|
||||
+ std::vector<GLColor> pixelsRed(2 * 2 * 4, GLColor::red);
|
||||
+ std::vector<GLColor> pixelsGreen(2 * 2 * 2, GLColor::green);
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsRed.data());
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsGreen.data());
|
||||
+
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Generate mipmaps
|
||||
+ glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glUseProgram(mArrayProgram);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Draw the first slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
|
||||
+ drawQuad(mArrayProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+
|
||||
+ // Draw the second slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 1);
|
||||
+ drawQuad(mArrayProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+}
|
||||
+
|
||||
+// Create a 2D array, use it, then redefine it to have fewer layers. Regression test for a bug in
|
||||
+// the Vulkan backend where the old higher-layer-count data upload was not removed.
|
||||
+TEST_P(MipmapTestES3, TextureArrayUseThenRedefineThenGenerateMipmap)
|
||||
+{
|
||||
+ int px = getWindowWidth() / 2;
|
||||
+ int py = getWindowHeight() / 2;
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture);
|
||||
+
|
||||
+ // Fill the whole texture with red.
|
||||
+ std::vector<GLColor> pixelsRed(2 * 2 * 4, GLColor::red);
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsRed.data());
|
||||
+
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_NEAREST);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Generate mipmap
|
||||
+ glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glUseProgram(mArrayProgram);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Draw the first slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
|
||||
+ drawQuad(mArrayProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
|
||||
+
|
||||
+ // Draw the fourth slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 3);
|
||||
+ drawQuad(mArrayProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
|
||||
+
|
||||
+ // Redefine the image and fill with green
|
||||
+ std::vector<GLColor> pixelsGreen(2 * 2 * 2, GLColor::green);
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsGreen.data());
|
||||
+
|
||||
+ // Generate mipmap
|
||||
+ glGenerateMipmap(GL_TEXTURE_2D_ARRAY);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Draw the first slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
|
||||
+ drawQuad(mArrayProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+
|
||||
+ // Draw the second slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 1);
|
||||
+ drawQuad(mArrayProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+}
|
||||
+
|
||||
// Create a 2D texture with levels 0-2, call GenerateMipmap with base level 1 so that level 0 stays
|
||||
// the same, and then sample levels 0 and 2.
|
||||
// GLES 3.0.4 section 3.8.10:
|
||||
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
|
||||
index 9ee0203153304080608a597ddbf1513d3a95616c..a46429331b3b272a89bdb8b31f7be75a4a82360c 100644
|
||||
--- a/src/tests/gl_tests/TextureTest.cpp
|
||||
+++ b/src/tests/gl_tests/TextureTest.cpp
|
||||
@@ -1030,31 +1030,37 @@ class SamplerArrayAsFunctionParameterTest : public SamplerArrayTest
|
||||
class Texture2DArrayTestES3 : public TexCoordDrawTest
|
||||
{
|
||||
protected:
|
||||
- Texture2DArrayTestES3() : TexCoordDrawTest(), m2DArrayTexture(0), mTextureArrayLocation(-1) {}
|
||||
+ Texture2DArrayTestES3()
|
||||
+ : TexCoordDrawTest(),
|
||||
+ m2DArrayTexture(0),
|
||||
+ mTextureArrayLocation(-1),
|
||||
+ mTextureArraySliceUniformLocation(-1)
|
||||
+ {}
|
||||
|
||||
const char *getVertexShaderSource() override
|
||||
{
|
||||
- return "#version 300 es\n"
|
||||
- "out vec2 texcoord;\n"
|
||||
- "in vec4 position;\n"
|
||||
- "void main()\n"
|
||||
- "{\n"
|
||||
- " gl_Position = vec4(position.xy, 0.0, 1.0);\n"
|
||||
- " texcoord = (position.xy * 0.5) + 0.5;\n"
|
||||
- "}\n";
|
||||
+ return R"(#version 300 es
|
||||
+out vec2 texcoord;
|
||||
+in vec4 position;
|
||||
+void main()
|
||||
+{
|
||||
+ gl_Position = vec4(position.xy, 0.0, 1.0);
|
||||
+ texcoord = (position.xy * 0.5) + 0.5;
|
||||
+})";
|
||||
}
|
||||
|
||||
const char *getFragmentShaderSource() override
|
||||
{
|
||||
- return "#version 300 es\n"
|
||||
- "precision highp float;\n"
|
||||
- "uniform highp sampler2DArray tex2DArray;\n"
|
||||
- "in vec2 texcoord;\n"
|
||||
- "out vec4 fragColor;\n"
|
||||
- "void main()\n"
|
||||
- "{\n"
|
||||
- " fragColor = texture(tex2DArray, vec3(texcoord.x, texcoord.y, 0.0));\n"
|
||||
- "}\n";
|
||||
+ return R"(#version 300 es
|
||||
+precision highp float;
|
||||
+uniform highp sampler2DArray tex2DArray;
|
||||
+uniform int slice;
|
||||
+in vec2 texcoord;
|
||||
+out vec4 fragColor;
|
||||
+void main()
|
||||
+{
|
||||
+ fragColor = texture(tex2DArray, vec3(texcoord, float(slice)));
|
||||
+})";
|
||||
}
|
||||
|
||||
void testSetUp() override
|
||||
@@ -1066,6 +1072,9 @@ class Texture2DArrayTestES3 : public TexCoordDrawTest
|
||||
mTextureArrayLocation = glGetUniformLocation(mProgram, "tex2DArray");
|
||||
ASSERT_NE(-1, mTextureArrayLocation);
|
||||
|
||||
+ mTextureArraySliceUniformLocation = glGetUniformLocation(mProgram, "slice");
|
||||
+ ASSERT_NE(-1, mTextureArraySliceUniformLocation);
|
||||
+
|
||||
glGenTextures(1, &m2DArrayTexture);
|
||||
ASSERT_GL_NO_ERROR();
|
||||
}
|
||||
@@ -1078,6 +1087,7 @@ class Texture2DArrayTestES3 : public TexCoordDrawTest
|
||||
|
||||
GLuint m2DArrayTexture;
|
||||
GLint mTextureArrayLocation;
|
||||
+ GLint mTextureArraySliceUniformLocation;
|
||||
};
|
||||
|
||||
class TextureSizeTextureArrayTest : public TexCoordDrawTest
|
||||
@@ -1720,28 +1730,28 @@ class Texture2DArrayIntegerTestES3 : public Texture2DArrayTestES3
|
||||
|
||||
const char *getVertexShaderSource() override
|
||||
{
|
||||
- return "#version 300 es\n"
|
||||
- "out vec2 texcoord;\n"
|
||||
- "in vec4 position;\n"
|
||||
- "void main()\n"
|
||||
- "{\n"
|
||||
- " gl_Position = vec4(position.xy, 0.0, 1.0);\n"
|
||||
- " texcoord = (position.xy * 0.5) + 0.5;\n"
|
||||
- "}\n";
|
||||
+ return R"(#version 300 es
|
||||
+out vec2 texcoord;
|
||||
+in vec4 position;
|
||||
+void main()
|
||||
+{
|
||||
+ gl_Position = vec4(position.xy, 0.0, 1.0);
|
||||
+ texcoord = (position.xy * 0.5) + 0.5;
|
||||
+})";
|
||||
}
|
||||
|
||||
const char *getFragmentShaderSource() override
|
||||
{
|
||||
- return "#version 300 es\n"
|
||||
- "precision highp float;\n"
|
||||
- "uniform highp usampler2DArray tex2DArray;\n"
|
||||
- "in vec2 texcoord;\n"
|
||||
- "out vec4 fragColor;\n"
|
||||
- "void main()\n"
|
||||
- "{\n"
|
||||
- " fragColor = vec4(texture(tex2DArray, vec3(texcoord.x, texcoord.y, "
|
||||
- "0.0)))/255.0;\n"
|
||||
- "}\n";
|
||||
+ return R"(#version 300 es
|
||||
+precision highp float;
|
||||
+uniform highp usampler2DArray tex2DArray;
|
||||
+uniform int slice;
|
||||
+in vec2 texcoord;
|
||||
+out vec4 fragColor;
|
||||
+void main()
|
||||
+{
|
||||
+ fragColor = vec4(texture(tex2DArray, vec3(texcoord, slice)))/255.0;
|
||||
+})";
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5162,6 +5172,94 @@ TEST_P(Texture2DArrayTestES3, DrawWithLevelsOutsideRangeWithInconsistentDimensio
|
||||
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::cyan);
|
||||
}
|
||||
|
||||
+// Create a 2D array, then immediately redefine it to have fewer layers. Regression test for a bug
|
||||
+// in the Vulkan backend where the old higher-layer-count data upload was not removed.
|
||||
+TEST_P(Texture2DArrayTestES3, TextureArrayRedefineThenUse)
|
||||
+{
|
||||
+ int px = getWindowWidth() / 2;
|
||||
+ int py = getWindowHeight() / 2;
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
|
||||
+
|
||||
+ // Fill the whole texture with red, then redefine it and fill with green
|
||||
+ std::vector<GLColor> pixelsRed(2 * 2 * 4, GLColor::red);
|
||||
+ std::vector<GLColor> pixelsGreen(2 * 2 * 2, GLColor::green);
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsRed.data());
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsGreen.data());
|
||||
+
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glUseProgram(mProgram);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Draw the first slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
|
||||
+ drawQuad(mProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+
|
||||
+ // Draw the second slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 1);
|
||||
+ drawQuad(mProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+}
|
||||
+
|
||||
+// Create a 2D array, use it, then redefine it to have fewer layers. Regression test for a bug in
|
||||
+// the Vulkan backend where the old higher-layer-count data upload was not removed.
|
||||
+TEST_P(Texture2DArrayTestES3, TextureArrayUseThenRedefineThenUse)
|
||||
+{
|
||||
+ int px = getWindowWidth() / 2;
|
||||
+ int py = getWindowHeight() / 2;
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, m2DArrayTexture);
|
||||
+
|
||||
+ // Fill the whole texture with red.
|
||||
+ std::vector<GLColor> pixelsRed(2 * 2 * 4, GLColor::red);
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 4, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsRed.data());
|
||||
+
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
+ glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glUseProgram(mProgram);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Draw the first slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
|
||||
+ drawQuad(mProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
|
||||
+
|
||||
+ // Draw the fourth slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 3);
|
||||
+ drawQuad(mProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::red);
|
||||
+
|
||||
+ // Redefine the image and fill with green
|
||||
+ std::vector<GLColor> pixelsGreen(2 * 2 * 2, GLColor::green);
|
||||
+ glTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_RGBA8, 2, 2, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ pixelsGreen.data());
|
||||
+
|
||||
+ // Draw the first slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 0);
|
||||
+ drawQuad(mProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+
|
||||
+ // Draw the second slice
|
||||
+ glUniform1i(mTextureArraySliceUniformLocation, 1);
|
||||
+ drawQuad(mProgram, "position", 0.5f);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(px, py, GLColor::green);
|
||||
+}
|
||||
+
|
||||
// Test that texture completeness is updated if texture max level changes.
|
||||
// GLES 3.0.4 section 3.8.13 Texture completeness
|
||||
TEST_P(Texture2DTestES3, TextureCompletenessChangesWithMaxLevel)
|
||||
58
patches/angle/cherry-pick-a4f71e40e571.patch
Normal file
58
patches/angle/cherry-pick-a4f71e40e571.patch
Normal file
@@ -0,0 +1,58 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Geoff Lang <geofflang@google.com>
|
||||
Date: Fri, 1 Apr 2022 11:38:17 -0400
|
||||
Subject: Fix CheckedNumeric using the wrong type.
|
||||
|
||||
Validation for glBufferSubData checks that the buffer is large enough
|
||||
for size+offset but verifies they fit in a size_t which is a different
|
||||
type than the deduced type for size+offset on 32-bit systems.
|
||||
|
||||
Use decltype to ensure that we always verify there is no overflow on the
|
||||
correct type.
|
||||
|
||||
Bug: chromium:1298867
|
||||
Change-Id: I82f534b2d227d3273a763e626ebeae068dc918dc
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3563515
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
|
||||
Commit-Queue: Geoff Lang <geofflang@chromium.org>
|
||||
(cherry picked from commit c458b5add432c3da98ef370680518d0af7e4d4e3)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3630020
|
||||
|
||||
diff --git a/src/libANGLE/validationES2.cpp b/src/libANGLE/validationES2.cpp
|
||||
index 1615b8c64476d43201b67bb69489efd01ac51c7b..8deba1e5f922f56607abdcbea8c69bc0e71aceb4 100644
|
||||
--- a/src/libANGLE/validationES2.cpp
|
||||
+++ b/src/libANGLE/validationES2.cpp
|
||||
@@ -3500,7 +3500,7 @@ bool ValidateBufferSubData(const Context *context,
|
||||
}
|
||||
|
||||
// Check for possible overflow of size + offset
|
||||
- angle::CheckedNumeric<size_t> checkedSize(size);
|
||||
+ angle::CheckedNumeric<decltype(size + offset)> checkedSize(size);
|
||||
checkedSize += offset;
|
||||
if (!checkedSize.IsValid())
|
||||
{
|
||||
diff --git a/src/tests/gl_tests/BufferDataTest.cpp b/src/tests/gl_tests/BufferDataTest.cpp
|
||||
index 59bc691abc00dd11a068898b25b403fa3a397e37..5b3ef6a1b208cfc3c32338024c347b515ecbfd6e 100644
|
||||
--- a/src/tests/gl_tests/BufferDataTest.cpp
|
||||
+++ b/src/tests/gl_tests/BufferDataTest.cpp
|
||||
@@ -824,6 +824,19 @@ TEST_P(BufferDataTest, MapWriteArrayBufferDataDrawArrays)
|
||||
EXPECT_GL_NO_ERROR();
|
||||
}
|
||||
|
||||
+// Verify that buffer sub data uploads are properly validated within the buffer size range on 32-bit
|
||||
+// systems.
|
||||
+TEST_P(BufferDataTest, BufferSizeValidation32Bit)
|
||||
+{
|
||||
+ GLBuffer buffer;
|
||||
+ glBindBuffer(GL_ARRAY_BUFFER, buffer);
|
||||
+ glBufferData(GL_ARRAY_BUFFER, 100, nullptr, GL_STATIC_DRAW);
|
||||
+
|
||||
+ GLubyte data = 0;
|
||||
+ glBufferSubData(GL_ARRAY_BUFFER, std::numeric_limits<uint32_t>::max(), 1, &data);
|
||||
+ EXPECT_GL_ERROR(GL_INVALID_VALUE);
|
||||
+}
|
||||
+
|
||||
// Tests a null crash bug caused by copying from null back-end buffer pointer
|
||||
// when calling bufferData again after drawing without calling bufferData in D3D11.
|
||||
TEST_P(BufferDataTestES3, DrawWithNotCallingBufferData)
|
||||
31
patches/angle/cherry-pick-a602a068e022.patch
Normal file
31
patches/angle/cherry-pick-a602a068e022.patch
Normal file
@@ -0,0 +1,31 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jamie Madill <jmadill@chromium.org>
|
||||
Date: Tue, 19 Apr 2022 17:01:20 -0400
|
||||
Subject: Fix validate state cache after XFB buffer deleted.
|
||||
|
||||
Bug: chromium:1317650
|
||||
Change-Id: Iec9f1167c3b2957091dd0f4ef3efcfcd7c4bf3c0
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3594250
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Auto-Submit: Jamie Madill <jmadill@chromium.org>
|
||||
Commit-Queue: Jamie Madill <jmadill@chromium.org>
|
||||
(cherry picked from commit 4efc4ee6830a8a53a0daf9daa3c7aa835db4220f)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3621779
|
||||
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
|
||||
|
||||
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
|
||||
index 35a819ef635c25a7ff442d75e49ba89cd7ad84a9..4fef5dc883d6de0f48bccd59835188b339ee379a 100644
|
||||
--- a/src/libANGLE/State.cpp
|
||||
+++ b/src/libANGLE/State.cpp
|
||||
@@ -2190,10 +2190,7 @@ angle::Result State::detachBuffer(Context *context, const Buffer *buffer)
|
||||
if (curTransformFeedback)
|
||||
{
|
||||
ANGLE_TRY(curTransformFeedback->detachBuffer(context, bufferID));
|
||||
- if (isTransformFeedbackActiveUnpaused())
|
||||
- {
|
||||
- context->getStateCache().onActiveTransformFeedbackChange(context);
|
||||
- }
|
||||
+ context->getStateCache().onActiveTransformFeedbackChange(context);
|
||||
}
|
||||
|
||||
if (getVertexArray()->detachBuffer(context, bufferID))
|
||||
100
patches/angle/cherry-pick-d27d9d059b51.patch
Normal file
100
patches/angle/cherry-pick-d27d9d059b51.patch
Normal file
@@ -0,0 +1,100 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Charlie Lao <cclao@google.com>
|
||||
Date: Tue, 15 Mar 2022 09:39:36 -0700
|
||||
Subject: Vulkan: Update mCurrentElementArrayBuffersync based on dirty bit
|
||||
|
||||
M96 merge issues:
|
||||
ContextVk.cpp:
|
||||
ContextVk::setupIndexedDraw: vertexArrayVk/getVertexArray() isn't present in M96
|
||||
ContextVk::syncState: M96 uses mVertexArray instead of vertexArrayVk
|
||||
VertexArrayVk.cpp:
|
||||
VertexArrayVk::updateCurrentElementArrayBuffer doesn't exist in M9
|
||||
Created it and kept M96 logic for retrieving buffer/offset
|
||||
|
||||
The previous fix crrev.com/c/3513553 has run into corner case that
|
||||
requires more follow up change crrev.com/c/3522565. But with that, there
|
||||
is report that now we are hitting assertion in
|
||||
handleDirtyGraphicsIndexBuffer(). This becomes a bit fragile This new
|
||||
fix relies on the DIRTY_BIT_INDEX_BUFFER dirty bit and should be more
|
||||
reliable as long as the dirty bit is set properly (if not, then we have
|
||||
other bug that it won't even send down vulkan command to bind the
|
||||
correct element buffer). We could further optimize the code path and
|
||||
create a fast path for most common usages in the future.
|
||||
|
||||
Bug: chromium:1299261
|
||||
Change-Id: Ifa8f86d431798c9ca4c128ed71a3e9e0a3537ccb
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3526021
|
||||
Commit-Queue: Charlie Lao <cclao@google.com>
|
||||
(cherry picked from commit 349636a05a3577a127adb6c79a1e947890bbe462)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3605834
|
||||
Reviewed-by: Achuith Bhandarkar <achuith@chromium.org>
|
||||
Reviewed-by: Charlie Lao <cclao@google.com>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp
|
||||
index 62d754143d7f1ffb37f5f2fe1de80c4a285af64c..22e17dbebc46cc6acecec0b57db47b92fc44e87f 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp
|
||||
@@ -933,6 +933,17 @@ angle::Result ContextVk::setupIndexedDraw(const gl::Context *context,
|
||||
mGraphicsDirtyBits.set(DIRTY_BIT_INDEX_BUFFER);
|
||||
mLastIndexBufferOffset = indices;
|
||||
}
|
||||
+
|
||||
+ // When you draw with LineLoop mode or GL_UNSIGNED_BYTE type, we may allocate its own
|
||||
+ // element buffer and modify mCurrentElementArrayBuffer. When we switch out of that draw
|
||||
+ // mode, we must reset mCurrentElementArrayBuffer back to the vertexArray's element buffer.
|
||||
+ // Since in either case we set DIRTY_BIT_INDEX_BUFFER dirty bit, we use this bit to re-sync
|
||||
+ // mCurrentElementArrayBuffer.
|
||||
+ if (mGraphicsDirtyBits[DIRTY_BIT_INDEX_BUFFER])
|
||||
+ {
|
||||
+ mVertexArray->updateCurrentElementArrayBuffer();
|
||||
+ }
|
||||
+
|
||||
if (shouldConvertUint8VkIndexType(indexType) && mGraphicsDirtyBits[DIRTY_BIT_INDEX_BUFFER])
|
||||
{
|
||||
ANGLE_PERF_WARNING(getDebug(), GL_DEBUG_SEVERITY_LOW,
|
||||
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
index 9043afe17e84737d3e8c295bbc9597fc078f82f9..816eb5fa720542e7cd000c7523fb439da8b8d4dd 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
@@ -494,6 +494,17 @@ angle::Result VertexArrayVk::convertVertexBufferCPU(ContextVk *contextVk,
|
||||
return angle::Result::Continue;
|
||||
}
|
||||
|
||||
+void VertexArrayVk::updateCurrentElementArrayBuffer()
|
||||
+{
|
||||
+ ASSERT(mState.getElementArrayBuffer() != nullptr);
|
||||
+ ASSERT(mState.getElementArrayBuffer()->getSize() > 0);
|
||||
+ gl::Buffer *bufferGL = mState.getElementArrayBuffer();
|
||||
+ BufferVk *bufferVk = vk::GetImpl(bufferGL);
|
||||
+ mCurrentElementArrayBuffer =
|
||||
+ &bufferVk->getBufferAndOffset(&mCurrentElementArrayBufferOffset);
|
||||
+
|
||||
+}
|
||||
+
|
||||
angle::Result VertexArrayVk::syncState(const gl::Context *context,
|
||||
const gl::VertexArray::DirtyBits &dirtyBits,
|
||||
gl::VertexArray::DirtyAttribBitsArray *attribBits,
|
||||
@@ -519,9 +530,7 @@ angle::Result VertexArrayVk::syncState(const gl::Context *context,
|
||||
{
|
||||
// Note that just updating buffer data may still result in a new
|
||||
// vk::BufferHelper allocation.
|
||||
- BufferVk *bufferVk = vk::GetImpl(bufferGL);
|
||||
- mCurrentElementArrayBuffer =
|
||||
- &bufferVk->getBufferAndOffset(&mCurrentElementArrayBufferOffset);
|
||||
+ updateCurrentElementArrayBuffer();
|
||||
}
|
||||
else
|
||||
{
|
||||
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.h b/src/libANGLE/renderer/vulkan/VertexArrayVk.h
|
||||
index c198265bf8ba2017a13fce558826862f450218b5..0b98a9ed46b7cd4b9588973c74b0bbaf9172ab6c 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.h
|
||||
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.h
|
||||
@@ -34,6 +34,8 @@ class VertexArrayVk : public VertexArrayImpl
|
||||
|
||||
angle::Result updateActiveAttribInfo(ContextVk *contextVk);
|
||||
|
||||
+ void updateCurrentElementArrayBuffer();
|
||||
+
|
||||
angle::Result updateDefaultAttrib(ContextVk *contextVk,
|
||||
size_t attribIndex,
|
||||
VkBuffer bufferHandle,
|
||||
33
patches/angle/cherry-pick-d49484c21e3c.patch
Normal file
33
patches/angle/cherry-pick-d49484c21e3c.patch
Normal file
@@ -0,0 +1,33 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jamie Madill <jmadill@chromium.org>
|
||||
Date: Mon, 11 Apr 2022 12:29:00 -0400
|
||||
Subject: Add error check on resuming XFB with deleted buffer.
|
||||
|
||||
Bug: chromium:1313905
|
||||
Change-Id: I22c6f6400b05ca32c922fba9a3b9d4b5841ca8b8
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3578378
|
||||
Auto-Submit: Jamie Madill <jmadill@chromium.org>
|
||||
Reviewed-by: Geoff Lang <geofflang@chromium.org>
|
||||
Commit-Queue: Jamie Madill <jmadill@chromium.org>
|
||||
(cherry picked from commit 5c85fd4e11a3835a0719223a7cedb978d309da21)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3594103
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
|
||||
index f9dac542366b51c2431885e1bf4fb85040234ca9..608524ad4df85e6fa81def3c4395e437702af990 100644
|
||||
--- a/src/libANGLE/validationES3.cpp
|
||||
+++ b/src/libANGLE/validationES3.cpp
|
||||
@@ -4309,6 +4309,13 @@ bool ValidateGetActiveUniformBlockName(const Context *context,
|
||||
return false;
|
||||
}
|
||||
|
||||
+ if (!ValidateProgramExecutableXFBBuffersPresent(context,
|
||||
+ context->getState().getProgramExecutable()))
|
||||
+ {
|
||||
+ context->validationError(GL_INVALID_OPERATION, kTransformFeedbackBufferMissing);
|
||||
+ return false;
|
||||
+ }
|
||||
+
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jamie Madill <jmadill@chromium.org>
|
||||
Date: Mon, 14 Mar 2022 10:37:31 -0400
|
||||
Subject: Fix crash when pausing XFB then deleting a buffer.
|
||||
|
||||
Fix is to validate XFB buffer bindings even if we're paused.
|
||||
This is undefined behaviour so we can use any non-crashing solution.
|
||||
|
||||
Bug: chromium:1305190
|
||||
Change-Id: Ib95404cdb13adbde7f34d6cc77473a8b3cbf1de7
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3522283
|
||||
Reviewed-by: Geoff Lang <geofflang@chromium.org>
|
||||
Commit-Queue: Jamie Madill <jmadill@chromium.org>
|
||||
(cherry picked from commit 708ce9cfd63bc8eab7c48987612a2dedce78c69a)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3594105
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
|
||||
index ccc1f17f8db2ef85ca3b80bfa35e175dd9434c49..d633334b6ed252da632059cf410e4ca6b9d0c092 100644
|
||||
--- a/src/libANGLE/validationES.cpp
|
||||
+++ b/src/libANGLE/validationES.cpp
|
||||
@@ -3978,7 +3978,7 @@ const char *ValidateDrawStates(const Context *context)
|
||||
}
|
||||
}
|
||||
|
||||
- if (state.isTransformFeedbackActiveUnpaused())
|
||||
+ if (state.isTransformFeedbackActive())
|
||||
{
|
||||
if (!ValidateProgramExecutableXFBBuffersPresent(context, executable))
|
||||
{
|
||||
diff --git a/src/tests/gl_tests/TransformFeedbackTest.cpp b/src/tests/gl_tests/TransformFeedbackTest.cpp
|
||||
index 0e9aafabc603011fc48d761e32eb9204800a8557..5638cd447958b829dfad5ca270db303f85bed491 100644
|
||||
--- a/src/tests/gl_tests/TransformFeedbackTest.cpp
|
||||
+++ b/src/tests/gl_tests/TransformFeedbackTest.cpp
|
||||
@@ -3674,6 +3674,25 @@ void main() {
|
||||
EXPECT_GL_ERROR(GL_INVALID_OPERATION);
|
||||
}
|
||||
|
||||
+// Same as the above, with a paused transform feedback.
|
||||
+TEST_P(TransformFeedbackTest, DeletePausedTransformFeedbackBuffer)
|
||||
+{
|
||||
+ ANGLE_GL_PROGRAM_TRANSFORM_FEEDBACK(testProgram, essl1_shaders::vs::Simple(),
|
||||
+ essl1_shaders::fs::Green(), {"gl_Position"},
|
||||
+ GL_INTERLEAVED_ATTRIBS);
|
||||
+ glUseProgram(testProgram);
|
||||
+
|
||||
+ GLBuffer buffer;
|
||||
+ glBindBuffer(GL_PIXEL_UNPACK_BUFFER, buffer);
|
||||
+ glBufferData(GL_PIXEL_UNPACK_BUFFER, 3, nullptr, GL_STATIC_DRAW);
|
||||
+ glBindBufferBase(GL_TRANSFORM_FEEDBACK_BUFFER, 0, buffer);
|
||||
+
|
||||
+ glBeginTransformFeedback(GL_POINTS);
|
||||
+ glPauseTransformFeedback();
|
||||
+ buffer.reset();
|
||||
+ glDrawArrays(GL_POINTS, 0, 1);
|
||||
+}
|
||||
+
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(TransformFeedbackTest);
|
||||
ANGLE_INSTANTIATE_TEST_ES3(TransformFeedbackTest);
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jamie Madill <jmadill@chromium.org>
|
||||
Date: Tue, 1 Mar 2022 14:55:00 -0500
|
||||
Subject: Fix base level changes not updating FBO completeness check.
|
||||
|
||||
M96 merge issues:
|
||||
- RendererVk.cpp:
|
||||
conflicting kSkippedSyncvalMessages entries
|
||||
- vk_helpers.cpp
|
||||
getRenderPassWriteCommandCount() not present in M96
|
||||
- capture_replay_expectations.txt:
|
||||
conflicting skipped test entries
|
||||
- src/tests/gl_tests/FramebufferTest.cpp
|
||||
RedefineLayerAttachment not present in M96
|
||||
|
||||
Bug: chromium:1299264
|
||||
Change-Id: I0881a4916c3eeb9ee023d28d207795899417d530
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3498282
|
||||
Commit-Queue: Jamie Madill <jmadill@chromium.org>
|
||||
Auto-Submit: Jamie Madill <jmadill@chromium.org>
|
||||
(cherry picked from commit ea70300ba04404ba0c1cacf2173a0a1e3b443adf)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3516115
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/Texture.cpp b/src/libANGLE/Texture.cpp
|
||||
index 0c15d244343b70e418bf0927cf942f6ade46b272..58512ff3da88a8289cf715c34e485d6f428f4d23 100644
|
||||
--- a/src/libANGLE/Texture.cpp
|
||||
+++ b/src/libANGLE/Texture.cpp
|
||||
@@ -1188,7 +1188,15 @@ void Texture::signalDirtyState(size_t dirtyBit)
|
||||
mDirtyBits.set(dirtyBit);
|
||||
invalidateCompletenessCache();
|
||||
mState.mCachedSamplerFormatValid = false;
|
||||
- onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
|
||||
+
|
||||
+ if (dirtyBit == DIRTY_BIT_BASE_LEVEL || dirtyBit == DIRTY_BIT_MAX_LEVEL)
|
||||
+ {
|
||||
+ onStateChange(angle::SubjectMessage::SubjectChanged);
|
||||
+ }
|
||||
+ else
|
||||
+ {
|
||||
+ onStateChange(angle::SubjectMessage::DirtyBitsFlagged);
|
||||
+ }
|
||||
}
|
||||
|
||||
angle::Result Texture::setImage(Context *context,
|
||||
diff --git a/src/libANGLE/renderer/vulkan/RendererVk.cpp b/src/libANGLE/renderer/vulkan/RendererVk.cpp
|
||||
index d18fe88018cbca5fafbf19321340bb85e8e7ec9b..f513631f7b2170e72be17db93dc1437e681dbea9 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/RendererVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/RendererVk.cpp
|
||||
@@ -351,6 +351,13 @@ constexpr SkippedSyncvalMessage kSkippedSyncvalMessages[] = {
|
||||
"prior_usage: SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, write_barriers: 0, "
|
||||
"command: vkCmdBeginRenderPass, seq_no: 11",
|
||||
"", false},
|
||||
+ // http://anglebug.com/7070
|
||||
+ {"SYNC-HAZARD-READ_AFTER_WRITE",
|
||||
+ "type: VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, imageLayout: VK_IMAGE_LAYOUT_GENERAL, "
|
||||
+ "binding #0, index 0. Access info (usage: SYNC_FRAGMENT_SHADER_SHADER_STORAGE_READ, "
|
||||
+ "prior_usage: SYNC_COLOR_ATTACHMENT_OUTPUT_COLOR_ATTACHMENT_WRITE, write_barriers: 0, "
|
||||
+ "command: vkCmdBeginRenderPass",
|
||||
+ "", false},
|
||||
};
|
||||
|
||||
enum class DebugMessageReport
|
||||
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
|
||||
index fd3dd35fbe78fa59b3d4537d0cdcb981ebc36f09..1838b18f48de7a7a2fca0ae9c4d7ad4367647756 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
|
||||
@@ -1721,7 +1721,7 @@ void CommandBufferHelper::invalidateRenderPassDepthAttachment(const gl::DepthSte
|
||||
{
|
||||
ASSERT(mIsRenderPassCommandBuffer);
|
||||
// Keep track of the size of commands in the command buffer. If the size grows in the
|
||||
- // future, that implies that drawing occured since invalidated.
|
||||
+ // future, that implies that drawing occurred since invalidated.
|
||||
mDepthCmdSizeInvalidated = mCommandBuffer.getCommandSize();
|
||||
|
||||
// Also track the size if the attachment is currently disabled.
|
||||
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
index 46740cae77df7b6e9509093c02b79a332b4f106d..bb0d5db59f1126587c25f786e4f39461e34e4155 100644
|
||||
--- a/src/tests/gl_tests/FramebufferTest.cpp
|
||||
+++ b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
@@ -4019,6 +4019,38 @@ TEST_P(FramebufferTest_ES3, RedefineLayerAttachment)
|
||||
ASSERT_GL_NO_ERROR();
|
||||
}
|
||||
|
||||
+// Covers a bug when changing a base level of a texture bound to a FBO.
|
||||
+TEST_P(FramebufferTest_ES3, ReattachToInvalidBaseLevel)
|
||||
+{
|
||||
+ ANGLE_GL_PROGRAM(testProgram, essl1_shaders::vs::Texture2D(), essl1_shaders::fs::Texture2D());
|
||||
+ glUseProgram(testProgram);
|
||||
+
|
||||
+ GLTexture tex;
|
||||
+ glBindTexture(GL_TEXTURE_2D, tex);
|
||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
|
||||
+
|
||||
+ for (int mip = 0; mip <= 2; ++mip)
|
||||
+ {
|
||||
+ int size = 10 >> mip;
|
||||
+ glTexImage2D(GL_TEXTURE_2D, mip, GL_RGBA8, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ nullptr);
|
||||
+ }
|
||||
+
|
||||
+ GLFramebuffer fb;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, fb);
|
||||
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, tex, 1);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ // Set base level 1 and draw.
|
||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 1);
|
||||
+ glDrawArrays(GL_POINTS, 0, 1);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+ // Set base level 0. The FBO is incomplete because the FBO attachment binds to level 1.
|
||||
+ glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_BASE_LEVEL, 0);
|
||||
+ glDrawArrays(GL_POINTS, 0, 1);
|
||||
+ EXPECT_GL_ERROR(GL_INVALID_FRAMEBUFFER_OPERATION);
|
||||
+}
|
||||
+
|
||||
ANGLE_INSTANTIATE_TEST_ES2(AddMockTextureNoRenderTargetTest);
|
||||
ANGLE_INSTANTIATE_TEST_ES2(FramebufferTest);
|
||||
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(FramebufferFormatsTest);
|
||||
@@ -0,0 +1,109 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jamie Madill <jmadill@chromium.org>
|
||||
Date: Tue, 1 Mar 2022 16:14:47 -0500
|
||||
Subject: Protect against deleting a current XFB buffer.
|
||||
|
||||
Bug: chromium:1295411
|
||||
Change-Id: I097f272c38e444e0af71aa55c0dc508a07aa0bd3
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3498262
|
||||
Reviewed-by: Amirali Abdolrashidi <abdolrashidi@google.com>
|
||||
Reviewed-by: Geoff Lang <geofflang@chromium.org>
|
||||
Commit-Queue: Jamie Madill <jmadill@chromium.org>
|
||||
(cherry picked from commit d9002eef2a5f27fc5d6b65d01d02afcfb9a35db1)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3514175
|
||||
Reviewed-by: Ian Elliott <ianelliott@google.com>
|
||||
|
||||
diff --git a/src/libANGLE/State.cpp b/src/libANGLE/State.cpp
|
||||
index 24b9a460c7ecff24c1c3de48619aabc116727f9a..35a819ef635c25a7ff442d75e49ba89cd7ad84a9 100644
|
||||
--- a/src/libANGLE/State.cpp
|
||||
+++ b/src/libANGLE/State.cpp
|
||||
@@ -2190,6 +2190,10 @@ angle::Result State::detachBuffer(Context *context, const Buffer *buffer)
|
||||
if (curTransformFeedback)
|
||||
{
|
||||
ANGLE_TRY(curTransformFeedback->detachBuffer(context, bufferID));
|
||||
+ if (isTransformFeedbackActiveUnpaused())
|
||||
+ {
|
||||
+ context->getStateCache().onActiveTransformFeedbackChange(context);
|
||||
+ }
|
||||
}
|
||||
|
||||
if (getVertexArray()->detachBuffer(context, bufferID))
|
||||
diff --git a/src/libANGLE/validationES.cpp b/src/libANGLE/validationES.cpp
|
||||
index a37ca3061faac60395613f0a4c238d20b00f366a..ccc1f17f8db2ef85ca3b80bfa35e175dd9434c49 100644
|
||||
--- a/src/libANGLE/validationES.cpp
|
||||
+++ b/src/libANGLE/validationES.cpp
|
||||
@@ -3977,6 +3977,14 @@ const char *ValidateDrawStates(const Context *context)
|
||||
return kTessellationShaderRequiresBothControlAndEvaluation;
|
||||
}
|
||||
}
|
||||
+
|
||||
+ if (state.isTransformFeedbackActiveUnpaused())
|
||||
+ {
|
||||
+ if (!ValidateProgramExecutableXFBBuffersPresent(context, executable))
|
||||
+ {
|
||||
+ return kTransformFeedbackBufferMissing;
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
if (programIsYUVOutput != framebufferIsYUV)
|
||||
@@ -8055,4 +8063,21 @@ bool ValidateInvalidateTextureANGLE(const Context *context, TextureType target)
|
||||
return true;
|
||||
}
|
||||
|
||||
+bool ValidateProgramExecutableXFBBuffersPresent(const Context *context,
|
||||
+ const ProgramExecutable *programExecutable)
|
||||
+{
|
||||
+ size_t programXfbCount = programExecutable->getTransformFeedbackBufferCount();
|
||||
+ const TransformFeedback *transformFeedback = context->getState().getCurrentTransformFeedback();
|
||||
+ for (size_t programXfbIndex = 0; programXfbIndex < programXfbCount; ++programXfbIndex)
|
||||
+ {
|
||||
+ const OffsetBindingPointer<Buffer> &buffer =
|
||||
+ transformFeedback->getIndexedBuffer(programXfbIndex);
|
||||
+ if (!buffer.get())
|
||||
+ {
|
||||
+ return false;
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ return true;
|
||||
+}
|
||||
} // namespace gl
|
||||
diff --git a/src/libANGLE/validationES.h b/src/libANGLE/validationES.h
|
||||
index 5e531464d8d2239b5d2cb36bc9e23c031f23320f..f8796245178476c0ed4f5c2e611e968944bb6bb4 100644
|
||||
--- a/src/libANGLE/validationES.h
|
||||
+++ b/src/libANGLE/validationES.h
|
||||
@@ -753,6 +753,9 @@ bool ValidateGetMultisamplefvBase(const Context *context,
|
||||
const GLfloat *val);
|
||||
bool ValidateSampleMaskiBase(const Context *context, GLuint maskNumber, GLbitfield mask);
|
||||
|
||||
+bool ValidateProgramExecutableXFBBuffersPresent(const Context *context,
|
||||
+ const ProgramExecutable *programExecutable);
|
||||
+
|
||||
// We should check with Khronos if returning INVALID_FRAMEBUFFER_OPERATION is OK when querying
|
||||
// implementation format info for incomplete framebuffers. It seems like these queries are
|
||||
// incongruent with the other errors.
|
||||
diff --git a/src/libANGLE/validationES3.cpp b/src/libANGLE/validationES3.cpp
|
||||
index ec44c916587fc2d31e7e0201b00753d5140bf580..f9dac542366b51c2431885e1bf4fb85040234ca9 100644
|
||||
--- a/src/libANGLE/validationES3.cpp
|
||||
+++ b/src/libANGLE/validationES3.cpp
|
||||
@@ -2843,16 +2843,10 @@ bool ValidateBeginTransformFeedback(const Context *context, PrimitiveMode primit
|
||||
return false;
|
||||
}
|
||||
|
||||
- size_t programXfbCount = programExecutable->getTransformFeedbackBufferCount();
|
||||
- for (size_t programXfbIndex = 0; programXfbIndex < programXfbCount; ++programXfbIndex)
|
||||
+ if (!ValidateProgramExecutableXFBBuffersPresent(context, programExecutable))
|
||||
{
|
||||
- const OffsetBindingPointer<Buffer> &buffer =
|
||||
- transformFeedback->getIndexedBuffer(programXfbIndex);
|
||||
- if (!buffer.get())
|
||||
- {
|
||||
- context->validationError(GL_INVALID_OPERATION, kTransformFeedbackBufferMissing);
|
||||
- return false;
|
||||
- }
|
||||
+ context->validationError(GL_INVALID_OPERATION, kTransformFeedbackBufferMissing);
|
||||
+ return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
112
patches/angle/m98_vulkan_fix_vkcmdresolveimage_extents.patch
Normal file
112
patches/angle/m98_vulkan_fix_vkcmdresolveimage_extents.patch
Normal file
@@ -0,0 +1,112 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Date: Mon, 31 Jan 2022 12:07:43 -0500
|
||||
Subject: M98: Vulkan: Fix vkCmdResolveImage extents
|
||||
|
||||
The source framebuffer's extents were accidentally used instead of the
|
||||
blit area extents.
|
||||
|
||||
Bug: chromium:1288020
|
||||
Change-Id: I5c6128a191deeea2f972dc7f010be9d40c674ce6
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3457022
|
||||
Reviewed-by: Tim Van Patten <timvp@google.com>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
|
||||
index 2eeaa66d2692fe7631591377f32b89cdd90aaf05..4ece465efb683c40027dfee083dd2ed567a0bfc4 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
|
||||
@@ -1464,8 +1464,8 @@ angle::Result FramebufferVk::resolveColorWithCommand(ContextVk *contextVk,
|
||||
resolveRegion.dstOffset.x = params.destOffset[0];
|
||||
resolveRegion.dstOffset.y = params.destOffset[1];
|
||||
resolveRegion.dstOffset.z = 0;
|
||||
- resolveRegion.extent.width = params.srcExtents[0];
|
||||
- resolveRegion.extent.height = params.srcExtents[1];
|
||||
+ resolveRegion.extent.width = params.blitArea.width;
|
||||
+ resolveRegion.extent.height = params.blitArea.height;
|
||||
resolveRegion.extent.depth = 1;
|
||||
|
||||
vk::PerfCounters &perfCounters = contextVk->getPerfCounters();
|
||||
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
|
||||
index 18b80da7f6f30f7e8e8e1df2b05c13be6c5b85de..844d38a75ad054c6ed5cd95cb20a49dadbae267b 100644
|
||||
--- a/src/tests/angle_end2end_tests_expectations.txt
|
||||
+++ b/src/tests/angle_end2end_tests_expectations.txt
|
||||
@@ -81,6 +81,7 @@
|
||||
// the test says. The test also fails on Intel/Vulkan/Windows.
|
||||
6068 INTEL VULKAN : MultiviewRenderPrimitiveTest.LineLoop/* = SKIP
|
||||
6068 INTEL VULKAN : MultiviewRenderPrimitiveTest.LineStrip/* = SKIP
|
||||
+6962 WIN INTEL VULKAN : BlitFramebufferTestES31.PartialResolve/* = SKIP
|
||||
|
||||
// Mac
|
||||
|
||||
diff --git a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
|
||||
index 6fd3f08ef20e315d10c593868d94f6653fa5cdf4..e4d82a511b77ac3b379ec088703d21ce480fafa9 100644
|
||||
--- a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
|
||||
+++ b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
|
||||
@@ -2603,6 +2603,67 @@ TEST_P(BlitFramebufferTest, BlitDepthStencilPixelByPixel)
|
||||
EXPECT_PIXEL_RECT_EQ(64, 0, 128, 1, GLColor::blue);
|
||||
}
|
||||
|
||||
+// Regression test for a bug in the Vulkan backend where vkCmdResolveImage was using the src extents
|
||||
+// as the resolve area instead of the area passed to glBlitFramebuffer.
|
||||
+TEST_P(BlitFramebufferTestES31, PartialResolve)
|
||||
+{
|
||||
+ constexpr GLint kWidth = 16;
|
||||
+ constexpr GLint kHeight = 32;
|
||||
+
|
||||
+ // Read framebuffer is multisampled.
|
||||
+ GLTexture readTexture;
|
||||
+ glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, readTexture);
|
||||
+ glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kWidth, kHeight, GL_TRUE);
|
||||
+
|
||||
+ GLFramebuffer readFbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, readFbo);
|
||||
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
|
||||
+ readTexture, 0);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
|
||||
+
|
||||
+ glClearColor(1, 0, 0, 1);
|
||||
+ glClear(GL_COLOR_BUFFER_BIT);
|
||||
+
|
||||
+ // Draw framebuffer is single sampled. It's bound to a texture with base level the same size as
|
||||
+ // the read framebuffer, but it's bound to mip 1.
|
||||
+ GLTexture drawTexture;
|
||||
+ glBindTexture(GL_TEXTURE_2D, drawTexture);
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ nullptr);
|
||||
+ glGenerateMipmap(GL_TEXTURE_2D);
|
||||
+
|
||||
+ GLFramebuffer drawFbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, drawFbo);
|
||||
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, drawTexture, 1);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
|
||||
+
|
||||
+ glClearColor(0, 1, 0, 1);
|
||||
+ glClear(GL_COLOR_BUFFER_BIT);
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, 0, kWidth / 2, kHeight / 2, GLColor::green);
|
||||
+
|
||||
+ constexpr GLint kResolveX0 = 1;
|
||||
+ constexpr GLint kResolveY0 = 2;
|
||||
+ constexpr GLint kResolveX1 = 4;
|
||||
+ constexpr GLint kResolveY1 = 6;
|
||||
+
|
||||
+ // Resolve only a portion of the read framebuffer.
|
||||
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
|
||||
+ glBlitFramebuffer(kResolveX0, kResolveY0, kResolveX1, kResolveY1, kResolveX0, kResolveY0,
|
||||
+ kResolveX1, kResolveY1, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+
|
||||
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, drawFbo);
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, 0, kWidth / 2, kResolveY0, GLColor::green);
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, 0, kResolveX0, kHeight / 2, GLColor::green);
|
||||
+ EXPECT_PIXEL_RECT_EQ(kResolveX1, 0, kWidth / 2 - kResolveX1, kHeight / 2, GLColor::green);
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, kResolveY1, kWidth / 2, kHeight / 2 - kResolveY1, GLColor::green);
|
||||
+
|
||||
+ EXPECT_PIXEL_RECT_EQ(kResolveX0, kResolveY0, kResolveX1 - kResolveX0, kResolveY1 - kResolveY0,
|
||||
+ GLColor::red);
|
||||
+}
|
||||
+
|
||||
// Test that a draw call to a small FBO followed by a resolve of a large FBO works.
|
||||
TEST_P(BlitFramebufferTestES31, DrawToSmallFBOThenResolveLargeFBO)
|
||||
{
|
||||
109
patches/angle/m98_vulkan_fix_vkcmdresolveimage_offsets.patch
Normal file
109
patches/angle/m98_vulkan_fix_vkcmdresolveimage_offsets.patch
Normal file
@@ -0,0 +1,109 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Date: Mon, 7 Feb 2022 13:46:46 -0500
|
||||
Subject: M98: Vulkan: Fix vkCmdResolveImage offsets
|
||||
|
||||
glBlitFramebuffer takes identical regions for src and dst when
|
||||
resolving. vkCmdResolveImage should use the clipped area instead of
|
||||
using the actual offsets passed to this function.
|
||||
|
||||
Bug: chromium:1292537
|
||||
Change-Id: If283a8acbca3249b771facbc30bd9f8080a03656
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3457023
|
||||
Reviewed-by: Tim Van Patten <timvp@google.com>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
|
||||
index 4ece465efb683c40027dfee083dd2ed567a0bfc4..8099a00084eebcd41603087627a7254a2ef04fd4 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/FramebufferVk.cpp
|
||||
@@ -1456,13 +1456,13 @@ angle::Result FramebufferVk::resolveColorWithCommand(ContextVk *contextVk,
|
||||
resolveRegion.srcSubresource.mipLevel = 0;
|
||||
resolveRegion.srcSubresource.baseArrayLayer = params.srcLayer;
|
||||
resolveRegion.srcSubresource.layerCount = 1;
|
||||
- resolveRegion.srcOffset.x = params.srcOffset[0];
|
||||
- resolveRegion.srcOffset.y = params.srcOffset[1];
|
||||
+ resolveRegion.srcOffset.x = params.blitArea.x;
|
||||
+ resolveRegion.srcOffset.y = params.blitArea.y;
|
||||
resolveRegion.srcOffset.z = 0;
|
||||
resolveRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||
resolveRegion.dstSubresource.layerCount = 1;
|
||||
- resolveRegion.dstOffset.x = params.destOffset[0];
|
||||
- resolveRegion.dstOffset.y = params.destOffset[1];
|
||||
+ resolveRegion.dstOffset.x = params.blitArea.x;
|
||||
+ resolveRegion.dstOffset.y = params.blitArea.y;
|
||||
resolveRegion.dstOffset.z = 0;
|
||||
resolveRegion.extent.width = params.blitArea.width;
|
||||
resolveRegion.extent.height = params.blitArea.height;
|
||||
diff --git a/src/tests/angle_end2end_tests_expectations.txt b/src/tests/angle_end2end_tests_expectations.txt
|
||||
index 844d38a75ad054c6ed5cd95cb20a49dadbae267b..5cde8f7170688686cde3e1984a788d64d85d9c2d 100644
|
||||
--- a/src/tests/angle_end2end_tests_expectations.txt
|
||||
+++ b/src/tests/angle_end2end_tests_expectations.txt
|
||||
@@ -21,6 +21,8 @@
|
||||
6347 OPENGL : FramebufferTestWithFormatFallback.RGBA4444_BlitCopyTexImage/* = SKIP
|
||||
6347 GLES : FramebufferTestWithFormatFallback.R5G5B5A1_BlitCopyTexImage/* = SKIP
|
||||
6347 GLES : FramebufferTestWithFormatFallback.RGBA4444_BlitCopyTexImage/* = SKIP
|
||||
+6989 OPENGL : BlitFramebufferTestES31.OOBResolve/* = SKIP
|
||||
+6989 GLES : BlitFramebufferTestES31.OOBResolve/* = SKIP
|
||||
|
||||
// Direct SPIR-V generation. The following tests pass on some platforms but not others. Need to investigate.
|
||||
4889 VULKAN : GeometryShaderTest.LayeredFramebufferPreRenderClear2DArrayColor/ES3_1_Vulkan_DirectSPIRVGen = SKIP
|
||||
diff --git a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
|
||||
index e4d82a511b77ac3b379ec088703d21ce480fafa9..92559f965137c8a2202b0840e298648c5e8b8740 100644
|
||||
--- a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
|
||||
+++ b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp
|
||||
@@ -2603,6 +2603,55 @@ TEST_P(BlitFramebufferTest, BlitDepthStencilPixelByPixel)
|
||||
EXPECT_PIXEL_RECT_EQ(64, 0, 128, 1, GLColor::blue);
|
||||
}
|
||||
|
||||
+// Regression test for a bug in the Vulkan backend where vkCmdResolveImage was used with
|
||||
+// out-of-bounds regions.
|
||||
+TEST_P(BlitFramebufferTestES31, OOBResolve)
|
||||
+{
|
||||
+ constexpr GLint kWidth = 16;
|
||||
+ constexpr GLint kHeight = 32;
|
||||
+
|
||||
+ // Read framebuffer is multisampled.
|
||||
+ GLTexture readTexture;
|
||||
+ glBindTexture(GL_TEXTURE_2D_MULTISAMPLE, readTexture);
|
||||
+ glTexStorage2DMultisample(GL_TEXTURE_2D_MULTISAMPLE, 4, GL_RGBA8, kWidth, kHeight, GL_TRUE);
|
||||
+
|
||||
+ GLFramebuffer readFbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, readFbo);
|
||||
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D_MULTISAMPLE,
|
||||
+ readTexture, 0);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
|
||||
+
|
||||
+ glClearColor(1, 0, 0, 1);
|
||||
+ glClear(GL_COLOR_BUFFER_BIT);
|
||||
+
|
||||
+ // Draw framebuffer is single sampled.
|
||||
+ GLTexture drawTexture;
|
||||
+ glBindTexture(GL_TEXTURE_2D, drawTexture);
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kWidth, kHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ nullptr);
|
||||
+ glGenerateMipmap(GL_TEXTURE_2D);
|
||||
+
|
||||
+ GLFramebuffer drawFbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, drawFbo);
|
||||
+ glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, drawTexture, 0);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+ ASSERT_GL_FRAMEBUFFER_COMPLETE(GL_FRAMEBUFFER);
|
||||
+
|
||||
+ glClearColor(0, 1, 0, 1);
|
||||
+ glClear(GL_COLOR_BUFFER_BIT);
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::green);
|
||||
+
|
||||
+ // Resolve the read framebuffer, using bounds that are outside the size of the image.
|
||||
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, readFbo);
|
||||
+ glBlitFramebuffer(-kWidth * 2, -kHeight * 3, kWidth * 11, kHeight * 8, -kWidth * 2,
|
||||
+ -kHeight * 3, kWidth * 11, kHeight * 8, GL_COLOR_BUFFER_BIT, GL_NEAREST);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+
|
||||
+ glBindFramebuffer(GL_READ_FRAMEBUFFER, drawFbo);
|
||||
+ EXPECT_PIXEL_RECT_EQ(0, 0, kWidth, kHeight, GLColor::red);
|
||||
+}
|
||||
+
|
||||
// Regression test for a bug in the Vulkan backend where vkCmdResolveImage was using the src extents
|
||||
// as the resolve area instead of the area passed to glBlitFramebuffer.
|
||||
TEST_P(BlitFramebufferTestES31, PartialResolve)
|
||||
@@ -0,0 +1,304 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Roman Lavrov <romanl@google.com>
|
||||
Date: Tue, 18 Jan 2022 20:05:55 +0000
|
||||
Subject: M99: Vulkan: Prevent out of bounds read in divisor emulation path.
|
||||
|
||||
Split the replicated part of StreamVertexData out to
|
||||
StreamVertexDataWithDivisor, there is only a partial argument
|
||||
overlap between the two.
|
||||
|
||||
Bug: chromium:1285885
|
||||
Change-Id: Ibf6ab3efc6b12b430b1d391c6ae61bd9668b4407
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3398816
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Commit-Queue: Roman Lavrov <romanl@google.com>
|
||||
(cherry picked from commit 5f0badf4541ba52659c937cfe9190d3735a76c10)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3461414
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
index 09cb058a612bfb62ece95b5a1bb7beb4df96e0e3..4e443ed34c29284739fbefb16a38feefed90b726 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
@@ -82,34 +82,63 @@ angle::Result StreamVertexData(ContextVk *contextVk,
|
||||
size_t destOffset,
|
||||
size_t vertexCount,
|
||||
size_t sourceStride,
|
||||
- size_t destStride,
|
||||
VertexCopyFunction vertexLoadFunction,
|
||||
vk::BufferHelper **bufferOut,
|
||||
- VkDeviceSize *bufferOffsetOut,
|
||||
- uint32_t replicateCount)
|
||||
+ VkDeviceSize *bufferOffsetOut)
|
||||
{
|
||||
uint8_t *dst = nullptr;
|
||||
ANGLE_TRY(dynamicBuffer->allocate(contextVk, bytesToAllocate, &dst, nullptr, bufferOffsetOut,
|
||||
nullptr));
|
||||
*bufferOut = dynamicBuffer->getCurrentBuffer();
|
||||
dst += destOffset;
|
||||
- if (replicateCount == 1)
|
||||
+ vertexLoadFunction(sourceData, sourceStride, vertexCount, dst);
|
||||
+
|
||||
+ ANGLE_TRY(dynamicBuffer->flush(contextVk));
|
||||
+
|
||||
+ return angle::Result::Continue;
|
||||
+}
|
||||
+
|
||||
+angle::Result StreamVertexDataWithDivisor(ContextVk *contextVk,
|
||||
+ vk::DynamicBuffer *dynamicBuffer,
|
||||
+ const uint8_t *sourceData,
|
||||
+ size_t bytesToAllocate,
|
||||
+ size_t sourceStride,
|
||||
+ size_t destStride,
|
||||
+ VertexCopyFunction vertexLoadFunction,
|
||||
+ vk::BufferHelper **bufferOut,
|
||||
+ VkDeviceSize *bufferOffsetOut,
|
||||
+ uint32_t divisor,
|
||||
+ size_t numSrcVertices)
|
||||
+{
|
||||
+ uint8_t *dst = nullptr;
|
||||
+ ANGLE_TRY(dynamicBuffer->allocate(contextVk, bytesToAllocate, &dst, nullptr, bufferOffsetOut,
|
||||
+ nullptr));
|
||||
+ *bufferOut = dynamicBuffer->getCurrentBuffer();
|
||||
+
|
||||
+ // Each source vertex is used `divisor` times before advancing. Clamp to avoid OOB reads.
|
||||
+ size_t clampedSize = std::min(numSrcVertices * destStride * divisor, bytesToAllocate);
|
||||
+
|
||||
+ ASSERT(clampedSize % destStride == 0);
|
||||
+ ASSERT(divisor > 0);
|
||||
+
|
||||
+ uint32_t sourceVertexUseCount = 0;
|
||||
+ for (size_t dataCopied = 0; dataCopied < clampedSize; dataCopied += destStride, dst += destStride)
|
||||
{
|
||||
- vertexLoadFunction(sourceData, sourceStride, vertexCount, dst);
|
||||
+ vertexLoadFunction(sourceData, sourceStride, 1, dst);
|
||||
+ sourceVertexUseCount++;
|
||||
+ if (sourceVertexUseCount == divisor)
|
||||
+ {
|
||||
+ sourceData += sourceStride;
|
||||
+ sourceVertexUseCount = 0;
|
||||
+ }
|
||||
}
|
||||
- else
|
||||
+
|
||||
+ // Satisfy robustness constraints (only if extension enabled)
|
||||
+ if (contextVk->getExtensions().robustnessEXT)
|
||||
{
|
||||
- ASSERT(replicateCount > 1);
|
||||
- uint32_t sourceRemainingCount = replicateCount - 1;
|
||||
- for (size_t dataCopied = 0; dataCopied < bytesToAllocate;
|
||||
- dataCopied += destStride, dst += destStride, sourceRemainingCount--)
|
||||
+ if (clampedSize < bytesToAllocate)
|
||||
{
|
||||
- vertexLoadFunction(sourceData, sourceStride, 1, dst);
|
||||
- if (sourceRemainingCount == 0)
|
||||
- {
|
||||
- sourceData += sourceStride;
|
||||
- sourceRemainingCount = replicateCount;
|
||||
- }
|
||||
+ memset(dst + clampedSize, 0, bytesToAllocate - clampedSize);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -125,6 +154,7 @@ size_t GetVertexCount(BufferVk *srcBuffer, const gl::VertexBinding &binding, uin
|
||||
return 0;
|
||||
|
||||
// Count the last vertex. It may occupy less than a full stride.
|
||||
+ // This is also correct if stride happens to be less than srcFormatSize.
|
||||
size_t numVertices = 1;
|
||||
bytes -= srcFormatSize;
|
||||
|
||||
@@ -453,8 +483,8 @@ angle::Result VertexArrayVk::convertVertexBufferCPU(ContextVk *contextVk,
|
||||
ASSERT(vertexFormat.getVertexInputAlignment(compressed) <= vk::kVertexBufferAlignment);
|
||||
ANGLE_TRY(StreamVertexData(
|
||||
contextVk, &conversion->data, srcBytes, numVertices * dstFormatSize, 0, numVertices,
|
||||
- binding.getStride(), srcFormatSize, vertexFormat.getVertexLoadFunction(compressed),
|
||||
- &mCurrentArrayBuffers[attribIndex], &conversion->lastAllocationOffset, 1));
|
||||
+ binding.getStride(), vertexFormat.getVertexLoadFunction(compressed),
|
||||
+ &mCurrentArrayBuffers[attribIndex], &conversion->lastAllocationOffset));
|
||||
ANGLE_TRY(srcBuffer->unmapImpl(contextVk));
|
||||
|
||||
ASSERT(conversion->dirty);
|
||||
@@ -817,28 +847,41 @@ angle::Result VertexArrayVk::updateStreamedAttribs(const gl::Context *context,
|
||||
// Instanced attrib
|
||||
if (divisor > renderer->getMaxVertexAttribDivisor())
|
||||
{
|
||||
- // Emulated attrib
|
||||
- BufferVk *bufferVk = nullptr;
|
||||
+ // Divisor will be set to 1 & so update buffer to have 1 attrib per instance
|
||||
+ size_t bytesToAllocate = instanceCount * stride;
|
||||
+
|
||||
if (binding.getBuffer().get() != nullptr)
|
||||
{
|
||||
// Map buffer to expand attribs for divisor emulation
|
||||
- bufferVk = vk::GetImpl(binding.getBuffer().get());
|
||||
- void *buffSrc = nullptr;
|
||||
+ BufferVk *bufferVk = vk::GetImpl(binding.getBuffer().get());
|
||||
+ void *buffSrc = nullptr;
|
||||
ANGLE_TRY(bufferVk->mapImpl(contextVk, &buffSrc));
|
||||
src = reinterpret_cast<const uint8_t *>(buffSrc) + binding.getOffset();
|
||||
- }
|
||||
- // Divisor will be set to 1 & so update buffer to have 1 attrib per instance
|
||||
- size_t bytesToAllocate = instanceCount * stride;
|
||||
|
||||
- ANGLE_TRY(StreamVertexData(contextVk, &mDynamicVertexData, src, bytesToAllocate, 0,
|
||||
- instanceCount, binding.getStride(), stride,
|
||||
- vertexFormat.getVertexLoadFunction(compressed),
|
||||
- &mCurrentArrayBuffers[attribIndex],
|
||||
- &mCurrentArrayBufferOffsets[attribIndex], divisor));
|
||||
- if (bufferVk)
|
||||
- {
|
||||
+ uint32_t srcAttributeSize =
|
||||
+ static_cast<uint32_t>(ComputeVertexAttributeTypeSize(attrib));
|
||||
+
|
||||
+ size_t numVertices = GetVertexCount(bufferVk, binding, srcAttributeSize);
|
||||
+
|
||||
+ ANGLE_TRY(StreamVertexDataWithDivisor(
|
||||
+ contextVk, &mDynamicVertexData, src, bytesToAllocate, binding.getStride(),
|
||||
+ stride, vertexFormat.getVertexLoadFunction(compressed),
|
||||
+ &mCurrentArrayBuffers[attribIndex],
|
||||
+ &mCurrentArrayBufferOffsets[attribIndex], divisor,
|
||||
+ numVertices));
|
||||
+
|
||||
ANGLE_TRY(bufferVk->unmapImpl(contextVk));
|
||||
}
|
||||
+ else
|
||||
+ {
|
||||
+ size_t numVertices = instanceCount;
|
||||
+ ANGLE_TRY(StreamVertexDataWithDivisor(
|
||||
+ contextVk, &mDynamicVertexData, src, bytesToAllocate, binding.getStride(),
|
||||
+ stride, vertexFormat.getVertexLoadFunction(compressed),
|
||||
+ &mCurrentArrayBuffers[attribIndex],
|
||||
+ &mCurrentArrayBufferOffsets[attribIndex], divisor,
|
||||
+ numVertices));
|
||||
+ }
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -847,10 +890,10 @@ angle::Result VertexArrayVk::updateStreamedAttribs(const gl::Context *context,
|
||||
size_t bytesToAllocate = count * stride;
|
||||
|
||||
ANGLE_TRY(StreamVertexData(contextVk, &mDynamicVertexData, src, bytesToAllocate, 0,
|
||||
- count, binding.getStride(), stride,
|
||||
+ count, binding.getStride(),
|
||||
vertexFormat.getVertexLoadFunction(compressed),
|
||||
&mCurrentArrayBuffers[attribIndex],
|
||||
- &mCurrentArrayBufferOffsets[attribIndex], 1));
|
||||
+ &mCurrentArrayBufferOffsets[attribIndex]));
|
||||
}
|
||||
}
|
||||
else
|
||||
@@ -865,8 +908,8 @@ angle::Result VertexArrayVk::updateStreamedAttribs(const gl::Context *context,
|
||||
|
||||
ANGLE_TRY(StreamVertexData(
|
||||
contextVk, &mDynamicVertexData, src, bytesToAllocate, destOffset, vertexCount,
|
||||
- binding.getStride(), stride, vertexFormat.getVertexLoadFunction(compressed),
|
||||
- &mCurrentArrayBuffers[attribIndex], &mCurrentArrayBufferOffsets[attribIndex], 1));
|
||||
+ binding.getStride(), vertexFormat.getVertexLoadFunction(compressed),
|
||||
+ &mCurrentArrayBuffers[attribIndex], &mCurrentArrayBufferOffsets[attribIndex]));
|
||||
}
|
||||
|
||||
mCurrentArrayBufferHandles[attribIndex] =
|
||||
diff --git a/src/tests/gl_tests/RobustBufferAccessBehaviorTest.cpp b/src/tests/gl_tests/RobustBufferAccessBehaviorTest.cpp
|
||||
index 08917a2de3385eb853bced1dd576aff46f34f709..db708b0a2a3ce90a370b43786af6884b2f992bef 100644
|
||||
--- a/src/tests/gl_tests/RobustBufferAccessBehaviorTest.cpp
|
||||
+++ b/src/tests/gl_tests/RobustBufferAccessBehaviorTest.cpp
|
||||
@@ -564,6 +564,98 @@ TEST_P(RobustBufferAccessBehaviorTest, DynamicBuffer)
|
||||
}
|
||||
}
|
||||
|
||||
+// Tests out of bounds read by divisor emulation due to a user-provided offset.
|
||||
+// Adapted from https://crbug.com/1285885.
|
||||
+TEST_P(RobustBufferAccessBehaviorTest, IndexOutOfBounds)
|
||||
+{
|
||||
+ ANGLE_SKIP_TEST_IF(!initExtension());
|
||||
+
|
||||
+ constexpr char kVS[] = R"(precision highp float;
|
||||
+attribute vec4 a_position;
|
||||
+void main(void) {
|
||||
+ gl_Position = a_position;
|
||||
+})";
|
||||
+
|
||||
+ constexpr char kFS[] = R"(precision highp float;
|
||||
+uniform sampler2D oTexture;
|
||||
+uniform float oColor[3];
|
||||
+void main(void) {
|
||||
+ gl_FragData[0] = texture2DProj(oTexture, vec3(0.1,0.1,0.1));
|
||||
+})";
|
||||
+
|
||||
+ GLfloat singleFloat = 1.0f;
|
||||
+
|
||||
+ GLBuffer buf;
|
||||
+ glBindBuffer(GL_ARRAY_BUFFER, buf);
|
||||
+ glBufferData(GL_ARRAY_BUFFER, 4, &singleFloat, GL_STATIC_DRAW);
|
||||
+
|
||||
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
|
||||
+ glBindAttribLocation(program, 0, "a_position");
|
||||
+ glLinkProgram(program);
|
||||
+ ASSERT_TRUE(CheckLinkStatusAndReturnProgram(program, true));
|
||||
+
|
||||
+ glEnableVertexAttribArray(0);
|
||||
+
|
||||
+ // Trying to exceed renderer->getMaxVertexAttribDivisor()
|
||||
+ GLuint constexpr kDivisor = 4096;
|
||||
+ glVertexAttribDivisor(0, kDivisor);
|
||||
+
|
||||
+ size_t outOfBoundsOffset = 0x50000000;
|
||||
+ glVertexAttribPointer(0, 1, GL_FLOAT, false, 8, reinterpret_cast<void *>(outOfBoundsOffset));
|
||||
+
|
||||
+ glUseProgram(program);
|
||||
+
|
||||
+ glDrawArrays(GL_TRIANGLES, 0, 32);
|
||||
+
|
||||
+ // No assertions, just checking for crashes.
|
||||
+}
|
||||
+
|
||||
+// Similar to the test above but index is first within bounds then goes out of bounds.
|
||||
+TEST_P(RobustBufferAccessBehaviorTest, IndexGoingOutOfBounds)
|
||||
+{
|
||||
+ ANGLE_SKIP_TEST_IF(!initExtension());
|
||||
+
|
||||
+ constexpr char kVS[] = R"(precision highp float;
|
||||
+attribute vec4 a_position;
|
||||
+void main(void) {
|
||||
+ gl_Position = a_position;
|
||||
+})";
|
||||
+
|
||||
+ constexpr char kFS[] = R"(precision highp float;
|
||||
+uniform sampler2D oTexture;
|
||||
+uniform float oColor[3];
|
||||
+void main(void) {
|
||||
+ gl_FragData[0] = texture2DProj(oTexture, vec3(0.1,0.1,0.1));
|
||||
+})";
|
||||
+
|
||||
+ GLBuffer buf;
|
||||
+ glBindBuffer(GL_ARRAY_BUFFER, buf);
|
||||
+ std::array<GLfloat, 2> buffer = {{0.2f, 0.2f}};
|
||||
+ glBufferData(GL_ARRAY_BUFFER, sizeof(GLfloat) * buffer.size(), buffer.data(), GL_STATIC_DRAW);
|
||||
+
|
||||
+ ANGLE_GL_PROGRAM(program, kVS, kFS);
|
||||
+ glBindAttribLocation(program, 0, "a_position");
|
||||
+ glLinkProgram(program);
|
||||
+ ASSERT_TRUE(CheckLinkStatusAndReturnProgram(program, true));
|
||||
+
|
||||
+ glEnableVertexAttribArray(0);
|
||||
+
|
||||
+ // Trying to exceed renderer->getMaxVertexAttribDivisor()
|
||||
+ GLuint constexpr kDivisor = 4096;
|
||||
+ glVertexAttribDivisor(0, kDivisor);
|
||||
+
|
||||
+ // 6 bytes remaining in the buffer from offset so only a single vertex can be read
|
||||
+ glVertexAttribPointer(0, 1, GL_FLOAT, false, 8, reinterpret_cast<void *>(2));
|
||||
+
|
||||
+ glUseProgram(program);
|
||||
+
|
||||
+ // Each vertex is read `kDivisor` times so the last read goes out of bounds
|
||||
+ GLsizei instanceCount = kDivisor + 1;
|
||||
+ glDrawArraysInstanced(GL_TRIANGLES, 0, 32, instanceCount);
|
||||
+
|
||||
+ // No assertions, just checking for crashes.
|
||||
+}
|
||||
+
|
||||
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3_AND_ES31(RobustBufferAccessBehaviorTest);
|
||||
|
||||
} // namespace
|
||||
@@ -0,0 +1,51 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Charlie Lao <cclao@google.com>
|
||||
Date: Mon, 7 Feb 2022 15:08:15 -0800
|
||||
Subject: M99: Vulkan: StreamVertexDataWithDivisor write beyond buffer boundary
|
||||
|
||||
StreamVertexDataWithDivisor() function is advancing dst with dstStride,
|
||||
but then later on it is treating dst as if it never advanced, thus
|
||||
result in write out of buffer boundary. This was hidden by VMA's memory
|
||||
suballocation, which means it may result in some rendering artifacts.
|
||||
|
||||
Bug: angleproject:6990
|
||||
Change-Id: Ic91e917cedd247dfe85b12a69ae26b21b7a4e67a
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3445528
|
||||
Reviewed-by: Roman Lavrov <romanl@google.com>
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
Commit-Queue: Charlie Lao <cclao@google.com>
|
||||
(cherry picked from commit 5204587698099207ce8ae70779ef44ffae877996)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3461417
|
||||
Reviewed-by: Charlie Lao <cclao@google.com>
|
||||
Commit-Queue: Roman Lavrov <romanl@google.com>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
index 4e443ed34c29284739fbefb16a38feefed90b726..9043afe17e84737d3e8c295bbc9597fc078f82f9 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/VertexArrayVk.cpp
|
||||
@@ -122,7 +122,7 @@ angle::Result StreamVertexDataWithDivisor(ContextVk *contextVk,
|
||||
ASSERT(divisor > 0);
|
||||
|
||||
uint32_t sourceVertexUseCount = 0;
|
||||
- for (size_t dataCopied = 0; dataCopied < clampedSize; dataCopied += destStride, dst += destStride)
|
||||
+ for (size_t dataCopied = 0; dataCopied < clampedSize; dataCopied += destStride)
|
||||
{
|
||||
vertexLoadFunction(sourceData, sourceStride, 1, dst);
|
||||
sourceVertexUseCount++;
|
||||
@@ -131,6 +131,7 @@ angle::Result StreamVertexDataWithDivisor(ContextVk *contextVk,
|
||||
sourceData += sourceStride;
|
||||
sourceVertexUseCount = 0;
|
||||
}
|
||||
+ dst += destStride;
|
||||
}
|
||||
|
||||
// Satisfy robustness constraints (only if extension enabled)
|
||||
@@ -138,7 +139,7 @@ angle::Result StreamVertexDataWithDivisor(ContextVk *contextVk,
|
||||
{
|
||||
if (clampedSize < bytesToAllocate)
|
||||
{
|
||||
- memset(dst + clampedSize, 0, bytesToAllocate - clampedSize);
|
||||
+ memset(dst, 0, bytesToAllocate - clampedSize);
|
||||
}
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user