mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
94 Commits
v16.0.0-be
...
v15.0.0-be
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
@@ -45,7 +45,7 @@ executors:
|
||||
type: enum
|
||||
enum: ["medium", "xlarge", "2xlarge+"]
|
||||
docker:
|
||||
- image: ghcr.io/electron/build:27db4a3e3512bfd2e47f58cea69922da0835f1d9
|
||||
- image: electron.azurecr.io/build:d818f06a9b1540c7fd38f75ad5a2c493dd6843b6
|
||||
resource_class: << parameters.size >>
|
||||
|
||||
macos:
|
||||
@@ -1209,9 +1209,6 @@ steps-tests: &steps-tests
|
||||
(cd electron && node script/yarn test --runners=main --trace-uncaught --enable-logging)
|
||||
(cd electron && node script/yarn test --runners=remote --trace-uncaught --enable-logging)
|
||||
else
|
||||
if [ "$TARGET_ARCH" == "ia32" ]; then
|
||||
npm_config_arch=x64 node electron/node_modules/dugite/script/download-git.js
|
||||
fi
|
||||
(cd electron && node script/yarn test --runners=main --trace-uncaught --enable-logging --files $(circleci tests glob spec-main/*-spec.ts | circleci tests split --split-by=timings))
|
||||
(cd electron && node script/yarn test --runners=remote --trace-uncaught --enable-logging --files $(circleci tests glob spec/*-spec.js | circleci tests split --split-by=timings))
|
||||
fi
|
||||
|
||||
@@ -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
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -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
|
||||
|
||||
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
4
.github/ISSUE_TEMPLATE/bug_report.yml
vendored
@@ -8,9 +8,9 @@ body:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
|
||||
required: true
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- label: I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.
|
||||
required: true
|
||||
|
||||
6
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
6
.github/ISSUE_TEMPLATE/feature_request.yml
vendored
@@ -8,9 +8,9 @@ body:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
|
||||
required: true
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- label: I have searched the [issue tracker](https://www.github.com/electron/electron/issues) for a feature request that matches the one I want to file, without success.
|
||||
required: true
|
||||
@@ -37,4 +37,4 @@ body:
|
||||
label: Additional Information
|
||||
description: Add any other context about the problem here.
|
||||
validations:
|
||||
required: false
|
||||
required: true
|
||||
|
||||
@@ -7,9 +7,9 @@ body:
|
||||
label: Preflight Checklist
|
||||
description: Please ensure you've completed all of the following.
|
||||
options:
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) for this project.
|
||||
- label: I have read the [Contributing Guidelines](https://github.com/electron/electron/blob/master/CONTRIBUTING.md) for this project.
|
||||
required: true
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
- label: I agree to follow the [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) that this project adheres to.
|
||||
required: true
|
||||
- type: input
|
||||
attributes:
|
||||
|
||||
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
4
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -3,7 +3,7 @@
|
||||
Thank you for your Pull Request. Please provide a description above and review
|
||||
the requirements below.
|
||||
|
||||
Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
|
||||
Contributors guide: https://github.com/electron/electron/blob/master/CONTRIBUTING.md
|
||||
-->
|
||||
|
||||
#### Checklist
|
||||
@@ -11,7 +11,7 @@ Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.
|
||||
|
||||
- [ ] PR description included and stakeholders cc'd
|
||||
- [ ] `npm test` passes
|
||||
- [ ] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
|
||||
- [ ] tests are [changed or added](https://github.com/electron/electron/blob/master/docs/development/testing.md)
|
||||
- [ ] relevant documentation is changed or added
|
||||
- [ ] [PR release notes](https://github.com/electron/clerk/blob/master/README.md) describe the change in a way relevant to app developers, and are [capitalized, punctuated, and past tense](https://github.com/electron/clerk/blob/master/README.md#examples).
|
||||
|
||||
|
||||
6
.github/config.yml
vendored
6
.github/config.yml
vendored
@@ -2,7 +2,7 @@
|
||||
newPRWelcomeComment: |
|
||||
💖 Thanks for opening this pull request! 💖
|
||||
|
||||
We use [semantic commit messages](https://github.com/electron/electron/blob/main/docs/development/pull-requests.md#commit-message-guidelines) to streamline the release process. Before your pull request can be merged, you should **update your pull request title** to start with a semantic prefix.
|
||||
We use [semantic commit messages](https://github.com/electron/electron/blob/master/docs/development/pull-requests.md#commit-message-guidelines) to streamline the release process. Before your pull request can be merged, you should **update your pull request title** to start with a semantic prefix.
|
||||
|
||||
Examples of commit messages with semantic prefixes:
|
||||
|
||||
@@ -12,9 +12,9 @@ newPRWelcomeComment: |
|
||||
|
||||
Things that will help get your PR across the finish line:
|
||||
|
||||
- Follow the JavaScript, C++, and Python [coding style](https://github.com/electron/electron/blob/main/docs/development/coding-style.md).
|
||||
- Follow the JavaScript, C++, and Python [coding style](https://github.com/electron/electron/blob/master/docs/development/coding-style.md).
|
||||
- Run `npm run lint` locally to catch formatting errors earlier.
|
||||
- Document any user-facing changes you've made following the [documentation styleguide](https://github.com/electron/electron/blob/main/docs/styleguide.md).
|
||||
- Document any user-facing changes you've made following the [documentation styleguide](https://github.com/electron/electron/blob/master/docs/styleguide.md).
|
||||
- Include tests when adding/changing behavior.
|
||||
- Include screenshots and animated GIFs whenever possible.
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -26,7 +26,6 @@ compile_commands.json
|
||||
# npm package
|
||||
/npm/dist
|
||||
/npm/path.txt
|
||||
/npm/checksums.json
|
||||
|
||||
.npmrc
|
||||
|
||||
|
||||
35
BUILD.gn
35
BUILD.gn
@@ -187,12 +187,6 @@ action("electron_js2c") {
|
||||
rebase_path(sources, root_build_dir)
|
||||
}
|
||||
|
||||
action("generate_config_gypi") {
|
||||
outputs = [ "$root_gen_dir/config.gypi" ]
|
||||
script = "script/generate-config-gypi.py"
|
||||
args = rebase_path(outputs) + [ target_cpu ]
|
||||
}
|
||||
|
||||
target_gen_default_app_js = "$target_gen_dir/js/default_app"
|
||||
|
||||
typescript_build("default_app_js") {
|
||||
@@ -313,23 +307,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 +318,6 @@ source_set("electron_lib") {
|
||||
|
||||
deps = [
|
||||
":electron_fuses",
|
||||
":electron_generate_node_defines",
|
||||
":electron_js2c",
|
||||
":electron_version_header",
|
||||
":resources",
|
||||
@@ -353,7 +329,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",
|
||||
@@ -386,7 +361,6 @@ source_set("electron_lib") {
|
||||
"//ppapi/shared_impl",
|
||||
"//printing/buildflags",
|
||||
"//services/device/public/cpp/geolocation",
|
||||
"//services/device/public/cpp/hid",
|
||||
"//services/device/public/mojom",
|
||||
"//services/proxy_resolver:lib",
|
||||
"//services/video_capture/public/mojom:constants",
|
||||
@@ -1032,12 +1006,6 @@ if (is_mac) {
|
||||
outputs = [ "{{bundle_resources_dir}}/{{source_file_part}}" ]
|
||||
}
|
||||
|
||||
asar_hashed_info_plist("electron_app_plist") {
|
||||
keys = [ "DEFAULT_APP_ASAR_HEADER_SHA" ]
|
||||
hash_targets = [ ":default_app_asar_header_hash" ]
|
||||
plist_file = "shell/browser/resources/mac/Info.plist"
|
||||
}
|
||||
|
||||
mac_app_bundle("electron_app") {
|
||||
output_name = electron_product_name
|
||||
sources = filenames.app_sources
|
||||
@@ -1045,7 +1013,6 @@ if (is_mac) {
|
||||
include_dirs = [ "." ]
|
||||
deps = [
|
||||
":electron_app_framework_bundle_data",
|
||||
":electron_app_plist",
|
||||
":electron_app_resources",
|
||||
":electron_fuses",
|
||||
"//base",
|
||||
@@ -1054,7 +1021,7 @@ if (is_mac) {
|
||||
if (is_mas_build) {
|
||||
deps += [ ":electron_login_helper_app" ]
|
||||
}
|
||||
info_plist_target = ":electron_app_plist"
|
||||
info_plist = "shell/browser/resources/mac/Info.plist"
|
||||
extra_substitutions = [
|
||||
"ELECTRON_BUNDLE_ID=$electron_mac_bundle_id",
|
||||
"ELECTRON_VERSION=$electron_version",
|
||||
|
||||
@@ -36,7 +36,7 @@ Having the original text _as well as_ the translation can help mitigate translat
|
||||
|
||||
Responses to posted issues may or may not be in the original language.
|
||||
|
||||
**Please note** that using non-English as an attempt to circumvent our [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md) will be an immediate, and possibly indefinite, ban from the project.
|
||||
**Please note** that using non-English as an attempt to circumvent our [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md) will be an immediate, and possibly indefinite, ban from the project.
|
||||
|
||||
## [Pull Requests](https://electronjs.org/docs/development/pull-requests)
|
||||
|
||||
|
||||
4
DEPS
4
DEPS
@@ -15,9 +15,9 @@ gclient_gn_args = [
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'96.0.4647.0',
|
||||
'94.0.4606.31',
|
||||
'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.0.0-beta.2
|
||||
15.0.0-beta.5
|
||||
@@ -1,7 +1,7 @@
|
||||
[](https://electronjs.org)
|
||||
|
||||
[](https://circleci.com/gh/electron/electron/tree/main)
|
||||
[](https://ci.appveyor.com/project/electron-bot/electron-ljo26/branch/main)
|
||||
[](https://circleci.com/gh/electron/electron/tree/master)
|
||||
[](https://ci.appveyor.com/project/electron-bot/electron-ljo26/branch/master)
|
||||
[](https://discord.com/invite/electron)
|
||||
|
||||
:memo: Available Translations: 🇨🇳 🇧🇷 🇪🇸 🇯🇵 🇷🇺 🇫🇷 🇺🇸 🇩🇪.
|
||||
@@ -16,7 +16,7 @@ Follow [@ElectronJS](https://twitter.com/electronjs) on Twitter for important
|
||||
announcements.
|
||||
|
||||
This project adheres to the Contributor Covenant
|
||||
[code of conduct](https://github.com/electron/electron/tree/main/CODE_OF_CONDUCT.md).
|
||||
[code of conduct](https://github.com/electron/electron/tree/master/CODE_OF_CONDUCT.md).
|
||||
By participating, you are expected to uphold this code. Please report unacceptable
|
||||
behavior to [coc@electronjs.org](mailto:coc@electronjs.org).
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -97,6 +98,6 @@ and more can be found in the [support document](docs/tutorial/support.md#finding
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/electron/electron/blob/main/LICENSE)
|
||||
[MIT](https://github.com/electron/electron/blob/master/LICENSE)
|
||||
|
||||
When using the Electron or other GitHub logos, be sure to follow the [GitHub logo guidelines](https://github.com/logos).
|
||||
|
||||
@@ -10,7 +10,7 @@ Report security bugs in third-party modules to the person or team maintaining th
|
||||
|
||||
## The Electron Security Notification Process
|
||||
|
||||
For context on Electron's security notification process, please see the [Notifications](https://github.com/electron/governance/blob/main/wg-security/membership-and-notifications.md#notifications) section of the Security WG's [Membership and Notifications](https://github.com/electron/governance/blob/main/wg-security/membership-and-notifications.md) Governance document.
|
||||
For context on Electron's security notification process, please see the [Notifications](https://github.com/electron/governance/blob/master/wg-security/membership-and-notifications.md#notifications) section of the Security WG's [Membership and Notifications](https://github.com/electron/governance/blob/master/wg-security/membership-and-notifications.md) Governance document.
|
||||
|
||||
## Learning More About Security
|
||||
|
||||
|
||||
@@ -112,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
|
||||
|
||||
@@ -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 = 89
|
||||
|
||||
v8_promise_internal_field_count = 1
|
||||
v8_typed_array_max_size_in_heap = 0
|
||||
|
||||
@@ -57,42 +57,4 @@ template("asar") {
|
||||
rebase_path(outputs[0]),
|
||||
]
|
||||
}
|
||||
|
||||
node_action(target_name + "_header_hash") {
|
||||
invoker_out = invoker.outputs
|
||||
|
||||
deps = [ ":" + invoker.target_name ]
|
||||
sources = invoker.outputs
|
||||
|
||||
script = "//electron/script/gn-asar-hash.js"
|
||||
outputs = [ "$target_gen_dir/asar_hashes/$target_name.hash" ]
|
||||
|
||||
args = [
|
||||
rebase_path(invoker_out[0]),
|
||||
rebase_path(outputs[0]),
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
template("asar_hashed_info_plist") {
|
||||
node_action(target_name) {
|
||||
assert(defined(invoker.plist_file),
|
||||
"Need plist_file to add hashed assets to")
|
||||
assert(defined(invoker.keys), "Need keys to replace with asset hash")
|
||||
assert(defined(invoker.hash_targets), "Need hash_targets to read hash from")
|
||||
|
||||
deps = invoker.hash_targets
|
||||
|
||||
script = "//electron/script/gn-plist-but-with-hashes.js"
|
||||
inputs = [ invoker.plist_file ]
|
||||
outputs = [ "$target_gen_dir/hashed_plists/$target_name.plist" ]
|
||||
hash_files = []
|
||||
foreach(hash_target, invoker.hash_targets) {
|
||||
hash_files += get_target_outputs(hash_target)
|
||||
}
|
||||
args = [
|
||||
rebase_path(invoker.plist_file),
|
||||
rebase_path(outputs[0]),
|
||||
] + invoker.keys + rebase_path(hash_files)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,5 @@
|
||||
"run_as_node": "1",
|
||||
"cookie_encryption": "0",
|
||||
"node_options": "1",
|
||||
"node_cli_inspect": "1",
|
||||
"embedded_asar_integrity_validation": "0",
|
||||
"only_load_app_from_asar": "0"
|
||||
"node_cli_inspect": "1"
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 ]
|
||||
}
|
||||
|
||||
@@ -47,9 +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/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",
|
||||
@@ -60,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",
|
||||
@@ -72,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",
|
||||
]
|
||||
@@ -84,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",
|
||||
@@ -212,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 += [
|
||||
@@ -324,13 +314,17 @@ source_set("plugins") {
|
||||
sources += [
|
||||
"//chrome/renderer/pepper/chrome_renderer_pepper_host_factory.cc",
|
||||
"//chrome/renderer/pepper/chrome_renderer_pepper_host_factory.h",
|
||||
"//chrome/renderer/pepper/pepper_flash_font_file_host.cc",
|
||||
"//chrome/renderer/pepper/pepper_flash_font_file_host.h",
|
||||
"//chrome/renderer/pepper/pepper_shared_memory_message_filter.cc",
|
||||
"//chrome/renderer/pepper/pepper_shared_memory_message_filter.h",
|
||||
]
|
||||
if (enable_pdf_viewer) {
|
||||
deps += [ "//components/pdf/renderer" ]
|
||||
sources += [
|
||||
"//chrome/renderer/pepper/pepper_flash_font_file_host.cc",
|
||||
"//chrome/renderer/pepper/pepper_flash_font_file_host.h",
|
||||
]
|
||||
if (enable_pdf_viewer) {
|
||||
deps += [ "//components/pdf/renderer" ]
|
||||
}
|
||||
}
|
||||
deps += [
|
||||
"//components/strings",
|
||||
|
||||
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_
|
||||
@@ -277,7 +277,6 @@ Returns:
|
||||
* `certificate` [Certificate](structures/certificate.md)
|
||||
* `callback` Function
|
||||
* `isTrusted` Boolean - Whether to consider the certificate as trusted
|
||||
* `isMainFrame` Boolean
|
||||
|
||||
Emitted when failed to verify the `certificate` for `url`, to trust the
|
||||
certificate you should prevent the default behavior with
|
||||
@@ -483,7 +482,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()`.
|
||||
@@ -942,8 +940,6 @@ app.setJumpList([
|
||||
|
||||
### `app.requestSingleInstanceLock()`
|
||||
|
||||
* `additionalData` unknown (optional) - A JSON object containing additional data to send to the first instance.
|
||||
|
||||
Returns `Boolean`
|
||||
|
||||
The return value of this method indicates whether or not this instance of your
|
||||
@@ -969,16 +965,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()
|
||||
@@ -1079,7 +1071,7 @@ indicates success while any other value indicates failure according to Chromium
|
||||
Linux.
|
||||
* `secureDnsMode` String (optional) - Can be "off", "automatic" or "secure".
|
||||
Configures the DNS-over-HTTP mode. When "off", no DoH lookups will be
|
||||
performed. When "automatic", DoH lookups will be performed first if DoH is
|
||||
performed. When "automatic", DoH lookups will be peformed first if DoH is
|
||||
available, and insecure DNS lookups will be performed as a fallback. When
|
||||
"secure", only DoH lookups will be performed. Defaults to "automatic".
|
||||
* `secureDnsServers` String[] (optional) - A list of DNS-over-HTTP
|
||||
|
||||
@@ -22,13 +22,12 @@ win.loadFile('index.html')
|
||||
To create a window without chrome, or a transparent window in arbitrary shape,
|
||||
you can use the [Frameless Window](frameless-window.md) API.
|
||||
|
||||
## Showing the window gracefully
|
||||
## Showing window gracefully
|
||||
|
||||
When loading a page in the window directly, users may see the page load incrementally,
|
||||
which is not a good experience for a native app. To make the window display
|
||||
without a visual flash, there are two solutions for different situations.
|
||||
When loading a page in the window directly, users may see the page load incrementally, which is not a good experience for a native app. To make the window display
|
||||
without visual flash, there are two solutions for different situations.
|
||||
|
||||
### Using the `ready-to-show` event
|
||||
## Using `ready-to-show` event
|
||||
|
||||
While loading the page, the `ready-to-show` event will be emitted when the renderer
|
||||
process has rendered the page for the first time if the window has not been shown yet. Showing
|
||||
@@ -49,7 +48,7 @@ event.
|
||||
Please note that using this event implies that the renderer will be considered "visible" and
|
||||
paint even though `show` is false. This event will never fire if you use `paintWhenInitiallyHidden: false`
|
||||
|
||||
### Setting the `backgroundColor` property
|
||||
## Setting `backgroundColor`
|
||||
|
||||
For a complex app, the `ready-to-show` event could be emitted too late, making
|
||||
the app feel slow. In this case, it is recommended to show the window
|
||||
@@ -988,7 +987,7 @@ the player itself we would call this function with arguments of 16/9 and
|
||||
are within the content view--only that they exist. Sum any extra width and
|
||||
height areas you have within the overall content view.
|
||||
|
||||
The aspect ratio is not respected when window is resized programmatically with
|
||||
The aspect ratio is not respected when window is resized programmingly with
|
||||
APIs like `win.setSize`.
|
||||
|
||||
#### `win.setBackgroundColor(backgroundColor)`
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -53,12 +53,3 @@ Returns `Boolean` - Whether the command-line switch is present.
|
||||
Returns `String` - The command-line switch value.
|
||||
|
||||
**Note:** When the switch is not present or has no value, it returns empty string.
|
||||
|
||||
#### `commandLine.removeSwitch(switch)`
|
||||
|
||||
* `switch` String - A command-line switch
|
||||
|
||||
Removes the specified switch from Chromium's command line.
|
||||
|
||||
**Note:** This will not affect `process.argv`. The intended usage of this function is to
|
||||
control Chromium's behavior.
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -234,7 +234,6 @@ expanding and collapsing the dialog.
|
||||
* `title` String (optional) - Title of the message box, some platforms will not show it.
|
||||
* `detail` String (optional) - Extra information of the message.
|
||||
* `icon` ([NativeImage](native-image.md) | String) (optional)
|
||||
* `textWidth` Integer (optional) _macOS_ - Custom width of the text in the message box.
|
||||
* `cancelId` Integer (optional) - The index of the button to be used to cancel the dialog, via
|
||||
the `Esc` key. By default this is assigned to the first button with "cancel" or "no" as the
|
||||
label. If no such labeled buttons exist and this option is not set, `0` will be used as the
|
||||
@@ -286,7 +285,6 @@ If `browserWindow` is not shown dialog will not be attached to it. In such case
|
||||
* `checkboxChecked` Boolean (optional) - Initial checked state of the
|
||||
checkbox. `false` by default.
|
||||
* `icon` [NativeImage](native-image.md) (optional)
|
||||
* `textWidth` Integer (optional) _macOS_ - Custom width of the text in the message box.
|
||||
* `cancelId` Integer (optional) - The index of the button to be used to cancel the dialog, via
|
||||
the `Esc` key. By default this is assigned to the first button with "cancel" or "no" as the
|
||||
label. If no such labeled buttons exist and this option is not set, `0` will be used as the
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -86,8 +86,8 @@ available from next tick of the process.
|
||||
const { session } = require('electron')
|
||||
session.defaultSession.on('will-download', (event, item, webContents) => {
|
||||
event.preventDefault()
|
||||
require('got')(item.getURL()).then((response) => {
|
||||
require('fs').writeFileSync('/somewhere', response.body)
|
||||
require('request')(item.getURL(), (data) => {
|
||||
require('fs').writeFileSync('/somewhere', data)
|
||||
})
|
||||
})
|
||||
```
|
||||
@@ -180,96 +180,6 @@ Emitted when a hunspell dictionary file download fails. For details
|
||||
on the failure you should collect a netlog and inspect the download
|
||||
request.
|
||||
|
||||
#### Event: 'select-hid-device'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `details` Object
|
||||
* `deviceList` [HIDDevice[]](structures/hid-device.md)
|
||||
* `frame` [WebFrameMain](web-frame-main.md)
|
||||
* `callback` Function
|
||||
* `deviceId` String | null (optional)
|
||||
|
||||
Emitted when a HID device needs to be selected when a call to
|
||||
`navigator.hid.requestDevice` is made. `callback` should be called with
|
||||
`deviceId` to be selected; passing no arguments to `callback` will
|
||||
cancel the request. Additionally, permissioning on `navigator.hid` can
|
||||
be further managed by using [ses.setPermissionCheckHandler(handler)](#sessetpermissioncheckhandlerhandler)
|
||||
and [ses.setDevicePermissionHandler(handler)`](#sessetdevicepermissionhandlerhandler).
|
||||
|
||||
```javascript
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let win = null
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow()
|
||||
|
||||
win.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'hid') {
|
||||
// Add logic here to determine if permission should be given to allow HID selection
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Optionally, retrieve previously persisted devices from a persistent store
|
||||
const grantedDevices = fetchGrantedDevices()
|
||||
|
||||
win.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (new URL(details.origin).hostname === 'some-host' && details.deviceType === 'hid') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.hid.requestDevice` first)
|
||||
return true
|
||||
}
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
win.webContents.session.on('select-hid-device', (event, details, callback) => {
|
||||
event.preventDefault()
|
||||
const selectedDevice = details.deviceList.find((device) => {
|
||||
return device.vendorId === '9025' && device.productId === '67'
|
||||
})
|
||||
callback(selectedPort?.deviceId)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### Event: 'hid-device-added'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `details` Object
|
||||
* `device` [HIDDevice[]](structures/hid-device.md)
|
||||
* `frame` [WebFrameMain](web-frame-main.md)
|
||||
|
||||
Emitted when a new HID device becomes available. For example, when a new USB device is plugged in.
|
||||
|
||||
This event will only be emitted after `navigator.hid.requestDevice` has been called and `select-hid-device` has fired.
|
||||
|
||||
#### Event: 'hid-device-removed'
|
||||
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `details` Object
|
||||
* `device` [HIDDevice[]](structures/hid-device.md)
|
||||
* `frame` [WebFrameMain](web-frame-main.md)
|
||||
|
||||
Emitted when a HID device has been removed. For example, this event will fire when a USB device is unplugged.
|
||||
|
||||
This event will only be emitted after `navigator.hid.requestDevice` has been called and `select-hid-device` has fired.
|
||||
|
||||
#### Event: 'select-serial-port'
|
||||
|
||||
Returns:
|
||||
@@ -297,35 +207,6 @@ app.whenReady().then(() => {
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
win.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'serial') {
|
||||
// Add logic here to determine if permission should be given to allow serial selection
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Optionally, retrieve previously persisted devices from a persistent store
|
||||
const grantedDevices = fetchGrantedDevices()
|
||||
|
||||
win.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (new URL(details.origin).hostname === 'some-host' && details.deviceType === 'serial') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.serial.requestPort` first)
|
||||
return true
|
||||
}
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
})
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
win.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
|
||||
event.preventDefault()
|
||||
const selectedPort = portList.find((device) => {
|
||||
@@ -562,8 +443,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
|
||||
@@ -618,7 +498,6 @@ win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
||||
* `permissionGranted` Boolean - Allow or deny the permission.
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
* `externalURL` String (optional) - The url of the `openExternal` request.
|
||||
* `securityOrigin` String (optional) - The security origin of the `media` request.
|
||||
* `mediaTypes` String[] (optional) - The types of media access being requested, elements can be `video`
|
||||
or `audio`
|
||||
* `requestingUrl` String - The last URL the requesting frame loaded
|
||||
@@ -645,7 +524,7 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents
|
||||
|
||||
* `handler` Function\<Boolean> | null
|
||||
* `webContents` ([WebContents](web-contents.md) | null) - WebContents checking the permission. Please note that if the request comes from a subframe you should use `requestingUrl` to check the request origin. All cross origin sub frames making permission checks will pass a `null` webContents to this handler, while certain other permission checks such as `notifications` checks will always pass `null`. You should use `embeddingOrigin` and `requestingOrigin` to determine what origin the owning frame and the requesting frame are on respectively.
|
||||
* `permission` String - Type of permission check. Valid values are `midiSysex`, `notifications`, `geolocation`, `media`,`mediaKeySystem`,`midi`, `pointerLock`, `fullscreen`, `openExternal`, `hid`, or `serial`.
|
||||
* `permission` String - Type of permission check. Valid values are `midiSysex`, `notifications`, `geolocation`, `media`,`mediaKeySystem`,`midi`, `pointerLock`, `fullscreen`, `openExternal`, or `serial`.
|
||||
* `requestingOrigin` String - The origin URL of the permission check
|
||||
* `details` Object - Some properties are only available on certain permission types.
|
||||
* `embeddingOrigin` String (optional) - The origin of the frame embedding the frame that made the permission check. Only set for cross-origin sub frames making permission checks.
|
||||
@@ -673,78 +552,6 @@ session.fromPartition('some-partition').setPermissionCheckHandler((webContents,
|
||||
})
|
||||
```
|
||||
|
||||
#### `ses.setDevicePermissionHandler(handler)`
|
||||
|
||||
* `handler` Function\<Boolean> | null
|
||||
* `details` Object
|
||||
* `deviceType` String - The type of device that permission is being requested on, can be `hid` or `serial`.
|
||||
* `origin` String - The origin URL of the device permission check.
|
||||
* `device` [HIDDevice](structures/hid-device.md) | [SerialPort](structures/serial-port.md)- the device that permission is being requested for.
|
||||
* `frame` [WebFrameMain](web-frame-main.md) - WebFrameMain checking the device permission.
|
||||
|
||||
Sets the handler which can be used to respond to device permission checks for the `session`.
|
||||
Returning `true` will allow the device to be permitted and `false` will reject it.
|
||||
To clear the handler, call `setDevicePermissionHandler(null)`.
|
||||
This handler can be used to provide default permissioning to devices without first calling for permission
|
||||
to devices (eg via `navigator.hid.requestDevice`). If this handler is not defined, the default device
|
||||
permissions as granted through device selection (eg via `navigator.hid.requestDevice`) will be used.
|
||||
Additionally, the default behavior of Electron is to store granted device permision through the lifetime
|
||||
of the corresponding WebContents. If longer term storage is needed, a developer can store granted device
|
||||
permissions (eg when handling the `select-hid-device` event) and then read from that storage with `setDevicePermissionHandler`.
|
||||
|
||||
```javascript
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let win = null
|
||||
|
||||
app.whenReady().then(() => {
|
||||
win = new BrowserWindow()
|
||||
|
||||
win.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'hid') {
|
||||
// Add logic here to determine if permission should be given to allow HID selection
|
||||
return true
|
||||
} else if (permission === 'serial') {
|
||||
// Add logic here to determine if permission should be given to allow serial port selection
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// Optionally, retrieve previously persisted devices from a persistent store
|
||||
const grantedDevices = fetchGrantedDevices()
|
||||
|
||||
win.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (new URL(details.origin).hostname === 'some-host' && details.deviceType === 'hid') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.hid.requestDevice` first)
|
||||
return true
|
||||
}
|
||||
|
||||
// Search through the list of devices that have previously been granted permission
|
||||
return grantedDevices.some((grantedDevice) => {
|
||||
return grantedDevice.vendorId === details.device.vendorId &&
|
||||
grantedDevice.productId === details.device.productId &&
|
||||
grantedDevice.serialNumber && grantedDevice.serialNumber === details.device.serialNumber
|
||||
})
|
||||
} else if (details.deviceType === 'serial') {
|
||||
if (details.device.vendorId === 123 && details.device.productId === 345) {
|
||||
// Always allow this type of device (this allows skipping the call to `navigator.hid.requestDevice` first)
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
win.webContents.session.on('select-hid-device', (event, details, callback) => {
|
||||
event.preventDefault()
|
||||
const selectedDevice = details.deviceList.find((device) => {
|
||||
return device.vendorId === '9025' && device.productId === '67'
|
||||
})
|
||||
callback(selectedPort?.deviceId)
|
||||
})
|
||||
})
|
||||
```
|
||||
|
||||
#### `ses.clearHostResolverCache()`
|
||||
|
||||
Returns `Promise<void>` - Resolves when the operation is complete.
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
# HIDDevice Object
|
||||
|
||||
* `deviceId` String - Unique identifier for the device.
|
||||
* `name` String - Name of the device.
|
||||
* `vendorId` Integer - The USB vendor ID.
|
||||
* `productId` Integer - The USB product ID.
|
||||
* `serialNumber` String (optional) - The USB device serial number.
|
||||
* `guid` String (optional) - Unique identifier for the HID interface. A device may have multiple HID interfaces.
|
||||
@@ -530,7 +530,6 @@ Returns:
|
||||
* `certificate` [Certificate](structures/certificate.md)
|
||||
* `callback` Function
|
||||
* `isTrusted` Boolean - Indicates whether the certificate can be considered trusted.
|
||||
* `isMainFrame` Boolean
|
||||
|
||||
Emitted when failed to verify the `certificate` for `url`.
|
||||
|
||||
@@ -651,7 +650,6 @@ Returns:
|
||||
* `params` Object
|
||||
* `x` Integer - x coordinate.
|
||||
* `y` Integer - y coordinate.
|
||||
* `frame` WebFrameMain - Frame from which the context menu was invoked.
|
||||
* `linkURL` String - URL of the link that encloses the node the context menu
|
||||
was invoked on.
|
||||
* `linkText` String - Text associated with the link. May be an empty
|
||||
@@ -1494,8 +1492,8 @@ win.loadURL('http://github.com')
|
||||
|
||||
win.webContents.on('did-finish-load', () => {
|
||||
// Use default printing options
|
||||
const pdfPath = path.join(os.homedir(), 'Desktop', 'temp.pdf')
|
||||
win.webContents.printToPDF({}).then(data => {
|
||||
const pdfPath = path.join(os.homedir(), 'Desktop', 'temp.pdf')
|
||||
fs.writeFile(pdfPath, data, (error) => {
|
||||
if (error) throw error
|
||||
console.log(`Wrote PDF successfully to ${pdfPath}`)
|
||||
@@ -1827,8 +1825,7 @@ End subscribing for frame presentation events.
|
||||
#### `contents.startDrag(item)`
|
||||
|
||||
* `item` Object
|
||||
* `file` String - The path to the file being dragged.
|
||||
* `files` String[] (optional) - The paths to the files being dragged. (`files` will override `file` field)
|
||||
* `file` String[] | String - The path(s) to the file(s) being dragged.
|
||||
* `icon` [NativeImage](native-image.md) | String - The image must be
|
||||
non-empty on macOS.
|
||||
|
||||
|
||||
@@ -1025,78 +1025,3 @@ Emitted when DevTools is focused / opened.
|
||||
|
||||
[runtime-enabled-features]: https://cs.chromium.org/chromium/src/third_party/blink/renderer/platform/runtime_enabled_features.json5?l=70
|
||||
[chrome-webview]: https://developer.chrome.com/docs/extensions/reference/webviewTag/
|
||||
|
||||
### Event: 'context-menu'
|
||||
|
||||
Returns:
|
||||
|
||||
* `params` Object
|
||||
* `x` Integer - x coordinate.
|
||||
* `y` Integer - y coordinate.
|
||||
* `linkURL` String - URL of the link that encloses the node the context menu
|
||||
was invoked on.
|
||||
* `linkText` String - Text associated with the link. May be an empty
|
||||
string if the contents of the link are an image.
|
||||
* `pageURL` String - URL of the top level page that the context menu was
|
||||
invoked on.
|
||||
* `frameURL` String - URL of the subframe that the context menu was invoked
|
||||
on.
|
||||
* `srcURL` String - Source URL for the element that the context menu
|
||||
was invoked on. Elements with source URLs are images, audio and video.
|
||||
* `mediaType` String - Type of the node the context menu was invoked on. Can
|
||||
be `none`, `image`, `audio`, `video`, `canvas`, `file` or `plugin`.
|
||||
* `hasImageContents` Boolean - Whether the context menu was invoked on an image
|
||||
which has non-empty contents.
|
||||
* `isEditable` Boolean - Whether the context is editable.
|
||||
* `selectionText` String - Text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `titleText` String - Title text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `altText` String - Alt text of the selection that the context menu was
|
||||
invoked on.
|
||||
* `suggestedFilename` String - Suggested filename to be used when saving file through 'Save
|
||||
Link As' option of context menu.
|
||||
* `selectionRect` [Rectangle](structures/rectangle.md) - Rect representing the coordinates in the document space of the selection.
|
||||
* `selectionStartOffset` Number - Start position of the selection text.
|
||||
* `referrerPolicy` [Referrer](structures/referrer.md) - The referrer policy of the frame on which the menu is invoked.
|
||||
* `misspelledWord` String - The misspelled word under the cursor, if any.
|
||||
* `dictionarySuggestions` String[] - An array of suggested words to show the
|
||||
user to replace the `misspelledWord`. Only available if there is a misspelled
|
||||
word and spellchecker is enabled.
|
||||
* `frameCharset` String - The character encoding of the frame on which the
|
||||
menu was invoked.
|
||||
* `inputFieldType` String - If the context menu was invoked on an input
|
||||
field, the type of that field. Possible values are `none`, `plainText`,
|
||||
`password`, `other`.
|
||||
* `spellcheckEnabled` Boolean - If the context is editable, whether or not spellchecking is enabled.
|
||||
* `menuSourceType` String - Input source that invoked the context menu.
|
||||
Can be `none`, `mouse`, `keyboard`, `touch`, `touchMenu`, `longPress`, `longTap`, `touchHandle`, `stylus`, `adjustSelection`, or `adjustSelectionReset`.
|
||||
* `mediaFlags` Object - The flags for the media element the context menu was
|
||||
invoked on.
|
||||
* `inError` Boolean - Whether the media element has crashed.
|
||||
* `isPaused` Boolean - Whether the media element is paused.
|
||||
* `isMuted` Boolean - Whether the media element is muted.
|
||||
* `hasAudio` Boolean - Whether the media element has audio.
|
||||
* `isLooping` Boolean - Whether the media element is looping.
|
||||
* `isControlsVisible` Boolean - Whether the media element's controls are
|
||||
visible.
|
||||
* `canToggleControls` Boolean - Whether the media element's controls are
|
||||
toggleable.
|
||||
* `canPrint` Boolean - Whether the media element can be printed.
|
||||
* `canSave` Boolean - Whether or not the media element can be downloaded.
|
||||
* `canShowPictureInPicture` Boolean - Whether the media element can show picture-in-picture.
|
||||
* `isShowingPictureInPicture` Boolean - Whether the media element is currently showing picture-in-picture.
|
||||
* `canRotate` Boolean - Whether the media element can be rotated.
|
||||
* `canLoop` Boolean - Whether the media element can be looped.
|
||||
* `editFlags` Object - These flags indicate whether the renderer believes it
|
||||
is able to perform the corresponding action.
|
||||
* `canUndo` Boolean - Whether the renderer believes it can undo.
|
||||
* `canRedo` Boolean - Whether the renderer believes it can redo.
|
||||
* `canCut` Boolean - Whether the renderer believes it can cut.
|
||||
* `canCopy` Boolean - Whether the renderer believes it can copy.
|
||||
* `canPaste` Boolean - Whether the renderer believes it can paste.
|
||||
* `canDelete` Boolean - Whether the renderer believes it can delete.
|
||||
* `canSelectAll` Boolean - Whether the renderer believes it can select all.
|
||||
* `canEditRichly` Boolean - Whether the renderer believes it can edit text richly.
|
||||
|
||||
Emitted when there is a new context menu that needs to be handled.
|
||||
|
||||
@@ -12,75 +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`
|
||||
|
||||
Prior to Electron 15, `window.open` was by default shimmed to use
|
||||
`BrowserWindowProxy`. This meant that `window.open('about:blank')` did not work
|
||||
to open synchronously scriptable child windows, among other incompatibilities.
|
||||
`nativeWindowOpen` is no longer experimental, and is now the default.
|
||||
|
||||
See the documentation for [window.open in Electron](api/window-open.md)
|
||||
for more details.
|
||||
|
||||
## Planned Breaking API Changes (14.0)
|
||||
|
||||
### Removed: `remote` module
|
||||
@@ -131,6 +62,16 @@ ensure your code works with this property enabled. It has been enabled by defau
|
||||
|
||||
You will be affected by this change if you use either `webFrame.executeJavaScript` or `webFrame.executeJavaScriptInIsolatedWorld`. You will need to ensure that values returned by either of those methods are supported by the [Context Bridge API](api/context-bridge.md#parameter--error--return-type-support) as these methods use the same value passing semantics.
|
||||
|
||||
### Default Changed: `nativeWindowOpen` defaults to `true`
|
||||
|
||||
Prior to Electron 14, `window.open` was by default shimmed to use
|
||||
`BrowserWindowProxy`. This meant that `window.open('about:blank')` did not work
|
||||
to open synchronously scriptable child windows, among other incompatibilities.
|
||||
`nativeWindowOpen` is no longer experimental, and is now the default.
|
||||
|
||||
See the documentation for [window.open in Electron](api/window-open.md)
|
||||
for more details.
|
||||
|
||||
### Removed: BrowserWindowConstructorOptions inheriting from parent windows
|
||||
|
||||
Prior to Electron 14, windows opened with `window.open` would inherit
|
||||
@@ -634,7 +575,7 @@ error.
|
||||
### API Changed: `shell.openItem` is now `shell.openPath`
|
||||
|
||||
The `shell.openItem` API has been replaced with an asynchronous `shell.openPath` API.
|
||||
You can see the original API proposal and reasoning [here](https://github.com/electron/governance/blob/main/wg-api/spec-documents/shell-openitem.md).
|
||||
You can see the original API proposal and reasoning [here](https://github.com/electron/governance/blob/master/wg-api/spec-documents/shell-openitem.md).
|
||||
|
||||
## Planned Breaking API Changes (8.0)
|
||||
|
||||
|
||||
@@ -4,8 +4,8 @@ These guides are intended for people working on the Electron project itself.
|
||||
For guides on Electron app development, see
|
||||
[/docs/README.md](../README.md#guides-and-tutorials).
|
||||
|
||||
* [Code of Conduct](https://github.com/electron/electron/blob/main/CODE_OF_CONDUCT.md)
|
||||
* [Contributing to Electron](https://github.com/electron/electron/blob/main/CONTRIBUTING.md)
|
||||
* [Code of Conduct](https://github.com/electron/electron/blob/master/CODE_OF_CONDUCT.md)
|
||||
* [Contributing to Electron](https://github.com/electron/electron/blob/master/CONTRIBUTING.md)
|
||||
* [Issues](issues.md)
|
||||
* [Pull Requests](pull-requests.md)
|
||||
* [Documentation Styleguide](coding-style.md#documentation)
|
||||
|
||||
@@ -8,7 +8,7 @@ Example Use Case:
|
||||
* We need `VS15.9` and we have `VS15.7` installed; this would require us to update an Azure image.
|
||||
|
||||
1. Identify the image you wish to modify.
|
||||
* In [appveyor.yml](https://github.com/electron/electron/blob/main/appveyor.yml), the image is identified by the property *image*.
|
||||
* In [appveyor.yml](https://github.com/electron/electron/blob/master/appveyor.yml), the image is identified by the property *image*.
|
||||
* The names used correspond to the *"images"* defined for a build cloud, eg the [libcc-20 cloud](https://windows-ci.electronjs.org/build-clouds/8).
|
||||
* Find the image you wish to modify in the build cloud and make note of the **VHD Blob Path** for that image, which is the value for that corresponding key.
|
||||
* You will need this URI path to copy into a new image.
|
||||
|
||||
@@ -65,8 +65,8 @@ origin URLs.
|
||||
$ cd src/electron
|
||||
$ git remote remove origin
|
||||
$ git remote add origin https://github.com/electron/electron
|
||||
$ git checkout main
|
||||
$ git branch --set-upstream-to=origin/main
|
||||
$ git checkout master
|
||||
$ git branch --set-upstream-to=origin/master
|
||||
$ cd -
|
||||
```
|
||||
|
||||
|
||||
@@ -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') },
|
||||
];
|
||||
```
|
||||
@@ -26,9 +26,7 @@ you prefer a graphical interface.
|
||||
* **.lldbinit**: Create or edit `~/.lldbinit` to allow Chromium code to be properly source-mapped.
|
||||
|
||||
```text
|
||||
# e.g: ['~/electron/src/tools/lldb']
|
||||
script sys.path[:0] = ['<...path/to/electron/src/tools/lldb>']
|
||||
script import lldbinit
|
||||
command script import ~/electron/src/tools/lldb/lldbinit.py
|
||||
```
|
||||
|
||||
## Attaching to and Debugging Electron
|
||||
|
||||
@@ -72,4 +72,4 @@ to try NW.js.
|
||||
|
||||
[nwjs]: https://nwjs.io/
|
||||
[electron-modules]: https://www.npmjs.com/search?q=electron
|
||||
[node-bindings]: https://github.com/electron/electron/tree/main/lib/common
|
||||
[node-bindings]: https://github.com/electron/electron/tree/master/lib/common
|
||||
|
||||
@@ -45,10 +45,10 @@ Once you've built the project locally, you're ready to start making changes!
|
||||
### Step 3: Branch
|
||||
|
||||
To keep your development environment organized, create local branches to
|
||||
hold your work. These should be branched directly off of the `main` branch.
|
||||
hold your work. These should be branched directly off of the `master` branch.
|
||||
|
||||
```sh
|
||||
$ git checkout -b my-branch -t upstream/main
|
||||
$ git checkout -b my-branch -t upstream/master
|
||||
```
|
||||
|
||||
## Making Changes
|
||||
@@ -134,11 +134,11 @@ Once you have committed your changes, it is a good idea to use `git rebase`
|
||||
|
||||
```sh
|
||||
$ git fetch upstream
|
||||
$ git rebase upstream/main
|
||||
$ git rebase upstream/master
|
||||
```
|
||||
|
||||
This ensures that your working branch has the latest changes from `electron/electron`
|
||||
main.
|
||||
master.
|
||||
|
||||
### Step 7: Test
|
||||
|
||||
@@ -189,7 +189,7 @@ the requirements below.
|
||||
|
||||
Bug fixes and new features should include tests and possibly benchmarks.
|
||||
|
||||
Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
|
||||
Contributors guide: https://github.com/electron/electron/blob/master/CONTRIBUTING.md
|
||||
-->
|
||||
```
|
||||
|
||||
@@ -222,7 +222,7 @@ seem unfamiliar, refer to this
|
||||
#### Approval and Request Changes Workflow
|
||||
|
||||
All pull requests require approval from a
|
||||
[Code Owner](https://github.com/electron/electron/blob/main/.github/CODEOWNERS)
|
||||
[Code Owner](https://github.com/electron/electron/blob/master/.github/CODEOWNERS)
|
||||
of the area you modified in order to land. Whenever a maintainer reviews a pull
|
||||
request they may request changes. These may be small, such as fixing a typo, or
|
||||
may involve substantive changes. Such requests are intended to be helpful, but
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>Web Bluetooth API</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Web Bluetooth API</h1>
|
||||
|
||||
<button id="clickme">Test Bluetooth</button>
|
||||
|
||||
<p>Currently selected bluetooth device: <strong id="device-name""></strong></p>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
mainWindow.webContents.on('select-bluetooth-device', (event, deviceList, callback) => {
|
||||
event.preventDefault()
|
||||
if (deviceList && deviceList.length > 0) {
|
||||
callback(deviceList[0].deviceId)
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
@@ -1,8 +0,0 @@
|
||||
async function testIt() {
|
||||
const device = await navigator.bluetooth.requestDevice({
|
||||
acceptAllDevices: true
|
||||
})
|
||||
document.getElementById('device-name').innerHTML = device.name || `ID: ${device.id}`
|
||||
}
|
||||
|
||||
document.getElementById('clickme').addEventListener('click',testIt)
|
||||
@@ -1,21 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>WebHID API</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>WebHID API</h1>
|
||||
|
||||
<button id="clickme">Test WebHID</button>
|
||||
|
||||
<h3>HID devices automatically granted access via <i>setDevicePermissionHandler</i></h3>
|
||||
<div id="granted-devices"></div>
|
||||
|
||||
<h3>HID devices automatically granted access via <i>select-hid-device</i></h3>
|
||||
<div id="granted-devices2"></div>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,50 +0,0 @@
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('select-hid-device', (event, details, callback) => {
|
||||
event.preventDefault()
|
||||
if (details.deviceList && details.deviceList.length > 0) {
|
||||
callback(details.deviceList[0].deviceId)
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('hid-device-added', (event, device) => {
|
||||
console.log('hid-device-added FIRED WITH', device)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('hid-device-removed', (event, device) => {
|
||||
console.log('hid-device-removed FIRED WITH', device)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'hid' && details.securityOrigin === 'file:///') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (details.deviceType === 'hid' && details.origin === 'file://') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
@@ -1,19 +0,0 @@
|
||||
async function testIt() {
|
||||
const grantedDevices = await navigator.hid.getDevices()
|
||||
let grantedDeviceList = ''
|
||||
grantedDevices.forEach(device => {
|
||||
grantedDeviceList += `<hr>${device.productName}</hr>`
|
||||
})
|
||||
document.getElementById('granted-devices').innerHTML = grantedDeviceList
|
||||
const grantedDevices2 = await navigator.hid.requestDevice({
|
||||
filters: []
|
||||
})
|
||||
|
||||
grantedDeviceList = ''
|
||||
grantedDevices2.forEach(device => {
|
||||
grantedDeviceList += `<hr>${device.productName}</hr>`
|
||||
})
|
||||
document.getElementById('granted-devices2').innerHTML = grantedDeviceList
|
||||
}
|
||||
|
||||
document.getElementById('clickme').addEventListener('click',testIt)
|
||||
@@ -1,16 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self'">
|
||||
<title>Web Serial API</title>
|
||||
<body>
|
||||
<h1>Web Serial API</h1>
|
||||
|
||||
<button id="clickme">Test Web Serial API</button>
|
||||
|
||||
<p>Matching Arduino Uno device: <strong id="device-name""></strong></p>
|
||||
|
||||
<script src="./renderer.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,54 +0,0 @@
|
||||
const {app, BrowserWindow} = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
function createWindow () {
|
||||
const mainWindow = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('select-serial-port', (event, portList, webContents, callback) => {
|
||||
event.preventDefault()
|
||||
if (portList && portList.length > 0) {
|
||||
callback(portList[0].portId)
|
||||
} else {
|
||||
callback('') //Could not find any matching devices
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('serial-port-added', (event, port) => {
|
||||
console.log('serial-port-added FIRED WITH', port)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.on('serial-port-removed', (event, port) => {
|
||||
console.log('serial-port-removed FIRED WITH', port)
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setPermissionCheckHandler((webContents, permission, requestingOrigin, details) => {
|
||||
if (permission === 'serial' && details.securityOrigin === 'file:///') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.webContents.session.setDevicePermissionHandler((details) => {
|
||||
if (details.deviceType === 'serial' && details.origin === 'file://') {
|
||||
return true
|
||||
}
|
||||
})
|
||||
|
||||
mainWindow.loadFile('index.html')
|
||||
|
||||
mainWindow.webContents.openDevTools()
|
||||
}
|
||||
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
|
||||
app.on('activate', function () {
|
||||
if (BrowserWindow.getAllWindows().length === 0) createWindow()
|
||||
})
|
||||
})
|
||||
|
||||
app.on('window-all-closed', function () {
|
||||
if (process.platform !== 'darwin') app.quit()
|
||||
})
|
||||
@@ -1,19 +0,0 @@
|
||||
async function testIt() {
|
||||
const filters = [
|
||||
{ usbVendorId: 0x2341, usbProductId: 0x0043 },
|
||||
{ usbVendorId: 0x2341, usbProductId: 0x0001 }
|
||||
];
|
||||
try {
|
||||
const port = await navigator.serial.requestPort({filters});
|
||||
const portInfo = port.getInfo();
|
||||
document.getElementById('device-name').innerHTML = `vendorId: ${portInfo.usbVendorId} | productId: ${portInfo.usbProductId} `
|
||||
} catch (ex) {
|
||||
if (ex.name === 'NotFoundError') {
|
||||
document.getElementById('device-name').innerHTML = 'Device NOT found'
|
||||
} else {
|
||||
document.getElementById('device-name').innerHTML = ex
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
document.getElementById('clickme').addEventListener('click',testIt)
|
||||
@@ -1,5 +1,5 @@
|
||||
// Modules to control application life and create native browser window
|
||||
const { app, BrowserWindow, ipcMain, shell, dialog } = require('electron')
|
||||
const { app, BrowserWindow, ipcMain, shell } = require('electron')
|
||||
const path = require('path')
|
||||
|
||||
let mainWindow;
|
||||
|
||||
118
docs/glossary.md
118
docs/glossary.md
@@ -4,39 +4,15 @@ This page defines some terminology that is commonly used in Electron development
|
||||
|
||||
### ASAR
|
||||
|
||||
ASAR stands for Atom Shell Archive Format. An [asar] archive is a simple
|
||||
ASAR stands for Atom Shell Archive Format. An [asar][asar] archive is a simple
|
||||
`tar`-like format that concatenates files into a single file. Electron can read
|
||||
arbitrary files from it without unpacking the whole file.
|
||||
|
||||
The ASAR format was created primarily to improve performance on Windows when
|
||||
reading large quantities of small files (e.g. when loading your app's JavaScript
|
||||
dependency tree from `node_modules`).
|
||||
|
||||
### code signing
|
||||
|
||||
Code signing is a process where an app developer digitally signs their code to
|
||||
ensure that it hasn't been tampered with after packaging. Both Windows and
|
||||
macOS implement their own version of code signing. As a desktop app developer,
|
||||
it's important that you sign your code if you plan on distributing it to the
|
||||
general public.
|
||||
|
||||
For more information, read the [Code Signing] tutorial.
|
||||
|
||||
### context isolation
|
||||
|
||||
Context isolation is a security measure in Electron that ensures that your
|
||||
preload script cannot leak privileged Electron or Node.js APIs to the web
|
||||
contents in your renderer process. With context isolation enabled, the
|
||||
only way to expose APIs from your preload script is through the
|
||||
`contextBridge` API.
|
||||
|
||||
For more information, read the [Context Isolation] tutorial.
|
||||
|
||||
See also: [preload script](#preload-script), [renderer process](#renderer-process)
|
||||
The ASAR format was created primarily to improve performance on Windows... TODO
|
||||
|
||||
### CRT
|
||||
|
||||
The C Runtime Library (CRT) is the part of the C++ Standard Library that
|
||||
The C Run-time Library (CRT) is the part of the C++ Standard Library that
|
||||
incorporates the ISO C99 standard library. The Visual C++ libraries that
|
||||
implement the CRT support native code development, and both mixed native and
|
||||
managed code, and pure managed code for .NET development.
|
||||
@@ -44,7 +20,8 @@ managed code, and pure managed code for .NET development.
|
||||
### DMG
|
||||
|
||||
An Apple Disk Image is a packaging format used by macOS. DMG files are
|
||||
commonly used for distributing application "installers".
|
||||
commonly used for distributing application "installers". [electron-builder]
|
||||
supports `dmg` as a build target.
|
||||
|
||||
### IME
|
||||
|
||||
@@ -54,15 +31,19 @@ keyboards to input Chinese, Japanese, Korean and Indic characters.
|
||||
|
||||
### IDL
|
||||
|
||||
Interface description language. Write function signatures and data types in a
|
||||
format that can be used to generate interfaces in Java, C++, JavaScript, etc.
|
||||
Interface description language. Write function signatures and data types in a format that can be used to generate interfaces in Java, C++, JavaScript, etc.
|
||||
|
||||
### IPC
|
||||
|
||||
IPC stands for inter-process communication. Electron uses IPC to send
|
||||
serialized JSON messages between the main and renderer processes.
|
||||
IPC stands for Inter-Process Communication. Electron uses IPC to send
|
||||
serialized JSON messages between the [main] and [renderer] processes.
|
||||
|
||||
see also: [main process](#main-process), [renderer process](#renderer-process)
|
||||
### libchromiumcontent
|
||||
|
||||
A shared library that includes the [Chromium Content module] and all its
|
||||
dependencies (e.g., Blink, [V8], etc.). Also referred to as "libcc".
|
||||
|
||||
- [github.com/electron/libchromiumcontent](https://github.com/electron/libchromiumcontent)
|
||||
|
||||
### main process
|
||||
|
||||
@@ -87,22 +68,10 @@ MAS, see the [Mac App Store Submission Guide].
|
||||
|
||||
### Mojo
|
||||
|
||||
An IPC system for communicating intra- or inter-process, and that's important
|
||||
because Chrome is keen on being able to split its work into separate processes
|
||||
or not, depending on memory pressures etc.
|
||||
An IPC system for communicating intra- or inter-process, and that's important because Chrome is keen on being able to split its work into separate processes or not, depending on memory pressures etc.
|
||||
|
||||
See https://chromium.googlesource.com/chromium/src/+/master/mojo/README.md
|
||||
|
||||
See also: [IPC](#ipc)
|
||||
|
||||
### MSI
|
||||
|
||||
On Windows, MSI packages are used by the Windows Installer
|
||||
(also known as Microsoft Installer) service to install and configure
|
||||
applications.
|
||||
|
||||
More information can be found in [Microsoft's documentation][msi].
|
||||
|
||||
### native modules
|
||||
|
||||
Native modules (also called [addons] in
|
||||
@@ -116,33 +85,22 @@ likely to use a different V8 version from the Node binary installed in your
|
||||
system, you have to manually specify the location of Electron’s headers when
|
||||
building native modules.
|
||||
|
||||
For more information, read the [Native Node Modules] tutorial.
|
||||
See also [Using Native Node Modules].
|
||||
|
||||
### notarization
|
||||
### NSIS
|
||||
|
||||
Notarization is a macOS-specific process where a developer can send a
|
||||
code-signed app to Apple servers to get verified for malicious
|
||||
components through an automated service.
|
||||
|
||||
See also: [code signing](#code-signing)
|
||||
Nullsoft Scriptable Install System is a script-driven Installer
|
||||
authoring tool for Microsoft Windows. It is released under a combination of
|
||||
free software licenses, and is a widely-used alternative to commercial
|
||||
proprietary products like InstallShield. [electron-builder] supports NSIS
|
||||
as a build target.
|
||||
|
||||
### OSR
|
||||
|
||||
OSR (offscreen rendering) can be used for loading heavy page in
|
||||
OSR (Off-screen rendering) can be used for loading heavy page in
|
||||
background and then displaying it after (it will be much faster).
|
||||
It allows you to render page without showing it on screen.
|
||||
|
||||
For more information, read the [Offscreen Rendering][osr] tutorial.
|
||||
|
||||
### preload script
|
||||
|
||||
Preload scripts contain code that executes in a renderer process
|
||||
before its web contents begin loading. These scripts run within
|
||||
the renderer context, but are granted more privileges by having
|
||||
access to Node.js APIs.
|
||||
|
||||
See also: [renderer process](#renderer-process), [context isolation](#context-isolation)
|
||||
|
||||
### process
|
||||
|
||||
A process is an instance of a computer program that is being executed. Electron
|
||||
@@ -162,17 +120,13 @@ The renderer process is a browser window in your app. Unlike the main process,
|
||||
there can be multiple of these and each is run in a separate process.
|
||||
They can also be hidden.
|
||||
|
||||
In normal browsers, web pages usually run in a sandboxed environment and are not
|
||||
allowed access to native resources. Electron users, however, have the power to
|
||||
use Node.js APIs in web pages allowing lower level operating system
|
||||
interactions.
|
||||
|
||||
See also: [process](#process), [main process](#main-process)
|
||||
|
||||
### sandbox
|
||||
|
||||
The sandbox is a security feature inherited from Chromium that restricts
|
||||
your renderer processes to a limited set of permissions.
|
||||
|
||||
For more information, read the [Process Sandboxing] tutorial.
|
||||
|
||||
See also: [process](#process)
|
||||
|
||||
### Squirrel
|
||||
|
||||
Squirrel is an open-source framework that enables Electron apps to update
|
||||
@@ -220,15 +174,13 @@ embedded content.
|
||||
|
||||
[addons]: https://nodejs.org/api/addons.html
|
||||
[asar]: https://github.com/electron/asar
|
||||
[autoupdater]: api/auto-updater.md
|
||||
[code signing]: tutorial/code-signing.md
|
||||
[context isolation]: tutorial/context-isolation.md
|
||||
[mac app store submission guide]: tutorial/mac-app-store-submission-guide.md
|
||||
[autoUpdater]: api/auto-updater.md
|
||||
[Chromium Content module]: https://www.chromium.org/developers/content-module
|
||||
[electron-builder]: https://github.com/electron-userland/electron-builder
|
||||
[libchromiumcontent]: #libchromiumcontent
|
||||
[Mac App Store Submission Guide]: tutorial/mac-app-store-submission-guide.md
|
||||
[main]: #main-process
|
||||
[msi]: https://docs.microsoft.com/en-us/windows/win32/msi/windows-installer-portal
|
||||
[offscreen rendering]: tutorial/offscreen-rendering.md
|
||||
[process sandboxing]: tutorial/sandbox.md
|
||||
[renderer]: #renderer-process
|
||||
[userland]: #userland
|
||||
[using native node modules]: tutorial/using-native-node-modules.md
|
||||
[v8]: #v8
|
||||
[Using Native Node Modules]: tutorial/using-native-node-modules.md
|
||||
[V8]: #v8
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 19 KiB |
@@ -21,7 +21,7 @@ In the testing framework Spectron, you can now audit each window and `<webview>`
|
||||
tag in your application. For example:
|
||||
|
||||
```javascript
|
||||
app.client.auditAccessibility().then((audit) => {
|
||||
app.client.auditAccessibility().then(function (audit) {
|
||||
if (audit.failed) {
|
||||
console.error(audit.message)
|
||||
}
|
||||
|
||||
@@ -84,15 +84,11 @@ class TestDriver {
|
||||
In the app, you'd need to write a simple handler for the RPC calls:
|
||||
|
||||
```js
|
||||
const METHODS = {
|
||||
isReady () {
|
||||
// do any setup needed
|
||||
return true
|
||||
}
|
||||
// define your RPC-able methods here
|
||||
if (process.env.APP_TEST_DRIVER) {
|
||||
process.on('message', onMessage)
|
||||
}
|
||||
|
||||
const onMessage = async ({ msgId, cmd, args }) => {
|
||||
async function onMessage ({ msgId, cmd, args }) {
|
||||
let method = METHODS[cmd]
|
||||
if (!method) method = () => new Error('Invalid method: ' + cmd)
|
||||
try {
|
||||
@@ -108,8 +104,12 @@ const onMessage = async ({ msgId, cmd, args }) => {
|
||||
}
|
||||
}
|
||||
|
||||
if (process.env.APP_TEST_DRIVER) {
|
||||
process.on('message', onMessage)
|
||||
const METHODS = {
|
||||
isReady () {
|
||||
// do any setup needed
|
||||
return true
|
||||
}
|
||||
// define your RPC-able methods here
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -4,38 +4,39 @@
|
||||
|
||||
Context Isolation is a feature that ensures that both your `preload` scripts and Electron's internal logic run in a separate context to the website you load in a [`webContents`](../api/web-contents.md). This is important for security purposes as it helps prevent the website from accessing Electron internals or the powerful APIs your preload script has access to.
|
||||
|
||||
This means that the `window` object that your preload script has access to is actually a **different** object than the website would have access to. For example, if you set `window.hello = 'wave'` in your preload script and context isolation is enabled, `window.hello` will be undefined if the website tries to access it.
|
||||
This means that the `window` object that your preload script has access to is actually a **different** object than the website would have access to. For example, if you set `window.hello = 'wave'` in your preload script and context isolation is enabled `window.hello` will be undefined if the website tries to access it.
|
||||
|
||||
Context isolation has been enabled by default since Electron 12, and it is a recommended security setting for _all applications_.
|
||||
Every single application should have context isolation enabled and from Electron 12 it will be enabled by default.
|
||||
|
||||
## How do I enable it?
|
||||
|
||||
From Electron 12, it will be enabled by default. For lower versions it is an option in the `webPreferences` option when constructing `new BrowserWindow`'s.
|
||||
|
||||
```javascript
|
||||
const mainWindow = new BrowserWindow({
|
||||
webPreferences: {
|
||||
contextIsolation: true
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
## Migration
|
||||
|
||||
> Without context isolation, I used to provide APIs from my preload script using `window.X = apiObject`. Now what?
|
||||
> I used to provide APIs from my preload script using `window.X = apiObject` now what?
|
||||
|
||||
### Before: context isolation disabled
|
||||
Exposing APIs from your preload script to the loaded website is a common usecase and there is a dedicated module in Electron to help you do this in a painless way.
|
||||
|
||||
Exposing APIs from your preload script to a loaded website in the renderer process is a common use-case. With context isolation disabled, your preload script would share a common global `window` object with the renderer. You could then attach arbitrary properties to a preload script:
|
||||
**Before: With context isolation disabled**
|
||||
|
||||
```javascript title='preload.js'
|
||||
// preload with contextIsolation disabled
|
||||
```javascript
|
||||
window.myAPI = {
|
||||
doAThing: () => {}
|
||||
}
|
||||
```
|
||||
|
||||
The `doAThing()` function could then be used directly in the renderer process:
|
||||
**After: With context isolation enabled**
|
||||
|
||||
```javascript title='renderer.js'
|
||||
// use the exposed API in the renderer
|
||||
window.myAPI.doAThing()
|
||||
```
|
||||
|
||||
### After: context isolation enabled
|
||||
|
||||
There is a dedicated module in Electron to help you do this in a painless way. The [`contextBridge`](../api/context-bridge.md) module can be used to **safely** expose APIs from your preload script's isolated context to the context the website is running in. The API will also be accessible from the website on `window.myAPI` just like it was before.
|
||||
|
||||
```javascript title='preload.js'
|
||||
// preload with contextIsolation enabled
|
||||
```javascript
|
||||
const { contextBridge } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld('myAPI', {
|
||||
@@ -43,63 +44,26 @@ contextBridge.exposeInMainWorld('myAPI', {
|
||||
})
|
||||
```
|
||||
|
||||
```javascript title='renderer.js'
|
||||
// use the exposed API in the renderer
|
||||
window.myAPI.doAThing()
|
||||
```
|
||||
The [`contextBridge`](../api/context-bridge.md) module can be used to **safely** expose APIs from the isolated context your preload script runs in to the context the website is running in. The API will also be accessible from the website on `window.myAPI` just like it was before.
|
||||
|
||||
Please read the `contextBridge` documentation linked above to fully understand its limitations. For instance, you can't send custom prototypes or symbols over the bridge.
|
||||
You should read the `contextBridge` documentation linked above to fully understand its limitations. For instance you can't send custom prototypes or symbols over the bridge.
|
||||
|
||||
## Security considerations
|
||||
## Security Considerations
|
||||
|
||||
Just enabling `contextIsolation` and using `contextBridge` does not automatically mean that everything you do is safe. For instance, this code is **unsafe**.
|
||||
Just enabling `contextIsolation` and using `contextBridge` does not automatically mean that everything you do is safe. For instance this code is **unsafe**.
|
||||
|
||||
```javascript title='preload.js'
|
||||
```javascript
|
||||
// ❌ Bad code
|
||||
contextBridge.exposeInMainWorld('myAPI', {
|
||||
send: ipcRenderer.send
|
||||
})
|
||||
```
|
||||
|
||||
It directly exposes a powerful API without any kind of argument filtering. This would allow any website to send arbitrary IPC messages, which you do not want to be possible. The correct way to expose IPC-based APIs would instead be to provide one method per IPC message.
|
||||
It directly exposes a powerful API without any kind of argument filtering. This would allow any website to send arbitrary IPC messages which you do not want to be possible. The correct way to expose IPC-based APIs would instead be to provide one method per IPC message.
|
||||
|
||||
```javascript title='preload.js'
|
||||
```javascript
|
||||
// ✅ Good code
|
||||
contextBridge.exposeInMainWorld('myAPI', {
|
||||
loadPreferences: () => ipcRenderer.invoke('load-prefs')
|
||||
})
|
||||
```
|
||||
|
||||
## Usage with TypeScript
|
||||
|
||||
If you're building your Electron app with TypeScript, you'll want to add types to your APIs exposed over the context bridge. The renderer's `window` object won't have the correct typings unless you extend the types with a [declaration file].
|
||||
|
||||
For example, given this `preload.ts` script:
|
||||
|
||||
```typescript title='preload.ts'
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
loadPreferences: () => ipcRenderer.invoke('load-prefs')
|
||||
})
|
||||
```
|
||||
|
||||
You can create a `renderer.d.ts` declaration file and globally augment the `Window` interface:
|
||||
|
||||
```typescript title='renderer.d.ts'
|
||||
export interface IElectronAPI {
|
||||
loadPreferences: () => Promise<void>,
|
||||
}
|
||||
|
||||
declare global {
|
||||
interface Window {
|
||||
electronAPI: IElectronAPI
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Doing so will ensure that the TypeScript compiler will know about the `electronAPI` property on your global `window` object when writing scripts in your renderer process:
|
||||
|
||||
```typescript title='renderer.ts'
|
||||
window.electronAPI.loadPreferences()
|
||||
```
|
||||
|
||||
[declaration file]: https://www.typescriptlang.org/docs/handbook/declaration-files/introduction.html
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -1,108 +0,0 @@
|
||||
# Device Access
|
||||
|
||||
Like Chromium based browsers, Electron provides access to device hardware
|
||||
through web APIs. For the most part these APIs work like they do in a browser,
|
||||
but there are some differences that need to be taken into account. The primary
|
||||
difference between Electron and browsers is what happens when device access is
|
||||
requested. In a browser, users are presented with a popup where they can grant
|
||||
access to an individual device. In Electron APIs are provided which can be
|
||||
used by a developer to either automatically pick a device or prompt users to
|
||||
pick a device via a developer created interface.
|
||||
|
||||
## Web Bluetooth API
|
||||
|
||||
The [Web Bluetooth API](https://web.dev/bluetooth/) can be used to communicate
|
||||
with bluetooth devices. In order to use this API in Electron, developers will
|
||||
need to handle the [`select-bluetooth-device` event on the webContents](../api/web-contents.md#event-select-bluetooth-device)
|
||||
associated with the device request.
|
||||
|
||||
### Example
|
||||
|
||||
This example demonstrates an Electron application that automatically selects
|
||||
the first available bluetooth device when the `Test Bluetooth` button is
|
||||
clicked.
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/web-bluetooth'
|
||||
|
||||
```
|
||||
|
||||
## WebHID API
|
||||
|
||||
The [WebHID API](https://web.dev/hid/) can be used to access HID devices such
|
||||
as keyboards and gamepads. Electron provides several APIs for working with
|
||||
the WebHID API:
|
||||
|
||||
* The [`select-hid-device` event on the Session](../api/session.md#event-select-hid-device)
|
||||
can be used to select a HID device when a call to
|
||||
`navigator.hid.requestDevice` is made. Additionally the [`hid-device-added`](../api/session.md#event-hid-device-added)
|
||||
and [`hid-device-removed`](../api/session.md#event-hid-device-removed) events
|
||||
on the Session can be used to handle devices being plugged in or unplugged during the
|
||||
`navigator.hid.requestDevice` process.
|
||||
* [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
can be used to provide default permissioning to devices without first calling
|
||||
for permission to devices via `navigator.hid.requestDevice`. Additionally,
|
||||
the default behavior of Electron is to store granted device permision through
|
||||
the lifetime of the corresponding WebContents. If longer term storage is
|
||||
needed, a developer can store granted device permissions (eg when handling
|
||||
the `select-hid-device` event) and then read from that storage with
|
||||
`setDevicePermissionHandler`.
|
||||
* [`ses.setPermissionCheckHandler(handler)`](../api/session.md#sessetpermissioncheckhandlerhandler)
|
||||
can be used to disable HID access for specific origins.
|
||||
|
||||
### Blocklist
|
||||
|
||||
By default Electron employs the same [blocklist](https://github.com/WICG/webhid/blob/main/blocklist.txt)
|
||||
used by Chromium. If you wish to override this behavior, you can do so by
|
||||
setting the `disable-hid-blocklist` flag:
|
||||
|
||||
```javascript
|
||||
app.commandLine.appendSwitch('disable-hid-blocklist')
|
||||
```
|
||||
|
||||
### Example
|
||||
|
||||
This example demonstrates an Electron application that automatically selects
|
||||
HID devices through [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
and through [`select-hid-device` event on the Session](../api/session.md#event-select-hid-device)
|
||||
when the `Test WebHID` button is clicked.
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/web-hid'
|
||||
|
||||
```
|
||||
|
||||
## Web Serial API
|
||||
|
||||
The [Web Serial API](https://web.dev/serial/) can be used to access serial
|
||||
devices that are connected via serial port, USB, or Bluetooth. In order to use
|
||||
this API in Electron, developers will need to handle the
|
||||
[`select-serial-port` event on the Session](../api/session.md#event-select-serial-port)
|
||||
associated with the serial port request.
|
||||
|
||||
There are several additional APIs for working with the Web Serial API:
|
||||
|
||||
* The [`serial-port-added`](../api/session.md#event-serial-port-added)
|
||||
and [`serial-port-removed`](../api/session.md#event-serial-port-removed) events
|
||||
on the Session can be used to handle devices being plugged in or unplugged during the
|
||||
`navigator.serial.requestPort` process.
|
||||
* [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
can be used to provide default permissioning to devices without first calling
|
||||
for permission to devices via `navigator.serial.requestPort`. Additionally,
|
||||
the default behavior of Electron is to store granted device permision through
|
||||
the lifetime of the corresponding WebContents. If longer term storage is
|
||||
needed, a developer can store granted device permissions (eg when handling
|
||||
the `select-serial-port` event) and then read from that storage with
|
||||
`setDevicePermissionHandler`.
|
||||
* [`ses.setPermissionCheckHandler(handler)`](../api/session.md#sessetpermissioncheckhandlerhandler)
|
||||
can be used to disable serial access for specific origins.
|
||||
|
||||
### Example
|
||||
|
||||
This example demonstrates an Electron application that automatically selects
|
||||
serial devices through [`ses.setDevicePermissionHandler(handler)`](../api/session.md#sessetdevicepermissionhandlerhandler)
|
||||
as well as demonstrating selecting the first available Arduino Uno serial device (if connected) through
|
||||
[`select-serial-port` event on the Session](../api/session.md#event-select-serial-port)
|
||||
when the `Test Web Serial` button is clicked.
|
||||
|
||||
```javascript fiddle='docs/fiddles/features/web-serial'
|
||||
|
||||
```
|
||||
@@ -23,6 +23,5 @@ Special notes:
|
||||
| 11.0.0 | -- | 2020-Aug-27 | 2020-Nov-17 | M87 | v12.18 |
|
||||
| 12.0.0 | -- | 2020-Nov-19 | 2021-Mar-02 | M89 | v14.16 |
|
||||
| 13.0.0 | -- | 2021-Mar-04 | 2021-May-25 | M91 | v14.16 |
|
||||
| 14.0.0 | -- | 2021-May-27 | 2021-Aug-31 | M93 | v14.17 |
|
||||
| 15.0.0 | 2021-Jul-20 | 2021-Sep-01 | 2021-Sep-21 | M94 | v16.5 |
|
||||
| 16.0.0 | -- | 2021-Sep-23 | 2021-Nov-16 | M96 | TBD |
|
||||
| 14.0.0 | -- | 2021-May-27 | 2021-Aug-31 | M93 | TBD |
|
||||
| 15.0.0 | 2021-Jul-20 | 2021-Sep-01 | 2021-Sep-21 | M94 | TBD |
|
||||
|
||||
@@ -2,31 +2,43 @@
|
||||
|
||||
> A detailed look at our versioning policy and implementation.
|
||||
|
||||
As of version 2.0.0, Electron follows the [SemVer](#semver) spec. The following command will install the most recent stable build of Electron:
|
||||
As of version 2.0.0, Electron follows [SemVer](#semver). The following command will install the most recent stable build of Electron:
|
||||
|
||||
```sh npm2yarn
|
||||
```sh
|
||||
npm install --save-dev electron
|
||||
```
|
||||
|
||||
To update an existing project to use the latest stable version:
|
||||
|
||||
```sh npm2yarn
|
||||
```sh
|
||||
npm install --save-dev electron@latest
|
||||
```
|
||||
|
||||
## Versioning scheme
|
||||
## Version 1.x
|
||||
|
||||
Electron versions *< 2.0* did not conform to the [SemVer](https://semver.org) spec: major versions corresponded to end-user API changes, minor versions corresponded to Chromium major releases, and patch versions corresponded to new features and bug fixes. While convenient for developers merging features, it creates problems for developers of client-facing applications. The QA testing cycles of major apps like Slack, Stride, Teams, Skype, VS Code, Atom, and Desktop can be lengthy and stability is a highly desired outcome. There is a high risk in adopting new features while trying to absorb bug fixes.
|
||||
|
||||
Here is an example of the 1.x strategy:
|
||||
|
||||

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

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

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

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

|
||||
|
||||
An app developed with `1.8.1` cannot take the `1.8.3` bug fix without either absorbing the `1.8.2` feature, or by backporting the fix and maintaining a new release line.
|
||||
* The `master` branch will always contain the next major version `X.0.0-nightly.DATE` in its `package.json`
|
||||
* Release branches are never merged back to master
|
||||
* Release branches _do_ contain the correct version in their `package.json`
|
||||
* As soon as a release branch is cut for a major, master must be bumped to the next major. I.e. `master` is always versioned as the next theoretical release branch
|
||||
|
||||
@@ -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(
|
||||
@@ -51,4 +51,4 @@ Somewhere in the Electron binary there will be a sequence of bytes that look lik
|
||||
|
||||
To flip a fuse you find its position in the fuse wire and change it to "0" or "1" depending on the state you'd like.
|
||||
|
||||
You can view the current schema [here](https://github.com/electron/electron/blob/main/build/fuses/fuses.json5).
|
||||
You can view the current schema [here](https://github.com/electron/electron/blob/master/build/fuses/fuses.json5).
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -90,11 +90,6 @@ ELECTRON_CUSTOM_DIR="{{ version }}"
|
||||
The above configuration will download from URLs such as
|
||||
`https://npm.taobao.org/mirrors/electron/8.0.0/electron-v8.0.0-linux-x64.zip`.
|
||||
|
||||
If your mirror serves artifacts with different checksums to the official
|
||||
Electron release you may have to set `ELECTRON_USE_REMOTE_CHECKSUMS=1` to
|
||||
force Electron to use the remote `SHASUMS256.txt` file to verify the checksum
|
||||
instead of the embedded checksums.
|
||||
|
||||
#### Cache
|
||||
|
||||
Alternatively, you can override the local cache. `@electron/get` will cache
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -120,9 +120,9 @@ file in the directory you executed it in. Both files can be analyzed using
|
||||
the Chrome Developer Tools, using the `Performance` and `Memory` tabs
|
||||
respectively.
|
||||
|
||||

|
||||
![Performance CPU Profile][performance-cpu-prof]
|
||||
|
||||

|
||||
![Performance Heap Memory Profile][performance-heap-prof]
|
||||
|
||||
In this example, on the author's machine, we saw that loading `request` took
|
||||
almost half a second, whereas `node-fetch` took dramatically less memory
|
||||
@@ -412,6 +412,8 @@ As of writing this article, the popular choices include [Webpack][webpack],
|
||||
[Parcel][parcel], and [rollup.js][rollup].
|
||||
|
||||
[security]: ./security.md
|
||||
[performance-cpu-prof]: ../images/performance-cpu-prof.png
|
||||
[performance-heap-prof]: ../images/performance-heap-prof.png
|
||||
[chrome-devtools-tutorial]: https://developers.google.com/web/tools/chrome-devtools/evaluate-performance/
|
||||
[worker-threads]: https://nodejs.org/api/worker_threads.html
|
||||
[web-workers]: https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Using_web_workers
|
||||
|
||||
@@ -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()
|
||||
})
|
||||
```
|
||||
|
||||
@@ -7,16 +7,16 @@ without the need of switching to the window itself.
|
||||
|
||||
On Windows, you can use a taskbar button to display a progress bar.
|
||||
|
||||

|
||||
![Windows Progress Bar][windows-progress-bar]
|
||||
|
||||
On macOS, the progress bar will be displayed as a part of the dock icon.
|
||||
|
||||

|
||||
![macOS Progress Bar][macos-progress-bar]
|
||||
|
||||
On Linux, the Unity graphical interface also has a similar feature that allows
|
||||
you to specify the progress bar in the launcher.
|
||||
|
||||

|
||||
![Linux Progress Bar][linux-progress-bar]
|
||||
|
||||
> NOTE: on Windows, each window can have its own progress bar, whereas on macOS
|
||||
and Linux (Unity) there can be only one progress bar for the application.
|
||||
@@ -48,7 +48,7 @@ const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let progressInterval
|
||||
|
||||
const createWindow = () => {
|
||||
function createWindow () {
|
||||
const win = new BrowserWindow({
|
||||
width: 800,
|
||||
height: 600
|
||||
@@ -102,4 +102,8 @@ For macOS, the progress bar will also be indicated for your application
|
||||
when using [Mission Control](https://support.apple.com/en-us/HT204100):
|
||||
|
||||

|
||||
|
||||
[windows-progress-bar]: https://cloud.githubusercontent.com/assets/639601/5081682/16691fda-6f0e-11e4-9676-49b6418f1264.png
|
||||
[macos-progress-bar]: ../images/macos-progress-bar.png
|
||||
[linux-progress-bar]: ../images/linux-progress-bar.png
|
||||
[setprogressbar]: ../api/browser-window.md#winsetprogressbarprogress-options
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -23,7 +23,7 @@ your responsibility to ensure that the code is not malicious.
|
||||
## Reporting Security Issues
|
||||
|
||||
For information on how to properly disclose an Electron vulnerability,
|
||||
see [SECURITY.md](https://github.com/electron/electron/tree/main/SECURITY.md)
|
||||
see [SECURITY.md](https://github.com/electron/electron/tree/master/SECURITY.md)
|
||||
|
||||
## Chromium Security Issues and Upgrades
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
## Finding Support
|
||||
|
||||
If you have a security concern,
|
||||
please see the [security document](https://github.com/electron/electron/tree/main/SECURITY.md).
|
||||
please see the [security document](https://github.com/electron/electron/tree/master/SECURITY.md).
|
||||
|
||||
If you're looking for programming help,
|
||||
for answers to questions,
|
||||
@@ -26,7 +26,7 @@ you can interact with the community in these locations:
|
||||
* [`electron-pl`](https://electronpl.github.io) *(Poland)*
|
||||
|
||||
If you'd like to contribute to Electron,
|
||||
see the [contributing document](https://github.com/electron/electron/blob/main/CONTRIBUTING.md).
|
||||
see the [contributing document](https://github.com/electron/electron/blob/master/CONTRIBUTING.md).
|
||||
|
||||
If you've found a bug in a [supported version](#supported-versions) of Electron,
|
||||
please report it with the [issue tracker](../development/issues.md).
|
||||
@@ -50,15 +50,15 @@ as the 4.2.x series are supported. We only support the latest minor release
|
||||
for each stable release series. This means that in the case of a security fix
|
||||
6.1.x will receive the fix, but we will not release a new version of 6.0.x.
|
||||
|
||||
The latest stable release unilaterally receives all fixes from `main`,
|
||||
The latest stable release unilaterally receives all fixes from `master`,
|
||||
and the version prior to that receives the vast majority of those fixes
|
||||
as time and bandwidth warrants. The oldest supported release line will receive
|
||||
only security fixes directly.
|
||||
|
||||
All supported release lines will accept external pull requests to backport
|
||||
fixes previously merged to `main`, though this may be on a case-by-case
|
||||
fixes previously merged to `master`, though this may be on a case-by-case
|
||||
basis for some older supported lines. All contested decisions around release
|
||||
line backports will be resolved by the [Releases Working Group](https://github.com/electron/governance/tree/main/wg-releases) as an agenda item at their weekly meeting the week the backport PR is raised.
|
||||
line backports will be resolved by the [Releases Working Group](https://github.com/electron/governance/tree/master/wg-releases) as an agenda item at their weekly meeting the week the backport PR is raised.
|
||||
|
||||
When an API is changed or removed in a way that breaks existing functionality, the
|
||||
previous functionality will be supported for a minimum of two major versions when
|
||||
@@ -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
|
||||
* 13.x.y
|
||||
* 12.x.y
|
||||
* 11.x.y
|
||||
|
||||
### 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>
|
||||
|
||||
@@ -125,7 +125,5 @@
|
||||
</message>
|
||||
<message name="IDS_BADGE_UNREAD_NOTIFICATIONS" desc="The accessibility text which will be read by a screen reader when there are notifcatications">
|
||||
{UNREAD_NOTIFICATIONS, plural, =1 {1 Unread Notification} other {# Unread Notifications}}
|
||||
</message>
|
||||
<message name="IDS_HID_CHOOSER_ITEM_WITHOUT_NAME" desc="User option displaying the device IDs for a Human Interface Device (HID) without a device name.">
|
||||
Unknown Device (<ph name="DEVICE_ID">$1<ex>1234:abcd</ex></ph>) </message>
|
||||
</message>
|
||||
</grit-part>
|
||||
|
||||
@@ -84,7 +84,6 @@ auto_filenames = {
|
||||
"docs/api/structures/file-filter.md",
|
||||
"docs/api/structures/file-path-with-headers.md",
|
||||
"docs/api/structures/gpu-feature-status.md",
|
||||
"docs/api/structures/hid-device.md",
|
||||
"docs/api/structures/input-event.md",
|
||||
"docs/api/structures/io-counters.md",
|
||||
"docs/api/structures/ipc-main-event.md",
|
||||
@@ -136,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",
|
||||
@@ -168,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",
|
||||
@@ -204,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",
|
||||
@@ -238,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",
|
||||
@@ -261,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",
|
||||
@@ -275,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",
|
||||
@@ -300,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",
|
||||
@@ -313,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",
|
||||
@@ -191,7 +195,6 @@ filenames = {
|
||||
"shell/browser/ui/tray_icon_cocoa.mm",
|
||||
"shell/common/api/electron_api_clipboard_mac.mm",
|
||||
"shell/common/api/electron_api_native_image_mac.mm",
|
||||
"shell/common/asar/archive_mac.mm",
|
||||
"shell/common/application_info_mac.mm",
|
||||
"shell/common/language_util_mac.mm",
|
||||
"shell/common/mac/main_application_bundle.h",
|
||||
@@ -233,6 +236,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",
|
||||
@@ -383,18 +387,8 @@ filenames = {
|
||||
"shell/browser/file_select_helper.cc",
|
||||
"shell/browser/file_select_helper.h",
|
||||
"shell/browser/file_select_helper_mac.mm",
|
||||
"shell/browser/font/electron_font_access_delegate.cc",
|
||||
"shell/browser/font/electron_font_access_delegate.h",
|
||||
"shell/browser/font_defaults.cc",
|
||||
"shell/browser/font_defaults.h",
|
||||
"shell/browser/hid/electron_hid_delegate.cc",
|
||||
"shell/browser/hid/electron_hid_delegate.h",
|
||||
"shell/browser/hid/hid_chooser_context.cc",
|
||||
"shell/browser/hid/hid_chooser_context.h",
|
||||
"shell/browser/hid/hid_chooser_context_factory.cc",
|
||||
"shell/browser/hid/hid_chooser_context_factory.h",
|
||||
"shell/browser/hid/hid_chooser_controller.cc",
|
||||
"shell/browser/hid/hid_chooser_controller.h",
|
||||
"shell/browser/javascript_environment.cc",
|
||||
"shell/browser/javascript_environment.h",
|
||||
"shell/browser/lib/bluetooth_chooser.cc",
|
||||
@@ -414,8 +408,6 @@ filenames = {
|
||||
"shell/browser/native_window.cc",
|
||||
"shell/browser/native_window.h",
|
||||
"shell/browser/native_window_observer.h",
|
||||
"shell/browser/net/asar/asar_file_validator.cc",
|
||||
"shell/browser/net/asar/asar_file_validator.h",
|
||||
"shell/browser/net/asar/asar_url_loader.cc",
|
||||
"shell/browser/net/asar/asar_url_loader.h",
|
||||
"shell/browser/net/asar/asar_url_loader_factory.cc",
|
||||
@@ -504,6 +496,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",
|
||||
@@ -683,8 +677,6 @@ filenames = {
|
||||
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",
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { Buffer } from 'buffer';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
import type * as Crypto from 'crypto';
|
||||
|
||||
const asar = process._linkedBinding('electron_common_asar');
|
||||
|
||||
@@ -195,20 +194,6 @@ const overrideAPI = function (module: Record<string, any>, name: string, pathArg
|
||||
}
|
||||
};
|
||||
|
||||
let crypto: typeof Crypto;
|
||||
function validateBufferIntegrity (buffer: Buffer, integrity: NodeJS.AsarFileInfo['integrity']) {
|
||||
if (!integrity) return;
|
||||
|
||||
// Delay load crypto to improve app boot performance
|
||||
// when integrity protection is not enabled
|
||||
crypto = crypto || require('crypto');
|
||||
const actual = crypto.createHash(integrity.algorithm).update(buffer).digest('hex');
|
||||
if (actual !== integrity.hash) {
|
||||
console.error(`ASAR Integrity Violation: got a hash mismatch (${actual} vs ${integrity.hash})`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
const makePromiseFunction = function (orig: Function, pathArgumentIndex: number) {
|
||||
return function (this: any, ...args: any[]) {
|
||||
const pathArgument = args[pathArgumentIndex];
|
||||
@@ -546,7 +531,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
}
|
||||
|
||||
const buffer = Buffer.alloc(info.size);
|
||||
const fd = archive.getFdAndValidateIntegrityLater();
|
||||
const fd = archive.getFd();
|
||||
if (!(fd >= 0)) {
|
||||
const error = createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
nextTick(callback, [error]);
|
||||
@@ -555,7 +540,6 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
fs.read(fd, buffer, 0, info.size, info.offset, (error: Error) => {
|
||||
validateBufferIntegrity(buffer, info.integrity);
|
||||
callback(error, encoding ? buffer.toString(encoding) : buffer);
|
||||
});
|
||||
}
|
||||
@@ -611,12 +595,11 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
|
||||
const { encoding } = options;
|
||||
const buffer = Buffer.alloc(info.size);
|
||||
const fd = archive.getFdAndValidateIntegrityLater();
|
||||
const fd = archive.getFd();
|
||||
if (!(fd >= 0)) throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
|
||||
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
fs.readSync(fd, buffer, 0, info.size, info.offset);
|
||||
validateBufferIntegrity(buffer, info.integrity);
|
||||
return (encoding) ? buffer.toString(encoding) : buffer;
|
||||
};
|
||||
|
||||
@@ -730,12 +713,11 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
}
|
||||
|
||||
const buffer = Buffer.alloc(info.size);
|
||||
const fd = archive.getFdAndValidateIntegrityLater();
|
||||
const fd = archive.getFd();
|
||||
if (!(fd >= 0)) return [];
|
||||
|
||||
logASARAccess(asarPath, filePath, info.offset);
|
||||
fs.readSync(fd, buffer, 0, info.size, info.offset);
|
||||
validateBufferIntegrity(buffer, info.integrity);
|
||||
const str = buffer.toString('utf8');
|
||||
return [str, str.length > 0];
|
||||
};
|
||||
|
||||
@@ -39,8 +39,7 @@ Object.assign(app, {
|
||||
hasSwitch: (theSwitch: string) => commandLine.hasSwitch(String(theSwitch)),
|
||||
getSwitchValue: (theSwitch: string) => commandLine.getSwitchValue(String(theSwitch)),
|
||||
appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
|
||||
appendArgument: (arg: string) => commandLine.appendArgument(String(arg)),
|
||||
removeSwitch: (theSwitch: string) => commandLine.removeSwitch(String(theSwitch))
|
||||
appendArgument: (arg: string) => commandLine.appendArgument(String(arg))
|
||||
} as Electron.CommandLine
|
||||
});
|
||||
|
||||
|
||||
@@ -168,7 +168,6 @@ const messageBox = (sync: boolean, window: BrowserWindow | null, options?: Messa
|
||||
defaultId = -1,
|
||||
detail = '',
|
||||
icon = null,
|
||||
textWidth = 0,
|
||||
noLink = false,
|
||||
message = '',
|
||||
title = '',
|
||||
@@ -226,8 +225,7 @@ const messageBox = (sync: boolean, window: BrowserWindow | null, options?: Messa
|
||||
detail,
|
||||
checkboxLabel,
|
||||
checkboxChecked,
|
||||
icon,
|
||||
textWidth
|
||||
icon
|
||||
};
|
||||
|
||||
if (sync) {
|
||||
|
||||
@@ -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') },
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { BrowserWindowConstructorOptions, LoadURLOptions } from 'electron/m
|
||||
import * as url from 'url';
|
||||
import * as path from 'path';
|
||||
import { openGuestWindow, makeWebPreferences, parseContentTypeFormat } from '@electron/internal/browser/guest-window-manager';
|
||||
import { parseFeatures } from '@electron/internal/common/parse-features-string';
|
||||
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
|
||||
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||
@@ -593,9 +592,6 @@ WebContents.prototype._init = function () {
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event);
|
||||
if (this.listenerCount('ipc-message-sync') === 0 && ipcMain.listenerCount(channel) === 0) {
|
||||
console.warn(`WebContents #${this.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
|
||||
}
|
||||
this.emit('ipc-message-sync', event, channel, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
@@ -669,16 +665,6 @@ WebContents.prototype._init = function () {
|
||||
postBody
|
||||
};
|
||||
windowOpenOverriddenOptions = this._callWindowOpenHandler(event, details);
|
||||
// if attempting to use this API with the deprecated new-window event,
|
||||
// windowOpenOverriddenOptions will always return null. This ensures
|
||||
// short-term backwards compatibility until new-window is removed.
|
||||
const parsedFeatures = parseFeatures(rawFeatures);
|
||||
const overriddenFeatures: BrowserWindowConstructorOptions = {
|
||||
...parsedFeatures.options,
|
||||
webPreferences: parsedFeatures.webPreferences
|
||||
};
|
||||
windowOpenOverriddenOptions = windowOpenOverriddenOptions || overriddenFeatures;
|
||||
|
||||
if (!event.defaultPrevented) {
|
||||
const secureOverrideWebPreferences = windowOpenOverriddenOptions ? {
|
||||
// Allow setting of backgroundColor as a webPreference even though
|
||||
|
||||
@@ -7,11 +7,7 @@ WebFrameMain.prototype.send = function (channel, ...args) {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
try {
|
||||
return this._send(false /* internal */, channel, args);
|
||||
} catch (e) {
|
||||
console.error('Error sending from webFrameMain: ', e);
|
||||
}
|
||||
return this._send(false /* internal */, channel, args);
|
||||
};
|
||||
|
||||
WebFrameMain.prototype._sendInternal = function (channel, ...args) {
|
||||
@@ -19,11 +15,7 @@ WebFrameMain.prototype._sendInternal = function (channel, ...args) {
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
try {
|
||||
return this._send(true /* internal */, channel, args);
|
||||
} catch (e) {
|
||||
console.error('Error sending from webFrameMain: ', e);
|
||||
}
|
||||
return this._send(true /* internal */, channel, args);
|
||||
};
|
||||
|
||||
WebFrameMain.prototype.postMessage = function (...args) {
|
||||
|
||||
@@ -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,17 +4,17 @@ 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 {
|
||||
elementInstanceId: number;
|
||||
elementInstanceId?: number;
|
||||
visibilityState?: VisibilityState;
|
||||
embedder: Electron.WebContents;
|
||||
guest: Electron.WebContents;
|
||||
}
|
||||
|
||||
const webViewManager = process._linkedBinding('electron_browser_web_view_manager');
|
||||
const eventBinding = process._linkedBinding('electron_browser_event');
|
||||
|
||||
const supportedWebViewEvents = Object.keys(webViewEvents);
|
||||
|
||||
@@ -28,73 +28,16 @@ function sanitizeOptionsForGuest (options: Record<string, any>) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
function makeWebPreferences (embedder: Electron.WebContents, params: Record<string, any>) {
|
||||
// parse the 'webpreferences' attribute string, if set
|
||||
// this uses the same parsing rules as window.open uses for its features
|
||||
const parsedWebPreferences =
|
||||
typeof params.webpreferences === 'string'
|
||||
? parseWebViewWebPreferences(params.webpreferences)
|
||||
: null;
|
||||
|
||||
const webPreferences: Electron.WebPreferences = {
|
||||
nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
|
||||
nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false,
|
||||
plugins: params.plugins,
|
||||
zoomFactor: embedder.zoomFactor,
|
||||
disablePopups: !params.allowpopups,
|
||||
webSecurity: !params.disablewebsecurity,
|
||||
enableBlinkFeatures: params.blinkfeatures,
|
||||
disableBlinkFeatures: params.disableblinkfeatures,
|
||||
partition: params.partition,
|
||||
...parsedWebPreferences
|
||||
};
|
||||
|
||||
if (params.preload) {
|
||||
webPreferences.preloadURL = params.preload;
|
||||
}
|
||||
|
||||
// Security options that guest will always inherit from embedder
|
||||
const inheritedWebPreferences = new Map([
|
||||
['contextIsolation', true],
|
||||
['javascript', false],
|
||||
['nativeWindowOpen', true],
|
||||
['nodeIntegration', false],
|
||||
['sandbox', true],
|
||||
['nodeIntegrationInSubFrames', false],
|
||||
['enableWebSQL', false]
|
||||
]);
|
||||
|
||||
// Inherit certain option values from embedder
|
||||
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;
|
||||
}
|
||||
|
||||
// Create a new guest instance.
|
||||
const createGuest = function (embedder: Electron.WebContents, embedderFrameId: number, elementInstanceId: number, params: Record<string, any>) {
|
||||
const webPreferences = makeWebPreferences(embedder, params);
|
||||
const event = eventBinding.createWithSender(embedder);
|
||||
|
||||
embedder.emit('will-attach-webview', event, webPreferences, params);
|
||||
if (event.defaultPrevented) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
const createGuest = function (embedder: Electron.WebContents, params: Record<string, any>) {
|
||||
// eslint-disable-next-line no-undef
|
||||
const guest = (webContents as typeof ElectronInternal.WebContents).create({
|
||||
...webPreferences,
|
||||
type: 'webview',
|
||||
partition: params.partition,
|
||||
embedder
|
||||
});
|
||||
|
||||
const guestInstanceId = guest.id;
|
||||
guestInstances.set(guestInstanceId, {
|
||||
elementInstanceId,
|
||||
guest,
|
||||
embedder
|
||||
});
|
||||
@@ -108,6 +51,9 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
|
||||
// Init guest web view after attached.
|
||||
guest.once('did-attach' as any, function (this: Electron.WebContents, event: Electron.Event) {
|
||||
params = this.attachParams!;
|
||||
delete this.attachParams;
|
||||
|
||||
const previouslyAttached = this.viewInstanceId != null;
|
||||
this.viewInstanceId = params.instanceId;
|
||||
|
||||
@@ -135,33 +81,27 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
}
|
||||
};
|
||||
|
||||
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) {
|
||||
const fn = function (event: string) {
|
||||
guest.on(event as any, function (_, ...args: any[]) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, event, makeProps(event, args));
|
||||
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) {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT, 'new-window', {
|
||||
url,
|
||||
frameName,
|
||||
disposition,
|
||||
options: sanitizeOptionsForGuest(options)
|
||||
});
|
||||
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_DISPATCH_EVENT, 'ipc-message', {
|
||||
sendToEmbedder(IPC_MESSAGES.GUEST_VIEW_INTERNAL_IPC_MESSAGE, {
|
||||
frameId: [event.processId, event.frameId],
|
||||
channel,
|
||||
args
|
||||
@@ -177,25 +117,111 @@ const createGuest = function (embedder: Electron.WebContents, embedderFrameId: n
|
||||
}
|
||||
});
|
||||
|
||||
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 =
|
||||
typeof params.webpreferences === 'string'
|
||||
? parseWebViewWebPreferences(params.webpreferences)
|
||||
: null;
|
||||
|
||||
const webPreferences: Electron.WebPreferences = {
|
||||
guestInstanceId,
|
||||
nodeIntegration: params.nodeintegration != null ? params.nodeintegration : false,
|
||||
nodeIntegrationInSubFrames: params.nodeintegrationinsubframes != null ? params.nodeintegrationinsubframes : false,
|
||||
plugins: params.plugins,
|
||||
zoomFactor: embedder.zoomFactor,
|
||||
disablePopups: !params.allowpopups,
|
||||
webSecurity: !params.disablewebsecurity,
|
||||
enableBlinkFeatures: params.blinkfeatures,
|
||||
disableBlinkFeatures: params.disableblinkfeatures,
|
||||
...parsedWebPreferences
|
||||
};
|
||||
|
||||
if (params.preload) {
|
||||
webPreferences.preloadURL = params.preload;
|
||||
}
|
||||
|
||||
// Security options that guest will always inherit from embedder
|
||||
const inheritedWebPreferences = new Map([
|
||||
['contextIsolation', true],
|
||||
['javascript', false],
|
||||
['nativeWindowOpen', true],
|
||||
['nodeIntegration', false],
|
||||
['sandbox', true],
|
||||
['nodeIntegrationInSubFrames', false],
|
||||
['enableWebSQL', false]
|
||||
]);
|
||||
|
||||
// Inherit certain option values from embedder
|
||||
const lastWebPreferences = embedder.getLastWebPreferences();
|
||||
for (const [name, value] of inheritedWebPreferences) {
|
||||
if (lastWebPreferences[name as keyof Electron.WebPreferences] === value) {
|
||||
(webPreferences as any)[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
embedder.emit('will-attach-webview', event, webPreferences, params);
|
||||
if (event.defaultPrevented) {
|
||||
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId;
|
||||
guest.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
guest.attachParams = params;
|
||||
embedderElementsMap.set(key, guestInstanceId);
|
||||
|
||||
guest.setEmbedder(embedder);
|
||||
guestInstance.embedder = embedder;
|
||||
guestInstance.elementInstanceId = elementInstanceId;
|
||||
|
||||
watchEmbedder(embedder);
|
||||
|
||||
webViewManager.addGuest(guestInstanceId, embedder, guest, webPreferences);
|
||||
guest.attachToIframe(embedder, embedderFrameId);
|
||||
|
||||
return guestInstanceId;
|
||||
};
|
||||
|
||||
// Remove an guest-embedder relationship.
|
||||
@@ -280,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) {
|
||||
@@ -329,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);
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user