mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
292 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a636ab7c61 | ||
|
|
53e39eb9c0 | ||
|
|
cccd74e6a6 | ||
|
|
1efa631b56 | ||
|
|
221d2220e1 | ||
|
|
ae3ee8ad00 | ||
|
|
b39a34650f | ||
|
|
8292b3dc0c | ||
|
|
8653dfc8b3 | ||
|
|
1ef9a756e5 | ||
|
|
fdfa5d41d4 | ||
|
|
86e10b06dd | ||
|
|
d2497a14e0 | ||
|
|
5963024ac7 | ||
|
|
26690b12f2 | ||
|
|
1a148a8232 | ||
|
|
cff70f15aa | ||
|
|
a6f094c983 | ||
|
|
04dcbe4c74 | ||
|
|
28def9ed7d | ||
|
|
2089370bbb | ||
|
|
837dc05204 | ||
|
|
9f08e46018 | ||
|
|
6a70d8a6ca | ||
|
|
aab276c532 | ||
|
|
053fea1b44 | ||
|
|
0b0e991a54 | ||
|
|
ec7408a2d4 | ||
|
|
250b7ed3db | ||
|
|
d136430b35 | ||
|
|
79abfe7ac8 | ||
|
|
a18a4609ad | ||
|
|
cdf661939d | ||
|
|
98cf0e5dff | ||
|
|
be9dccfcc6 | ||
|
|
e4bb98195f | ||
|
|
099560c59c | ||
|
|
efd276d31f | ||
|
|
e2cb43768d | ||
|
|
e654f5ecb3 | ||
|
|
f74940b536 | ||
|
|
fe8508a287 | ||
|
|
d5dd50f87e | ||
|
|
952994c591 | ||
|
|
536b5c4e3b | ||
|
|
210bae878f | ||
|
|
9b78227260 | ||
|
|
0a50571460 | ||
|
|
248a6012d5 | ||
|
|
ed52801e09 | ||
|
|
6226507536 | ||
|
|
098a118dce | ||
|
|
56e08fe5f1 | ||
|
|
8dba1cf5fa | ||
|
|
1fc7634f69 | ||
|
|
52e443ec52 | ||
|
|
16be926e0a | ||
|
|
5c2d65c749 | ||
|
|
7bf1d3c110 | ||
|
|
92969d6032 | ||
|
|
b48d0f2cbe | ||
|
|
ff8594b11b | ||
|
|
a41f04028e | ||
|
|
1f320e0688 | ||
|
|
1f23e83ef9 | ||
|
|
11ea44925b | ||
|
|
21a6c6dc76 | ||
|
|
057c24feec | ||
|
|
69014e7aad | ||
|
|
ec293f4de0 | ||
|
|
15038974ad | ||
|
|
d2287a1935 | ||
|
|
e298bf46e4 | ||
|
|
5426d2c920 | ||
|
|
3e0a2edec4 | ||
|
|
f74d89ba80 | ||
|
|
4e14dca687 | ||
|
|
1aaf1e7241 | ||
|
|
1e7c8a3c63 | ||
|
|
8496a12321 | ||
|
|
98bab0ca28 | ||
|
|
a630db8c86 | ||
|
|
7e19dbad6f | ||
|
|
2b8fc8a139 | ||
|
|
30b9d15af0 | ||
|
|
bcf04f5878 | ||
|
|
9fba8b35cb | ||
|
|
ad3563687f | ||
|
|
4e2c19944f | ||
|
|
95814288cf | ||
|
|
b711b8f98d | ||
|
|
a1744e5de0 | ||
|
|
e9446f0dc4 | ||
|
|
1cda1d0e86 | ||
|
|
c2023dd9f5 | ||
|
|
c98059abd9 | ||
|
|
df726b619d | ||
|
|
c2b1db192d | ||
|
|
235c32ee55 | ||
|
|
7e66d6a952 | ||
|
|
0db4d5add5 | ||
|
|
9380803004 | ||
|
|
2a12d5fa7f | ||
|
|
93e8de87a5 | ||
|
|
b6c9110923 | ||
|
|
5e815df20f | ||
|
|
e676b3862c | ||
|
|
cd8f678a8d | ||
|
|
79554ba989 | ||
|
|
ad67a27dcf | ||
|
|
c9476eb821 | ||
|
|
2c6321c9af | ||
|
|
715de396d4 | ||
|
|
98d6b9bf15 | ||
|
|
a05ed73f43 | ||
|
|
f7fdd34c45 | ||
|
|
55e6ce81e0 | ||
|
|
b5c81db591 | ||
|
|
cffbeeaefa | ||
|
|
c81380848f | ||
|
|
45a1d81315 | ||
|
|
ded013e4a7 | ||
|
|
2cabee9128 | ||
|
|
0c9571c89f | ||
|
|
c93204ec0d | ||
|
|
3c47d9e0d7 | ||
|
|
9c54bf7973 | ||
|
|
d6f44a75d2 | ||
|
|
259f27070c | ||
|
|
6d1fa337c2 | ||
|
|
b51f82c73f | ||
|
|
e19f7fbaf7 | ||
|
|
955d6c82ff | ||
|
|
5fbb4fd8c9 | ||
|
|
9407a3ee09 | ||
|
|
86bb866ffb | ||
|
|
2b22f02354 | ||
|
|
c3ee7b4bd1 | ||
|
|
d788d817ee | ||
|
|
6fbd50ab37 | ||
|
|
097fd4c206 | ||
|
|
844d104d06 | ||
|
|
b62cef5a01 | ||
|
|
4cdb2c4bd4 | ||
|
|
bc655b8eaf | ||
|
|
4891e92f32 | ||
|
|
62b88b2a0b | ||
|
|
2cbfd19d94 | ||
|
|
da132e0289 | ||
|
|
622b79cd9d | ||
|
|
2e91d6e10c | ||
|
|
4a495542cb | ||
|
|
45813a0342 | ||
|
|
b55362b588 | ||
|
|
b39b6cde8c | ||
|
|
6919520c88 | ||
|
|
71fcb8d8f8 | ||
|
|
ee23788acf | ||
|
|
52196791b9 | ||
|
|
6dbf0c52ec | ||
|
|
5d95688753 | ||
|
|
6e60fa6d59 | ||
|
|
d072d408c0 | ||
|
|
cde7f04409 | ||
|
|
e143f654ca | ||
|
|
cc62c4a33a | ||
|
|
c3e6746fa9 | ||
|
|
436c3ba644 | ||
|
|
599babae00 | ||
|
|
5f7da14447 | ||
|
|
dc6f951f43 | ||
|
|
f4fa6c0cf4 | ||
|
|
98de50451a | ||
|
|
c78ab7a1f2 | ||
|
|
0928f322fa | ||
|
|
c6c59bf0b3 | ||
|
|
9d3bcfc559 | ||
|
|
fb2473bb5e | ||
|
|
4664b5da57 | ||
|
|
18c5401d08 | ||
|
|
3f81697e48 | ||
|
|
a00a43ba25 | ||
|
|
ce1435be30 | ||
|
|
c625b77bde | ||
|
|
4969c4ab19 | ||
|
|
9e1736f5bc | ||
|
|
200e26c602 | ||
|
|
bbc4545742 | ||
|
|
9874e940ab | ||
|
|
304453fa5c | ||
|
|
16a32a30d9 | ||
|
|
a227474809 | ||
|
|
3a2055ebba | ||
|
|
87042e1493 | ||
|
|
86f3aa0376 | ||
|
|
33eb6263c0 | ||
|
|
05d5ee4486 | ||
|
|
98845392d8 | ||
|
|
946baab086 | ||
|
|
e3aeba3581 | ||
|
|
e03c11598e | ||
|
|
3b1c9dfc54 | ||
|
|
2724456dc7 | ||
|
|
9333b091f5 | ||
|
|
cb12c4e392 | ||
|
|
1d38e14e92 | ||
|
|
9f39865999 | ||
|
|
2d5b45659a | ||
|
|
dab8ec7711 | ||
|
|
8f81c73908 | ||
|
|
79580b1809 | ||
|
|
835c4a1bb4 | ||
|
|
81f773eb6f | ||
|
|
2708f66627 | ||
|
|
190085141c | ||
|
|
4111f2b3a7 | ||
|
|
f894f8a92d | ||
|
|
439a295776 | ||
|
|
99868688f1 | ||
|
|
3dc92be74c | ||
|
|
a9e141305b | ||
|
|
4fb3b585f9 | ||
|
|
ceb65338a5 | ||
|
|
30a2a0bf8d | ||
|
|
1401789817 | ||
|
|
a20f19bc8f | ||
|
|
87f07534b1 | ||
|
|
326de41302 | ||
|
|
53fee846d4 | ||
|
|
2c406ff566 | ||
|
|
a2ae0e16b5 | ||
|
|
b69d7af656 | ||
|
|
4b4bd037e1 | ||
|
|
ebc21d751a | ||
|
|
ea17543800 | ||
|
|
2bc618e7ac | ||
|
|
ba8f4660fd | ||
|
|
b19ccb708e | ||
|
|
24b3ee8444 | ||
|
|
941ab704e1 | ||
|
|
9d9c7a2f70 | ||
|
|
e406ebd472 | ||
|
|
e44b02b9ad | ||
|
|
b26760bed5 | ||
|
|
33fd679843 | ||
|
|
87399cc4f4 | ||
|
|
33631b44f8 | ||
|
|
10a8752e70 | ||
|
|
12c54ab670 | ||
|
|
1ca52e9b93 | ||
|
|
b444944a6d | ||
|
|
766a58e465 | ||
|
|
8130613364 | ||
|
|
e1859a3657 | ||
|
|
daf533dabb | ||
|
|
6cf9e12dd4 | ||
|
|
0b5c95abdf | ||
|
|
84df964d2f | ||
|
|
6781a2c6e0 | ||
|
|
5dce02a3bb | ||
|
|
9aafee9cf0 | ||
|
|
5df3e7e8fb | ||
|
|
889be2327f | ||
|
|
68e87ea4f5 | ||
|
|
d3462596fa | ||
|
|
f3fd8492cf | ||
|
|
a3ca79f883 | ||
|
|
e2aaf5d18a | ||
|
|
e49b33cad8 | ||
|
|
5ac3192dd0 | ||
|
|
ea63f2ea98 | ||
|
|
73b280d6b4 | ||
|
|
2de6939db0 | ||
|
|
022620f4cd | ||
|
|
d1894977de | ||
|
|
6765eec7a9 | ||
|
|
d462204dce | ||
|
|
52d9bf41e6 | ||
|
|
0cd714a3bb | ||
|
|
1228c69e1d | ||
|
|
c09b22bf85 | ||
|
|
65d83f2054 | ||
|
|
6fa95afd71 | ||
|
|
d58211304b | ||
|
|
760cb4ab50 | ||
|
|
4acdb97543 | ||
|
|
011a874021 | ||
|
|
bac728a134 | ||
|
|
9d6c37ac88 | ||
|
|
25fc9acec6 | ||
|
|
edb65a07f1 | ||
|
|
82b5fbc396 |
@@ -322,10 +322,6 @@ step-setup-goma-for-build: &step-setup-goma-for-build
|
||||
node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })"
|
||||
export GOMA_FALLBACK_ON_AUTH_FAILURE=true
|
||||
third_party/goma/goma_ctl.py ensure_start
|
||||
if [ ! -z "$RAW_GOMA_AUTH" ] && [ "`third_party/goma/goma_auth.py info`" != "Login as Fermi Planck" ]; then
|
||||
echo "WARNING!!!!!! Goma authentication is incorrect; please update Goma auth token."
|
||||
exit 1
|
||||
fi
|
||||
echo 'export GN_GOMA_FILE='`node -e "console.log(require('./src/utils/goma.js').gnFilePath)"` >> $BASH_ENV
|
||||
echo 'export LOCAL_GOMA_DIR='`node -e "console.log(require('./src/utils/goma.js').dir)"` >> $BASH_ENV
|
||||
echo 'export GOMA_FALLBACK_ON_AUTH_FAILURE=true' >> $BASH_ENV
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# Electron Dev on Codespaces
|
||||
|
||||
Welcome to the Codespaces Electron Developer Environment.
|
||||
|
||||
## Quick Start
|
||||
|
||||
Upon creation of your codespace you should have [build tools](https://github.com/electron/build-tools) installed and an initialized gclient checkout of Electron. In order to build electron you'll need to run the following commands.
|
||||
|
||||
```bash
|
||||
e sync -vv
|
||||
e build
|
||||
```
|
||||
|
||||
The initial sync will take approximately ~30 minutes and the build will take ~8 minutes. Incremental syncs and incremental builds are substantially quicker.
|
||||
|
||||
## Directory Structure
|
||||
|
||||
Codespaces doesn't lean very well into gclient based checkouts, the directory structure is slightly strange. There are two locations for the `electron` checkout that both map to the same files under the hood.
|
||||
|
||||
```graphql
|
||||
# Primary gclient checkout container
|
||||
/workspaces/gclient/*
|
||||
└─ src/* - # Chromium checkout
|
||||
└─ electron - # Electron checkout
|
||||
# Symlinked Electron checkout (identical to the above)
|
||||
/workspaces/electron
|
||||
```
|
||||
|
||||
## Goma
|
||||
|
||||
If you are a maintainer [with Goma access](../docs/development/goma.md) it should be automatically configured and authenticated when you spin up a new codespaces instance. You can validate this by checking `e d goma_auth info` or by checking that your build-tools configuration has a goma mode of `cluster`.
|
||||
|
||||
## Running Electron
|
||||
|
||||
You can run Electron in a few ways. If you just want to see if it launches:
|
||||
|
||||
```bash
|
||||
# Enter an interactive JS prompt headlessly
|
||||
xvfb-run e start -i
|
||||
```
|
||||
|
||||
But if you want to actually see Electron you will need to use the built-in VNC capability. If you click "Ports" in codespaces and then open the `VNC web client` forwarded port you should see a web based VNC portal in your browser. When you are asked for a password use `builduser`.
|
||||
|
||||
Once in the VNC UI you can open `Applications -> System -> XTerm` which will open a VNC based terminal app and then you can run `e start` like normal and Electron will open in your VNC session.
|
||||
|
||||
## Running Tests
|
||||
|
||||
You run tests via build-tools and `xvfb`.
|
||||
|
||||
```bash
|
||||
# Run all tests
|
||||
xvfb-run e test
|
||||
|
||||
# Run the main process tests
|
||||
xvfb-run e test --runners=main
|
||||
|
||||
# Run the old remote tests
|
||||
xvfb-run e test --runners=remote
|
||||
```
|
||||
@@ -1,43 +0,0 @@
|
||||
{
|
||||
"dockerComposeFile": "docker-compose.yml",
|
||||
"service": "buildtools",
|
||||
"onCreateCommand": ".devcontainer/on-create-command.sh",
|
||||
"workspaceFolder": "/workspaces/gclient/src/electron",
|
||||
"extensions": [
|
||||
"joeleinbinder.mojom-language",
|
||||
"rafaelmaiolla.diff",
|
||||
"surajbarkale.ninja",
|
||||
"ms-vscode.cpptools",
|
||||
"mutantdino.resourcemonitor",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"shakram02.bash-beautify",
|
||||
"marshallofsound.gnls-electron"
|
||||
],
|
||||
"settings": {
|
||||
"[gn]": {
|
||||
"editor.formatOnSave": true
|
||||
},
|
||||
"editor.tabSize": 2,
|
||||
"bashBeautify.tabSize": 2
|
||||
},
|
||||
"forwardPorts": [8088, 6080, 5901],
|
||||
"portsAttributes": {
|
||||
"8088": {
|
||||
"label": "Goma Control Panel",
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
"6080": {
|
||||
"label": "VNC web client (noVNC)",
|
||||
"onAutoForward": "silent"
|
||||
},
|
||||
"5901": {
|
||||
"label": "VNC TCP port",
|
||||
"onAutoForward": "silent"
|
||||
}
|
||||
},
|
||||
"hostRequirements": {
|
||||
"storage": "32gb",
|
||||
"cpus": 8
|
||||
},
|
||||
"remoteUser": "builduser"
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
version: '3'
|
||||
|
||||
services:
|
||||
buildtools:
|
||||
image: ghcr.io/electron/devcontainer:27db4a3e3512bfd2e47f58cea69922da0835f1d9
|
||||
|
||||
volumes:
|
||||
- ..:/workspaces/gclient/src/electron:cached
|
||||
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
command: /bin/sh -c "while sleep 1000; do :; done"
|
||||
|
||||
user: builduser
|
||||
|
||||
cap_add:
|
||||
- SYS_PTRACE
|
||||
security_opt:
|
||||
- seccomp:unconfined
|
||||
@@ -1,74 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
set -eo pipefail
|
||||
|
||||
buildtools=$HOME/.electron_build_tools
|
||||
gclient_root=/workspaces/gclient
|
||||
buildtools_configs=/workspaces/buildtools-configs
|
||||
|
||||
export PATH="$PATH:$buildtools/src"
|
||||
|
||||
# Create the persisted buildtools config folder
|
||||
mkdir -p $buildtools_configs
|
||||
rm -f $buildtools/configs
|
||||
ln -s $buildtools_configs $buildtools/configs
|
||||
|
||||
# Write the gclient config if it does not already exist
|
||||
if [ ! -f $gclient_root/.gclient ]; then
|
||||
echo "solutions = [
|
||||
{ \"name\" : \"src/electron\",
|
||||
\"url\" : \"https://github.com/electron/electron\",
|
||||
\"deps_file\" : \"DEPS\",
|
||||
\"managed\" : False,
|
||||
\"custom_deps\" : {
|
||||
},
|
||||
\"custom_vars\": {},
|
||||
},
|
||||
]
|
||||
" >$gclient_root/.gclient
|
||||
fi
|
||||
|
||||
# Write the default buildtools config file if it does
|
||||
# not already exist
|
||||
if [ ! -f $buildtools/configs/evm.testing.json ]; then
|
||||
write_config() {
|
||||
echo "
|
||||
{
|
||||
\"root\": \"/workspaces/gclient\",
|
||||
\"goma\": \"$1\",
|
||||
\"gen\": {
|
||||
\"args\": [
|
||||
\"import(\\\"//electron/build/args/testing.gn\\\")\",
|
||||
\"import(\\\"/home/builduser/.electron_build_tools/third_party/goma.gn\\\")\"
|
||||
],
|
||||
\"out\": \"Testing\"
|
||||
},
|
||||
\"env\": {
|
||||
\"CHROMIUM_BUILDTOOLS_PATH\": \"/workspaces/gclient/src/buildtools\",
|
||||
\"GIT_CACHE_PATH\": \"/workspaces/gclient/.git-cache\"
|
||||
},
|
||||
\"remotes\": {
|
||||
\"electron\": {
|
||||
\"origin\": \"https://github.com/electron/electron.git\"
|
||||
}
|
||||
}
|
||||
}
|
||||
" >$buildtools/configs/evm.testing.json
|
||||
}
|
||||
|
||||
# Start out as cache only
|
||||
write_config cache-only
|
||||
|
||||
e use testing
|
||||
|
||||
# Attempt to auth to the goma service via codespaces tokens
|
||||
# if it works we can use the goma cluster
|
||||
export NOTGOMA_CODESPACES_TOKEN=$GITHUB_TOKEN
|
||||
if e d goma_auth login; then
|
||||
write_config cluster
|
||||
fi
|
||||
else
|
||||
# Even if the config file existed we still need to re-auth with the goma
|
||||
# cluster
|
||||
NOTGOMA_CODESPACES_TOKEN=$GITHUB_TOKEN e d goma_auth login || true
|
||||
fi
|
||||
3
.github/CODEOWNERS
vendored
3
.github/CODEOWNERS
vendored
@@ -4,7 +4,7 @@
|
||||
# https://git-scm.com/docs/gitignore
|
||||
|
||||
# Upgrades WG
|
||||
/patches/ @electron/wg-upgrades @electron/wg-security
|
||||
/patches/ @electron/wg-upgrades
|
||||
DEPS @electron/wg-upgrades
|
||||
|
||||
# Releases WG
|
||||
@@ -16,4 +16,3 @@ DEPS @electron/wg-upgrades
|
||||
/lib/browser/guest-view-manager.ts @electron/wg-security
|
||||
/lib/browser/guest-window-proxy.ts @electron/wg-security
|
||||
/lib/browser/rpc-server.ts @electron/wg-security
|
||||
/lib/renderer/security-warnings.ts @electron/wg-security
|
||||
|
||||
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
2
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -37,4 +37,4 @@ body:
|
||||
label: Additional Information
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
|
||||
30
BUILD.gn
30
BUILD.gn
@@ -313,23 +313,6 @@ action("electron_fuses") {
|
||||
args = rebase_path(outputs)
|
||||
}
|
||||
|
||||
action("electron_generate_node_defines") {
|
||||
script = "build/generate_node_defines.py"
|
||||
|
||||
inputs = [
|
||||
"//third_party/electron_node/src/tracing/trace_event_common.h",
|
||||
"//third_party/electron_node/src/tracing/trace_event.h",
|
||||
"//third_party/electron_node/src/util.h",
|
||||
]
|
||||
|
||||
outputs = [
|
||||
"$target_gen_dir/push_and_undef_node_defines.h",
|
||||
"$target_gen_dir/pop_node_defines.h",
|
||||
]
|
||||
|
||||
args = [ rebase_path(target_gen_dir) ] + rebase_path(inputs)
|
||||
}
|
||||
|
||||
source_set("electron_lib") {
|
||||
configs += [ "//v8:external_startup_data" ]
|
||||
configs += [ "//third_party/electron_node:node_internals" ]
|
||||
@@ -341,7 +324,6 @@ source_set("electron_lib") {
|
||||
|
||||
deps = [
|
||||
":electron_fuses",
|
||||
":electron_generate_node_defines",
|
||||
":electron_js2c",
|
||||
":electron_version_header",
|
||||
":resources",
|
||||
@@ -353,7 +335,6 @@ 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",
|
||||
@@ -420,6 +401,7 @@ source_set("electron_lib") {
|
||||
]
|
||||
|
||||
include_dirs = [
|
||||
"chromium_src",
|
||||
".",
|
||||
"$target_gen_dir",
|
||||
|
||||
@@ -553,9 +535,8 @@ 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",
|
||||
@@ -1386,13 +1367,11 @@ 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" ]
|
||||
}
|
||||
|
||||
@@ -1410,7 +1389,6 @@ group("electron_chromedriver") {
|
||||
dist_zip("electron_chromedriver_zip") {
|
||||
testonly = true
|
||||
data_deps = electron_chromedriver_deps
|
||||
deps = data_deps
|
||||
outputs = [ "$root_build_dir/chromedriver.zip" ]
|
||||
}
|
||||
|
||||
@@ -1429,7 +1407,6 @@ group("electron_mksnapshot") {
|
||||
|
||||
dist_zip("electron_mksnapshot_zip") {
|
||||
data_deps = mksnapshot_deps
|
||||
deps = data_deps
|
||||
outputs = [ "$root_build_dir/mksnapshot.zip" ]
|
||||
}
|
||||
|
||||
@@ -1440,7 +1417,6 @@ copy("hunspell_dictionaries") {
|
||||
|
||||
dist_zip("hunspell_dictionaries_zip") {
|
||||
data_deps = [ ":hunspell_dictionaries" ]
|
||||
deps = data_deps
|
||||
flatten = true
|
||||
|
||||
outputs = [ "$root_build_dir/hunspell_dictionaries.zip" ]
|
||||
@@ -1454,7 +1430,6 @@ 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",
|
||||
@@ -1470,7 +1445,6 @@ 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",
|
||||
|
||||
4
DEPS
4
DEPS
@@ -15,9 +15,9 @@ gclient_gn_args = [
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'96.0.4664.174',
|
||||
'94.0.4606.81',
|
||||
'node_version':
|
||||
'v16.9.1',
|
||||
'v16.5.0',
|
||||
'nan_version':
|
||||
# The following commit hash of NAN is v2.14.2 with *only* changes to the
|
||||
# test suite. This should be updated to a specific tag when one becomes
|
||||
|
||||
@@ -1 +1 @@
|
||||
16.2.2
|
||||
15.3.6
|
||||
@@ -60,6 +60,7 @@ npm start
|
||||
- [electronjs.org/community#boilerplates](https://electronjs.org/community#boilerplates) - Sample starter apps created by the community
|
||||
- [electron/simple-samples](https://github.com/electron/simple-samples) - Small applications with ideas for taking them further
|
||||
- [electron/electron-api-demos](https://github.com/electron/electron-api-demos) - An Electron app that teaches you how to use Electron
|
||||
- [hokein/electron-sample-apps](https://github.com/hokein/electron-sample-apps) - Small demo apps for the various Electron APIs
|
||||
|
||||
## Programmatic usage
|
||||
|
||||
|
||||
42
appveyor.yml
42
appveyor.yml
@@ -66,31 +66,6 @@ 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') {
|
||||
@@ -137,7 +112,7 @@ build_script:
|
||||
}
|
||||
}
|
||||
}
|
||||
- if "%RUN_GCLIENT_SYNC%"=="true" ( gclient sync )
|
||||
- if "%RUN_GCLIENT_SYNC%"=="true" ( gclient sync --with_branch_heads --with_tags --ignore_locks)
|
||||
- ps: >-
|
||||
if ($env:SAVE_GCLIENT_SRC -eq 'true') {
|
||||
# archive current source for future use
|
||||
@@ -154,6 +129,21 @@ 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% "
|
||||
|
||||
@@ -2,7 +2,7 @@ is_electron_build = true
|
||||
root_extra_deps = [ "//electron" ]
|
||||
|
||||
# Registry of NMVs --> https://github.com/nodejs/node/blob/master/doc/abi_version_registry.json
|
||||
node_module_version = 99
|
||||
node_module_version = 98
|
||||
|
||||
v8_promise_internal_field_count = 1
|
||||
v8_typed_array_max_size_in_heap = 0
|
||||
|
||||
@@ -1,34 +0,0 @@
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
|
||||
DEFINE_EXTRACT_REGEX = re.compile('^ *# *define (\w*)', re.MULTILINE)
|
||||
|
||||
def main(outDir, headers):
|
||||
defines = []
|
||||
for filename in headers:
|
||||
with open(filename, 'r') as f:
|
||||
content = f.read()
|
||||
defines += read_defines(content)
|
||||
|
||||
push_and_undef = ''
|
||||
for define in defines:
|
||||
push_and_undef += '#pragma push_macro("%s")\n' % define
|
||||
push_and_undef += '#undef %s\n' % define
|
||||
with open(os.path.join(outDir, 'push_and_undef_node_defines.h'), 'w') as o:
|
||||
o.write(push_and_undef)
|
||||
|
||||
pop = ''
|
||||
for define in defines:
|
||||
pop += '#pragma pop_macro("%s")\n' % define
|
||||
with open(os.path.join(outDir, 'pop_node_defines.h'), 'w') as o:
|
||||
o.write(pop)
|
||||
|
||||
def read_defines(content):
|
||||
defines = []
|
||||
for match in DEFINE_EXTRACT_REGEX.finditer(content):
|
||||
defines.append(match.group(1))
|
||||
return defines
|
||||
|
||||
if __name__ == '__main__':
|
||||
main(sys.argv[1], sys.argv[2:])
|
||||
@@ -5,8 +5,8 @@ template("npm_action") {
|
||||
|
||||
action("npm_pre_flight_" + target_name) {
|
||||
inputs = [
|
||||
"//electron/package.json",
|
||||
"//electron/yarn.lock",
|
||||
"package.json",
|
||||
"yarn.lock",
|
||||
]
|
||||
script = "//electron/build/npm-run.py"
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from __future__ import with_statement
|
||||
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__)))
|
||||
|
||||
@@ -36,56 +33,36 @@ def calculate_hash(root):
|
||||
return CalculateHash('.', None)
|
||||
|
||||
def windows_installed_software():
|
||||
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",
|
||||
]
|
||||
import win32com.client
|
||||
strComputer = "."
|
||||
objWMIService = win32com.client.Dispatch("WbemScripting.SWbemLocator")
|
||||
objSWbemServices = objWMIService.ConnectServer(strComputer, "root\cimv2")
|
||||
colItems = objSWbemServices.ExecQuery("Select * from Win32_Product")
|
||||
items = []
|
||||
|
||||
proc = subprocess.Popen(
|
||||
["powershell.exe", "-Command", "-"],
|
||||
stdin=subprocess.PIPE,
|
||||
stdout=subprocess.PIPE,
|
||||
)
|
||||
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)
|
||||
|
||||
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)),
|
||||
)
|
||||
)
|
||||
return items
|
||||
|
||||
|
||||
def windows_profile():
|
||||
@@ -112,7 +89,7 @@ def windows_profile():
|
||||
|
||||
def main(options):
|
||||
if sys.platform == 'win32':
|
||||
with open(options.output_json, 'w') as f:
|
||||
with open(options.output_json, 'wb') as f:
|
||||
json.dump(windows_profile(), f)
|
||||
else:
|
||||
raise OSError("Unsupported OS")
|
||||
|
||||
@@ -36,7 +36,7 @@ template("webpack_build") {
|
||||
rebase_path("$target_gen_dir/buildflags/buildflags.h"),
|
||||
"--env.mode=" + mode,
|
||||
]
|
||||
deps += [ "//electron/buildflags" ]
|
||||
deps += [ "buildflags" ]
|
||||
|
||||
outputs = [ invoker.out_file ]
|
||||
}
|
||||
|
||||
@@ -15,10 +15,6 @@ 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",
|
||||
@@ -29,7 +25,6 @@ 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",
|
||||
@@ -52,23 +47,6 @@ static_library("chrome") {
|
||||
"//chrome/browser/predictors/proxy_lookup_client_impl.h",
|
||||
"//chrome/browser/predictors/resolve_host_client_impl.cc",
|
||||
"//chrome/browser/predictors/resolve_host_client_impl.h",
|
||||
"//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",
|
||||
@@ -79,10 +57,6 @@ static_library("chrome") {
|
||||
"//extensions/browser/app_window/size_constraints.h",
|
||||
]
|
||||
|
||||
if (is_posix) {
|
||||
sources += [ "//chrome/browser/process_singleton_posix.cc" ]
|
||||
}
|
||||
|
||||
if (is_mac) {
|
||||
sources += [
|
||||
"//chrome/browser/extensions/global_shortcut_listener_mac.h",
|
||||
@@ -91,7 +65,6 @@ static_library("chrome") {
|
||||
"//chrome/browser/media/webrtc/system_media_capture_permissions_mac.h",
|
||||
"//chrome/browser/media/webrtc/system_media_capture_permissions_mac.mm",
|
||||
"//chrome/browser/media/webrtc/window_icon_util_mac.mm",
|
||||
"//chrome/browser/process_singleton_mac.mm",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.h",
|
||||
"//chrome/browser/ui/views/eye_dropper/eye_dropper_view_mac.mm",
|
||||
]
|
||||
@@ -103,7 +76,6 @@ static_library("chrome") {
|
||||
"//chrome/browser/extensions/global_shortcut_listener_win.h",
|
||||
"//chrome/browser/icon_loader_win.cc",
|
||||
"//chrome/browser/media/webrtc/window_icon_util_win.cc",
|
||||
"//chrome/browser/process_singleton_win.cc",
|
||||
"//chrome/browser/ui/frame/window_frame_util.h",
|
||||
"//chrome/browser/ui/view_ids.h",
|
||||
"//chrome/browser/win/chrome_process_finder.cc",
|
||||
@@ -115,7 +87,7 @@ static_library("chrome") {
|
||||
}
|
||||
|
||||
if (is_linux) {
|
||||
sources += [ "//chrome/browser/media/webrtc/window_icon_util_ozone.cc" ]
|
||||
sources += [ "//chrome/browser/media/webrtc/window_icon_util_linux.cc" ]
|
||||
}
|
||||
|
||||
if (use_aura) {
|
||||
@@ -132,13 +104,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",
|
||||
]
|
||||
|
||||
@@ -148,6 +120,16 @@ 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 += [
|
||||
@@ -177,7 +159,6 @@ 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",
|
||||
@@ -215,13 +196,6 @@ 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",
|
||||
@@ -229,7 +203,6 @@ static_library("chrome") {
|
||||
"//components/services/print_compositor",
|
||||
"//components/services/print_compositor/public/cpp",
|
||||
"//components/services/print_compositor/public/mojom",
|
||||
"//printing/backend",
|
||||
]
|
||||
|
||||
deps += [
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/certificate_manager_model.h"
|
||||
#include "chrome/browser/certificate_manager_model.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
|
||||
#define SHELL_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
|
||||
#ifndef CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
|
||||
#define CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
@@ -112,4 +112,4 @@ class CertificateManagerModel {
|
||||
DISALLOW_COPY_AND_ASSIGN(CertificateManagerModel);
|
||||
};
|
||||
|
||||
#endif // SHELL_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
|
||||
#endif // CHROME_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
|
||||
185
chromium_src/chrome/browser/process_singleton.h
Normal file
185
chromium_src/chrome/browser/process_singleton.h
Normal file
@@ -0,0 +1,185 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef CHROME_BROWSER_PROCESS_SINGLETON_H_
|
||||
#define CHROME_BROWSER_PROCESS_SINGLETON_H_
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/sequence_checker.h"
|
||||
#include "ui/gfx/native_widget_types.h"
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
#include "base/files/scoped_temp_dir.h"
|
||||
#endif
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include "base/win/message_window.h"
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
namespace base {
|
||||
class CommandLine;
|
||||
}
|
||||
|
||||
// ProcessSingleton ----------------------------------------------------------
|
||||
//
|
||||
// This class allows different browser processes to communicate with
|
||||
// each other. It is named according to the user data directory, so
|
||||
// we can be sure that no more than one copy of the application can be
|
||||
// running at once with a given data directory.
|
||||
//
|
||||
// Implementation notes:
|
||||
// - the Windows implementation uses an invisible global message window;
|
||||
// - the Linux implementation uses a Unix domain socket in the user data dir.
|
||||
|
||||
class ProcessSingleton {
|
||||
public:
|
||||
enum NotifyResult {
|
||||
PROCESS_NONE,
|
||||
PROCESS_NOTIFIED,
|
||||
PROFILE_IN_USE,
|
||||
LOCK_ERROR,
|
||||
};
|
||||
|
||||
// Implement this callback to handle notifications from other processes. The
|
||||
// callback will receive the command line and directory with which the other
|
||||
// Chrome process was launched. Return true if the command line will be
|
||||
// handled within the current browser instance or false if the remote process
|
||||
// should handle it (i.e., because the current process is shutting down).
|
||||
using NotificationCallback = base::RepeatingCallback<bool(
|
||||
const base::CommandLine::StringVector& command_line,
|
||||
const base::FilePath& current_directory)>;
|
||||
|
||||
ProcessSingleton(const base::FilePath& user_data_dir,
|
||||
const NotificationCallback& notification_callback);
|
||||
~ProcessSingleton();
|
||||
|
||||
// Notify another process, if available. Otherwise sets ourselves as the
|
||||
// singleton instance. Returns PROCESS_NONE if we became the singleton
|
||||
// instance. Callers are guaranteed to either have notified an existing
|
||||
// process or have grabbed the singleton (unless the profile is locked by an
|
||||
// unreachable process).
|
||||
// TODO(brettw): Make the implementation of this method non-platform-specific
|
||||
// by making Linux re-use the Windows implementation.
|
||||
NotifyResult NotifyOtherProcessOrCreate();
|
||||
void StartListeningOnSocket();
|
||||
void OnBrowserReady();
|
||||
|
||||
// Sets ourself up as the singleton instance. Returns true on success. If
|
||||
// false is returned, we are not the singleton instance and the caller must
|
||||
// exit.
|
||||
// NOTE: Most callers should generally prefer NotifyOtherProcessOrCreate() to
|
||||
// this method, only callers for whom failure is preferred to notifying
|
||||
// another process should call this directly.
|
||||
bool Create();
|
||||
|
||||
// Clear any lock state during shutdown.
|
||||
void Cleanup();
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
static void DisablePromptForTesting();
|
||||
#endif
|
||||
#if defined(OS_WIN)
|
||||
// Called to query whether to kill a hung browser process that has visible
|
||||
// windows. Return true to allow killing the hung process.
|
||||
using ShouldKillRemoteProcessCallback = base::RepeatingCallback<bool()>;
|
||||
void OverrideShouldKillRemoteProcessCallbackForTesting(
|
||||
const ShouldKillRemoteProcessCallback& display_dialog_callback);
|
||||
#endif
|
||||
|
||||
protected:
|
||||
// Notify another process, if available.
|
||||
// Returns true if another process was found and notified, false if we should
|
||||
// continue with the current process.
|
||||
// On Windows, Create() has to be called before this.
|
||||
NotifyResult NotifyOtherProcess();
|
||||
|
||||
#if defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
// Exposed for testing. We use a timeout on Linux, and in tests we want
|
||||
// this timeout to be short.
|
||||
NotifyResult NotifyOtherProcessWithTimeout(
|
||||
const base::CommandLine& command_line,
|
||||
int retry_attempts,
|
||||
const base::TimeDelta& timeout,
|
||||
bool kill_unresponsive);
|
||||
NotifyResult NotifyOtherProcessWithTimeoutOrCreate(
|
||||
const base::CommandLine& command_line,
|
||||
int retry_attempts,
|
||||
const base::TimeDelta& timeout);
|
||||
void OverrideCurrentPidForTesting(base::ProcessId pid);
|
||||
void OverrideKillCallbackForTesting(
|
||||
const base::RepeatingCallback<void(int)>& callback);
|
||||
#endif
|
||||
|
||||
private:
|
||||
NotificationCallback notification_callback_; // Handler for notifications.
|
||||
|
||||
#if defined(OS_WIN)
|
||||
HWND remote_window_ = nullptr; // The HWND_MESSAGE of another browser.
|
||||
base::win::MessageWindow window_; // The message-only window.
|
||||
bool is_virtualized_ =
|
||||
false; // Stuck inside Microsoft Softricity VM environment.
|
||||
HANDLE lock_file_ = INVALID_HANDLE_VALUE;
|
||||
base::FilePath user_data_dir_;
|
||||
ShouldKillRemoteProcessCallback should_kill_remote_process_callback_;
|
||||
#elif defined(OS_POSIX) && !defined(OS_ANDROID)
|
||||
// Start listening to the socket.
|
||||
void StartListening(int sock);
|
||||
|
||||
// Return true if the given pid is one of our child processes.
|
||||
// Assumes that the current pid is the root of all pids of the current
|
||||
// instance.
|
||||
bool IsSameChromeInstance(pid_t pid);
|
||||
|
||||
// Extract the process's pid from a symbol link path and if it is on
|
||||
// the same host, kill the process, unlink the lock file and return true.
|
||||
// If the process is part of the same chrome instance, unlink the lock file
|
||||
// and return true without killing it.
|
||||
// If the process is on a different host, return false.
|
||||
bool KillProcessByLockPath();
|
||||
|
||||
// Default function to kill a process, overridable by tests.
|
||||
void KillProcess(int pid);
|
||||
|
||||
// Allow overriding for tests.
|
||||
base::ProcessId current_pid_;
|
||||
|
||||
// Function to call when the other process is hung and needs to be killed.
|
||||
// Allows overriding for tests.
|
||||
base::RepeatingCallback<void(int)> kill_callback_;
|
||||
|
||||
// Path in file system to the socket.
|
||||
base::FilePath socket_path_;
|
||||
|
||||
// Path in file system to the lock.
|
||||
base::FilePath lock_path_;
|
||||
|
||||
// Path in file system to the cookie file.
|
||||
base::FilePath cookie_path_;
|
||||
|
||||
// Temporary directory to hold the socket.
|
||||
base::ScopedTempDir socket_dir_;
|
||||
|
||||
// Helper class for linux specific messages. LinuxWatcher is ref counted
|
||||
// because it posts messages between threads.
|
||||
class LinuxWatcher;
|
||||
scoped_refptr<LinuxWatcher> watcher_;
|
||||
int sock_ = -1;
|
||||
bool listen_on_ready_ = false;
|
||||
#endif
|
||||
|
||||
SEQUENCE_CHECKER(sequence_checker_);
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProcessSingleton);
|
||||
};
|
||||
|
||||
#endif // CHROME_BROWSER_PROCESS_SINGLETON_H_
|
||||
1100
chromium_src/chrome/browser/process_singleton_posix.cc
Normal file
1100
chromium_src/chrome/browser/process_singleton_posix.cc
Normal file
File diff suppressed because it is too large
Load Diff
315
chromium_src/chrome/browser/process_singleton_win.cc
Normal file
315
chromium_src/chrome/browser/process_singleton_win.cc
Normal file
@@ -0,0 +1,315 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "chrome/browser/process_singleton.h"
|
||||
|
||||
#include <windows.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include "base/base_paths.h"
|
||||
#include "base/bind.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/process/process.h"
|
||||
#include "base/process/process_info.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/stringprintf.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/win/registry.h"
|
||||
#include "base/win/scoped_handle.h"
|
||||
#include "base/win/windows_version.h"
|
||||
#include "chrome/browser/win/chrome_process_finder.h"
|
||||
#include "content/public/common/result_codes.h"
|
||||
#include "net/base/escape.h"
|
||||
#include "ui/gfx/win/hwnd_util.h"
|
||||
|
||||
namespace {
|
||||
|
||||
const char kLockfile[] = "lockfile";
|
||||
|
||||
// A helper class that acquires the given |mutex| while the AutoLockMutex is in
|
||||
// scope.
|
||||
class AutoLockMutex {
|
||||
public:
|
||||
explicit AutoLockMutex(HANDLE mutex) : mutex_(mutex) {
|
||||
DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
|
||||
DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
|
||||
}
|
||||
|
||||
~AutoLockMutex() {
|
||||
BOOL released = ::ReleaseMutex(mutex_);
|
||||
DPCHECK(released);
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE mutex_;
|
||||
DISALLOW_COPY_AND_ASSIGN(AutoLockMutex);
|
||||
};
|
||||
|
||||
// A helper class that releases the given |mutex| while the AutoUnlockMutex is
|
||||
// in scope and immediately re-acquires it when going out of scope.
|
||||
class AutoUnlockMutex {
|
||||
public:
|
||||
explicit AutoUnlockMutex(HANDLE mutex) : mutex_(mutex) {
|
||||
BOOL released = ::ReleaseMutex(mutex_);
|
||||
DPCHECK(released);
|
||||
}
|
||||
|
||||
~AutoUnlockMutex() {
|
||||
DWORD result = ::WaitForSingleObject(mutex_, INFINITE);
|
||||
DPCHECK(result == WAIT_OBJECT_0) << "Result = " << result;
|
||||
}
|
||||
|
||||
private:
|
||||
HANDLE mutex_;
|
||||
DISALLOW_COPY_AND_ASSIGN(AutoUnlockMutex);
|
||||
};
|
||||
|
||||
// Checks the visibility of the enumerated window and signals once a visible
|
||||
// window has been found.
|
||||
BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
||||
bool* result = reinterpret_cast<bool*>(param);
|
||||
*result = ::IsWindowVisible(window) != 0;
|
||||
// Stops enumeration if a visible window has been found.
|
||||
return !*result;
|
||||
}
|
||||
|
||||
bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
base::CommandLine::StringVector* parsed_command_line,
|
||||
base::FilePath* current_directory) {
|
||||
// We should have enough room for the shortest command (min_message_size)
|
||||
// and also be a multiple of wchar_t bytes. The shortest command
|
||||
// possible is L"START\0\0" (empty current directory and command line).
|
||||
static const int min_message_size = 7;
|
||||
if (cds->cbData < min_message_size * sizeof(wchar_t) ||
|
||||
cds->cbData % sizeof(wchar_t) != 0) {
|
||||
LOG(WARNING) << "Invalid WM_COPYDATA, length = " << cds->cbData;
|
||||
return false;
|
||||
}
|
||||
|
||||
// We split the string into 4 parts on NULLs.
|
||||
DCHECK(cds->lpData);
|
||||
const std::wstring msg(static_cast<wchar_t*>(cds->lpData),
|
||||
cds->cbData / sizeof(wchar_t));
|
||||
const std::wstring::size_type first_null = msg.find_first_of(L'\0');
|
||||
if (first_null == 0 || first_null == std::wstring::npos) {
|
||||
// no NULL byte, don't know what to do
|
||||
LOG(WARNING) << "Invalid WM_COPYDATA, length = " << msg.length()
|
||||
<< ", first null = " << first_null;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Decode the command, which is everything until the first NULL.
|
||||
if (msg.substr(0, first_null) == L"START") {
|
||||
// Another instance is starting parse the command line & do what it would
|
||||
// have done.
|
||||
VLOG(1) << "Handling STARTUP request from another process";
|
||||
const std::wstring::size_type second_null =
|
||||
msg.find_first_of(L'\0', first_null + 1);
|
||||
if (second_null == std::wstring::npos || first_null == msg.length() - 1 ||
|
||||
second_null == msg.length()) {
|
||||
LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
|
||||
"parts separated by NULLs";
|
||||
return false;
|
||||
}
|
||||
|
||||
// Get current directory.
|
||||
*current_directory =
|
||||
base::FilePath(msg.substr(first_null + 1, second_null - first_null));
|
||||
|
||||
const std::wstring::size_type third_null =
|
||||
msg.find_first_of(L'\0', second_null + 1);
|
||||
if (third_null == std::wstring::npos || third_null == msg.length()) {
|
||||
LOG(WARNING) << "Invalid format for start command, we need a string in 4 "
|
||||
"parts separated by NULLs";
|
||||
}
|
||||
|
||||
// Get command line.
|
||||
const std::wstring cmd_line =
|
||||
msg.substr(second_null + 1, third_null - second_null);
|
||||
*parsed_command_line = base::CommandLine::FromString(cmd_line).argv();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ProcessLaunchNotification(
|
||||
const ProcessSingleton::NotificationCallback& notification_callback,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam,
|
||||
LRESULT* result) {
|
||||
if (message != WM_COPYDATA)
|
||||
return false;
|
||||
|
||||
// Handle the WM_COPYDATA message from another process.
|
||||
const COPYDATASTRUCT* cds = reinterpret_cast<COPYDATASTRUCT*>(lparam);
|
||||
|
||||
base::CommandLine::StringVector parsed_command_line;
|
||||
base::FilePath current_directory;
|
||||
if (!ParseCommandLine(cds, &parsed_command_line, ¤t_directory)) {
|
||||
*result = TRUE;
|
||||
return true;
|
||||
}
|
||||
|
||||
*result = notification_callback.Run(parsed_command_line, current_directory)
|
||||
? TRUE
|
||||
: FALSE;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool TerminateAppWithError() {
|
||||
// TODO: This is called when the secondary process can't ping the primary
|
||||
// process. Need to find out what to do here.
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const base::FilePath& user_data_dir,
|
||||
const NotificationCallback& notification_callback)
|
||||
: notification_callback_(notification_callback),
|
||||
user_data_dir_(user_data_dir),
|
||||
should_kill_remote_process_callback_(
|
||||
base::BindRepeating(&TerminateAppWithError)) {
|
||||
// The user_data_dir may have not been created yet.
|
||||
base::CreateDirectoryAndGetError(user_data_dir, nullptr);
|
||||
}
|
||||
|
||||
ProcessSingleton::~ProcessSingleton() {
|
||||
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
|
||||
if (lock_file_ != INVALID_HANDLE_VALUE)
|
||||
::CloseHandle(lock_file_);
|
||||
}
|
||||
|
||||
// Code roughly based on Mozilla.
|
||||
ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
||||
if (is_virtualized_)
|
||||
return PROCESS_NOTIFIED; // We already spawned the process in this case.
|
||||
if (lock_file_ == INVALID_HANDLE_VALUE && !remote_window_) {
|
||||
return LOCK_ERROR;
|
||||
} else if (!remote_window_) {
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
|
||||
switch (chrome::AttemptToNotifyRunningChrome(remote_window_)) {
|
||||
case chrome::NOTIFY_SUCCESS:
|
||||
return PROCESS_NOTIFIED;
|
||||
case chrome::NOTIFY_FAILED:
|
||||
remote_window_ = NULL;
|
||||
return PROCESS_NONE;
|
||||
case chrome::NOTIFY_WINDOW_HUNG:
|
||||
// Fall through and potentially terminate the hung browser.
|
||||
break;
|
||||
}
|
||||
|
||||
DWORD process_id = 0;
|
||||
DWORD thread_id = ::GetWindowThreadProcessId(remote_window_, &process_id);
|
||||
if (!thread_id || !process_id) {
|
||||
remote_window_ = NULL;
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
base::Process process = base::Process::Open(process_id);
|
||||
|
||||
// The window is hung. Scan for every window to find a visible one.
|
||||
bool visible_window = false;
|
||||
::EnumThreadWindows(thread_id, &BrowserWindowEnumeration,
|
||||
reinterpret_cast<LPARAM>(&visible_window));
|
||||
|
||||
// If there is a visible browser window, ask the user before killing it.
|
||||
if (visible_window && !should_kill_remote_process_callback_.Run()) {
|
||||
// The user denied. Quit silently.
|
||||
return PROCESS_NOTIFIED;
|
||||
}
|
||||
|
||||
// Time to take action. Kill the browser process.
|
||||
process.Terminate(content::RESULT_CODE_HUNG, true);
|
||||
remote_window_ = NULL;
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
|
||||
ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessOrCreate() {
|
||||
ProcessSingleton::NotifyResult result = PROCESS_NONE;
|
||||
if (!Create()) {
|
||||
result = NotifyOtherProcess();
|
||||
if (result == PROCESS_NONE)
|
||||
result = PROFILE_IN_USE;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
void ProcessSingleton::StartListeningOnSocket() {}
|
||||
void ProcessSingleton::OnBrowserReady() {}
|
||||
|
||||
// Look for a Chrome instance that uses the same profile directory. If there
|
||||
// isn't one, create a message window with its title set to the profile
|
||||
// directory path.
|
||||
bool ProcessSingleton::Create() {
|
||||
static const wchar_t kMutexName[] = L"Local\\AtomProcessSingletonStartup!";
|
||||
|
||||
remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
|
||||
if (!remote_window_) {
|
||||
// Make sure we will be the one and only process creating the window.
|
||||
// We use a named Mutex since we are protecting against multi-process
|
||||
// access. As documented, it's clearer to NOT request ownership on creation
|
||||
// since it isn't guaranteed we will get it. It is better to create it
|
||||
// without ownership and explicitly get the ownership afterward.
|
||||
base::win::ScopedHandle only_me(::CreateMutex(NULL, FALSE, kMutexName));
|
||||
if (!only_me.IsValid()) {
|
||||
DPLOG(FATAL) << "CreateMutex failed";
|
||||
return false;
|
||||
}
|
||||
|
||||
AutoLockMutex auto_lock_only_me(only_me.Get());
|
||||
|
||||
// We now own the mutex so we are the only process that can create the
|
||||
// window at this time, but we must still check if someone created it
|
||||
// between the time where we looked for it above and the time the mutex
|
||||
// was given to us.
|
||||
remote_window_ = chrome::FindRunningChromeWindow(user_data_dir_);
|
||||
if (!remote_window_) {
|
||||
// We have to make sure there is no Chrome instance running on another
|
||||
// machine that uses the same profile.
|
||||
base::FilePath lock_file_path = user_data_dir_.AppendASCII(kLockfile);
|
||||
lock_file_ =
|
||||
::CreateFile(lock_file_path.value().c_str(), GENERIC_WRITE,
|
||||
FILE_SHARE_READ, NULL, CREATE_ALWAYS,
|
||||
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_DELETE_ON_CLOSE, NULL);
|
||||
DWORD error = ::GetLastError();
|
||||
LOG_IF(WARNING, lock_file_ != INVALID_HANDLE_VALUE &&
|
||||
error == ERROR_ALREADY_EXISTS)
|
||||
<< "Lock file exists but is writable.";
|
||||
LOG_IF(ERROR, lock_file_ == INVALID_HANDLE_VALUE)
|
||||
<< "Lock file can not be created! Error code: " << error;
|
||||
|
||||
if (lock_file_ != INVALID_HANDLE_VALUE) {
|
||||
// Set the window's title to the path of our user data directory so
|
||||
// other Chrome instances can decide if they should forward to us.
|
||||
bool result =
|
||||
window_.CreateNamed(base::BindRepeating(&ProcessLaunchNotification,
|
||||
notification_callback_),
|
||||
user_data_dir_.value());
|
||||
|
||||
// NB: Ensure that if the primary app gets started as elevated
|
||||
// admin inadvertently, secondary windows running not as elevated
|
||||
// will still be able to send messages
|
||||
::ChangeWindowMessageFilterEx(window_.hwnd(), WM_COPYDATA, MSGFLT_ALLOW,
|
||||
NULL);
|
||||
CHECK(result && window_.hwnd());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return window_.hwnd() != NULL;
|
||||
}
|
||||
|
||||
void ProcessSingleton::Cleanup() {}
|
||||
|
||||
void ProcessSingleton::OverrideShouldKillRemoteProcessCallbackForTesting(
|
||||
const ShouldKillRemoteProcessCallback& display_dialog_callback) {
|
||||
should_kill_remote_process_callback_ = display_dialog_callback;
|
||||
}
|
||||
@@ -2,9 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/views/global_menu_bar_registrar_x11.h"
|
||||
|
||||
#include <string>
|
||||
#include "chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h"
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/debug/leak_annotations.h"
|
||||
@@ -51,7 +49,7 @@ GlobalMenuBarRegistrarX11::GlobalMenuBarRegistrarX11() {
|
||||
G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START),
|
||||
nullptr, kAppMenuRegistrarName, kAppMenuRegistrarPath,
|
||||
kAppMenuRegistrarName,
|
||||
nullptr, // Probably want a real cancelable.
|
||||
nullptr, // TODO: Probalby want a real cancelable.
|
||||
static_cast<GAsyncReadyCallback>(OnProxyCreatedThunk), this);
|
||||
}
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_REGISTRAR_X11_H_
|
||||
#define SHELL_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_REGISTRAR_X11_H_
|
||||
#ifndef CHROME_BROWSER_UI_VIEWS_FRAME_GLOBAL_MENU_BAR_REGISTRAR_X11_H_
|
||||
#define CHROME_BROWSER_UI_VIEWS_FRAME_GLOBAL_MENU_BAR_REGISTRAR_X11_H_
|
||||
|
||||
#include <gio/gio.h>
|
||||
|
||||
@@ -57,4 +57,4 @@ class GlobalMenuBarRegistrarX11 {
|
||||
DISALLOW_COPY_AND_ASSIGN(GlobalMenuBarRegistrarX11);
|
||||
};
|
||||
|
||||
#endif // SHELL_BROWSER_UI_VIEWS_GLOBAL_MENU_BAR_REGISTRAR_X11_H_
|
||||
#endif // CHROME_BROWSER_UI_VIEWS_FRAME_GLOBAL_MENU_BAR_REGISTRAR_X11_H_
|
||||
@@ -483,7 +483,6 @@ 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()`.
|
||||
@@ -940,9 +939,7 @@ app.setJumpList([
|
||||
])
|
||||
```
|
||||
|
||||
### `app.requestSingleInstanceLock([additionalData])`
|
||||
|
||||
* `additionalData` Record<any, any> (optional) - A JSON object containing additional data to send to the first instance.
|
||||
### `app.requestSingleInstanceLock()`
|
||||
|
||||
Returns `Boolean`
|
||||
|
||||
@@ -969,16 +966,12 @@ starts:
|
||||
const { app } = require('electron')
|
||||
let myWindow = null
|
||||
|
||||
const additionalData = { myKey: 'myValue' }
|
||||
const gotTheLock = app.requestSingleInstanceLock(additionalData)
|
||||
const gotTheLock = app.requestSingleInstanceLock()
|
||||
|
||||
if (!gotTheLock) {
|
||||
app.quit()
|
||||
} else {
|
||||
app.on('second-instance', (event, commandLine, workingDirectory, additionalData) => {
|
||||
// Print out data received from the second instance.
|
||||
console.log(additionalData)
|
||||
|
||||
app.on('second-instance', (event, commandLine, workingDirectory) => {
|
||||
// Someone tried to run a second instance, we should focus our window.
|
||||
if (myWindow) {
|
||||
if (myWindow.isMinimized()) myWindow.restore()
|
||||
|
||||
@@ -15,16 +15,14 @@ Process: [Main](../glossary.md#main-process)
|
||||
|
||||
```javascript
|
||||
// In the main process.
|
||||
const { app, BrowserView, BrowserWindow } = require('electron')
|
||||
const { BrowserView, BrowserWindow } = require('electron')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const win = new BrowserWindow({ width: 800, height: 600 })
|
||||
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_
|
||||
|
||||
@@ -394,7 +394,6 @@ 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
|
||||
@@ -560,7 +559,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 moved manually. Moving the window with `setPosition`/`setBounds`/`center` will not emit this event.
|
||||
Note that this is only emitted when the window is being resized manually. Resizing the window with `setBounds`/`setSize` will not emit this event.
|
||||
|
||||
#### Event: 'move'
|
||||
|
||||
|
||||
@@ -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])`
|
||||
@@ -131,15 +131,15 @@ Returns `Object`:
|
||||
|
||||
Returns an Object containing `title` and `url` keys representing the bookmark in
|
||||
the clipboard. The `title` and `url` values will be empty strings when the
|
||||
bookmark is unavailable. The `title` value will always be empty on Windows.
|
||||
bookmark is unavailable.
|
||||
|
||||
### `clipboard.writeBookmark(title, url[, type])` _macOS_ _Windows_
|
||||
|
||||
* `title` String - Unused on Windows
|
||||
* `title` String
|
||||
* `url` String
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Writes the `title` (macOS only) and `url` into the clipboard as a bookmark.
|
||||
Writes the `title` and `url` into the clipboard as a bookmark.
|
||||
|
||||
**Note:** Most apps on Windows don't support pasting bookmarks into them so
|
||||
you can use `clipboard.write` to write both a bookmark and fallback text to the
|
||||
|
||||
@@ -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, and any custom properties on the Error object [will be lost](https://github.com/electron/electron/issues/25596) |
|
||||
| `Promise` | Complex | ✅ | ✅ | N/A
|
||||
| `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. |
|
||||
| `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 `lax`.
|
||||
* `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`.
|
||||
|
||||
Returns `Promise<void>` - A promise which resolves when the cookie has been set
|
||||
|
||||
|
||||
@@ -19,9 +19,6 @@ following projects:
|
||||
* [socorro](https://github.com/mozilla/socorro)
|
||||
* [mini-breakpad-server](https://github.com/electron/mini-breakpad-server)
|
||||
|
||||
> **Note:** Electron uses Crashpad, not Breakpad, to collect and upload
|
||||
> crashes, but for the time being, the [upload protocol is the same](https://chromium.googlesource.com/crashpad/crashpad/+/HEAD/doc/overview_design.md#Upload-to-collection-server).
|
||||
|
||||
Or use a 3rd party hosted solution:
|
||||
|
||||
* [Backtrace](https://backtrace.io/electron/)
|
||||
@@ -29,12 +26,49 @@ Or use a 3rd party hosted solution:
|
||||
* [BugSplat](https://www.bugsplat.com/docs/platforms/electron)
|
||||
|
||||
Crash reports are stored temporarily before being uploaded in a directory
|
||||
underneath the app's user data directory, called 'Crashpad'. You can override
|
||||
this directory by calling `app.setPath('crashDumps', '/path/to/crashes')`
|
||||
before starting the crash reporter.
|
||||
underneath the app's user data directory (called 'Crashpad' on Windows and Mac,
|
||||
or 'Crash Reports' on Linux). You can override this directory by calling
|
||||
`app.setPath('crashDumps', '/path/to/crashes')` before starting the crash
|
||||
reporter.
|
||||
|
||||
Electron uses [crashpad](https://chromium.googlesource.com/crashpad/crashpad/+/refs/heads/main/README.md)
|
||||
to monitor and report crashes.
|
||||
On Windows and macOS, Electron uses
|
||||
[crashpad](https://chromium.googlesource.com/crashpad/crashpad/+/master/README.md)
|
||||
to monitor and report crashes. On Linux, Electron uses
|
||||
[breakpad](https://chromium.googlesource.com/breakpad/breakpad/+/master/). This
|
||||
is an implementation detail driven by Chromium, and it may change in future. In
|
||||
particular, crashpad is newer and will likely eventually replace breakpad on
|
||||
all platforms.
|
||||
|
||||
### Note about Node child processes on Linux
|
||||
|
||||
If you are using the Node.js `child_process` module and want to report crashes
|
||||
from those processes on Linux, there is an extra step you will need to take to
|
||||
properly initialize the crash reporter in the child process. This is not
|
||||
necessary on Mac or Windows, as those platforms use Crashpad, which
|
||||
automatically monitors child processes.
|
||||
|
||||
Since `require('electron')` is not available in Node child processes, the
|
||||
following APIs are available on the `process` object in Node child processes.
|
||||
Note that, on Linux, each Node child process has its own separate instance of
|
||||
the breakpad crash reporter. This is dissimilar to renderer child processes,
|
||||
which have a "stub" breakpad reporter which returns information to the main
|
||||
process for reporting.
|
||||
|
||||
#### `process.crashReporter.start(options)`
|
||||
|
||||
See [`crashReporter.start()`](#crashreporterstartoptions).
|
||||
|
||||
#### `process.crashReporter.getParameters()`
|
||||
|
||||
See [`crashReporter.getParameters()`](#crashreportergetparameters).
|
||||
|
||||
#### `process.crashReporter.addExtraParameter(key, value)`
|
||||
|
||||
See [`crashReporter.addExtraParameter(key, value)`](#crashreporteraddextraparameterkey-value).
|
||||
|
||||
#### `process.crashReporter.removeExtraParameter(key)`
|
||||
|
||||
See [`crashReporter.removeExtraParameter(key)`](#crashreporterremoveextraparameterkey).
|
||||
|
||||
## Methods
|
||||
|
||||
@@ -152,6 +186,12 @@ names must be no longer than 39 bytes, and values must be no longer than 20320
|
||||
bytes. Keys with names longer than the maximum will be silently ignored. Key
|
||||
values longer than the maximum length will be truncated.
|
||||
|
||||
**Note:** On linux values that are longer than 127 bytes will be chunked into
|
||||
multiple keys, each 127 bytes in length. E.g. `addExtraParameter('foo', 'a'.repeat(130))`
|
||||
will result in two chunked keys `foo__1` and `foo__2`, the first will contain
|
||||
the first 127 bytes and the second will contain the remaining 3 bytes. On
|
||||
your crash reporting backend you should stitch together keys in this format.
|
||||
|
||||
### `crashReporter.removeExtraParameter(key)`
|
||||
|
||||
* `key` String - Parameter key, must be no longer than 39 bytes.
|
||||
@@ -163,32 +203,6 @@ will not include this parameter.
|
||||
|
||||
Returns `Record<String, String>` - The current 'extra' parameters of the crash reporter.
|
||||
|
||||
## In Node child processes
|
||||
|
||||
Since `require('electron')` is not available in Node child processes, the
|
||||
following APIs are available on the `process` object in Node child processes.
|
||||
|
||||
#### `process.crashReporter.start(options)`
|
||||
|
||||
See [`crashReporter.start()`](#crashreporterstartoptions).
|
||||
|
||||
Note that if the crash reporter is started in the main process, it will
|
||||
automatically monitor child processes, so it should not be started in the child
|
||||
process. Only use this method if the main process does not initialize the crash
|
||||
reporter.
|
||||
|
||||
#### `process.crashReporter.getParameters()`
|
||||
|
||||
See [`crashReporter.getParameters()`](#crashreportergetparameters).
|
||||
|
||||
#### `process.crashReporter.addExtraParameter(key, value)`
|
||||
|
||||
See [`crashReporter.addExtraParameter(key, value)`](#crashreporteraddextraparameterkey-value).
|
||||
|
||||
#### `process.crashReporter.removeExtraParameter(key)`
|
||||
|
||||
See [`crashReporter.removeExtraParameter(key)`](#crashreporterremoveextraparameterkey).
|
||||
|
||||
## Crash Report Payload
|
||||
|
||||
The crash reporter will send the following data to the `submitURL` as
|
||||
|
||||
@@ -100,8 +100,6 @@ The following methods of `chrome.tabs` are supported:
|
||||
|
||||
- `chrome.tabs.sendMessage`
|
||||
- `chrome.tabs.executeScript`
|
||||
- `chrome.tabs.update` (partial support)
|
||||
- supported properties: `url`, `muted`.
|
||||
|
||||
> **Note:** In Chrome, passing `-1` as a tab ID signifies the "currently active
|
||||
> tab". Since Electron has no such concept, passing `-1` as a tab ID is not
|
||||
|
||||
@@ -67,8 +67,3 @@ 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,6 +178,7 @@ 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.
|
||||
|
||||
@@ -562,8 +562,7 @@ the original network configuration.
|
||||
* `hostname` String
|
||||
* `certificate` [Certificate](structures/certificate.md)
|
||||
* `validatedCertificate` [Certificate](structures/certificate.md)
|
||||
* `isIssuedByKnownRoot` Boolean - `true` if Chromium recognises the root CA as a standard root. If it isn't then it's probably the case that this certificate was generated by a MITM proxy whose root has been installed locally (for example, by a corporate proxy). This should not be trusted if the `verificationResult` is not `OK`.
|
||||
* `verificationResult` String - `OK` if the certificate is trusted, otherwise an error like `CERT_REVOKED`.
|
||||
* `verificationResult` String - Verification result from chromium.
|
||||
* `errorCode` Integer - Error code.
|
||||
* `callback` Function
|
||||
* `verificationResult` Integer - Value can be one of certificate error codes
|
||||
@@ -903,10 +902,8 @@ 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**. 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.
|
||||
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.
|
||||
|
||||
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
|
||||
|
||||
@@ -1840,7 +1840,7 @@ the cursor when dragging.
|
||||
|
||||
#### `contents.savePage(fullPath, saveType)`
|
||||
|
||||
* `fullPath` String - The absolute file path.
|
||||
* `fullPath` String - The full 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 current frame. Sub-frames can be retrieved by
|
||||
certain properties and methods (e.g. `webFrame.firstChild`).
|
||||
class representing the top frame of the current `BrowserWindow`. Sub-frames can
|
||||
be retrieved by certain properties and methods (e.g. `webFrame.firstChild`).
|
||||
|
||||
An example of zooming current page to 200%.
|
||||
|
||||
|
||||
@@ -12,63 +12,6 @@ This document uses the following convention to categorize breaking changes:
|
||||
* **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release.
|
||||
* **Removed:** An API or feature was removed, and is no longer supported by Electron.
|
||||
|
||||
## Planned Breaking API Changes (17.0)
|
||||
|
||||
### Removed: `desktopCapturer.getSources` in the renderer
|
||||
|
||||
The `desktopCapturer.getSources` API is now only available in the main process.
|
||||
This has been changed in order to improve the default security of Electron
|
||||
apps.
|
||||
|
||||
If you need this functionality, it can be replaced as follows:
|
||||
|
||||
```js
|
||||
// Main process
|
||||
const { ipcMain, desktopCapturer } = require('electron')
|
||||
|
||||
ipcMain.handle(
|
||||
'DESKTOP_CAPTURER_GET_SOURCES',
|
||||
(event, opts) => desktopCapturer.getSources(opts)
|
||||
)
|
||||
```
|
||||
|
||||
```js
|
||||
// Renderer process
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
const desktopCapturer = {
|
||||
getSources: (opts) => ipcRenderer.invoke('DESKTOP_CAPTURER_GET_SOURCES', opts)
|
||||
}
|
||||
```
|
||||
|
||||
However, you should consider further restricting the information returned to
|
||||
the renderer; for instance, displaying a source selector to the user and only
|
||||
returning the selected source.
|
||||
|
||||
## Planned Breaking API Changes (16.0)
|
||||
|
||||
### Behavior Changed: `crashReporter` implementation switched to Crashpad on Linux
|
||||
|
||||
The underlying implementation of the `crashReporter` API on Linux has changed
|
||||
from Breakpad to Crashpad, bringing it in line with Windows and Mac. As a
|
||||
result of this, child processes are now automatically monitored, and calling
|
||||
`process.crashReporter.start` in Node child processes is no longer needed (and
|
||||
is not advisable, as it will start a second instance of the Crashpad reporter).
|
||||
|
||||
There are also some subtle changes to how annotations will be reported on
|
||||
Linux, including that long values will no longer be split between annotations
|
||||
appended with `__1`, `__2` and so on, and instead will be truncated at the
|
||||
(new, longer) annotation value limit.
|
||||
|
||||
### Deprecated: `desktopCapturer.getSources` in the renderer
|
||||
|
||||
Usage of the `desktopCapturer.getSources` API in the renderer has been
|
||||
deprecated and will be removed. This change improves the default security of
|
||||
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`
|
||||
|
||||
@@ -85,37 +85,42 @@ $ 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"
|
||||
```
|
||||
|
||||
On Windows:
|
||||
Or on Windows (without the optional argument):
|
||||
|
||||
```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\")"
|
||||
```
|
||||
|
||||
**To generate Release build config of Electron:**
|
||||
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:**
|
||||
|
||||
```sh
|
||||
$ gn gen out/Release --args="import(\"//electron/build/args/release.gn\")"
|
||||
$ gn gen out/Testing --args="import(\"//electron/build/args/testing.gn\") $GN_EXTRA_ARGS"
|
||||
```
|
||||
|
||||
**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`.
|
||||
**For generating Release (aka "non-component" or "static") build config of
|
||||
Electron:**
|
||||
|
||||
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`.
|
||||
```sh
|
||||
$ gn gen out/Release --args="import(\"//electron/build/args/release.gn\") $GN_EXTRA_ARGS"
|
||||
```
|
||||
|
||||
**To build, run `ninja` with the `electron` target:**
|
||||
Nota Bene: This will also take a while and probably heat up your lap.
|
||||
@@ -151,13 +156,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
|
||||
|
||||
@@ -1,171 +0,0 @@
|
||||
# Creating a New Electron Browser Module
|
||||
|
||||
Welcome to the Electron API guide! If you are unfamiliar with creating a new Electron API module within the [`browser`](https://github.com/electron/electron/tree/main/shell/browser) directory, this guide serves as a checklist for some of the necessary steps that you will need to implement.
|
||||
|
||||
This is not a comprehensive end-all guide to creating an Electron Browser API, rather an outline documenting some of the more unintuitive steps.
|
||||
|
||||
## Add your files to Electron's project configuration
|
||||
|
||||
Electron uses [GN](https://gn.googlesource.com/gn) as a meta build system to generate files for its compiler, [Ninja](https://ninja-build.org/). This means that in order to tell Electron to compile your code, we have to add your API's code and header file names into [`filenames.gni`](https://github.com/electron/electron/blob/main/filenames.gni).
|
||||
|
||||
You will need to append your API file names alphabetically into the appropriate files like so:
|
||||
|
||||
```cpp title='filenames.gni'
|
||||
lib_sources = [
|
||||
"path/to/api/api_name.cc",
|
||||
"path/to/api/api_name.h",
|
||||
]
|
||||
|
||||
lib_sources_mac = [
|
||||
"path/to/api/api_name_mac.h",
|
||||
"path/to/api/api_name_mac.mm",
|
||||
]
|
||||
|
||||
lib_sources_win = [
|
||||
"path/to/api/api_name_win.cc",
|
||||
"path/to/api/api_name_win.h",
|
||||
]
|
||||
|
||||
lib_sources_linux = [
|
||||
"path/to/api/api_name_linux.cc",
|
||||
"path/to/api/api_name_linux.h",
|
||||
]
|
||||
```
|
||||
|
||||
Note that the Windows, macOS and Linux array additions are optional and should only be added if your API has specific platform implementations.
|
||||
|
||||
## Create API documentation
|
||||
|
||||
Type definitions are generated by Electron using [`@electron/docs-parser`](https://github.com/electron/docs-parser) and [`@electron/typescript-definitions`](https://github.com/electron/typescript-definitions). This step is necessary to ensure consistency across Electron's API documentation. This means that for your API type definition to appear in the `electron.d.ts` file, we must create a `.md` file. Examples can be found in [this folder](https://github.com/electron/electron/tree/main/docs/api).
|
||||
|
||||
## Set up `ObjectTemplateBuilder` and `Wrappable`
|
||||
|
||||
Electron constructs its modules using [`object_template_builder`](https://www.electronjs.org/blog/from-native-to-js#mateobjecttemplatebuilder).
|
||||
|
||||
[`wrappable`](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/gin/wrappable.h) is a base class for C++ objects that have corresponding v8 wrapper objects.
|
||||
|
||||
Here is a basic example of code that you may need to add, in order to incorporate `object_template_builder` and `wrappable` into your API. For further reference, you can find more implementations [here](https://github.com/electron/electron/tree/main/shell/browser/api).
|
||||
|
||||
In your `api_name.h` file:
|
||||
|
||||
```cpp title='api_name.h'
|
||||
|
||||
#ifndef SHELL_BROWSER_API_ELECTRON_API_{API_NAME}_H_
|
||||
#define SHELL_BROWSER_API_ELECTRON_API_{API_NAME}_H_
|
||||
|
||||
#include "gin/handle.h"
|
||||
#include "gin/wrappable.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
class ApiName : public gin::Wrappable<ApiName> {
|
||||
public:
|
||||
static gin::Handle<ApiName> Create(v8::Isolate* isolate);
|
||||
|
||||
// gin::Wrappable
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override;
|
||||
const char* GetTypeName() override;
|
||||
} // namespace api
|
||||
} // namespace electron
|
||||
```
|
||||
|
||||
In your `api_name.cc` file:
|
||||
|
||||
```cpp title='api_name.cc'
|
||||
#include "shell/browser/api/electron_api_safe_storage.h"
|
||||
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/common/gin_converters/base_converter.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/platform_util.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
gin::WrapperInfo ApiName::kWrapperInfo = {gin::kEmbedderNativeGin};
|
||||
|
||||
gin::ObjectTemplateBuilder ApiName::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
return gin::ObjectTemplateBuilder(isolate)
|
||||
.SetMethod("methodName", &ApiName::methodName);
|
||||
}
|
||||
|
||||
const char* ApiName::GetTypeName() {
|
||||
return "ApiName";
|
||||
}
|
||||
|
||||
// static
|
||||
gin::Handle<ApiName> ApiName::Create(v8::Isolate* isolate) {
|
||||
return gin::CreateHandle(isolate, new ApiName());
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
|
||||
namespace {
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("apiName", electron::api::ApiName::Create(isolate));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
```
|
||||
|
||||
## Link your Electron API with Node
|
||||
|
||||
In the [`typings/internal-ambient.d.ts`](https://github.com/electron/electron/blob/main/typings/internal-ambient.d.ts) file, we need to append a new property onto the `Process` interface like so:
|
||||
|
||||
```ts title='typings/internal-ambient.d.ts'
|
||||
interface Process {
|
||||
_linkedBinding(name: 'electron_browser_{api_name}', Electron.ApiName);
|
||||
}
|
||||
```
|
||||
|
||||
At the very bottom of your `api_name.cc` file:
|
||||
|
||||
```cpp title='api_name.cc'
|
||||
NODE_LINKED_MODULE_CONTEXT_AWARE(electron_browser_{api_name},Initialize)
|
||||
```
|
||||
|
||||
In your [`shell/common/node_bindings.cc`](https://github.com/electron/electron/blob/main/shell/common/node_bindings.cc) file, add your node binding name to Electron's built-in modules.
|
||||
|
||||
```cpp title='shell/common/node_bindings.cc'
|
||||
#define ELECTRON_BUILTIN_MODULES(V) \
|
||||
V(electron_browser_{api_name})
|
||||
```
|
||||
|
||||
> Note: More technical details on how Node links with Electron can be found on [our blog](https://www.electronjs.org/blog/electron-internals-using-node-as-a-library#link-node-with-electron).
|
||||
|
||||
## Expose your API to TypeScript
|
||||
|
||||
### Export your API as a module
|
||||
|
||||
We will need to create a new TypeScript file in the path that follows:
|
||||
|
||||
`"lib/browser/api/{electron_browser_{api_name}}.ts"`
|
||||
|
||||
An example of the contents of this file can be found [here](https://github.com/electron/electron/blob/main/lib/browser/api/native-image.ts).
|
||||
|
||||
### Expose your module to TypeScript
|
||||
|
||||
Add your module to the module list found at `"lib/browser/api/module-list.ts"` like so:
|
||||
|
||||
```typescript title='lib/browser/api/module-list.ts'
|
||||
export const browserModuleList: ElectronInternal.ModuleEntry[] = [
|
||||
{ name: 'apiName', loader: () => require('./api-name') },
|
||||
];
|
||||
```
|
||||
@@ -58,6 +58,8 @@ To run your tests:
|
||||
$ npx wdio run wdio.conf.js
|
||||
```
|
||||
|
||||
[chrome-driver]: https://sites.google.com/chromium.org/driver/
|
||||
|
||||
### With Selenium
|
||||
|
||||
[Selenium](https://www.selenium.dev/) is a web automation framework that
|
||||
@@ -114,142 +116,6 @@ driver.wait(() => {
|
||||
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.
|
||||
@@ -397,13 +263,3 @@ 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/
|
||||
|
||||
@@ -138,7 +138,7 @@ Finally, the `main.js` file represents the main process and contains the actual
|
||||
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
|
||||
@@ -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.
|
||||
* 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 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.
|
||||
|
||||
| Electron | Alpha | Beta | Stable | Chrome | Node |
|
||||
| ------- | ----- | ------- | ------ | ------ | ---- |
|
||||
@@ -25,5 +25,4 @@ 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-Oct-20 | 2021-Nov-16 | M96 | v16.9 |
|
||||
| 17.0.0 | 2021-Nov-18 | 2022-Jan-06 | 2022-Feb-01 | M98 | TBD |
|
||||
| 16.0.0 | -- | 2021-Sep-23 | 2021-Nov-16 | M96 | TBD |
|
||||
|
||||
@@ -18,7 +18,7 @@ npm install --save-dev electron@latest
|
||||
|
||||
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 the the [SemVer](#semver) spec
|
||||
1. Strict use of 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
|
||||
|
||||
@@ -12,7 +12,7 @@ Fuses are the solution to this problem, at a high level they are "magic bits" in
|
||||
|
||||
### The easy way
|
||||
|
||||
We've made a handy module, [`@electron/fuses`](https://npmjs.com/package/@electron/fuses), to make flipping these fuses easy. Check out the README of that module for more details on usage and potential error cases.
|
||||
We've made a handy module `@electron/fuses` to make flipping these fuses easy. Check out the README of that module for more details on usage and potential error cases.
|
||||
|
||||
```js
|
||||
require('@electron/fuses').flipFuses(
|
||||
|
||||
@@ -39,7 +39,7 @@ inAppPurchase.on('transactions-updated', (event, transactions) => {
|
||||
}
|
||||
|
||||
// Check each transaction.
|
||||
transactions.forEach((transaction) => {
|
||||
transactions.forEach(function (transaction) {
|
||||
const payment = transaction.payment
|
||||
|
||||
switch (transaction.transactionState) {
|
||||
|
||||
@@ -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/electronjs
|
||||
[discord]: https://discord.gg/electron
|
||||
|
||||
@@ -82,7 +82,7 @@ listen for the `keyup` and `keydown` [DOM events][dom-events] inside the
|
||||
renderer process using the [addEventListener() API][addEventListener-api].
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/keyboard-shortcuts/web-apis|focus=renderer.js'
|
||||
const handleKeyPress = (event) => {
|
||||
function handleKeyPress(event) {
|
||||
// You can put code here to handle the keypress.
|
||||
document.getElementById("last-keypress").innerText = event.key;
|
||||
console.log(`You pressed ${event.key}`);
|
||||
|
||||
@@ -25,7 +25,7 @@ Starting with a working application from the
|
||||
```javascript fiddle='docs/fiddles/features/macos-dock-menu'
|
||||
const { app, BrowserWindow, Menu } = require('electron')
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
|
||||
@@ -134,7 +134,7 @@ app.whenReady().then(async () => {
|
||||
<script>
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
const doWork = (input) => {
|
||||
function doWork(input) {
|
||||
// Something cpu-intensive.
|
||||
return input * 2
|
||||
}
|
||||
@@ -185,7 +185,7 @@ stream of data.
|
||||
```js
|
||||
// renderer.js ///////////////////////////////////////////////////////////////
|
||||
|
||||
const makeStreamingRequest = (element, callback) => {
|
||||
function makeStreamingRequest (element, callback) {
|
||||
// MessageChannels are lightweight--it's cheap to create a new one for each
|
||||
// request.
|
||||
const { port1, port2 } = new MessageChannel()
|
||||
|
||||
@@ -54,7 +54,7 @@ const { Notification } = require('electron')
|
||||
const NOTIFICATION_TITLE = 'Basic Notification'
|
||||
const NOTIFICATION_BODY = 'Notification from the Main process'
|
||||
|
||||
const showNotification = () => {
|
||||
function showNotification () {
|
||||
new Notification({ title: NOTIFICATION_TITLE, body: NOTIFICATION_BODY }).show()
|
||||
}
|
||||
|
||||
@@ -146,7 +146,7 @@ desktop environment that follows [Desktop Notifications
|
||||
Specification][notification-spec], including Cinnamon, Enlightenment, Unity,
|
||||
GNOME, KDE.
|
||||
|
||||
[notification-spec]: https://developer-old.gnome.org/notification-spec/
|
||||
[notification-spec]: https://developer.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
|
||||
|
||||
@@ -41,7 +41,7 @@ Starting with an HTML file `index.html`, this example will demonstrate how the `
|
||||
In order to mutate the DOM, create a `renderer.js` file that adds event listeners to the `'online'` and `'offline'` `window` events. The event handler sets the content of the `<strong id='status'>` element depending on the result of `navigator.onLine`.
|
||||
|
||||
```js title='renderer.js'
|
||||
const updateOnlineStatus = () => {
|
||||
function updateOnlineStatus () {
|
||||
document.getElementById('status').innerHTML = navigator.onLine ? 'online' : 'offline'
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ Finally, create a `main.js` file for main process that creates the window.
|
||||
```js title='main.js'
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const onlineStatusWindow = new BrowserWindow({
|
||||
width: 400,
|
||||
height: 100
|
||||
|
||||
@@ -84,7 +84,7 @@ uses `app` APIs to create a more native application window experience.
|
||||
|
||||
```js title='main.js'
|
||||
// quitting the app when no windows are open on non-macOS platforms
|
||||
app.on('window-all-closed', () => {
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
```
|
||||
|
||||
@@ -48,7 +48,7 @@ const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let progressInterval
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
|
||||
@@ -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 a local HTML file or a remote URL.
|
||||
from either from 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:
|
||||
@@ -166,7 +166,7 @@ Then, add a `createWindow()` function that loads `index.html` into a new `Browse
|
||||
instance.
|
||||
|
||||
```js
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
@@ -216,7 +216,7 @@ To implement this, listen for the `app` module's [`'window-all-closed'`][window-
|
||||
event, and call [`app.quit()`][app-quit] if the user is not on macOS (`darwin`).
|
||||
|
||||
```js
|
||||
app.on('window-all-closed', () => {
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
```
|
||||
@@ -244,7 +244,7 @@ from within your existing `whenReady()` callback.
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', () => {
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
@@ -295,7 +295,7 @@ to the `webPreferences.preload` option in your existing `BrowserWindow` construc
|
||||
const path = require('path')
|
||||
|
||||
// modify your existing createWindow() function
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600,
|
||||
@@ -360,7 +360,7 @@ The full code is available below:
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
// Create the browser window.
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
@@ -383,7 +383,7 @@ const createWindow = () => {
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', () => {
|
||||
app.on('activate', function () {
|
||||
// On macOS it's common to re-create a window in the app when the
|
||||
// dock icon is clicked and there are no other windows open.
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
@@ -393,7 +393,7 @@ app.whenReady().then(() => {
|
||||
// Quit when all windows are closed, except on macOS. There, it's common
|
||||
// for applications and their menu bar to stay active until the user quits
|
||||
// explicitly with Cmd + Q.
|
||||
app.on('window-all-closed', () => {
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ const { app, BrowserWindow } = require('electron')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
|
||||
@@ -24,7 +24,7 @@ To set the represented file of window, you can use the
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
const os = require('os');
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
|
||||
@@ -216,7 +216,7 @@ access to a `window.readConfig()` method, but no Node.js features.
|
||||
```js
|
||||
const { readFileSync } = require('fs')
|
||||
|
||||
window.readConfig = () => {
|
||||
window.readConfig = function () {
|
||||
const data = readFileSync('./config.json')
|
||||
return data
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ until the maintainers feel the maintenance burden is too high to continue doing
|
||||
|
||||
### Currently supported versions
|
||||
|
||||
* 16.x.y
|
||||
* 15.x.y
|
||||
* 14.x.y
|
||||
* 13.x.y
|
||||
* 12
|
||||
|
||||
### End-of-life
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
<includes>
|
||||
<include name="IDR_CONTENT_SHELL_DEVTOOLS_DISCOVERY_PAGE" file="${target_gen_dir}/shell_devtools_discovery_page.html" use_base_dir="false" type="BINDATA" />
|
||||
<include name="IDR_PDF_MANIFEST" file="../chrome/browser/resources/pdf/manifest.json" type="BINDATA" />
|
||||
<include name="IDR_CRYPTOTOKEN_MANIFEST" file="../chrome/browser/resources/cryptotoken/manifest.json" type="BINDATA" />
|
||||
</includes>
|
||||
</release>
|
||||
</grit>
|
||||
|
||||
@@ -33,23 +33,6 @@
|
||||
{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.">
|
||||
|
||||
@@ -135,14 +135,16 @@ auto_filenames = {
|
||||
|
||||
sandbox_bundle_deps = [
|
||||
"lib/common/api/deprecate.ts",
|
||||
"lib/common/api/native-image.ts",
|
||||
"lib/common/define-properties.ts",
|
||||
"lib/common/ipc-messages.ts",
|
||||
"lib/common/type-utils.ts",
|
||||
"lib/common/web-view-events.ts",
|
||||
"lib/common/web-view-methods.ts",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
"lib/renderer/api/crash-reporter.ts",
|
||||
"lib/renderer/api/desktop-capturer.ts",
|
||||
"lib/renderer/api/ipc-renderer.ts",
|
||||
"lib/renderer/api/native-image.ts",
|
||||
"lib/renderer/api/web-frame.ts",
|
||||
"lib/renderer/inspector.ts",
|
||||
"lib/renderer/ipc-renderer-internal-utils.ts",
|
||||
@@ -167,6 +169,7 @@ auto_filenames = {
|
||||
]
|
||||
|
||||
isolated_bundle_deps = [
|
||||
"lib/common/type-utils.ts",
|
||||
"lib/common/web-view-methods.ts",
|
||||
"lib/isolated_renderer/init.ts",
|
||||
"lib/renderer/web-view/web-view-attributes.ts",
|
||||
@@ -203,6 +206,7 @@ auto_filenames = {
|
||||
"lib/browser/api/menu.ts",
|
||||
"lib/browser/api/message-channel.ts",
|
||||
"lib/browser/api/module-list.ts",
|
||||
"lib/browser/api/native-image.ts",
|
||||
"lib/browser/api/native-theme.ts",
|
||||
"lib/browser/api/net-log.ts",
|
||||
"lib/browser/api/net.ts",
|
||||
@@ -237,13 +241,13 @@ auto_filenames = {
|
||||
"lib/common/api/clipboard.ts",
|
||||
"lib/common/api/deprecate.ts",
|
||||
"lib/common/api/module-list.ts",
|
||||
"lib/common/api/native-image.ts",
|
||||
"lib/common/api/shell.ts",
|
||||
"lib/common/define-properties.ts",
|
||||
"lib/common/init.ts",
|
||||
"lib/common/ipc-messages.ts",
|
||||
"lib/common/parse-features-string.ts",
|
||||
"lib/common/reset-search-paths.ts",
|
||||
"lib/common/type-utils.ts",
|
||||
"lib/common/web-view-events.ts",
|
||||
"lib/common/web-view-methods.ts",
|
||||
"lib/common/webpack-globals-provider.ts",
|
||||
@@ -260,12 +264,13 @@ auto_filenames = {
|
||||
"lib/common/api/clipboard.ts",
|
||||
"lib/common/api/deprecate.ts",
|
||||
"lib/common/api/module-list.ts",
|
||||
"lib/common/api/native-image.ts",
|
||||
"lib/common/api/shell.ts",
|
||||
"lib/common/define-properties.ts",
|
||||
"lib/common/init.ts",
|
||||
"lib/common/ipc-messages.ts",
|
||||
"lib/common/reset-search-paths.ts",
|
||||
"lib/common/type-utils.ts",
|
||||
"lib/common/web-view-events.ts",
|
||||
"lib/common/web-view-methods.ts",
|
||||
"lib/common/webpack-provider.ts",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
@@ -274,6 +279,7 @@ auto_filenames = {
|
||||
"lib/renderer/api/exports/electron.ts",
|
||||
"lib/renderer/api/ipc-renderer.ts",
|
||||
"lib/renderer/api/module-list.ts",
|
||||
"lib/renderer/api/native-image.ts",
|
||||
"lib/renderer/api/web-frame.ts",
|
||||
"lib/renderer/init.ts",
|
||||
"lib/renderer/inspector.ts",
|
||||
@@ -299,12 +305,12 @@ auto_filenames = {
|
||||
"lib/common/api/clipboard.ts",
|
||||
"lib/common/api/deprecate.ts",
|
||||
"lib/common/api/module-list.ts",
|
||||
"lib/common/api/native-image.ts",
|
||||
"lib/common/api/shell.ts",
|
||||
"lib/common/define-properties.ts",
|
||||
"lib/common/init.ts",
|
||||
"lib/common/ipc-messages.ts",
|
||||
"lib/common/reset-search-paths.ts",
|
||||
"lib/common/type-utils.ts",
|
||||
"lib/common/webpack-provider.ts",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
"lib/renderer/api/crash-reporter.ts",
|
||||
@@ -312,6 +318,7 @@ auto_filenames = {
|
||||
"lib/renderer/api/exports/electron.ts",
|
||||
"lib/renderer/api/ipc-renderer.ts",
|
||||
"lib/renderer/api/module-list.ts",
|
||||
"lib/renderer/api/native-image.ts",
|
||||
"lib/renderer/api/web-frame.ts",
|
||||
"lib/renderer/ipc-renderer-internal-utils.ts",
|
||||
"lib/renderer/ipc-renderer-internal.ts",
|
||||
|
||||
@@ -44,8 +44,8 @@ filenames = {
|
||||
]
|
||||
|
||||
lib_sources_linux_x11 = [
|
||||
"shell/browser/ui/views/global_menu_bar_registrar_x11.cc",
|
||||
"shell/browser/ui/views/global_menu_bar_registrar_x11.h",
|
||||
"chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc",
|
||||
"chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.h",
|
||||
"shell/browser/ui/views/global_menu_bar_x11.cc",
|
||||
"shell/browser/ui/views/global_menu_bar_x11.h",
|
||||
"shell/browser/ui/x/event_disabler.cc",
|
||||
@@ -56,9 +56,13 @@ filenames = {
|
||||
"shell/browser/ui/x/x_window_utils.h",
|
||||
]
|
||||
|
||||
lib_sources_posix = [ "shell/browser/electron_browser_main_parts_posix.cc" ]
|
||||
lib_sources_posix = [
|
||||
"chromium_src/chrome/browser/process_singleton_posix.cc",
|
||||
"shell/browser/electron_browser_main_parts_posix.cc",
|
||||
]
|
||||
|
||||
lib_sources_win = [
|
||||
"chromium_src/chrome/browser/process_singleton_win.cc",
|
||||
"shell/browser/api/electron_api_power_monitor_win.cc",
|
||||
"shell/browser/api/electron_api_system_preferences_win.cc",
|
||||
"shell/browser/browser_win.cc",
|
||||
@@ -233,6 +237,7 @@ filenames = {
|
||||
]
|
||||
|
||||
lib_sources = [
|
||||
"chromium_src/chrome/browser/process_singleton.h",
|
||||
"shell/app/command_line_args.cc",
|
||||
"shell/app/command_line_args.h",
|
||||
"shell/app/electron_content_client.cc",
|
||||
@@ -504,6 +509,8 @@ filenames = {
|
||||
"shell/browser/web_contents_preferences.h",
|
||||
"shell/browser/web_contents_zoom_controller.cc",
|
||||
"shell/browser/web_contents_zoom_controller.h",
|
||||
"shell/browser/web_dialog_helper.cc",
|
||||
"shell/browser/web_dialog_helper.h",
|
||||
"shell/browser/web_view_guest_delegate.cc",
|
||||
"shell/browser/web_view_guest_delegate.h",
|
||||
"shell/browser/web_view_manager.cc",
|
||||
@@ -675,11 +682,14 @@ 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",
|
||||
"shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.cc",
|
||||
"shell/browser/extensions/api/cryptotoken_private/cryptotoken_private_api.h",
|
||||
"shell/browser/extensions/api/management/electron_management_api_delegate.cc",
|
||||
"shell/browser/extensions/api/management/electron_management_api_delegate.h",
|
||||
"shell/browser/extensions/api/resources_private/resources_private_api.cc",
|
||||
|
||||
@@ -143,10 +143,7 @@ export const roleList: Record<RoleId, Role> = {
|
||||
label: 'Toggle Developer Tools',
|
||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
nonNativeMacOSRole: true,
|
||||
webContentsMethod: wc => {
|
||||
const bw = wc.getOwnerBrowserWindow();
|
||||
if (bw) bw.webContents.toggleDevTools();
|
||||
}
|
||||
windowMethod: w => w.webContents.toggleDevTools()
|
||||
},
|
||||
togglefullscreen: {
|
||||
label: 'Toggle Full Screen',
|
||||
|
||||
@@ -16,6 +16,7 @@ export const browserModuleList: ElectronInternal.ModuleEntry[] = [
|
||||
{ name: 'Menu', loader: () => require('./menu') },
|
||||
{ name: 'MenuItem', loader: () => require('./menu-item') },
|
||||
{ name: 'MessageChannelMain', loader: () => require('./message-channel') },
|
||||
{ name: 'nativeImage', loader: () => require('./native-image') },
|
||||
{ name: 'nativeTheme', loader: () => require('./native-theme') },
|
||||
{ name: 'net', loader: () => require('./net') },
|
||||
{ name: 'netLog', loader: () => require('./net-log') },
|
||||
|
||||
@@ -67,7 +67,7 @@ ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, function (event, ite
|
||||
|
||||
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
const window = event.sender.getOwnerBrowserWindow()!;
|
||||
const window = event.sender.getOwnerBrowserWindow();
|
||||
|
||||
menu.popup({ window, callback: () => resolve() });
|
||||
});
|
||||
@@ -94,7 +94,7 @@ ipcMainUtils.handleSync(IPC_MESSAGES.INSPECTOR_CONFIRM, async function (event, m
|
||||
buttons: ['OK', 'Cancel'],
|
||||
cancelId: 1
|
||||
};
|
||||
const window = event.sender.getOwnerBrowserWindow()!;
|
||||
const window = event.sender.getOwnerBrowserWindow();
|
||||
const { response } = await dialog.showMessageBox(window, options);
|
||||
return response === 0;
|
||||
});
|
||||
|
||||
@@ -4,6 +4,7 @@ import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-util
|
||||
import { parseWebViewWebPreferences } from '@electron/internal/common/parse-features-string';
|
||||
import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods';
|
||||
import { webViewEvents } from '@electron/internal/common/web-view-events';
|
||||
import { serialize } from '@electron/internal/common/type-utils';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
|
||||
interface GuestInstance {
|
||||
@@ -14,7 +15,6 @@ interface GuestInstance {
|
||||
}
|
||||
|
||||
const webViewManager = process._linkedBinding('electron_browser_web_view_manager');
|
||||
const eventBinding = process._linkedBinding('electron_browser_event');
|
||||
|
||||
const supportedWebViewEvents = Object.keys(webViewEvents);
|
||||
|
||||
@@ -28,7 +28,139 @@ function sanitizeOptionsForGuest (options: Record<string, any>) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function makeWebPreferences (embedder: Electron.WebContents, params: Record<string, any>) {
|
||||
// Create a new guest instance.
|
||||
const createGuest = function (embedder: Electron.WebContents, params: Record<string, any>) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const guest = (webContents as typeof ElectronInternal.WebContents).create({
|
||||
type: 'webview',
|
||||
partition: params.partition,
|
||||
embedder
|
||||
});
|
||||
const guestInstanceId = guest.id;
|
||||
guestInstances.set(guestInstanceId, {
|
||||
guest,
|
||||
embedder
|
||||
});
|
||||
|
||||
// Clear the guest from map when it is destroyed.
|
||||
guest.once('destroyed', () => {
|
||||
if (guestInstances.has(guestInstanceId)) {
|
||||
detachGuest(embedder, guestInstanceId);
|
||||
}
|
||||
});
|
||||
|
||||
// Init guest web view after attached.
|
||||
guest.once('did-attach' as any, function (this: Electron.WebContents, event: Electron.Event) {
|
||||
params = this.attachParams!;
|
||||
delete this.attachParams;
|
||||
|
||||
const previouslyAttached = this.viewInstanceId != null;
|
||||
this.viewInstanceId = params.instanceId;
|
||||
|
||||
// Only load URL and set size on first attach
|
||||
if (previouslyAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
embedder.emit('did-attach-webview', event, guest);
|
||||
});
|
||||
|
||||
const sendToEmbedder = (channel: string, ...args: any[]) => {
|
||||
if (!embedder.isDestroyed()) {
|
||||
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
// Dispatch events to embedder.
|
||||
const fn = function (event: string) {
|
||||
guest.on(event as any, function (_, ...args: any[]) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, event, ...args);
|
||||
});
|
||||
};
|
||||
for (const event of supportedWebViewEvents) {
|
||||
if (event !== 'new-window') {
|
||||
fn(event);
|
||||
}
|
||||
}
|
||||
|
||||
guest.on('new-window', function (event, url, frameName, disposition, options, additionalFeatures, referrer) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'new-window', url,
|
||||
frameName, disposition, sanitizeOptionsForGuest(options),
|
||||
additionalFeatures, referrer);
|
||||
});
|
||||
|
||||
// Dispatch guest's IPC messages to embedder.
|
||||
guest.on('ipc-message-host' as any, function (event: Electron.IpcMainEvent, channel: string, args: any[]) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_IPC_MESSAGE, {
|
||||
frameId: [event.processId, event.frameId],
|
||||
channel,
|
||||
args
|
||||
});
|
||||
});
|
||||
|
||||
// Notify guest of embedder window visibility when it is ready
|
||||
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
|
||||
guest.on('dom-ready', function () {
|
||||
const guestInstance = guestInstances.get(guestInstanceId);
|
||||
if (guestInstance != null && guestInstance.visibilityState != null) {
|
||||
guest._sendInternal(IPC_MESSAGES.GUEST_INSTANCE_VISIBILITY_CHANGE, guestInstance.visibilityState);
|
||||
}
|
||||
});
|
||||
|
||||
return guestInstanceId;
|
||||
};
|
||||
|
||||
// Attach the guest to an element of embedder.
|
||||
const attachGuest = function (event: Electron.IpcMainInvokeEvent,
|
||||
embedderFrameId: number, elementInstanceId: number, guestInstanceId: number, params: Record<string, any>) {
|
||||
const embedder = event.sender;
|
||||
// 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;
|
||||
}
|
||||
|
||||
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) {
|
||||
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
|
||||
}
|
||||
const { guest } = guestInstance;
|
||||
if (guest.hostWebContents !== embedder) {
|
||||
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
|
||||
}
|
||||
|
||||
// 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 !== params.instanceId) {
|
||||
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId);
|
||||
guestInstance.embedder._sendInternal(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DESTROY_GUEST}-${guest.viewInstanceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
// parse the 'webpreferences' attribute string, if set
|
||||
// this uses the same parsing rules as window.open uses for its features
|
||||
const parsedWebPreferences =
|
||||
@@ -37,6 +169,7 @@ function makeWebPreferences (embedder: Electron.WebContents, params: Record<stri
|
||||
: null;
|
||||
|
||||
const webPreferences: Electron.WebPreferences = {
|
||||
guestInstanceId,
|
||||
nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
|
||||
nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false,
|
||||
plugins: params.plugins,
|
||||
@@ -64,176 +197,21 @@ function makeWebPreferences (embedder: Electron.WebContents, params: Record<stri
|
||||
]);
|
||||
|
||||
// Inherit certain option values from embedder
|
||||
const lastWebPreferences = embedder.getLastWebPreferences()!;
|
||||
const lastWebPreferences = embedder.getLastWebPreferences();
|
||||
for (const [name, value] of inheritedWebPreferences) {
|
||||
if (lastWebPreferences[name as keyof Electron.WebPreferences] === value) {
|
||||
(webPreferences as any)[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
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>) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const guest = (webContents as typeof ElectronInternal.WebContents).create({
|
||||
type: 'webview',
|
||||
partition: params.partition,
|
||||
embedder
|
||||
});
|
||||
const guestInstanceId = guest.id;
|
||||
guestInstances.set(guestInstanceId, {
|
||||
guest,
|
||||
embedder
|
||||
});
|
||||
|
||||
// Clear the guest from map when it is destroyed.
|
||||
guest.once('destroyed', () => {
|
||||
if (guestInstances.has(guestInstanceId)) {
|
||||
detachGuest(embedder, guestInstanceId);
|
||||
}
|
||||
});
|
||||
|
||||
// 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;
|
||||
|
||||
// Only load URL and set size on first attach
|
||||
if (previouslyAttached) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.src) {
|
||||
this.loadURL(params.src, params.opts);
|
||||
}
|
||||
embedder.emit('did-attach-webview', event, guest);
|
||||
});
|
||||
|
||||
const sendToEmbedder = (channel: string, ...args: any[]) => {
|
||||
if (!embedder.isDestroyed()) {
|
||||
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args);
|
||||
}
|
||||
};
|
||||
|
||||
const makeProps = (eventKey: string, args: any[]) => {
|
||||
const props: Record<string, any> = {};
|
||||
webViewEvents[eventKey].forEach((prop, index) => {
|
||||
props[prop] = args[index];
|
||||
});
|
||||
return props;
|
||||
};
|
||||
|
||||
// Dispatch events to embedder.
|
||||
for (const event of supportedWebViewEvents) {
|
||||
guest.on(event as any, function (_, ...args: any[]) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, event, makeProps(event, args));
|
||||
});
|
||||
}
|
||||
|
||||
guest.on('new-window', function (event, url, frameName, disposition, options) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'new-window', {
|
||||
url,
|
||||
frameName,
|
||||
disposition,
|
||||
options: sanitizeOptionsForGuest(options)
|
||||
});
|
||||
});
|
||||
|
||||
// Dispatch guest's IPC messages to embedder.
|
||||
guest.on('ipc-message-host' as any, function (event: Electron.IpcMainEvent, channel: string, args: any[]) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'ipc-message', {
|
||||
frameId: [event.processId, event.frameId],
|
||||
channel,
|
||||
args
|
||||
});
|
||||
});
|
||||
|
||||
// Notify guest of embedder window visibility when it is ready
|
||||
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
|
||||
guest.on('dom-ready', function () {
|
||||
const guestInstance = guestInstances.get(guestInstanceId);
|
||||
if (guestInstance != null && guestInstance.visibilityState != null) {
|
||||
guest._sendInternal(IPC_MESSAGES.GUEST_INSTANCE_VISIBILITY_CHANGE, guestInstance.visibilityState);
|
||||
}
|
||||
});
|
||||
|
||||
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;
|
||||
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId;
|
||||
guest.destroy();
|
||||
return false;
|
||||
return;
|
||||
}
|
||||
|
||||
guest.attachParams = { instanceId, src: params.src, opts: makeLoadURLOptions(params) };
|
||||
guest.attachParams = params;
|
||||
embedderElementsMap.set(key, guestInstanceId);
|
||||
|
||||
guest.setEmbedder(embedder);
|
||||
@@ -244,7 +222,6 @@ const attachGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
|
||||
webViewManager.addGuest(guestInstanceId, embedder, guest, webPreferences);
|
||||
guest.attachToIframe(embedder, embedderFrameId);
|
||||
return true;
|
||||
};
|
||||
|
||||
// Remove an guest-embedder relationship.
|
||||
@@ -329,8 +306,16 @@ const handleMessageSync = function (channel: string, handler: (event: ElectronIn
|
||||
ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler));
|
||||
};
|
||||
|
||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST, function (event, embedderFrameId: number, elementInstanceId: number, params) {
|
||||
return createGuest(event.sender, embedderFrameId, elementInstanceId, params);
|
||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_GUEST, function (event, params) {
|
||||
return createGuest(event.sender, params);
|
||||
});
|
||||
|
||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_ATTACH_GUEST, function (event, embedderFrameId: number, elementInstanceId: number, guestInstanceId: number, params) {
|
||||
try {
|
||||
attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params);
|
||||
} catch (error) {
|
||||
console.error(`Guest attach failed: ${error}`);
|
||||
}
|
||||
});
|
||||
|
||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, function (event, guestInstanceId: number) {
|
||||
@@ -378,6 +363,12 @@ handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET, function (event,
|
||||
(guest as any)[property] = val;
|
||||
});
|
||||
|
||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CAPTURE_PAGE, async function (event, guestInstanceId: number, args: any[]) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
|
||||
return serialize(await guest.capturePage(...args));
|
||||
});
|
||||
|
||||
// Returns WebContents from its guest id hosted in given webContents.
|
||||
const getGuestForWebContents = function (guestInstanceId: number, contents: Electron.WebContents) {
|
||||
const guestInstance = guestInstances.get(guestInstanceId);
|
||||
|
||||
@@ -149,7 +149,7 @@ function emitDeprecatedNewWindowEvent ({ event, embedder, guest, windowOpenArgs,
|
||||
postData?: PostData,
|
||||
}): boolean {
|
||||
const { url, frameName } = windowOpenArgs;
|
||||
const isWebViewWithPopupsDisabled = embedder.getType() === 'webview' && embedder.getLastWebPreferences()!.disablePopups;
|
||||
const isWebViewWithPopupsDisabled = embedder.getType() === 'webview' && embedder.getLastWebPreferences().disablePopups;
|
||||
const postBody = postData ? {
|
||||
data: postData,
|
||||
...parseContentTypeFormat(postData)
|
||||
@@ -238,7 +238,7 @@ export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {
|
||||
// have unvetted prefs, use parsedWebPreferences.
|
||||
secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'],
|
||||
}) {
|
||||
const parentWebPreferences = embedder.getLastWebPreferences()!;
|
||||
const parentWebPreferences = embedder.getLastWebPreferences();
|
||||
const securityWebPreferencesFromParent = (Object.keys(securityWebPreferences).reduce((map, key) => {
|
||||
if (securityWebPreferences[key] === parentWebPreferences[key as keyof Electron.WebPreferences]) {
|
||||
(map as any)[key] = parentWebPreferences[key as keyof Electron.WebPreferences];
|
||||
|
||||
@@ -28,7 +28,7 @@ const getGuestWindow = function (guestContents: WebContents) {
|
||||
};
|
||||
|
||||
const isChildWindow = function (sender: WebContents, target: WebContents) {
|
||||
return target.getLastWebPreferences()!.openerId === sender.id;
|
||||
return target.getLastWebPreferences().openerId === sender.id;
|
||||
};
|
||||
|
||||
const isRelatedWindow = function (sender: WebContents, target: WebContents) {
|
||||
@@ -43,7 +43,7 @@ const isScriptableWindow = function (sender: WebContents, target: WebContents) {
|
||||
};
|
||||
|
||||
const isNodeIntegrationEnabled = function (sender: WebContents) {
|
||||
return sender.getLastWebPreferences()!.nodeIntegration === true;
|
||||
return sender.getLastWebPreferences().nodeIntegration === true;
|
||||
};
|
||||
|
||||
// Checks whether |sender| can access the |target|:
|
||||
@@ -65,7 +65,7 @@ ipcMainInternal.on(
|
||||
features: string
|
||||
) => {
|
||||
// This should only be allowed for senders that have nativeWindowOpen: false
|
||||
const lastWebPreferences = event.sender.getLastWebPreferences()!;
|
||||
const lastWebPreferences = event.sender.getLastWebPreferences();
|
||||
if (lastWebPreferences.nativeWindowOpen || lastWebPreferences.sandbox) {
|
||||
event.returnValue = null;
|
||||
throw new Error(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { clipboard } from 'electron/common';
|
||||
import * as fs from 'fs';
|
||||
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
|
||||
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
||||
import * as typeUtils from '@electron/internal/common/type-utils';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
|
||||
import type * as desktopCapturerModule from '@electron/internal/browser/desktop-capturer';
|
||||
@@ -59,7 +60,7 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_CLIPBOARD_SYNC, function (event, me
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
|
||||
return (clipboard as any)[method](...args);
|
||||
return typeUtils.serialize((clipboard as any)[method](...typeUtils.deserialize(args)));
|
||||
});
|
||||
|
||||
if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
|
||||
@@ -74,7 +75,7 @@ if (BUILDFLAG(ENABLE_DESKTOP_CAPTURER)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return await desktopCapturer.getSourcesImpl(event.sender, options);
|
||||
return typeUtils.serialize(await desktopCapturer.getSourcesImpl(event.sender, options));
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
|
||||
import type * as ipcRendererUtilsModule from '@electron/internal/renderer/ipc-renderer-internal-utils';
|
||||
import type * as typeUtilsModule from '@electron/internal/common/type-utils';
|
||||
|
||||
const clipboard = process._linkedBinding('electron_common_clipboard');
|
||||
|
||||
if (process.type === 'renderer') {
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
|
||||
const typeUtils = require('@electron/internal/common/type-utils') as typeof typeUtilsModule;
|
||||
|
||||
const makeRemoteMethod = function (method: keyof Electron.Clipboard): any {
|
||||
return (...args: any[]) => ipcRendererUtils.invokeSync(IPC_MESSAGES.BROWSER_CLIPBOARD_SYNC, method, ...args);
|
||||
const makeRemoteMethod = function (method: keyof Electron.Clipboard) {
|
||||
return (...args: any[]) => {
|
||||
args = typeUtils.serialize(args);
|
||||
const result = ipcRendererUtils.invokeSync(IPC_MESSAGES.BROWSER_CLIPBOARD_SYNC, method, ...args);
|
||||
return typeUtils.deserialize(result);
|
||||
};
|
||||
};
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// Common modules, please sort alphabetically
|
||||
export const commonModuleList: ElectronInternal.ModuleEntry[] = [
|
||||
{ name: 'clipboard', loader: () => require('./clipboard') },
|
||||
{ name: 'nativeImage', loader: () => require('./native-image') },
|
||||
{ name: 'shell', loader: () => require('./shell') },
|
||||
// The internal modules, invisible unless you know their names.
|
||||
{ name: 'deprecate', loader: () => require('./deprecate'), private: true }
|
||||
|
||||
@@ -10,11 +10,14 @@ export const enum IPC_MESSAGES {
|
||||
|
||||
GUEST_VIEW_INTERNAL_DESTROY_GUEST = 'GUEST_VIEW_INTERNAL_DESTROY_GUEST',
|
||||
GUEST_VIEW_INTERNAL_DISPATCH_EVENT = 'GUEST_VIEW_INTERNAL_DISPATCH_EVENT',
|
||||
GUEST_VIEW_INTERNAL_IPC_MESSAGE = 'GUEST_VIEW_INTERNAL_IPC_MESSAGE',
|
||||
|
||||
GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST = 'GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST',
|
||||
GUEST_VIEW_MANAGER_CREATE_GUEST = 'GUEST_VIEW_MANAGER_CREATE_GUEST',
|
||||
GUEST_VIEW_MANAGER_ATTACH_GUEST = 'GUEST_VIEW_MANAGER_ATTACH_GUEST',
|
||||
GUEST_VIEW_MANAGER_DETACH_GUEST = 'GUEST_VIEW_MANAGER_DETACH_GUEST',
|
||||
GUEST_VIEW_MANAGER_FOCUS_CHANGE = 'GUEST_VIEW_MANAGER_FOCUS_CHANGE',
|
||||
GUEST_VIEW_MANAGER_CALL = 'GUEST_VIEW_MANAGER_CALL',
|
||||
GUEST_VIEW_MANAGER_CAPTURE_PAGE = 'GUEST_VIEW_MANAGER_CAPTURE_PAGE',
|
||||
GUEST_VIEW_MANAGER_PROPERTY_GET = 'GUEST_VIEW_MANAGER_PROPERTY_GET',
|
||||
GUEST_VIEW_MANAGER_PROPERTY_SET = 'GUEST_VIEW_MANAGER_PROPERTY_SET',
|
||||
|
||||
|
||||
110
lib/common/type-utils.ts
Normal file
110
lib/common/type-utils.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
function getCreateNativeImage () {
|
||||
return process._linkedBinding('electron_common_native_image').nativeImage.createEmpty;
|
||||
}
|
||||
|
||||
export function isPromise (val: any) {
|
||||
return (
|
||||
val &&
|
||||
val.then &&
|
||||
val.then instanceof Function &&
|
||||
val.constructor &&
|
||||
val.constructor.reject &&
|
||||
val.constructor.reject instanceof Function &&
|
||||
val.constructor.resolve &&
|
||||
val.constructor.resolve instanceof Function
|
||||
);
|
||||
}
|
||||
|
||||
const serializableTypes = [
|
||||
Boolean,
|
||||
Number,
|
||||
String,
|
||||
Date,
|
||||
Error,
|
||||
RegExp,
|
||||
ArrayBuffer
|
||||
];
|
||||
|
||||
// https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm#Supported_types
|
||||
export function isSerializableObject (value: any) {
|
||||
return value === null || ArrayBuffer.isView(value) || serializableTypes.some(type => value instanceof type);
|
||||
}
|
||||
|
||||
const objectMap = function (source: Object, mapper: (value: any) => any) {
|
||||
const sourceEntries = Object.entries(source);
|
||||
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]);
|
||||
return Object.fromEntries(targetEntries);
|
||||
};
|
||||
|
||||
function serializeNativeImage (image: Electron.NativeImage) {
|
||||
const representations = [];
|
||||
const scaleFactors = image.getScaleFactors();
|
||||
|
||||
// Use Buffer when there's only one representation for better perf.
|
||||
// This avoids compressing to/from PNG where it's not necessary to
|
||||
// ensure uniqueness of dataURLs (since there's only one).
|
||||
if (scaleFactors.length === 1) {
|
||||
const scaleFactor = scaleFactors[0];
|
||||
const size = image.getSize(scaleFactor);
|
||||
const buffer = image.toBitmap({ scaleFactor });
|
||||
representations.push({ scaleFactor, size, buffer });
|
||||
} else {
|
||||
// Construct from dataURLs to ensure that they are not lost in creation.
|
||||
for (const scaleFactor of scaleFactors) {
|
||||
const size = image.getSize(scaleFactor);
|
||||
const dataURL = image.toDataURL({ scaleFactor });
|
||||
representations.push({ scaleFactor, size, dataURL });
|
||||
}
|
||||
}
|
||||
return { __ELECTRON_SERIALIZED_NativeImage__: true, representations };
|
||||
}
|
||||
|
||||
function deserializeNativeImage (value: any, createNativeImage: typeof Electron.nativeImage['createEmpty']) {
|
||||
const image = createNativeImage();
|
||||
|
||||
// Use Buffer when there's only one representation for better perf.
|
||||
// This avoids compressing to/from PNG where it's not necessary to
|
||||
// ensure uniqueness of dataURLs (since there's only one).
|
||||
if (value.representations.length === 1) {
|
||||
const { buffer, size, scaleFactor } = value.representations[0];
|
||||
const { width, height } = size;
|
||||
image.addRepresentation({ buffer, scaleFactor, width, height });
|
||||
} else {
|
||||
// Construct from dataURLs to ensure that they are not lost in creation.
|
||||
for (const rep of value.representations) {
|
||||
const { dataURL, size, scaleFactor } = rep;
|
||||
const { width, height } = size;
|
||||
image.addRepresentation({ dataURL, scaleFactor, width, height });
|
||||
}
|
||||
}
|
||||
|
||||
return image;
|
||||
}
|
||||
|
||||
export function serialize (value: any): any {
|
||||
if (value && value.constructor && value.constructor.name === 'NativeImage') {
|
||||
return serializeNativeImage(value);
|
||||
} if (Array.isArray(value)) {
|
||||
return value.map(serialize);
|
||||
} else if (isSerializableObject(value)) {
|
||||
return value;
|
||||
} else if (value instanceof Object) {
|
||||
return objectMap(value, serialize);
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export function deserialize (value: any, createNativeImage: typeof Electron.nativeImage['createEmpty'] = getCreateNativeImage()): any {
|
||||
if (value && value.__ELECTRON_SERIALIZED_NativeImage__) {
|
||||
return deserializeNativeImage(value, createNativeImage);
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.map(value => deserialize(value, createNativeImage));
|
||||
} else if (isSerializableObject(value)) {
|
||||
return value;
|
||||
} else if (value instanceof Object) {
|
||||
return objectMap(value, value => deserialize(value, createNativeImage));
|
||||
} else {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@ export const webViewEvents: Record<string, readonly string[]> = {
|
||||
'devtools-opened': [],
|
||||
'devtools-closed': [],
|
||||
'devtools-focused': [],
|
||||
'new-window': ['url', 'frameName', 'disposition', 'options'],
|
||||
'will-navigate': ['url'],
|
||||
'did-start-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
|
||||
'did-redirect-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
|
||||
|
||||
@@ -59,7 +59,6 @@ export const properties = new Set([
|
||||
]);
|
||||
|
||||
export const asyncMethods = new Set([
|
||||
'capturePage',
|
||||
'loadURL',
|
||||
'executeJavaScript',
|
||||
'insertCSS',
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
|
||||
import deprecate from '@electron/internal/common/api/deprecate';
|
||||
import { deserialize } from '@electron/internal/common/type-utils';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
|
||||
const { hasSwitch } = process._linkedBinding('electron_common_command_line');
|
||||
@@ -14,11 +14,6 @@ function getCurrentStack () {
|
||||
return (target as any).stack;
|
||||
}
|
||||
|
||||
let warned = process.noDeprecation;
|
||||
export async function getSources (options: Electron.SourcesOptions) {
|
||||
if (!warned) {
|
||||
deprecate.log('The use of \'desktopCapturer.getSources\' in the renderer process is deprecated and will be removed. See https://www.electronjs.org/docs/breaking-changes#removed-desktopcapturergetsources-in-the-renderer for more details.');
|
||||
warned = true;
|
||||
}
|
||||
return await ipcRendererInternal.invoke(IPC_MESSAGES.DESKTOP_CAPTURER_GET_SOURCES, options, getCurrentStack());
|
||||
return deserialize(await ipcRendererInternal.invoke(IPC_MESSAGES.DESKTOP_CAPTURER_GET_SOURCES, options, getCurrentStack()));
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ export const rendererModuleList: ElectronInternal.ModuleEntry[] = [
|
||||
{ name: 'contextBridge', loader: () => require('./context-bridge') },
|
||||
{ name: 'crashReporter', loader: () => require('./crash-reporter') },
|
||||
{ name: 'ipcRenderer', loader: () => require('./ipc-renderer') },
|
||||
{ name: 'nativeImage', loader: () => require('./native-image') },
|
||||
{ name: 'webFrame', loader: () => require('./web-frame') }
|
||||
];
|
||||
|
||||
|
||||
3
lib/renderer/api/native-image.ts
Normal file
3
lib/renderer/api/native-image.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
const { nativeImage } = process._linkedBinding('electron_common_native_image');
|
||||
|
||||
export default nativeImage;
|
||||
@@ -78,7 +78,7 @@ const isHiddenPage = mainFrame.getWebPreference('hiddenPage');
|
||||
const usesNativeWindowOpen = mainFrame.getWebPreference('nativeWindowOpen');
|
||||
const preloadScript = mainFrame.getWebPreference('preload');
|
||||
const preloadScripts = mainFrame.getWebPreference('preloadScripts');
|
||||
const isWebView = mainFrame.getWebPreference('isWebView');
|
||||
const guestInstanceId = mainFrame.getWebPreference('guestInstanceId');
|
||||
const openerId = mainFrame.getWebPreference('openerId');
|
||||
const appPath = hasSwitch('app-path') ? getSwitchValue('app-path') : null;
|
||||
|
||||
@@ -102,14 +102,14 @@ switch (window.location.protocol) {
|
||||
default: {
|
||||
// Override default web functions.
|
||||
const { windowSetup } = require('@electron/internal/renderer/window-setup') as typeof windowSetupModule;
|
||||
windowSetup(isWebView, openerId, isHiddenPage, usesNativeWindowOpen);
|
||||
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen);
|
||||
}
|
||||
}
|
||||
|
||||
// Load webview tag implementation.
|
||||
if (process.isMainFrame) {
|
||||
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init') as typeof webViewInitModule;
|
||||
webViewInit(contextIsolation, webviewTag, isWebView);
|
||||
webViewInit(contextIsolation, webviewTag, guestInstanceId);
|
||||
}
|
||||
|
||||
if (nodeIntegration) {
|
||||
|
||||
@@ -77,8 +77,15 @@ const isLocalhost = function () {
|
||||
*
|
||||
* @returns {boolean} Is a CSP with `unsafe-eval` set?
|
||||
*/
|
||||
const isUnsafeEvalEnabled = () => {
|
||||
return webFrame._isEvalAllowed();
|
||||
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 moreInformation = `\nFor more information and help, consult
|
||||
@@ -167,14 +174,16 @@ const warnAboutDisabledWebSecurity = function (webPreferences?: Electron.WebPref
|
||||
* Logs a warning message about unset or insecure CSP
|
||||
*/
|
||||
const warnAboutInsecureCSP = function () {
|
||||
if (!isUnsafeEvalEnabled()) return;
|
||||
isUnsafeEvalEnabled().then((enabled) => {
|
||||
if (!enabled) 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);
|
||||
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
|
||||
'font-weight: bold;', warning);
|
||||
}).catch(() => {});
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
|
||||
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils';
|
||||
import { webViewEvents } from '@electron/internal/common/web-view-events';
|
||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||
|
||||
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
|
||||
@@ -13,27 +14,45 @@ const DEPRECATED_EVENTS: Record<string, string> = {
|
||||
'page-title-updated': 'page-title-set'
|
||||
} as const;
|
||||
|
||||
const dispatchEvent = function (delegate: GuestViewDelegate, eventName: string, eventKey: string, ...args: Array<any>) {
|
||||
if (DEPRECATED_EVENTS[eventName] != null) {
|
||||
dispatchEvent(delegate, DEPRECATED_EVENTS[eventName], eventKey, ...args);
|
||||
}
|
||||
|
||||
const props: Record<string, any> = {};
|
||||
webViewEvents[eventKey].forEach((prop, index) => {
|
||||
props[prop] = args[index];
|
||||
});
|
||||
|
||||
delegate.dispatchEvent(eventName, props);
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`, function (event, eventName, ...args) {
|
||||
dispatchEvent(delegate, eventName, eventName, ...args);
|
||||
});
|
||||
|
||||
delegate.dispatchEvent(eventName, props);
|
||||
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_IPC_MESSAGE}-${viewInstanceId}`, function (event, data) {
|
||||
delegate.dispatchEvent('ipc-message', data);
|
||||
});
|
||||
}
|
||||
|
||||
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}`);
|
||||
ipcRendererInternal.removeAllListeners(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_IPC_MESSAGE}-${viewInstanceId}`);
|
||||
}
|
||||
|
||||
export function createGuest (iframe: HTMLIFrameElement, elementInstanceId: number, params: Record<string, any>): Promise<number> {
|
||||
export function createGuest (params: Record<string, any>): Promise<number> {
|
||||
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_GUEST, params);
|
||||
}
|
||||
|
||||
export function attachGuest (iframe: HTMLIFrameElement, elementInstanceId: number, guestInstanceId: number, params: Record<string, any>) {
|
||||
if (!(iframe instanceof HTMLIFrameElement)) {
|
||||
throw new Error('Invalid embedder frame');
|
||||
}
|
||||
@@ -43,13 +62,17 @@ export function createGuest (iframe: HTMLIFrameElement, elementInstanceId: numbe
|
||||
throw new Error('Invalid embedder frame');
|
||||
}
|
||||
|
||||
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST, embedderFrameId, elementInstanceId, params);
|
||||
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_ATTACH_GUEST, embedderFrameId, elementInstanceId, guestInstanceId, params);
|
||||
}
|
||||
|
||||
export function detachGuest (guestInstanceId: number) {
|
||||
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, guestInstanceId);
|
||||
}
|
||||
|
||||
export function capturePage (guestInstanceId: number, args: any[]) {
|
||||
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CAPTURE_PAGE, guestInstanceId, args);
|
||||
}
|
||||
|
||||
export function invoke (guestInstanceId: number, method: string, args: any[]) {
|
||||
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, guestInstanceId, method, args);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-vie
|
||||
import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods';
|
||||
import type { WebViewAttribute, PartitionAttribute } from '@electron/internal/renderer/web-view/web-view-attributes';
|
||||
import { setupWebViewAttributes } from '@electron/internal/renderer/web-view/web-view-attributes';
|
||||
import { deserialize } from '@electron/internal/common/type-utils';
|
||||
|
||||
// ID generator.
|
||||
let nextId = 0;
|
||||
@@ -15,6 +16,7 @@ export interface WebViewImplHooks {
|
||||
readonly guestViewInternal: typeof guestViewInternalModule;
|
||||
readonly allowGuestViewElementDefinition: NodeJS.InternalWebFrame['allowGuestViewElementDefinition'];
|
||||
readonly setIsWebView: (iframe: HTMLIFrameElement) => void;
|
||||
readonly createNativeImage?: typeof Electron.nativeImage['createEmpty'];
|
||||
}
|
||||
|
||||
// Represents the internal state of the WebView node.
|
||||
@@ -25,6 +27,7 @@ export class WebViewImpl {
|
||||
public hasFocus = false
|
||||
public internalInstanceId?: number;
|
||||
public resizeObserver?: ResizeObserver;
|
||||
public userAgentOverride?: string;
|
||||
public viewInstanceId: number
|
||||
|
||||
// on* Event handlers.
|
||||
@@ -112,11 +115,9 @@ export class WebViewImpl {
|
||||
}
|
||||
|
||||
createGuest () {
|
||||
this.internalInstanceId = getNextId();
|
||||
this.hooks.guestViewInternal.createGuest(this.internalElement, this.internalInstanceId, this.buildParams())
|
||||
.then(guestInstanceId => {
|
||||
this.attachGuestInstance(guestInstanceId);
|
||||
});
|
||||
this.hooks.guestViewInternal.createGuest(this.buildParams()).then(guestInstanceId => {
|
||||
this.attachGuestInstance(guestInstanceId);
|
||||
});
|
||||
}
|
||||
|
||||
dispatchEvent (eventName: string, props: Record<string, any> = {}) {
|
||||
@@ -179,7 +180,8 @@ export class WebViewImpl {
|
||||
|
||||
buildParams () {
|
||||
const params: Record<string, any> = {
|
||||
instanceId: this.viewInstanceId
|
||||
instanceId: this.viewInstanceId,
|
||||
userAgentOverride: this.userAgentOverride
|
||||
};
|
||||
|
||||
for (const [attributeName, attribute] of this.attributes) {
|
||||
@@ -190,19 +192,20 @@ export class WebViewImpl {
|
||||
}
|
||||
|
||||
attachGuestInstance (guestInstanceId: number) {
|
||||
if (guestInstanceId === -1) {
|
||||
// Do nothing
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.elementAttached) {
|
||||
// The element could be detached before we got response from browser.
|
||||
// Destroy the backing webContents to avoid any zombie nodes in the frame tree.
|
||||
this.hooks.guestViewInternal.detachGuest(guestInstanceId);
|
||||
return;
|
||||
}
|
||||
|
||||
this.internalInstanceId = getNextId();
|
||||
this.guestInstanceId = guestInstanceId;
|
||||
|
||||
this.hooks.guestViewInternal.attachGuest(
|
||||
this.internalElement,
|
||||
this.internalInstanceId,
|
||||
this.guestInstanceId,
|
||||
this.buildParams()
|
||||
);
|
||||
|
||||
// TODO(zcbenz): Should we deprecate the "resize" event? Wait, it is not
|
||||
// even documented.
|
||||
this.resizeObserver = new ResizeObserver(this.onElementResize.bind(this));
|
||||
@@ -231,6 +234,10 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem
|
||||
};
|
||||
}
|
||||
|
||||
WebViewElement.prototype.capturePage = async function (...args) {
|
||||
return deserialize(await hooks.guestViewInternal.capturePage(this.getWebContentsId(), args), hooks.createNativeImage);
|
||||
};
|
||||
|
||||
const createPropertyGetter = function (property: string) {
|
||||
return function (this: ElectronInternal.WebViewElement) {
|
||||
return hooks.guestViewInternal.propertyGet(this.getWebContentsId(), property);
|
||||
|
||||
@@ -20,9 +20,9 @@ function handleFocusBlur () {
|
||||
});
|
||||
}
|
||||
|
||||
export function webViewInit (contextIsolation: boolean, webviewTag: boolean, isWebView: boolean) {
|
||||
export function webViewInit (contextIsolation: boolean, webviewTag: boolean, guestInstanceId: number) {
|
||||
// Don't allow recursive `<webview>`.
|
||||
if (webviewTag && !isWebView) {
|
||||
if (webviewTag && !guestInstanceId) {
|
||||
const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal') as typeof guestViewInternalModule;
|
||||
if (contextIsolation) {
|
||||
v8Util.setHiddenValue(window, 'guestViewInternal', guestViewInternal);
|
||||
@@ -36,7 +36,7 @@ export function webViewInit (contextIsolation: boolean, webviewTag: boolean, isW
|
||||
}
|
||||
}
|
||||
|
||||
if (isWebView) {
|
||||
if (guestInstanceId) {
|
||||
// Report focus/blur events of webview to browser.
|
||||
handleFocusBlur();
|
||||
}
|
||||
|
||||
@@ -243,8 +243,8 @@ class BrowserWindowProxy {
|
||||
}
|
||||
|
||||
export const windowSetup = (
|
||||
isWebView: boolean, openerId: number, isHiddenPage: boolean, usesNativeWindowOpen: boolean) => {
|
||||
if (!process.sandboxed && !isWebView) {
|
||||
guestInstanceId: number, openerId: number, isHiddenPage: boolean, usesNativeWindowOpen: boolean) => {
|
||||
if (!process.sandboxed && !guestInstanceId) {
|
||||
// Override default window.close.
|
||||
window.close = function () {
|
||||
ipcRendererInternal.send(IPC_MESSAGES.BROWSER_WINDOW_CLOSE);
|
||||
@@ -318,7 +318,7 @@ export const windowSetup = (
|
||||
});
|
||||
}
|
||||
|
||||
if (isWebView) {
|
||||
if (guestInstanceId) {
|
||||
// Webview `document.visibilityState` tracks window visibility (and ignores
|
||||
// the actual <webview> element visibility) for backwards compatibility.
|
||||
// See discussion in #9178.
|
||||
|
||||
@@ -13,7 +13,7 @@ export const moduleList: ElectronInternal.ModuleEntry[] = [
|
||||
},
|
||||
{
|
||||
name: 'nativeImage',
|
||||
loader: () => require('@electron/internal/common/api/native-image')
|
||||
loader: () => require('@electron/internal/renderer/api/native-image')
|
||||
},
|
||||
{
|
||||
name: 'webFrame',
|
||||
|
||||
@@ -131,7 +131,7 @@ const contextIsolation = mainFrame.getWebPreference('contextIsolation');
|
||||
const webviewTag = mainFrame.getWebPreference('webviewTag');
|
||||
const isHiddenPage = mainFrame.getWebPreference('hiddenPage');
|
||||
const usesNativeWindowOpen = true;
|
||||
const isWebView = mainFrame.getWebPreference('isWebView');
|
||||
const guestInstanceId = mainFrame.getWebPreference('guestInstanceId');
|
||||
const openerId = mainFrame.getWebPreference('openerId');
|
||||
|
||||
switch (window.location.protocol) {
|
||||
@@ -149,14 +149,14 @@ switch (window.location.protocol) {
|
||||
default: {
|
||||
// Override default web functions.
|
||||
const { windowSetup } = require('@electron/internal/renderer/window-setup') as typeof windowSetupModule;
|
||||
windowSetup(isWebView, openerId, isHiddenPage, usesNativeWindowOpen);
|
||||
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen);
|
||||
}
|
||||
}
|
||||
|
||||
// Load webview tag implementation.
|
||||
if (process.isMainFrame) {
|
||||
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init') as typeof webViewInitModule;
|
||||
webViewInit(contextIsolation, webviewTag, isWebView);
|
||||
webViewInit(contextIsolation, webviewTag, guestInstanceId);
|
||||
}
|
||||
|
||||
// Wrap the script into a function executed in global scope. It won't have
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "16.2.2",
|
||||
"version": "15.3.6",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
@@ -56,8 +56,8 @@
|
||||
"minimist": "^1.2.5",
|
||||
"null-loader": "^4.0.0",
|
||||
"pre-flight": "^1.1.0",
|
||||
"remark-cli": "^10.0.0",
|
||||
"remark-preset-lint-markdown-style-guide": "^4.0.0",
|
||||
"remark-cli": "^4.0.0",
|
||||
"remark-preset-lint-markdown-style-guide": "^2.1.1",
|
||||
"semver": "^5.6.0",
|
||||
"shx": "^0.3.2",
|
||||
"standard-markdown": "^6.0.0",
|
||||
@@ -78,7 +78,7 @@
|
||||
"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 shell/ || (echo \"\\nCode not formatted correctly.\" && exit 1)",
|
||||
"lint:clang-format": "python script/run-clang-format.py -r -c chromium_src/ 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",
|
||||
|
||||
@@ -1,9 +1,5 @@
|
||||
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
|
||||
cherry-pick-bdffa0ea5148.patch
|
||||
cherry-pick-05e69c75905f.patch
|
||||
cherry-pick-891020ed64d4.patch
|
||||
cherry-pick-2b98abd8cb6c.patch
|
||||
cherry-pick-cc44ae61f37b.patch
|
||||
|
||||
118
patches/angle/cherry-pick-05e69c75905f.patch
Normal file
118
patches/angle/cherry-pick-05e69c75905f.patch
Normal file
@@ -0,0 +1,118 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Lingfeng Yang <lfy@google.com>
|
||||
Date: Wed, 1 Dec 2021 18:16:14 -0800
|
||||
Subject: M96: Vulkan: remove staged updates on storage set
|
||||
|
||||
Previously we would allow staged updates to bigger versions of a texture
|
||||
to go through even if the texture was redefined via glTexStorage*.
|
||||
|
||||
Bug: chromium:1262080
|
||||
Change-Id: I9d861fed68d4a1fdcd0777b97caf729cc74c595e
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3312718
|
||||
Reviewed-by: Charlie Lao <cclao@google.com>
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Commit-Queue: Lingfeng Yang <lfy@google.com>
|
||||
(cherry picked from commit 929c8ed4e8c3912cf027d843e7a2af47b21e5612)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3328001
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/TextureVk.cpp b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
index e0be9676912e49d70473ffdcd4d7b1549210be25..b43f41dbae29356f1ffe4f961ed83e0f0e9cdcd2 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/TextureVk.cpp
|
||||
@@ -1236,6 +1236,10 @@ angle::Result TextureVk::setStorageMultisample(const gl::Context *context,
|
||||
{
|
||||
releaseAndDeleteImageAndViews(contextVk);
|
||||
}
|
||||
+ else if (mImage)
|
||||
+ {
|
||||
+ mImage->releaseStagingBuffer(contextVk->getRenderer());
|
||||
+ }
|
||||
|
||||
const vk::Format &format = renderer->getFormat(internalformat);
|
||||
ANGLE_TRY(ensureImageAllocated(contextVk, format));
|
||||
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
|
||||
index e79bc700d4991752289ff3d7c76f2ccbb3ba932a..a3dec629c9ea96b276ff8bf344d4a4628ff20b3b 100644
|
||||
--- a/src/tests/gl_tests/TextureTest.cpp
|
||||
+++ b/src/tests/gl_tests/TextureTest.cpp
|
||||
@@ -9703,6 +9703,73 @@ void main()
|
||||
glUnmapBuffer(GL_SHADER_STORAGE_BUFFER);
|
||||
}
|
||||
|
||||
+class TextureChangeStorageUploadTest : public ANGLETest
|
||||
+{
|
||||
+ protected:
|
||||
+ TextureChangeStorageUploadTest()
|
||||
+ {
|
||||
+ setWindowWidth(256);
|
||||
+ setWindowHeight(256);
|
||||
+ setConfigRedBits(8);
|
||||
+ setConfigGreenBits(8);
|
||||
+ setConfigBlueBits(8);
|
||||
+ setConfigAlphaBits(8);
|
||||
+ }
|
||||
+
|
||||
+ void testSetUp() override
|
||||
+ {
|
||||
+ mProgram = CompileProgram(essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
|
||||
+ if (mProgram == 0)
|
||||
+ {
|
||||
+ FAIL() << "shader compilation failed.";
|
||||
+ }
|
||||
+
|
||||
+ mColorLocation = glGetUniformLocation(mProgram, essl1_shaders::ColorUniform());
|
||||
+
|
||||
+ glUseProgram(mProgram);
|
||||
+
|
||||
+ glClearColor(0, 0, 0, 0);
|
||||
+ glClearDepthf(0.0);
|
||||
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
+
|
||||
+ glEnable(GL_BLEND);
|
||||
+ glDisable(GL_DEPTH_TEST);
|
||||
+
|
||||
+ glGenTextures(1, &mTexture);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+ }
|
||||
+
|
||||
+ void testTearDown() override
|
||||
+ {
|
||||
+ glDeleteTextures(1, &mTexture);
|
||||
+ glDeleteProgram(mProgram);
|
||||
+ }
|
||||
+
|
||||
+ GLuint mProgram;
|
||||
+ GLint mColorLocation;
|
||||
+ GLuint mTexture;
|
||||
+};
|
||||
+
|
||||
+// Verify that respecifying storage and re-uploading doesn't crash.
|
||||
+TEST_P(TextureChangeStorageUploadTest, Basic)
|
||||
+{
|
||||
+ constexpr int kImageSize = 8; // 4 doesn't trip ASAN
|
||||
+ constexpr int kSmallerImageSize = kImageSize / 2;
|
||||
+ EXPECT_GT(kImageSize, kSmallerImageSize);
|
||||
+ EXPECT_GT(kSmallerImageSize / 2, 0);
|
||||
+
|
||||
+ std::array<GLColor, kImageSize * kImageSize> kColor;
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D, mTexture);
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, kImageSize, kImageSize, 0, GL_RGBA, GL_UNSIGNED_BYTE,
|
||||
+ kColor.data());
|
||||
+ glTexStorage2D(GL_TEXTURE_2D, 1, GL_RGBA8, kSmallerImageSize, kSmallerImageSize);
|
||||
+ // need partial update to sidestep optimizations that remove the full upload
|
||||
+ glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, kSmallerImageSize / 2, kSmallerImageSize / 2, GL_RGBA,
|
||||
+ GL_UNSIGNED_BYTE, kColor.data());
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
|
||||
// tests should be run against.
|
||||
#define ES2_EMULATE_COPY_TEX_IMAGE() \
|
||||
@@ -9816,4 +9883,6 @@ ANGLE_INSTANTIATE_TEST_ES31_AND(TextureBufferTestES31, WithDirectSPIRVGeneration
|
||||
GTEST_ALLOW_UNINSTANTIATED_PARAMETERIZED_TEST(CopyImageTestES31);
|
||||
ANGLE_INSTANTIATE_TEST_ES31_AND(CopyImageTestES31, WithDirectSPIRVGeneration(ES31_VULKAN()));
|
||||
|
||||
+ANGLE_INSTANTIATE_TEST_ES3(TextureChangeStorageUploadTest);
|
||||
+
|
||||
} // anonymous namespace
|
||||
@@ -1,94 +0,0 @@
|
||||
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);
|
||||
96
patches/angle/cherry-pick-2b98abd8cb6c.patch
Normal file
96
patches/angle/cherry-pick-2b98abd8cb6c.patch
Normal file
@@ -0,0 +1,96 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Date: Tue, 30 Nov 2021 23:48:30 -0500
|
||||
Subject: M96: Vulkan: Fix image respecify's usage tracking
|
||||
|
||||
When respecifying an image due to mip level count changes, the previous
|
||||
image is staged as an update to the new image. The resource usage info
|
||||
was not being transferred to the image being staged as an update,
|
||||
causing it to be prematurely deleted.
|
||||
|
||||
Test based on one authored by sugoi@google.com.
|
||||
|
||||
Bug: chromium:1270658
|
||||
Bug: angleproject:4835
|
||||
Change-Id: I9810f8940e0107bc8a04fa3fb9c26a045c0d689c
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3318257
|
||||
Reviewed-by: Lingfeng Yang <lfy@google.com>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/vulkan/ResourceVk.cpp b/src/libANGLE/renderer/vulkan/ResourceVk.cpp
|
||||
index efb04acf0fe5dc7c2d860ac8cc33dbe30691c4a3..e2cd1e3a96b1fa4e034efe6ed78ea1a787af6af6 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/ResourceVk.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/ResourceVk.cpp
|
||||
@@ -26,6 +26,12 @@ Resource::Resource(Resource &&other) : Resource()
|
||||
mUse = std::move(other.mUse);
|
||||
}
|
||||
|
||||
+Resource &Resource::operator=(Resource &&rhs)
|
||||
+{
|
||||
+ std::swap(mUse, rhs.mUse);
|
||||
+ return *this;
|
||||
+}
|
||||
+
|
||||
Resource::~Resource()
|
||||
{
|
||||
mUse.release();
|
||||
diff --git a/src/libANGLE/renderer/vulkan/ResourceVk.h b/src/libANGLE/renderer/vulkan/ResourceVk.h
|
||||
index 67440122bf7fa0f72b5412816853b2eddd770fd4..abab9900b7361c8564cb1ad30e0841eaf873ee2e 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/ResourceVk.h
|
||||
+++ b/src/libANGLE/renderer/vulkan/ResourceVk.h
|
||||
@@ -192,6 +192,7 @@ class Resource : angle::NonCopyable
|
||||
protected:
|
||||
Resource();
|
||||
Resource(Resource &&other);
|
||||
+ Resource &operator=(Resource &&rhs);
|
||||
|
||||
// Current resource lifetime.
|
||||
SharedResourceUse mUse;
|
||||
diff --git a/src/libANGLE/renderer/vulkan/vk_helpers.cpp b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
|
||||
index a06b60d9c2c91280b925b12825e104bdbb757c05..893144ff07760dc1608a4b2d3af565e23ef27ba4 100644
|
||||
--- a/src/libANGLE/renderer/vulkan/vk_helpers.cpp
|
||||
+++ b/src/libANGLE/renderer/vulkan/vk_helpers.cpp
|
||||
@@ -6140,6 +6140,9 @@ void ImageHelper::stageSelfAsSubresourceUpdates(ContextVk *contextVk,
|
||||
// Move the necessary information for staged update to work, and keep the rest as part of this
|
||||
// object.
|
||||
|
||||
+ // Usage info
|
||||
+ prevImage->get().Resource::operator=(std::move(*this));
|
||||
+
|
||||
// Vulkan objects
|
||||
prevImage->get().mImage = std::move(mImage);
|
||||
prevImage->get().mDeviceMemory = std::move(mDeviceMemory);
|
||||
diff --git a/src/tests/gl_tests/MipmapTest.cpp b/src/tests/gl_tests/MipmapTest.cpp
|
||||
index 4db00e78a7d2f7375fdcb9228fbdc83395973125..8a6d01ca36a84a9e294de3f6f0114ee7a54e1d9a 100644
|
||||
--- a/src/tests/gl_tests/MipmapTest.cpp
|
||||
+++ b/src/tests/gl_tests/MipmapTest.cpp
|
||||
@@ -2106,6 +2106,30 @@ TEST_P(MipmapTestES3, GenerateMipmapZeroSize)
|
||||
glGenerateMipmap(GL_TEXTURE_3D);
|
||||
}
|
||||
|
||||
+// Test that reducing the size of the mipchain by resizing the base image then deleting it doesn't
|
||||
+// cause a crash. Issue found by fuzzer.
|
||||
+TEST_P(MipmapTestES3, ResizeBaseMipTo1x1ThenDelete)
|
||||
+{
|
||||
+ GLTexture tex;
|
||||
+ glBindTexture(GL_TEXTURE_2D, tex);
|
||||
+
|
||||
+ std::vector<GLColor> data(2, GLColor::blue);
|
||||
+
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 2, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 1, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
|
||||
+
|
||||
+ clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
|
||||
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::blue);
|
||||
+
|
||||
+ data[0] = GLColor::green;
|
||||
+ glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, data.data());
|
||||
+
|
||||
+ clearAndDrawQuad(m2DProgram, getWindowWidth(), getWindowHeight());
|
||||
+
|
||||
+ tex.reset();
|
||||
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::green);
|
||||
+}
|
||||
+
|
||||
// Use this to select which configurations (e.g. which renderer, which GLES major version) these
|
||||
// tests should be run against.
|
||||
ANGLE_INSTANTIATE_TEST_ES2_AND_ES3(MipmapTest);
|
||||
@@ -1,381 +0,0 @@
|
||||
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)
|
||||
163
patches/angle/cherry-pick-891020ed64d4.patch
Normal file
163
patches/angle/cherry-pick-891020ed64d4.patch
Normal file
@@ -0,0 +1,163 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jonah Ryan-Davis <jonahr@google.com>
|
||||
Date: Mon, 22 Nov 2021 14:30:52 -0500
|
||||
Subject: Ignore the pixel unpack state for compressed textures.
|
||||
|
||||
From OpenGL ES 3 spec: All pixel storage modes are ignored when decoding
|
||||
a compressed texture image
|
||||
This was causing a bad access when calling compressedTexImage3D
|
||||
with GL_UNPACK_IMAGE_HEIGHT greater than the image height.
|
||||
|
||||
Bug: chromium:1267496
|
||||
Change-Id: I9b1f4c645548af64f2695fd23262225a1ad07cd7
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3296622
|
||||
Commit-Queue: Jonah Ryan-Davis <jonahr@google.com>
|
||||
Reviewed-by: Geoff Lang <geofflang@chromium.org>
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
(cherry picked from commit 870f458f507ff7ba0f67b28a30a27955ce79dd3e)
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3309097
|
||||
Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp
|
||||
index c4ce77d612d88a898f8a8164466d25b210e2144b..eb60719ac38543b608c122daebd71b1084ac35f2 100644
|
||||
--- a/src/libANGLE/Context.cpp
|
||||
+++ b/src/libANGLE/Context.cpp
|
||||
@@ -4961,7 +4961,9 @@ void Context::compressedTexImage2D(TextureTarget target,
|
||||
|
||||
Extents size(width, height, 1);
|
||||
Texture *texture = getTextureByTarget(target);
|
||||
- ANGLE_CONTEXT_TRY(texture->setCompressedImage(this, mState.getUnpackState(), target, level,
|
||||
+ // From OpenGL ES 3 spec: All pixel storage modes are ignored when decoding a compressed texture
|
||||
+ // image. So we use an empty PixelUnpackState.
|
||||
+ ANGLE_CONTEXT_TRY(texture->setCompressedImage(this, PixelUnpackState(), target, level,
|
||||
internalformat, size, imageSize,
|
||||
static_cast<const uint8_t *>(data)));
|
||||
}
|
||||
@@ -4993,7 +4995,9 @@ void Context::compressedTexImage3D(TextureTarget target,
|
||||
|
||||
Extents size(width, height, depth);
|
||||
Texture *texture = getTextureByTarget(target);
|
||||
- ANGLE_CONTEXT_TRY(texture->setCompressedImage(this, mState.getUnpackState(), target, level,
|
||||
+ // From OpenGL ES 3 spec: All pixel storage modes are ignored when decoding a compressed texture
|
||||
+ // image. So we use an empty PixelUnpackState.
|
||||
+ ANGLE_CONTEXT_TRY(texture->setCompressedImage(this, PixelUnpackState(), target, level,
|
||||
internalformat, size, imageSize,
|
||||
static_cast<const uint8_t *>(data)));
|
||||
}
|
||||
@@ -5027,8 +5031,10 @@ void Context::compressedTexSubImage2D(TextureTarget target,
|
||||
|
||||
Box area(xoffset, yoffset, 0, width, height, 1);
|
||||
Texture *texture = getTextureByTarget(target);
|
||||
- ANGLE_CONTEXT_TRY(texture->setCompressedSubImage(this, mState.getUnpackState(), target, level,
|
||||
- area, format, imageSize,
|
||||
+ // From OpenGL ES 3 spec: All pixel storage modes are ignored when decoding a compressed texture
|
||||
+ // image. So we use an empty PixelUnpackState.
|
||||
+ ANGLE_CONTEXT_TRY(texture->setCompressedSubImage(this, PixelUnpackState(), target, level, area,
|
||||
+ format, imageSize,
|
||||
static_cast<const uint8_t *>(data)));
|
||||
}
|
||||
|
||||
@@ -5069,8 +5075,10 @@ void Context::compressedTexSubImage3D(TextureTarget target,
|
||||
|
||||
Box area(xoffset, yoffset, zoffset, width, height, depth);
|
||||
Texture *texture = getTextureByTarget(target);
|
||||
- ANGLE_CONTEXT_TRY(texture->setCompressedSubImage(this, mState.getUnpackState(), target, level,
|
||||
- area, format, imageSize,
|
||||
+ // From OpenGL ES 3 spec: All pixel storage modes are ignored when decoding a compressed texture
|
||||
+ // image. So we use an empty PixelUnpackState.
|
||||
+ ANGLE_CONTEXT_TRY(texture->setCompressedSubImage(this, PixelUnpackState(), target, level, area,
|
||||
+ format, imageSize,
|
||||
static_cast<const uint8_t *>(data)));
|
||||
}
|
||||
|
||||
diff --git a/src/tests/gl_tests/TextureTest.cpp b/src/tests/gl_tests/TextureTest.cpp
|
||||
index a3dec629c9ea96b276ff8bf344d4a4628ff20b3b..ba512a11cb023d9caae666ab2634d1e66057d12c 100644
|
||||
--- a/src/tests/gl_tests/TextureTest.cpp
|
||||
+++ b/src/tests/gl_tests/TextureTest.cpp
|
||||
@@ -5151,6 +5151,43 @@ TEST_P(Texture2DTestES3, TextureCompletenessChangesWithMaxLevel)
|
||||
EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::black);
|
||||
}
|
||||
|
||||
+// Test that compressed textures ignore the pixel unpack state.
|
||||
+// (https://crbug.org/1267496)
|
||||
+TEST_P(Texture3DTestES3, PixelUnpackStateTexImage)
|
||||
+{
|
||||
+ ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc") &&
|
||||
+ !IsGLExtensionEnabled("GL_ANGLE_texture_compression_dxt3"));
|
||||
+
|
||||
+ glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 5);
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture3D);
|
||||
+
|
||||
+ uint8_t data[64] = {0};
|
||||
+ glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 4, 4, 4, 0, 64,
|
||||
+ data);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
+// Test that compressed textures ignore the pixel unpack state.
|
||||
+// (https://crbug.org/1267496)
|
||||
+TEST_P(Texture3DTestES3, PixelUnpackStateTexSubImage)
|
||||
+{
|
||||
+ ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc") &&
|
||||
+ !IsGLExtensionEnabled("GL_ANGLE_texture_compression_dxt3"));
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D_ARRAY, mTexture3D);
|
||||
+
|
||||
+ uint8_t data[64] = {0};
|
||||
+ glCompressedTexImage3D(GL_TEXTURE_2D_ARRAY, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 4, 4, 4, 0, 64,
|
||||
+ data);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glPixelStorei(GL_UNPACK_IMAGE_HEIGHT, 5);
|
||||
+
|
||||
+ glCompressedTexSubImage3D(GL_TEXTURE_2D_ARRAY, 0, 0, 0, 0, 4, 4, 4,
|
||||
+ GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 64, data);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
// Test that 3D texture completeness is updated if texture max level changes.
|
||||
// GLES 3.0.4 section 3.8.13 Texture completeness
|
||||
TEST_P(Texture3DTestES3, Texture3DCompletenessChangesWithMaxLevel)
|
||||
@@ -5830,6 +5867,41 @@ TEST_P(Texture2DTestES3, TextureCOMPRESSEDSRGB8ETC2ImplicitAlpha1)
|
||||
EXPECT_PIXEL_ALPHA_EQ(0, 0, 255);
|
||||
}
|
||||
|
||||
+// Test that compressed textures ignore the pixel unpack state.
|
||||
+// (https://crbug.org/1267496)
|
||||
+TEST_P(Texture2DTestES3, PixelUnpackStateTexImage)
|
||||
+{
|
||||
+ ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc") &&
|
||||
+ !IsGLExtensionEnabled("GL_ANGLE_texture_compression_dxt3"));
|
||||
+
|
||||
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 5);
|
||||
+ glBindTexture(GL_TEXTURE_2D, mTexture2D);
|
||||
+
|
||||
+ uint8_t data[16] = {0};
|
||||
+ glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 4, 4, 0, 16, data);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
+// Test that compressed textures ignore the pixel unpack state.
|
||||
+// (https://crbug.org/1267496)
|
||||
+TEST_P(Texture2DTestES3, PixelUnpackStateTexSubImage)
|
||||
+{
|
||||
+ ANGLE_SKIP_TEST_IF(!IsGLExtensionEnabled("GL_EXT_texture_compression_s3tc") &&
|
||||
+ !IsGLExtensionEnabled("GL_ANGLE_texture_compression_dxt3"));
|
||||
+
|
||||
+ glBindTexture(GL_TEXTURE_2D, mTexture2D);
|
||||
+
|
||||
+ uint8_t data[16] = {0};
|
||||
+ glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 4, 4, 0, 16, data);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+
|
||||
+ glPixelStorei(GL_UNPACK_ROW_LENGTH, 5);
|
||||
+
|
||||
+ glCompressedTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 4, 4, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, 16,
|
||||
+ data);
|
||||
+ EXPECT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
// Copied from Texture2DTest::TexStorage
|
||||
// Test that glTexSubImage2D works properly when glTexStorage2DEXT has initialized the image with a
|
||||
// default color.
|
||||
169
patches/angle/cherry-pick-bdffa0ea5148.patch
Normal file
169
patches/angle/cherry-pick-bdffa0ea5148.patch
Normal file
@@ -0,0 +1,169 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
Date: Thu, 2 Dec 2021 14:30:42 -0500
|
||||
Subject: M96: Fix changing attached renderbuffer from MSRTT to non-MSRTT
|
||||
|
||||
FramebufferAttachment::mRenderToTextureSamples was never updated if the
|
||||
renderbuffer storage was changed after attaching to framebuffer.
|
||||
|
||||
Bug: chromium:1272068
|
||||
Change-Id: Icddbb5650354ea16d06c49532d6a8d0ae962ab5f
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3320923
|
||||
Reviewed-by: Jamie Madill <jmadill@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/FramebufferAttachment.cpp b/src/libANGLE/FramebufferAttachment.cpp
|
||||
index 00714d0a0303050893abcd9564e760b22d9b2de5..720d3000d42e6c8c23214e66eee4aa6982216a60 100644
|
||||
--- a/src/libANGLE/FramebufferAttachment.cpp
|
||||
+++ b/src/libANGLE/FramebufferAttachment.cpp
|
||||
@@ -129,7 +129,7 @@ void FramebufferAttachment::attach(const Context *context,
|
||||
mNumViews = numViews;
|
||||
mBaseViewIndex = baseViewIndex;
|
||||
mIsMultiview = isMultiview;
|
||||
- mRenderToTextureSamples = samples;
|
||||
+ mRenderToTextureSamples = type == GL_RENDERBUFFER ? kDefaultRenderToTextureSamples : samples;
|
||||
resource->onAttach(context, framebufferSerial);
|
||||
|
||||
if (mResource != nullptr)
|
||||
@@ -222,6 +222,29 @@ GLint FramebufferAttachment::getBaseViewIndex() const
|
||||
return mBaseViewIndex;
|
||||
}
|
||||
|
||||
+bool FramebufferAttachment::isRenderToTexture() const
|
||||
+{
|
||||
+ ASSERT(mRenderToTextureSamples == kDefaultRenderToTextureSamples || mType == GL_TEXTURE);
|
||||
+
|
||||
+ if (mType == GL_RENDERBUFFER)
|
||||
+ {
|
||||
+ return getRenderbuffer()->getMultisamplingMode() ==
|
||||
+ MultisamplingMode::MultisampledRenderToTexture;
|
||||
+ }
|
||||
+ return mRenderToTextureSamples != kDefaultRenderToTextureSamples;
|
||||
+}
|
||||
+
|
||||
+GLsizei FramebufferAttachment::getRenderToTextureSamples() const
|
||||
+{
|
||||
+ ASSERT(mRenderToTextureSamples == kDefaultRenderToTextureSamples || mType == GL_TEXTURE);
|
||||
+
|
||||
+ if (mType == GL_RENDERBUFFER)
|
||||
+ {
|
||||
+ return getRenderbuffer()->getState().getSamples();
|
||||
+ }
|
||||
+ return mRenderToTextureSamples;
|
||||
+}
|
||||
+
|
||||
Texture *FramebufferAttachment::getTexture() const
|
||||
{
|
||||
return rx::GetAs<Texture>(mResource);
|
||||
diff --git a/src/libANGLE/FramebufferAttachment.h b/src/libANGLE/FramebufferAttachment.h
|
||||
index 7ebf5f5e620cfac47be176fa0bc668353e923104..a5d5bf0eb0c50e37656b241d105be3f35a657585 100644
|
||||
--- a/src/libANGLE/FramebufferAttachment.h
|
||||
+++ b/src/libANGLE/FramebufferAttachment.h
|
||||
@@ -117,11 +117,8 @@ class FramebufferAttachment final
|
||||
bool isMultiview() const;
|
||||
GLint getBaseViewIndex() const;
|
||||
|
||||
- bool isRenderToTexture() const
|
||||
- {
|
||||
- return mRenderToTextureSamples != kDefaultRenderToTextureSamples;
|
||||
- }
|
||||
- GLsizei getRenderToTextureSamples() const { return mRenderToTextureSamples; }
|
||||
+ bool isRenderToTexture() const;
|
||||
+ GLsizei getRenderToTextureSamples() const;
|
||||
|
||||
// The size of the underlying resource the attachment points to. The 'depth' value will
|
||||
// correspond to a 3D texture depth or the layer count of a 2D array texture. For Surfaces and
|
||||
@@ -195,6 +192,14 @@ class FramebufferAttachment final
|
||||
GLsizei mNumViews;
|
||||
bool mIsMultiview;
|
||||
GLint mBaseViewIndex;
|
||||
+ // A single-sampled texture can be attached to a framebuffer either as single-sampled or as
|
||||
+ // multisampled-render-to-texture. In the latter case, |mRenderToTextureSamples| will contain
|
||||
+ // the number of samples. For renderbuffers, the number of samples is inherited from the
|
||||
+ // renderbuffer itself.
|
||||
+ //
|
||||
+ // Note that textures cannot change storage between single and multisample once attached to a
|
||||
+ // framebuffer. Renderbuffers instead can, and caching the number of renderbuffer samples here
|
||||
+ // can lead to stale data.
|
||||
GLsizei mRenderToTextureSamples;
|
||||
};
|
||||
|
||||
@@ -254,8 +259,7 @@ inline Format FramebufferAttachment::getFormat() const
|
||||
|
||||
inline GLsizei FramebufferAttachment::getSamples() const
|
||||
{
|
||||
- return (mRenderToTextureSamples != kDefaultRenderToTextureSamples) ? getRenderToTextureSamples()
|
||||
- : getResourceSamples();
|
||||
+ return isRenderToTexture() ? getRenderToTextureSamples() : getResourceSamples();
|
||||
}
|
||||
|
||||
inline GLsizei FramebufferAttachment::getResourceSamples() const
|
||||
diff --git a/src/tests/gl_tests/FramebufferTest.cpp b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
index ff01848daff091d92e4fdce08bc6842a3bdd3ee9..cd733be3ae5c179860d882e305ec84d093a283ed 100644
|
||||
--- a/src/tests/gl_tests/FramebufferTest.cpp
|
||||
+++ b/src/tests/gl_tests/FramebufferTest.cpp
|
||||
@@ -3366,6 +3366,65 @@ void main() {
|
||||
|
||||
// This shouldn't crash.
|
||||
glDrawArrays(GL_POINTS, 0, 1);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
+// Modify renderbuffer attachment samples after bind
|
||||
+TEST_P(FramebufferTest_ES3, BindRenderbufferThenModifySamples)
|
||||
+{
|
||||
+ ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
|
||||
+ glUseProgram(program);
|
||||
+ GLint colorUniformLocation =
|
||||
+ glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
|
||||
+ ASSERT_NE(colorUniformLocation, -1);
|
||||
+
|
||||
+ GLFramebuffer fbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
+
|
||||
+ GLsizei size = 16;
|
||||
+ glViewport(0, 0, size, size);
|
||||
+
|
||||
+ GLRenderbuffer color;
|
||||
+ glBindRenderbuffer(GL_RENDERBUFFER, color);
|
||||
+
|
||||
+ glRenderbufferStorageMultisample(GL_RENDERBUFFER, 4, GL_RGBA8, size, size);
|
||||
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color);
|
||||
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, size, size);
|
||||
+
|
||||
+ glUniform4f(colorUniformLocation, 1, 0, 0, 1);
|
||||
+ drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
|
||||
+
|
||||
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
+}
|
||||
+
|
||||
+// Modify renderbuffer attachment size after bind
|
||||
+TEST_P(FramebufferTest_ES3, BindRenderbufferThenModifySize)
|
||||
+{
|
||||
+ ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::UniformColor());
|
||||
+ glUseProgram(program);
|
||||
+ GLint colorUniformLocation =
|
||||
+ glGetUniformLocation(program, angle::essl1_shaders::ColorUniform());
|
||||
+ ASSERT_NE(colorUniformLocation, -1);
|
||||
+
|
||||
+ GLFramebuffer fbo;
|
||||
+ glBindFramebuffer(GL_FRAMEBUFFER, fbo);
|
||||
+
|
||||
+ GLsizei size = 16;
|
||||
+ glViewport(0, 0, size, size);
|
||||
+
|
||||
+ GLRenderbuffer color;
|
||||
+ glBindRenderbuffer(GL_RENDERBUFFER, color);
|
||||
+
|
||||
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, size, size);
|
||||
+ glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, color);
|
||||
+ glRenderbufferStorage(GL_RENDERBUFFER, GL_RGBA8, size / 2, size * 2);
|
||||
+
|
||||
+ glUniform4f(colorUniformLocation, 1, 0, 0, 1);
|
||||
+ drawQuad(program, essl1_shaders::PositionAttrib(), 0.5f);
|
||||
+
|
||||
+ EXPECT_PIXEL_COLOR_EQ(0, 0, GLColor::red);
|
||||
+ ASSERT_GL_NO_ERROR();
|
||||
}
|
||||
|
||||
ANGLE_INSTANTIATE_TEST_ES2(AddMockTextureNoRenderTargetTest);
|
||||
43
patches/angle/cherry-pick-cc44ae61f37b.patch
Normal file
43
patches/angle/cherry-pick-cc44ae61f37b.patch
Normal file
@@ -0,0 +1,43 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jamie Madill <jmadill@chromium.org>
|
||||
Date: Tue, 4 Jan 2022 12:28:55 -0500
|
||||
Subject: M96: D3D11: Fix OOB access in vertex conversion code.
|
||||
|
||||
This could happen when using certain combinations of stride and
|
||||
offset. Fix the issue by using checked math.
|
||||
|
||||
Bug: chromium:1274499
|
||||
Change-Id: I3e286a30fe128ab4684ee5e172dc9e3345e3b2f4
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/3365657
|
||||
Reviewed-by: Shahbaz Youssefi <syoussefi@chromium.org>
|
||||
|
||||
diff --git a/src/libANGLE/renderer/d3d/VertexDataManager.cpp b/src/libANGLE/renderer/d3d/VertexDataManager.cpp
|
||||
index 43fcdc8de5a0d15aacf4b06e0e32df008d78f886..031a12e2b8b0e0dee91087c02cbd2f8b17435a2b 100644
|
||||
--- a/src/libANGLE/renderer/d3d/VertexDataManager.cpp
|
||||
+++ b/src/libANGLE/renderer/d3d/VertexDataManager.cpp
|
||||
@@ -58,17 +58,15 @@ int ElementsInBuffer(const gl::VertexAttribute &attrib,
|
||||
const gl::VertexBinding &binding,
|
||||
unsigned int size)
|
||||
{
|
||||
- // Size cannot be larger than a GLsizei
|
||||
- if (size > static_cast<unsigned int>(std::numeric_limits<int>::max()))
|
||||
- {
|
||||
- size = static_cast<unsigned int>(std::numeric_limits<int>::max());
|
||||
- }
|
||||
+ angle::CheckedNumeric<size_t> bufferSize(size);
|
||||
+ angle::CheckedNumeric<size_t> stride = ComputeVertexAttributeStride(attrib, binding);
|
||||
+ angle::CheckedNumeric<size_t> offset = ComputeVertexAttributeOffset(attrib, binding);
|
||||
+ angle::CheckedNumeric<size_t> elementSize = ComputeVertexAttributeTypeSize(attrib);
|
||||
+
|
||||
+ auto elementsInBuffer = (bufferSize - (offset % stride) + (stride - elementSize)) / stride;
|
||||
+ auto elementsInBufferInt = angle::CheckedNumeric<int>::cast(elementsInBuffer);
|
||||
|
||||
- GLsizei stride = static_cast<GLsizei>(ComputeVertexAttributeStride(attrib, binding));
|
||||
- GLsizei offset = static_cast<GLsizei>(ComputeVertexAttributeOffset(attrib, binding));
|
||||
- return (size - offset % stride +
|
||||
- (stride - static_cast<GLsizei>(ComputeVertexAttributeTypeSize(attrib)))) /
|
||||
- stride;
|
||||
+ return elementsInBufferInt.ValueOrDefault(0);
|
||||
}
|
||||
|
||||
// Warning: you should ensure binding really matches attrib.bindingIndex before using this function.
|
||||
@@ -1,119 +0,0 @@
|
||||
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);
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user