mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
115 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
959e80cc53 | ||
|
|
97010bf947 | ||
|
|
372c5b9518 | ||
|
|
4dc2c2800c | ||
|
|
ba1b75c218 | ||
|
|
5dac4531ca | ||
|
|
3cb788a030 | ||
|
|
9984dd9017 | ||
|
|
7efa4ccfb3 | ||
|
|
3bf5ad22f4 | ||
|
|
6e94fb3f9f | ||
|
|
9c92d87e2f | ||
|
|
8dcf7fc0b9 | ||
|
|
c7fd17a723 | ||
|
|
b68f8999ca | ||
|
|
35cb95b3d8 | ||
|
|
3b3cc0215a | ||
|
|
3cb2484184 | ||
|
|
bafcac7913 | ||
|
|
6a0c701a43 | ||
|
|
5ed3bd8724 | ||
|
|
779e3250c0 | ||
|
|
4c5197fe6e | ||
|
|
72a75889de | ||
|
|
242658102a | ||
|
|
7586295378 | ||
|
|
e7fce15152 | ||
|
|
456c9f09a2 | ||
|
|
f229d12056 | ||
|
|
4a2d94d031 | ||
|
|
df09e278a1 | ||
|
|
10f65979ff | ||
|
|
cac3884d75 | ||
|
|
b9734edfd1 | ||
|
|
ace3216392 | ||
|
|
12cb11f99b | ||
|
|
c16c4c25ed | ||
|
|
8e30e21ee6 | ||
|
|
a80f7fc81f | ||
|
|
83e7b00e7c | ||
|
|
eabb872345 | ||
|
|
b12090a129 | ||
|
|
0bf12e4e76 | ||
|
|
411b9f2ae7 | ||
|
|
a632f1f1b7 | ||
|
|
2543517443 | ||
|
|
5736df41c8 | ||
|
|
fbbe8afc3e | ||
|
|
6fdd055642 | ||
|
|
0f082ecd5c | ||
|
|
f29a600fd8 | ||
|
|
1bde50636f | ||
|
|
7d739a3608 | ||
|
|
8d3f0f47c1 | ||
|
|
dfd338d8bf | ||
|
|
dd242f4b28 | ||
|
|
4c61f62149 | ||
|
|
fd654afae9 | ||
|
|
cadddfec23 | ||
|
|
0e8aa5c252 | ||
|
|
edac24c9be | ||
|
|
0251f5aa93 | ||
|
|
cccd3778f8 | ||
|
|
680f3432ae | ||
|
|
420fb46121 | ||
|
|
6ef605eaa2 | ||
|
|
d7247bc77d | ||
|
|
5e6e118595 | ||
|
|
b1f19970e7 | ||
|
|
c30ecc38d0 | ||
|
|
14d9b8a8b4 | ||
|
|
f0d872947d | ||
|
|
40a212e132 | ||
|
|
cb9b3b29af | ||
|
|
da65c881e2 | ||
|
|
aa5e51dbe0 | ||
|
|
b5d38bfff0 | ||
|
|
a3054443f4 | ||
|
|
1f631ca337 | ||
|
|
e7576019e9 | ||
|
|
77448c75c6 | ||
|
|
538ebabf7e | ||
|
|
d17dfabfcb | ||
|
|
b6cb396893 | ||
|
|
4da56797c3 | ||
|
|
570e4f6a34 | ||
|
|
8c5a2c0a4a | ||
|
|
1c1e9fc7a7 | ||
|
|
0a3a0def25 | ||
|
|
ae9ee76e56 | ||
|
|
7908013a49 | ||
|
|
c7bb1d0300 | ||
|
|
4cf67fea52 | ||
|
|
2c29a4e93d | ||
|
|
04234f41f2 | ||
|
|
0799c8721c | ||
|
|
442174da0f | ||
|
|
ef94a79cac | ||
|
|
38afcff63a | ||
|
|
7ddfffb723 | ||
|
|
f8e601adaf | ||
|
|
dd27520637 | ||
|
|
85c07d0715 | ||
|
|
4dac36d111 | ||
|
|
ae27218296 | ||
|
|
c92efa4ec5 | ||
|
|
cbf499b18a | ||
|
|
7b1117e186 | ||
|
|
3bd3d9440a | ||
|
|
704cdd3a2b | ||
|
|
c56e5abd8c | ||
|
|
ec61041d41 | ||
|
|
32b51bce75 | ||
|
|
e0576ef11a | ||
|
|
a889ec7957 |
@@ -1,3 +1,46 @@
|
||||
version: 2.1
|
||||
|
||||
parameters:
|
||||
upload-to-s3:
|
||||
type: string
|
||||
default: '1'
|
||||
|
||||
run-lint:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
run-build-linux:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
run-build-mac:
|
||||
type: boolean
|
||||
default: true
|
||||
|
||||
run-linux-x64-publish:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
run-linux-ia32-publish:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
run-linux-arm-publish:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
run-linux-arm64-publish:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
run-osx-publish:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
run-mas-publish:
|
||||
type: boolean
|
||||
default: false
|
||||
|
||||
# The config expects the following environment variables to be set:
|
||||
# - "SLACK_WEBHOOK" Slack hook URL to send notifications.
|
||||
#
|
||||
@@ -88,22 +131,31 @@ env-enable-sccache: &env-enable-sccache
|
||||
env-send-slack-notifications: &env-send-slack-notifications
|
||||
NOTIFY_SLACK: true
|
||||
|
||||
env-global: &env-global
|
||||
ELECTRON_OUT_DIR: Default
|
||||
|
||||
env-linux-medium: &env-linux-medium
|
||||
<<: *env-global
|
||||
NUMBER_OF_NINJA_PROCESSES: 3
|
||||
|
||||
env-linux-2xlarge: &env-linux-2xlarge
|
||||
<<: *env-global
|
||||
NUMBER_OF_NINJA_PROCESSES: 34
|
||||
|
||||
env-linux-2xlarge-release: &env-linux-2xlarge-release
|
||||
<<: *env-global
|
||||
NUMBER_OF_NINJA_PROCESSES: 16
|
||||
|
||||
env-machine-mac: &env-machine-mac
|
||||
<<: *env-global
|
||||
NUMBER_OF_NINJA_PROCESSES: 6
|
||||
|
||||
env-mac-large: &env-mac-large
|
||||
<<: *env-global
|
||||
NUMBER_OF_NINJA_PROCESSES: 18
|
||||
|
||||
env-mac-large-release: &env-mac-large-release
|
||||
<<: *env-global
|
||||
NUMBER_OF_NINJA_PROCESSES: 8
|
||||
|
||||
env-disable-crash-reporter-tests: &env-disable-crash-reporter-tests
|
||||
@@ -165,7 +217,25 @@ step-gclient-sync: &step-gclient-sync
|
||||
$GCLIENT_EXTRA_ARGS \
|
||||
"$CIRCLE_REPOSITORY_URL"
|
||||
|
||||
gclient sync --with_branch_heads --with_tags
|
||||
ELECTRON_USE_THREE_WAY_MERGE_FOR_PATCHES=1 gclient sync --with_branch_heads --with_tags
|
||||
# Re-export all the patches to check if there were changes.
|
||||
python src/electron/script/export_all_patches.py src/electron/patches/config.json
|
||||
cd src/electron
|
||||
git update-index --refresh || true
|
||||
if ! git diff-index --quiet HEAD --; then
|
||||
# There are changes to the patches. Make a git commit with the updated patches
|
||||
git add patches
|
||||
GIT_COMMITTER_NAME="Electron Bot" GIT_COMMITTER_EMAIL="anonymous@electronjs.org" git commit -m "update patches" --author="Electron Bot <anonymous@electronjs.org>"
|
||||
# Export it
|
||||
mkdir -p ../../patches
|
||||
git format-patch -1 --stdout --keep-subject --no-stat --full-index > ../../patches/update-patches.patch
|
||||
echo
|
||||
echo "======================================================================"
|
||||
echo "There were changes to the patches when applying."
|
||||
echo "Check the CI artifacts for a patch you can apply to fix it."
|
||||
echo "======================================================================"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
step-setup-env-for-build: &step-setup-env-for-build
|
||||
@@ -250,7 +320,7 @@ step-install-signing-cert-on-mac: &step-install-signing-cert-on-mac
|
||||
command: |
|
||||
if [ "`uname`" == "Darwin" ]; then
|
||||
cd src/electron
|
||||
./script/codesign/import-testing-cert-ci.sh
|
||||
./script/codesign/generate-identity.sh
|
||||
fi
|
||||
|
||||
step-install-gnutar-on-mac: &step-install-gnutar-on-mac
|
||||
@@ -346,7 +416,9 @@ step-electron-chromedriver-build: &step-electron-chromedriver-build
|
||||
command: |
|
||||
cd src
|
||||
ninja -C out/Default chrome/test/chromedriver -j $NUMBER_OF_NINJA_PROCESSES
|
||||
electron/script/strip-binaries.py --target-cpu="$TARGET_ARCH" --file $PWD/out/Default/chromedriver
|
||||
if [ "`uname`" == "Linux" ]; then
|
||||
electron/script/strip-binaries.py --target-cpu="$TARGET_ARCH" --file $PWD/out/Default/chromedriver
|
||||
fi
|
||||
ninja -C out/Default electron:electron_chromedriver_zip
|
||||
|
||||
step-electron-chromedriver-store: &step-electron-chromedriver-store
|
||||
@@ -466,6 +538,7 @@ step-mksnapshot-build: &step-mksnapshot-build
|
||||
name: mksnapshot build
|
||||
command: |
|
||||
cd src
|
||||
ninja -C out/Default electron:electron_mksnapshot -j $NUMBER_OF_NINJA_PROCESSES
|
||||
if [ "`uname`" != "Darwin" ]; then
|
||||
if [ "$TARGET_ARCH" == "arm" ]; then
|
||||
electron/script/strip-binaries.py --file $PWD/out/Default/clang_x86_v8_arm/mksnapshot
|
||||
@@ -473,6 +546,7 @@ step-mksnapshot-build: &step-mksnapshot-build
|
||||
electron/script/strip-binaries.py --file $PWD/out/Default/clang_x64_v8_arm64/mksnapshot
|
||||
else
|
||||
electron/script/strip-binaries.py --file $PWD/out/Default/mksnapshot
|
||||
electron/script/strip-binaries.py --file $PWD/out/Default/v8_context_snapshot_generator
|
||||
fi
|
||||
fi
|
||||
ninja -C out/Default electron:electron_mksnapshot_zip -j $NUMBER_OF_NINJA_PROCESSES
|
||||
@@ -489,6 +563,8 @@ step-maybe-generate-breakpad-symbols: &step-maybe-generate-breakpad-symbols
|
||||
if [ "$GENERATE_SYMBOLS" == "true" ]; then
|
||||
cd src
|
||||
ninja -C out/Default electron:electron_symbols
|
||||
cd out/Default/breakpad_symbols
|
||||
find . -name \*.sym -print0 | xargs -0 npx @sentry/cli@1.51.1 difutil bundle-sources
|
||||
fi
|
||||
|
||||
step-maybe-zip-symbols: &step-maybe-zip-symbols
|
||||
@@ -661,7 +737,7 @@ steps-lint: &steps-lint
|
||||
chromium_revision="$(grep -A1 chromium_version src/electron/DEPS | tr -d '\n' | cut -d\' -f4)"
|
||||
gn_version="$(curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/DEPS?format=TEXT" | base64 -d | grep gn_version | head -n1 | cut -d\' -f4)"
|
||||
|
||||
cipd ensure -ensure-file - -root . <<-CIPD
|
||||
cipd ensure -ensure-file - -root . \<<-CIPD
|
||||
\$ServiceURL https://chrome-infra-packages.appspot.com/
|
||||
@Subdir src/buildtools/linux64
|
||||
gn/gn/linux-amd64 $gn_version
|
||||
@@ -706,6 +782,8 @@ steps-checkout-fast: &steps-checkout-fast
|
||||
- *step-set-git-cache-path
|
||||
# This sync call only runs if .circle-sync-done is an EMPTY file
|
||||
- *step-gclient-sync
|
||||
- store_artifacts:
|
||||
path: patches
|
||||
# These next few steps reset Electron to the correct commit regardless of which cache was restored
|
||||
- run:
|
||||
name: Wipe Electron
|
||||
@@ -737,6 +815,8 @@ steps-checkout-and-save-cache: &steps-checkout-and-save-cache
|
||||
- *step-set-git-cache-path
|
||||
# This sync call only runs if .circle-sync-done is an EMPTY file
|
||||
- *step-gclient-sync
|
||||
- store_artifacts:
|
||||
path: patches
|
||||
- *step-save-git-cache
|
||||
# These next few steps reset Electron to the correct commit regardless of which cache was restored
|
||||
- run:
|
||||
@@ -961,7 +1041,6 @@ steps-tests: &steps-tests
|
||||
ELECTRON_DISABLE_SECURITY_WARNINGS: 1
|
||||
command: |
|
||||
cd src
|
||||
export ELECTRON_OUT_DIR=Default
|
||||
(cd electron && node script/yarn test -- --ci --enable-logging)
|
||||
- run:
|
||||
name: Check test results existence
|
||||
@@ -994,7 +1073,6 @@ steps-test-nan: &steps-test-nan
|
||||
name: Run Nan Tests
|
||||
command: |
|
||||
cd src
|
||||
export ELECTRON_OUT_DIR=Default
|
||||
node electron/script/nan-spec-runner.js
|
||||
|
||||
steps-test-node: &steps-test-node
|
||||
@@ -1009,7 +1087,6 @@ steps-test-node: &steps-test-node
|
||||
name: Run Node Tests
|
||||
command: |
|
||||
cd src
|
||||
export ELECTRON_OUT_DIR=Default
|
||||
node electron/script/node-spec-runner.js junit
|
||||
- store_test_results:
|
||||
path: src/junit
|
||||
@@ -1018,7 +1095,6 @@ chromium-upgrade-branches: &chromium-upgrade-branches
|
||||
/chromium\-upgrade\/[0-9]+/
|
||||
|
||||
# List of all jobs.
|
||||
version: 2
|
||||
jobs:
|
||||
# Layer 0: Lint. Standalone.
|
||||
lint:
|
||||
@@ -1138,6 +1214,8 @@ jobs:
|
||||
<<: *env-linux-2xlarge-release
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True'
|
||||
<<: *env-release-build
|
||||
<<: *env-enable-sccache
|
||||
UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >>
|
||||
<<: *steps-electron-build-for-publish
|
||||
|
||||
linux-ia32-debug:
|
||||
@@ -1188,6 +1266,8 @@ jobs:
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True'
|
||||
<<: *env-ia32
|
||||
<<: *env-release-build
|
||||
<<: *env-enable-sccache
|
||||
UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >>
|
||||
<<: *steps-electron-build-for-publish
|
||||
|
||||
linux-arm-debug:
|
||||
@@ -1238,7 +1318,9 @@ jobs:
|
||||
<<: *env-linux-2xlarge-release
|
||||
<<: *env-arm
|
||||
<<: *env-release-build
|
||||
<<: *env-enable-sccache
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_boto=True --custom-var=checkout_requests=True'
|
||||
UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >>
|
||||
<<: *steps-electron-build-for-publish
|
||||
|
||||
linux-arm64-debug:
|
||||
@@ -1305,7 +1387,9 @@ jobs:
|
||||
<<: *env-linux-2xlarge-release
|
||||
<<: *env-arm64
|
||||
<<: *env-release-build
|
||||
<<: *env-enable-sccache
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm64=True --custom-var=checkout_boto=True --custom-var=checkout_requests=True'
|
||||
UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >>
|
||||
<<: *steps-electron-build-for-publish
|
||||
|
||||
osx-testing:
|
||||
@@ -1363,7 +1447,9 @@ jobs:
|
||||
environment:
|
||||
<<: *env-mac-large-release
|
||||
<<: *env-release-build
|
||||
<<: *env-enable-sccache
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True'
|
||||
UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >>
|
||||
<<: *steps-electron-build-for-publish
|
||||
|
||||
mas-testing:
|
||||
@@ -1427,7 +1513,9 @@ jobs:
|
||||
<<: *env-mac-large-release
|
||||
<<: *env-mas
|
||||
<<: *env-release-build
|
||||
<<: *env-enable-sccache
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True'
|
||||
UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >>
|
||||
<<: *steps-electron-build-for-publish
|
||||
|
||||
# Layer 3: Tests.
|
||||
@@ -1683,11 +1771,56 @@ jobs:
|
||||
|
||||
workflows:
|
||||
version: 2
|
||||
|
||||
# The publish workflows below each contain one job so that they are
|
||||
# compatible with how sudowoodo works today. If these workflows are
|
||||
# changed to have multiple jobs, then scripts/release/ci-release-build.js
|
||||
# will need to be updated and there will most likely need to be changes to
|
||||
# sudowoodo
|
||||
|
||||
publish-x64-linux:
|
||||
when: << pipeline.parameters.run-linux-x64-publish >>
|
||||
jobs:
|
||||
- linux-x64-publish:
|
||||
context: release-env
|
||||
|
||||
publish-ia32-linux:
|
||||
when: << pipeline.parameters.run-linux-ia32-publish >>
|
||||
jobs:
|
||||
- linux-ia32-publish:
|
||||
context: release-env
|
||||
|
||||
publish-arm-linux:
|
||||
when: << pipeline.parameters.run-linux-arm-publish >>
|
||||
jobs:
|
||||
- linux-arm-publish:
|
||||
context: release-env
|
||||
|
||||
publish-arm64-linux:
|
||||
when: << pipeline.parameters.run-linux-arm64-publish >>
|
||||
jobs:
|
||||
- linux-arm64-publish:
|
||||
context: release-env
|
||||
|
||||
publish-osx:
|
||||
when: << pipeline.parameters.run-osx-publish >>
|
||||
jobs:
|
||||
- osx-publish:
|
||||
context: release-env
|
||||
|
||||
publish-mas:
|
||||
when: << pipeline.parameters.run-mas-publish >>
|
||||
jobs:
|
||||
- mas-publish:
|
||||
context: release-env
|
||||
|
||||
lint:
|
||||
when: << pipeline.parameters.run-lint >>
|
||||
jobs:
|
||||
- lint
|
||||
|
||||
build-linux:
|
||||
when: << pipeline.parameters.run-build-linux >>
|
||||
jobs:
|
||||
- linux-checkout-fast
|
||||
- linux-checkout-and-save-cache
|
||||
@@ -1754,6 +1887,7 @@ workflows:
|
||||
- linux-checkout-fast
|
||||
|
||||
build-mac:
|
||||
when: << pipeline.parameters.run-build-mac >>
|
||||
jobs:
|
||||
- mac-checkout-fast
|
||||
- mac-checkout-and-save-cache
|
||||
@@ -1808,11 +1942,11 @@ workflows:
|
||||
- master
|
||||
- *chromium-upgrade-branches
|
||||
jobs:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
|
||||
- linux-x64-release:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-x64-release-tests:
|
||||
requires:
|
||||
- linux-x64-release
|
||||
@@ -1824,7 +1958,7 @@ workflows:
|
||||
- linux-x64-release
|
||||
- linux-x64-chromedriver:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-x64-release-summary:
|
||||
requires:
|
||||
- linux-x64-release
|
||||
@@ -1834,7 +1968,7 @@ workflows:
|
||||
|
||||
- linux-ia32-release:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-ia32-release-tests:
|
||||
requires:
|
||||
- linux-ia32-release
|
||||
@@ -1846,7 +1980,7 @@ workflows:
|
||||
- linux-ia32-release
|
||||
- linux-ia32-chromedriver:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-ia32-release-summary:
|
||||
requires:
|
||||
- linux-ia32-release
|
||||
@@ -1856,10 +1990,10 @@ workflows:
|
||||
|
||||
- linux-arm-release:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm-chromedriver:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm-release-summary:
|
||||
requires:
|
||||
- linux-arm-release
|
||||
@@ -1868,10 +2002,10 @@ workflows:
|
||||
|
||||
- linux-arm64-release:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm64-chromedriver:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm64-release-summary:
|
||||
requires:
|
||||
- linux-arm64-release
|
||||
@@ -1887,11 +2021,11 @@ workflows:
|
||||
- master
|
||||
- *chromium-upgrade-branches
|
||||
jobs:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
|
||||
- osx-release:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- osx-release-tests:
|
||||
requires:
|
||||
- osx-release
|
||||
@@ -1903,7 +2037,7 @@ workflows:
|
||||
- osx-release
|
||||
- osx-chromedriver:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- osx-release-summary:
|
||||
requires:
|
||||
- osx-release
|
||||
@@ -1913,7 +2047,7 @@ workflows:
|
||||
|
||||
- mas-release:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- mas-release-tests:
|
||||
requires:
|
||||
- mas-release
|
||||
@@ -1925,7 +2059,7 @@ workflows:
|
||||
- mas-release
|
||||
- mas-chromedriver:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- mas-release-summary:
|
||||
requires:
|
||||
- mas-release
|
||||
|
||||
@@ -6,9 +6,11 @@
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"semi": ["error", "always"],
|
||||
"no-var": "error",
|
||||
"no-unused-vars": 0,
|
||||
"no-global-assign": 0,
|
||||
"guard-for-in": 2,
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
"vars": "all",
|
||||
"args": "after-used",
|
||||
|
||||
1
.gitattributes
vendored
1
.gitattributes
vendored
@@ -1,3 +1,4 @@
|
||||
# `git apply` and friends don't understand CRLF, even on windows. Force those
|
||||
# files to be checked out with LF endings even if core.autocrlf is true.
|
||||
*.patch text eol=lf
|
||||
patches/**/.patches merge=union
|
||||
|
||||
16
BUILD.gn
16
BUILD.gn
@@ -1289,12 +1289,18 @@ dist_zip("electron_chromedriver_zip") {
|
||||
]
|
||||
}
|
||||
|
||||
mksnapshot_deps = [
|
||||
":licenses",
|
||||
"//tools/v8_context_snapshot:v8_context_snapshot_generator",
|
||||
"//v8:mksnapshot($v8_snapshot_toolchain)",
|
||||
]
|
||||
|
||||
group("electron_mksnapshot") {
|
||||
public_deps = mksnapshot_deps
|
||||
}
|
||||
|
||||
dist_zip("electron_mksnapshot_zip") {
|
||||
data_deps = [
|
||||
"//v8:mksnapshot($v8_snapshot_toolchain)",
|
||||
"//tools/v8_context_snapshot:v8_context_snapshot_generator",
|
||||
":licenses",
|
||||
]
|
||||
data_deps = mksnapshot_deps
|
||||
outputs = [
|
||||
"$root_build_dir/mksnapshot.zip",
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
7.1.7
|
||||
7.2.2
|
||||
@@ -1,48 +1,48 @@
|
||||
import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron'
|
||||
import * as path from 'path'
|
||||
import { app, dialog, BrowserWindow, shell, ipcMain } from 'electron';
|
||||
import * as path from 'path';
|
||||
|
||||
let mainWindow: BrowserWindow | null = null
|
||||
let mainWindow: BrowserWindow | null = null;
|
||||
|
||||
// Quit when all windows are closed.
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit()
|
||||
})
|
||||
app.quit();
|
||||
});
|
||||
|
||||
function decorateURL (url: string) {
|
||||
// safely add `?utm_source=default_app
|
||||
const parsedUrl = new URL(url)
|
||||
parsedUrl.searchParams.append('utm_source', 'default_app')
|
||||
return parsedUrl.toString()
|
||||
const parsedUrl = new URL(url);
|
||||
parsedUrl.searchParams.append('utm_source', 'default_app');
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
// Find the shortest path to the electron binary
|
||||
const absoluteElectronPath = process.execPath
|
||||
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath)
|
||||
const absoluteElectronPath = process.execPath;
|
||||
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
|
||||
const electronPath = absoluteElectronPath.length < relativeElectronPath.length
|
||||
? absoluteElectronPath
|
||||
: relativeElectronPath
|
||||
: relativeElectronPath;
|
||||
|
||||
const indexPath = path.resolve(app.getAppPath(), 'index.html')
|
||||
const indexPath = path.resolve(app.getAppPath(), 'index.html');
|
||||
|
||||
function isTrustedSender (webContents: Electron.WebContents) {
|
||||
if (webContents !== (mainWindow && mainWindow.webContents)) {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
const parsedUrl = new URL(webContents.getURL())
|
||||
const parsedUrl = new URL(webContents.getURL());
|
||||
const urlPath = process.platform === 'win32'
|
||||
// Strip the prefixed "/" that occurs on windows
|
||||
? path.resolve(parsedUrl.pathname.substr(1))
|
||||
: parsedUrl.pathname
|
||||
return parsedUrl.protocol === 'file:' && urlPath === indexPath
|
||||
: parsedUrl.pathname;
|
||||
return parsedUrl.protocol === 'file:' && urlPath === indexPath;
|
||||
}
|
||||
|
||||
ipcMain.handle('bootstrap', (event) => {
|
||||
return isTrustedSender(event.sender) ? electronPath : null
|
||||
})
|
||||
return isTrustedSender(event.sender) ? electronPath : null;
|
||||
});
|
||||
|
||||
async function createWindow () {
|
||||
await app.whenReady()
|
||||
await app.whenReady();
|
||||
|
||||
const options: Electron.BrowserWindowConstructorOptions = {
|
||||
width: 900,
|
||||
@@ -57,46 +57,46 @@ async function createWindow () {
|
||||
},
|
||||
useContentSize: true,
|
||||
show: false
|
||||
}
|
||||
};
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
options.icon = path.join(__dirname, 'icon.png')
|
||||
options.icon = path.join(__dirname, 'icon.png');
|
||||
}
|
||||
|
||||
mainWindow = new BrowserWindow(options)
|
||||
mainWindow.on('ready-to-show', () => mainWindow!.show())
|
||||
mainWindow = new BrowserWindow(options);
|
||||
mainWindow.on('ready-to-show', () => mainWindow!.show());
|
||||
|
||||
mainWindow.webContents.on('new-window', (event, url) => {
|
||||
event.preventDefault()
|
||||
shell.openExternal(decorateURL(url))
|
||||
})
|
||||
event.preventDefault();
|
||||
shell.openExternal(decorateURL(url));
|
||||
});
|
||||
|
||||
mainWindow.webContents.session.setPermissionRequestHandler((webContents, permission, done) => {
|
||||
const parsedUrl = new URL(webContents.getURL())
|
||||
const parsedUrl = new URL(webContents.getURL());
|
||||
|
||||
const options: Electron.MessageBoxOptions = {
|
||||
title: 'Permission Request',
|
||||
message: `Allow '${parsedUrl.origin}' to access '${permission}'?`,
|
||||
buttons: ['OK', 'Cancel'],
|
||||
cancelId: 1
|
||||
}
|
||||
};
|
||||
|
||||
dialog.showMessageBox(mainWindow!, options).then(({ response }) => {
|
||||
done(response === 0)
|
||||
})
|
||||
})
|
||||
done(response === 0);
|
||||
});
|
||||
});
|
||||
|
||||
return mainWindow
|
||||
return mainWindow;
|
||||
}
|
||||
|
||||
export const loadURL = async (appUrl: string) => {
|
||||
mainWindow = await createWindow()
|
||||
mainWindow.loadURL(appUrl)
|
||||
mainWindow.focus()
|
||||
}
|
||||
mainWindow = await createWindow();
|
||||
mainWindow.loadURL(appUrl);
|
||||
mainWindow.focus();
|
||||
};
|
||||
|
||||
export const loadFile = async (appPath: string) => {
|
||||
mainWindow = await createWindow()
|
||||
mainWindow.loadFile(appPath)
|
||||
mainWindow.focus()
|
||||
}
|
||||
mainWindow = await createWindow();
|
||||
mainWindow.loadFile(appPath);
|
||||
mainWindow.focus();
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { app, dialog } from 'electron'
|
||||
import { app, dialog } from 'electron';
|
||||
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as url from 'url'
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as url from 'url';
|
||||
|
||||
type DefaultAppOptions = {
|
||||
file: null | string;
|
||||
@@ -14,10 +14,10 @@ type DefaultAppOptions = {
|
||||
modules: string[];
|
||||
}
|
||||
|
||||
const Module = require('module')
|
||||
const Module = require('module');
|
||||
|
||||
// Parse command line options.
|
||||
const argv = process.argv.slice(1)
|
||||
const argv = process.argv.slice(1);
|
||||
|
||||
const option: DefaultAppOptions = {
|
||||
file: null,
|
||||
@@ -27,50 +27,50 @@ const option: DefaultAppOptions = {
|
||||
interactive: false,
|
||||
abi: false,
|
||||
modules: []
|
||||
}
|
||||
};
|
||||
|
||||
let nextArgIsRequire = false
|
||||
let nextArgIsRequire = false;
|
||||
|
||||
for (const arg of argv) {
|
||||
if (nextArgIsRequire) {
|
||||
option.modules.push(arg)
|
||||
nextArgIsRequire = false
|
||||
continue
|
||||
option.modules.push(arg);
|
||||
nextArgIsRequire = false;
|
||||
continue;
|
||||
} else if (arg === '--version' || arg === '-v') {
|
||||
option.version = true
|
||||
break
|
||||
option.version = true;
|
||||
break;
|
||||
} else if (arg.match(/^--app=/)) {
|
||||
option.file = arg.split('=')[1]
|
||||
break
|
||||
option.file = arg.split('=')[1];
|
||||
break;
|
||||
} else if (arg === '--interactive' || arg === '-i' || arg === '-repl') {
|
||||
option.interactive = true
|
||||
option.interactive = true;
|
||||
} else if (arg === '--test-type=webdriver') {
|
||||
option.webdriver = true
|
||||
option.webdriver = true;
|
||||
} else if (arg === '--require' || arg === '-r') {
|
||||
nextArgIsRequire = true
|
||||
continue
|
||||
nextArgIsRequire = true;
|
||||
continue;
|
||||
} else if (arg === '--abi' || arg === '-a') {
|
||||
option.abi = true
|
||||
continue
|
||||
option.abi = true;
|
||||
continue;
|
||||
} else if (arg === '--no-help') {
|
||||
option.noHelp = true
|
||||
continue
|
||||
option.noHelp = true;
|
||||
continue;
|
||||
} else if (arg[0] === '-') {
|
||||
continue
|
||||
continue;
|
||||
} else {
|
||||
option.file = arg
|
||||
break
|
||||
option.file = arg;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (nextArgIsRequire) {
|
||||
console.error('Invalid Usage: --require [file]\n\n"file" is required')
|
||||
process.exit(1)
|
||||
console.error('Invalid Usage: --require [file]\n\n"file" is required');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Set up preload modules
|
||||
if (option.modules.length > 0) {
|
||||
Module._preloadModules(option.modules)
|
||||
Module._preloadModules(option.modules);
|
||||
}
|
||||
|
||||
function loadApplicationPackage (packagePath: string) {
|
||||
@@ -79,102 +79,102 @@ function loadApplicationPackage (packagePath: string) {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
value: true
|
||||
})
|
||||
});
|
||||
|
||||
try {
|
||||
// Override app name and version.
|
||||
packagePath = path.resolve(packagePath)
|
||||
const packageJsonPath = path.join(packagePath, 'package.json')
|
||||
let appPath
|
||||
packagePath = path.resolve(packagePath);
|
||||
const packageJsonPath = path.join(packagePath, 'package.json');
|
||||
let appPath;
|
||||
if (fs.existsSync(packageJsonPath)) {
|
||||
let packageJson
|
||||
let packageJson;
|
||||
try {
|
||||
packageJson = require(packageJsonPath)
|
||||
packageJson = require(packageJsonPath);
|
||||
} catch (e) {
|
||||
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${e.message}`)
|
||||
return
|
||||
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (packageJson.version) {
|
||||
app.setVersion(packageJson.version)
|
||||
app.setVersion(packageJson.version);
|
||||
}
|
||||
if (packageJson.productName) {
|
||||
app.name = packageJson.productName
|
||||
app.name = packageJson.productName;
|
||||
} else if (packageJson.name) {
|
||||
app.name = packageJson.name
|
||||
app.name = packageJson.name;
|
||||
}
|
||||
appPath = packagePath
|
||||
appPath = packagePath;
|
||||
}
|
||||
|
||||
try {
|
||||
const filePath = Module._resolveFilename(packagePath, module, true)
|
||||
app._setDefaultAppPaths(appPath || path.dirname(filePath))
|
||||
const filePath = Module._resolveFilename(packagePath, module, true);
|
||||
app._setDefaultAppPaths(appPath || path.dirname(filePath));
|
||||
} catch (e) {
|
||||
showErrorMessage(`Unable to find Electron app at ${packagePath}\n\n${e.message}`)
|
||||
return
|
||||
showErrorMessage(`Unable to find Electron app at ${packagePath}\n\n${e.message}`);
|
||||
return;
|
||||
}
|
||||
|
||||
// Run the app.
|
||||
Module._load(packagePath, module, true)
|
||||
Module._load(packagePath, module, true);
|
||||
} catch (e) {
|
||||
console.error('App threw an error during load')
|
||||
console.error(e.stack || e)
|
||||
throw e
|
||||
console.error('App threw an error during load');
|
||||
console.error(e.stack || e);
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
function showErrorMessage (message: string) {
|
||||
app.focus()
|
||||
dialog.showErrorBox('Error launching app', message)
|
||||
process.exit(1)
|
||||
app.focus();
|
||||
dialog.showErrorBox('Error launching app', message);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
async function loadApplicationByURL (appUrl: string) {
|
||||
const { loadURL } = await import('./default_app')
|
||||
loadURL(appUrl)
|
||||
const { loadURL } = await import('./default_app');
|
||||
loadURL(appUrl);
|
||||
}
|
||||
|
||||
async function loadApplicationByFile (appPath: string) {
|
||||
const { loadFile } = await import('./default_app')
|
||||
loadFile(appPath)
|
||||
const { loadFile } = await import('./default_app');
|
||||
loadFile(appPath);
|
||||
}
|
||||
|
||||
function startRepl () {
|
||||
if (process.platform === 'win32') {
|
||||
console.error('Electron REPL not currently supported on Windows')
|
||||
process.exit(1)
|
||||
console.error('Electron REPL not currently supported on Windows');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// prevent quitting
|
||||
app.on('window-all-closed', () => {})
|
||||
app.on('window-all-closed', () => {});
|
||||
|
||||
const repl = require('repl')
|
||||
const repl = require('repl');
|
||||
repl.start('> ').on('exit', () => {
|
||||
process.exit(0)
|
||||
})
|
||||
process.exit(0);
|
||||
});
|
||||
}
|
||||
|
||||
// Start the specified app if there is one specified in command line, otherwise
|
||||
// start the default app.
|
||||
if (option.file && !option.webdriver) {
|
||||
const file = option.file
|
||||
const protocol = url.parse(file).protocol
|
||||
const extension = path.extname(file)
|
||||
const file = option.file;
|
||||
const protocol = url.parse(file).protocol;
|
||||
const extension = path.extname(file);
|
||||
if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') {
|
||||
loadApplicationByURL(file)
|
||||
loadApplicationByURL(file);
|
||||
} else if (extension === '.html' || extension === '.htm') {
|
||||
loadApplicationByFile(path.resolve(file))
|
||||
loadApplicationByFile(path.resolve(file));
|
||||
} else {
|
||||
loadApplicationPackage(file)
|
||||
loadApplicationPackage(file);
|
||||
}
|
||||
} else if (option.version) {
|
||||
console.log('v' + process.versions.electron)
|
||||
process.exit(0)
|
||||
console.log('v' + process.versions.electron);
|
||||
process.exit(0);
|
||||
} else if (option.abi) {
|
||||
console.log(process.versions.modules)
|
||||
process.exit(0)
|
||||
console.log(process.versions.modules);
|
||||
process.exit(0);
|
||||
} else if (option.interactive) {
|
||||
startRepl()
|
||||
startRepl();
|
||||
} else {
|
||||
if (!option.noHelp) {
|
||||
const welcomeMessage = `
|
||||
@@ -192,10 +192,10 @@ Options:
|
||||
-i, --interactive Open a REPL to the main process.
|
||||
-r, --require Module to preload (option can be repeated).
|
||||
-v, --version Print the version.
|
||||
-a, --abi Print the Node ABI version.`
|
||||
-a, --abi Print the Node ABI version.`;
|
||||
|
||||
console.log(welcomeMessage)
|
||||
console.log(welcomeMessage);
|
||||
}
|
||||
|
||||
loadApplicationByFile('index.html')
|
||||
loadApplicationByFile('index.html');
|
||||
}
|
||||
|
||||
@@ -1,53 +1,53 @@
|
||||
import { ipcRenderer, contextBridge } from 'electron'
|
||||
import { ipcRenderer, contextBridge } from 'electron';
|
||||
|
||||
async function getOcticonSvg (name: string) {
|
||||
try {
|
||||
const response = await fetch(`octicon/${name}.svg`)
|
||||
const div = document.createElement('div')
|
||||
div.innerHTML = await response.text()
|
||||
return div
|
||||
const response = await fetch(`octicon/${name}.svg`);
|
||||
const div = document.createElement('div');
|
||||
div.innerHTML = await response.text();
|
||||
return div;
|
||||
} catch {
|
||||
return null
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSVG (element: HTMLSpanElement) {
|
||||
for (const cssClass of element.classList) {
|
||||
if (cssClass.startsWith('octicon-')) {
|
||||
const icon = await getOcticonSvg(cssClass.substr(8))
|
||||
const icon = await getOcticonSvg(cssClass.substr(8));
|
||||
if (icon) {
|
||||
for (const elemClass of element.classList) {
|
||||
icon.classList.add(elemClass)
|
||||
icon.classList.add(elemClass);
|
||||
}
|
||||
element.before(icon)
|
||||
element.remove()
|
||||
break
|
||||
element.before(icon);
|
||||
element.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function initialize () {
|
||||
const electronPath = await ipcRenderer.invoke('bootstrap')
|
||||
const electronPath = await ipcRenderer.invoke('bootstrap');
|
||||
|
||||
function replaceText (selector: string, text: string) {
|
||||
const element = document.querySelector<HTMLElement>(selector)
|
||||
const element = document.querySelector<HTMLElement>(selector);
|
||||
if (element) {
|
||||
element.innerText = text
|
||||
element.innerText = text;
|
||||
}
|
||||
}
|
||||
|
||||
replaceText('.electron-version', `Electron v${process.versions.electron}`)
|
||||
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`)
|
||||
replaceText('.node-version', `Node v${process.versions.node}`)
|
||||
replaceText('.v8-version', `v8 v${process.versions.v8}`)
|
||||
replaceText('.command-example', `${electronPath} path-to-app`)
|
||||
replaceText('.electron-version', `Electron v${process.versions.electron}`);
|
||||
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`);
|
||||
replaceText('.node-version', `Node v${process.versions.node}`);
|
||||
replaceText('.v8-version', `v8 v${process.versions.v8}`);
|
||||
replaceText('.command-example', `${electronPath} path-to-app`);
|
||||
|
||||
for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
|
||||
loadSVG(element)
|
||||
loadSVG(element);
|
||||
}
|
||||
}
|
||||
|
||||
contextBridge.exposeInMainWorld('electronDefaultApp', {
|
||||
initialize
|
||||
})
|
||||
});
|
||||
|
||||
@@ -711,34 +711,34 @@ Clears the recent documents list.
|
||||
|
||||
### `app.setAsDefaultProtocolClient(protocol[, path, args])`
|
||||
|
||||
* `protocol` String - The name of your protocol, without `://`. If you want your
|
||||
app to handle `electron://` links, call this method with `electron` as the
|
||||
parameter.
|
||||
* `path` String (optional) _Windows_ - Defaults to `process.execPath`
|
||||
* `args` String[] (optional) _Windows_ - Defaults to an empty array
|
||||
* `protocol` String - The name of your protocol, without `://`. For example,
|
||||
if you want your app to handle `electron://` links, call this method with
|
||||
`electron` as the parameter.
|
||||
* `path` String (optional) _Windows_ - The path to the Electron executable.
|
||||
Defaults to `process.execPath`
|
||||
* `args` String[] (optional) _Windows_ - Arguments passed to the executable.
|
||||
Defaults to an empty array
|
||||
|
||||
Returns `Boolean` - Whether the call succeeded.
|
||||
|
||||
This method sets the current executable as the default handler for a protocol
|
||||
(aka URI scheme). It allows you to integrate your app deeper into the operating
|
||||
system. Once registered, all links with `your-protocol://` will be opened with
|
||||
the current executable. The whole link, including protocol, will be passed to
|
||||
your application as a parameter.
|
||||
|
||||
On Windows, you can provide optional parameters path, the path to your executable,
|
||||
and args, an array of arguments to be passed to your executable when it launches.
|
||||
Sets the current executable as the default handler for a protocol (aka URI
|
||||
scheme). It allows you to integrate your app deeper into the operating system.
|
||||
Once registered, all links with `your-protocol://` will be opened with the
|
||||
current executable. The whole link, including protocol, will be passed to your
|
||||
application as a parameter.
|
||||
|
||||
**Note:** On macOS, you can only register protocols that have been added to
|
||||
your app's `info.plist`, which can not be modified at runtime. You can however
|
||||
change the file with a simple text editor or script during build time.
|
||||
Please refer to [Apple's documentation][CFBundleURLTypes] for details.
|
||||
your app's `info.plist`, which cannot be modified at runtime. However, you can
|
||||
change the file during build time via [Electron Forge][electron-forge],
|
||||
[Electron Packager][electron-packager], or by editing `info.plist` with a text
|
||||
editor. Please refer to [Apple's documentation][CFBundleURLTypes] for details.
|
||||
|
||||
**Note:** In a Windows Store environment (when packaged as an `appx`) this API
|
||||
will return `true` for all calls but the registry key it sets won't be accessible
|
||||
by other applications. In order to register your Windows Store application
|
||||
as a default protocol handler you must [declare the protocol in your manifest](https://docs.microsoft.com/en-us/uwp/schemas/appxpackage/uapmanifestschema/element-uap-protocol).
|
||||
|
||||
The API uses the Windows Registry and LSSetDefaultHandlerForURLScheme internally.
|
||||
The API uses the Windows Registry and `LSSetDefaultHandlerForURLScheme` internally.
|
||||
|
||||
### `app.removeAsDefaultProtocolClient(protocol[, path, args])` _macOS_ _Windows_
|
||||
|
||||
@@ -757,10 +757,8 @@ protocol (aka URI scheme). If so, it will remove the app as the default handler.
|
||||
* `path` String (optional) _Windows_ - Defaults to `process.execPath`
|
||||
* `args` String[] (optional) _Windows_ - Defaults to an empty array
|
||||
|
||||
Returns `Boolean`
|
||||
|
||||
This method checks if the current executable is the default handler for a protocol
|
||||
(aka URI scheme). If so, it will return true. Otherwise, it will return false.
|
||||
Returns `Boolean` - Whether the current executable is the default handler for a
|
||||
protocol (aka URI scheme).
|
||||
|
||||
**Note:** On macOS, you can use this method to check if the app has been
|
||||
registered as the default protocol handler for a protocol. You can also verify
|
||||
@@ -768,7 +766,7 @@ this by checking `~/Library/Preferences/com.apple.LaunchServices.plist` on the
|
||||
macOS machine. Please refer to
|
||||
[Apple's documentation][LSCopyDefaultHandlerForURLScheme] for details.
|
||||
|
||||
The API uses the Windows Registry and LSCopyDefaultHandlerForURLScheme internally.
|
||||
The API uses the Windows Registry and `LSCopyDefaultHandlerForURLScheme` internally.
|
||||
|
||||
### `app.setUserTasks(tasks)` _Windows_
|
||||
|
||||
@@ -1186,8 +1184,9 @@ Show the app's about panel options. These options can be overridden with `app.se
|
||||
* `website` String (optional) _Linux_ - The app's website.
|
||||
* `iconPath` String (optional) _Linux_ - Path to the app's icon. Will be shown as 64x64 pixels while retaining aspect ratio.
|
||||
|
||||
Set the about panel options. This will override the values defined in the app's
|
||||
`.plist` file on MacOS. See the [Apple docs][about-panel-options] for more details. On Linux, values must be set in order to be shown; there are no defaults.
|
||||
Set the about panel options. This will override the values defined in the app's `.plist` file on MacOS. See the [Apple docs][about-panel-options] for more details. On Linux, values must be set in order to be shown; there are no defaults.
|
||||
|
||||
If you do not set `credits` but still wish to surface them in your app, AppKit will look for a file named "Credits.html", "Credits.rtf", and "Credits.rtfd", in that order, in the bundle returned by the NSBundle class method main. The first file found is used, and if none is found, the info area is left blank. See Apple [documentation](https://developer.apple.com/documentation/appkit/nsaboutpaneloptioncredits?language=objc) for more information.
|
||||
|
||||
### `app.isEmojiPanelSupported()`
|
||||
|
||||
@@ -1308,6 +1307,8 @@ A `Boolean` property that returns `true` if the app is packaged, `false` otherw
|
||||
[dock-menu]:https://developer.apple.com/macos/human-interface-guidelines/menus/dock-menus/
|
||||
[tasks]:https://msdn.microsoft.com/en-us/library/windows/desktop/dd378460(v=vs.85).aspx#tasks
|
||||
[app-user-model-id]: https://msdn.microsoft.com/en-us/library/windows/desktop/dd378459(v=vs.85).aspx
|
||||
[electron-forge]: https://www.electronforge.io/
|
||||
[electron-packager]: https://github.com/electron/electron-packager
|
||||
[CFBundleURLTypes]: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html#//apple_ref/doc/uid/TP40009249-102207-TPXREF115
|
||||
[LSCopyDefaultHandlerForURLScheme]: https://developer.apple.com/library/mac/documentation/Carbon/Reference/LaunchServicesReference/#//apple_ref/c/func/LSCopyDefaultHandlerForURLScheme
|
||||
[handoff]: https://developer.apple.com/library/ios/documentation/UserExperience/Conceptual/Handoff/HandoffFundamentals/HandoffFundamentals.html
|
||||
|
||||
@@ -273,8 +273,6 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
|
||||
OS-level sandbox and disabling the Node.js engine. This is not the same as
|
||||
the `nodeIntegration` option and the APIs available to the preload script
|
||||
are more limited. Read more about the option [here](sandbox-option.md).
|
||||
**Note:** This option is currently experimental and may change or be
|
||||
removed in future Electron releases.
|
||||
* `enableRemoteModule` Boolean (optional) - Whether to enable the [`remote`](remote.md) module.
|
||||
Default is `true`.
|
||||
* `session` [Session](session.md#class-session) (optional) - Sets the session used by the
|
||||
|
||||
@@ -22,6 +22,9 @@ which the request is associated.
|
||||
with which the request is associated. Defaults to the empty string. The
|
||||
`session` option prevails on `partition`. Thus if a `session` is explicitly
|
||||
specified, `partition` is ignored.
|
||||
* `useSessionCookies` Boolean (optional) - Whether to send cookies with this
|
||||
request from the provided session. This will make the `net` request's
|
||||
cookie behavior match a `fetch` request. Default is `false`.
|
||||
* `protocol` String (optional) - The protocol scheme in the form 'scheme:'.
|
||||
Currently supported values are 'http:' or 'https:'. Defaults to 'http:'.
|
||||
* `host` String (optional) - The server host provided as a concatenation of
|
||||
|
||||
@@ -28,13 +28,14 @@ window.electron.doThing()
|
||||
|
||||
### Main World
|
||||
|
||||
The "Main World" is the javascript context that your main renderer code runs in. By default the page you load in your renderer
|
||||
executes code in this world.
|
||||
The "Main World" is the JavaScript context that your main renderer code runs in. By default, the
|
||||
page you load in your renderer executes code in this world.
|
||||
|
||||
### Isolated World
|
||||
|
||||
When `contextIsolation` is enabled in your `webPreferences` your `preload` scripts run in an "Isolated World". You can read more about
|
||||
context isolation and what it affects in the [BrowserWindow](browser-window.md) docs.
|
||||
When `contextIsolation` is enabled in your `webPreferences`, your `preload` scripts run in an
|
||||
"Isolated World". You can read more about context isolation and what it affects in the
|
||||
[security](../tutorial/security.md#3-enable-context-isolation-for-remote-content) docs.
|
||||
|
||||
## Methods
|
||||
|
||||
@@ -50,12 +51,12 @@ The `contextBridge` module has the following methods:
|
||||
### API Objects
|
||||
|
||||
The `api` object provided to [`exposeInMainWorld`](#contextbridgeexposeinmainworldapikey-api-experimental) must be an object
|
||||
whose keys are strings and values are a `Function`, `String`, `Number`, `Array`, `Boolean` or another nested object that meets the same conditions.
|
||||
whose keys are strings and values are a `Function`, `String`, `Number`, `Array`, `Boolean`, or another nested object that meets the same conditions.
|
||||
|
||||
`Function` values are proxied to the other context and all other values are **copied** and **frozen**. I.e. Any data / primitives sent in
|
||||
`Function` values are proxied to the other context and all other values are **copied** and **frozen**. Any data / primitives sent in
|
||||
the API object become immutable and updates on either side of the bridge do not result in an update on the other side.
|
||||
|
||||
An example of a complex API object is shown below.
|
||||
An example of a complex API object is shown below:
|
||||
|
||||
```javascript
|
||||
const { contextBridge } = require('electron')
|
||||
@@ -90,22 +91,22 @@ results in some key limitations that we've outlined below.
|
||||
|
||||
#### Parameter / Error / Return Type support
|
||||
|
||||
Because parameters, errors and return values are **copied** when they are sent over the bridge there are only certain types that can be used.
|
||||
At a high level if the type you want to use can be serialized and un-serialized into the same object it will work. A table of type support
|
||||
has been included below for completeness.
|
||||
Because parameters, errors and return values are **copied** when they are sent over the bridge, there are only certain types that can be used.
|
||||
At a high level, if the type you want to use can be serialized and deserialized into the same object it will work. A table of type support
|
||||
has been included below for completeness:
|
||||
|
||||
| Type | Complexity | Parameter Support | Return Value Support | Limitations |
|
||||
| ---- | ---------- | ----------------- | -------------------- | ----------- |
|
||||
| `String` | Simple | ✅ | ✅ | N/A |
|
||||
| `Number` | Simple | ✅ | ✅ | N/A |
|
||||
| `Boolean` | Simple | ✅ | ✅ | N/A |
|
||||
| `Object` | Complex | ✅ | ✅ | Keys must be supported "Simple" types in this table. Values must be supported in this table. Prototype modifications are dropped. Sending custom classes will copy values but not the prototype. |
|
||||
| `Object` | Complex | ✅ | ✅ | Keys must be supported using only "Simple" types in this table. Values must be supported in this table. Prototype modifications are dropped. Sending custom classes will copy values but not the prototype. |
|
||||
| `Array` | Complex | ✅ | ✅ | Same limitations as the `Object` type |
|
||||
| `Error` | Complex | ✅ | ✅ | Errors that are thrown are also copied, this can result in the message and stack trace of the error changing slightly due to being thrown in a different context |
|
||||
| `Promise` | Complex | ✅ | ✅ | Promises are only proxied if they are a the return value or exact parameter. Promises nested in arrays or obejcts will be dropped. |
|
||||
| `Promise` | Complex | ✅ | ✅ | Promises are only proxied if they are the return value or exact parameter. Promises nested in arrays or objects will be dropped. |
|
||||
| `Function` | Complex | ✅ | ✅ | Prototype modifications are dropped. Sending classes or constructors will not work. |
|
||||
| [Cloneable Types](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API/Structured_clone_algorithm) | Simple | ✅ | ✅ | See the linked document on cloneable types |
|
||||
| `Symbol` | N/A | ❌ | ❌ | Symbols cannot be copied across contexts so they are dropped |
|
||||
|
||||
|
||||
If the type you care about is not in the above table it is probably not supported.
|
||||
If the type you care about is not in the above table, it is probably not supported.
|
||||
|
||||
@@ -118,7 +118,7 @@ Returns `Promise<Object>` - Resolve with an object containing the following:
|
||||
|
||||
* `canceled` Boolean - whether or not the dialog was canceled.
|
||||
* `filePaths` String[] - An array of file paths chosen by the user. If the dialog is cancelled this will be an empty array.
|
||||
* `bookmarks` String[] (optional) _macOS_ _mas_ - An array matching the `filePaths` array of base64 encoded strings which contains security scoped bookmark data. `securityScopedBookmarks` must be enabled for this to be populated.
|
||||
* `bookmarks` String[] (optional) _macOS_ _mas_ - An array matching the `filePaths` array of base64 encoded strings which contains security scoped bookmark data. `securityScopedBookmarks` must be enabled for this to be populated. (For return values, see [table here](#bookmarks-array).)
|
||||
|
||||
The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal.
|
||||
|
||||
@@ -200,7 +200,7 @@ The `filters` specifies an array of file types that can be displayed, see
|
||||
Returns `Promise<Object>` - Resolve with an object containing the following:
|
||||
* `canceled` Boolean - whether or not the dialog was canceled.
|
||||
* `filePath` String (optional) - If the dialog is canceled, this will be `undefined`.
|
||||
* `bookmark` String (optional) _macOS_ _mas_ - Base64 encoded string which contains the security scoped bookmark data for the saved file. `securityScopedBookmarks` must be enabled for this to be present.
|
||||
* `bookmark` String (optional) _macOS_ _mas_ - Base64 encoded string which contains the security scoped bookmark data for the saved file. `securityScopedBookmarks` must be enabled for this to be present. (For return values, see [table here](#bookmarks-array).)
|
||||
|
||||
The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal.
|
||||
|
||||
@@ -254,6 +254,7 @@ Shows a message box, it will block the process until the message box is closed.
|
||||
It returns the index of the clicked button.
|
||||
|
||||
The `browserWindow` argument allows the dialog to attach itself to a parent window, making it modal.
|
||||
If the `browserWindow` is not shown, the dialog will not be attached to it. In that case it will be displayed as an independent window.
|
||||
|
||||
### `dialog.showMessageBox([browserWindow, ]options)`
|
||||
|
||||
@@ -335,6 +336,17 @@ On Windows the options are more limited, due to the Win32 APIs used:
|
||||
* The `browserWindow` argument is ignored since it is not possible to make
|
||||
this confirmation dialog modal.
|
||||
|
||||
## Bookmarks array
|
||||
|
||||
`showOpenDialog`, `showOpenDialogSync`, `showSaveDialog`, and `showSaveDialogSync` will return a `bookmarks` array.
|
||||
|
||||
| Build Type | securityScopedBookmarks boolean | Return Type | Return Value |
|
||||
|------------|---------------------------------|:-----------:|--------------------------------|
|
||||
| macOS mas | True | Success | `['LONGBOOKMARKSTRING']` |
|
||||
| macOS mas | True | Error | `['']` (array of empty string) |
|
||||
| macOS mas | False | NA | `[]` (empty array) |
|
||||
| non mas | any | NA | `[]` (empty array) |
|
||||
|
||||
## Sheets
|
||||
|
||||
On macOS, dialogs are presented as sheets attached to a window if you provide
|
||||
|
||||
@@ -402,8 +402,6 @@ Returns `Boolean` - whether or not this device has the ability to use Touch ID.
|
||||
|
||||
**NOTE:** This API will return `false` on macOS systems older than Sierra 10.12.2.
|
||||
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
### `systemPreferences.promptTouchID(reason)` _macOS_
|
||||
|
||||
* `reason` String - The reason you are asking for Touch ID authentication
|
||||
|
||||
@@ -1066,11 +1066,13 @@ Returns `Boolean` - Whether audio is currently playing.
|
||||
|
||||
#### `contents.setZoomFactor(factor)`
|
||||
|
||||
* `factor` Number - Zoom factor.
|
||||
* `factor` Double - Zoom factor; default is 1.0.
|
||||
|
||||
Changes the zoom factor to the specified factor. Zoom factor is
|
||||
zoom percent divided by 100, so 300% = 3.0.
|
||||
|
||||
The factor must be greater than 0.0.
|
||||
|
||||
**[Deprecated](modernization/property-updates.md)**
|
||||
|
||||
#### `contents.getZoomFactor()`
|
||||
@@ -1232,6 +1234,25 @@ Returns `Promise<NativeImage>` - Resolves with a [NativeImage](native-image.md)
|
||||
|
||||
Captures a snapshot of the page within `rect`. Omitting `rect` will capture the whole visible page.
|
||||
|
||||
#### `contents.isBeingCaptured()`
|
||||
|
||||
Returns `Boolean` - Whether this page is being captured. It returns true when the capturer count
|
||||
is large then 0.
|
||||
|
||||
#### `contents.incrementCapturerCount([size])`
|
||||
|
||||
* `size` [Size](structures/size.md) (optional) - The perferred size for the capturer.
|
||||
|
||||
Increase the capturer count by one. The page is considered visible when its browser window is
|
||||
hidden and the capturer count is non-zero.
|
||||
|
||||
This also affects the Page Visibility API.
|
||||
|
||||
#### `contents.decrementCapturerCount()`
|
||||
|
||||
Decrease the capturer count by one. The page will be set to hidden or occluded state when its
|
||||
browser window is hidden or occluded and the capturer count reaches zero.
|
||||
|
||||
#### `contents.getPrinters()`
|
||||
|
||||
Get the system printer list.
|
||||
@@ -1244,7 +1265,7 @@ Returns [`PrinterInfo[]`](structures/printer-info.md)
|
||||
* `silent` Boolean (optional) - Don't ask user for print settings. Default is `false`.
|
||||
* `printBackground` Boolean (optional) - Prints the background color and image of
|
||||
the web page. Default is `false`.
|
||||
* `deviceName` String (optional) - Set the printer device name to use. Default is `''`.
|
||||
* `deviceName` String (optional) - Set the printer device name to use. Must be the system-defined name and not the 'friendly' name, e.g 'Brother_QL_820NWB' and not 'Brother QL-820NWB'.
|
||||
* `color` Boolean (optional) - Set whether the printed web page will be in color or grayscale. Default is `true`.
|
||||
* `margins` Object (optional)
|
||||
* `marginType` String (optional) - Can be `default`, `none`, `printableArea`, or `custom`. If `custom` is chosen, you will also need to specify `top`, `bottom`, `left`, and `right`.
|
||||
@@ -1264,7 +1285,7 @@ Returns [`PrinterInfo[]`](structures/printer-info.md)
|
||||
* `vertical` Number (optional) - The vertical dpi.
|
||||
* `callback` Function (optional)
|
||||
* `success` Boolean - Indicates success of the print call.
|
||||
* `failureReason` String - Called back if the print fails; can be `cancelled` or `failed`.
|
||||
* `failureReason` String - Error description called back if the print fails.
|
||||
|
||||
Prints window's web page. When `silent` is set to `true`, Electron will pick
|
||||
the system's default printer if `deviceName` is empty and the default settings for printing.
|
||||
|
||||
@@ -22,11 +22,13 @@ The `WebFrame` class has the following instance methods:
|
||||
|
||||
### `webFrame.setZoomFactor(factor)`
|
||||
|
||||
* `factor` Number - Zoom factor.
|
||||
* `factor` Double - Zoom factor; default is 1.0.
|
||||
|
||||
Changes the zoom factor to the specified factor. Zoom factor is
|
||||
zoom percent divided by 100, so 300% = 3.0.
|
||||
|
||||
The factor must be greater than 0.0.
|
||||
|
||||
### `webFrame.getZoomFactor()`
|
||||
|
||||
Returns `Number` - The current zoom factor.
|
||||
|
||||
@@ -146,7 +146,8 @@ response are visible by the time this listener is fired.
|
||||
* `timestamp` Double
|
||||
* `statusLine` String
|
||||
* `statusCode` Integer
|
||||
* `responseHeaders` Record<string, string> (optional)
|
||||
* `requestHeaders` Record<string, string>
|
||||
* `responseHeaders` Record<string, string[]> (optional)
|
||||
* `callback` Function
|
||||
* `headersReceivedResponse` Object
|
||||
* `cancel` Boolean (optional)
|
||||
@@ -175,7 +176,7 @@ The `callback` has to be called with a `response` object.
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
* `responseHeaders` Record<string, string> (optional)
|
||||
* `responseHeaders` Record<string, string[]> (optional)
|
||||
* `fromCache` Boolean - Indicates whether the response was fetched from disk
|
||||
cache.
|
||||
* `statusCode` Integer
|
||||
@@ -205,7 +206,7 @@ and response headers are available.
|
||||
* `ip` String (optional) - The server IP address that the request was
|
||||
actually sent to.
|
||||
* `fromCache` Boolean
|
||||
* `responseHeaders` Record<string, string> (optional)
|
||||
* `responseHeaders` Record<string, string[]> (optional)
|
||||
|
||||
The `listener` will be called with `listener(details)` when a server initiated
|
||||
redirect is about to occur.
|
||||
@@ -224,10 +225,11 @@ redirect is about to occur.
|
||||
* `resourceType` String
|
||||
* `referrer` String
|
||||
* `timestamp` Double
|
||||
* `responseHeaders` Record<string, string> (optional)
|
||||
* `responseHeaders` Record<string, string[]> (optional)
|
||||
* `fromCache` Boolean
|
||||
* `statusCode` Integer
|
||||
* `statusLine` String
|
||||
* `error` String
|
||||
|
||||
The `listener` will be called with `listener(details)` when a request is
|
||||
completed.
|
||||
|
||||
@@ -166,6 +166,7 @@ auto_filenames = {
|
||||
"lib/renderer/web-view/web-view-element.ts",
|
||||
"lib/renderer/web-view/web-view-impl.ts",
|
||||
"lib/renderer/web-view/web-view-init.ts",
|
||||
"lib/renderer/window-setup.ts",
|
||||
"lib/sandboxed_renderer/api/exports/electron.js",
|
||||
"lib/sandboxed_renderer/api/module-list.js",
|
||||
"lib/sandboxed_renderer/init.js",
|
||||
|
||||
@@ -221,6 +221,8 @@ filenames = {
|
||||
"shell/browser/net/cert_verifier_client.h",
|
||||
"shell/browser/net/proxying_url_loader_factory.cc",
|
||||
"shell/browser/net/proxying_url_loader_factory.h",
|
||||
"shell/browser/net/proxying_websocket.cc",
|
||||
"shell/browser/net/proxying_websocket.h",
|
||||
"shell/browser/net/network_context_service_factory.cc",
|
||||
"shell/browser/net/network_context_service_factory.h",
|
||||
"shell/browser/net/network_context_service.cc",
|
||||
@@ -233,6 +235,7 @@ filenames = {
|
||||
"shell/browser/net/system_network_context_manager.h",
|
||||
"shell/browser/net/url_pipe_loader.cc",
|
||||
"shell/browser/net/url_pipe_loader.h",
|
||||
"shell/browser/net/web_request_api_interface.h",
|
||||
"shell/browser/notifications/linux/libnotify_notification.cc",
|
||||
"shell/browser/notifications/linux/libnotify_notification.h",
|
||||
"shell/browser/notifications/linux/notification_presenter_linux.cc",
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
|
||||
import { deprecate, Menu } from 'electron'
|
||||
import { EventEmitter } from 'events'
|
||||
import { deprecate, Menu } from 'electron';
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const bindings = process.electronBinding('app')
|
||||
const commandLine = process.electronBinding('command_line')
|
||||
const { app, App } = bindings
|
||||
const bindings = process.electronBinding('app');
|
||||
const commandLine = process.electronBinding('command_line');
|
||||
const { app, App } = bindings;
|
||||
|
||||
// Only one app object permitted.
|
||||
export default app
|
||||
export default app;
|
||||
|
||||
let dockMenu: Electron.Menu | null = null
|
||||
let dockMenu: Electron.Menu | null = null;
|
||||
|
||||
// App is an EventEmitter.
|
||||
Object.setPrototypeOf(App.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(app as any)
|
||||
Object.setPrototypeOf(App.prototype, EventEmitter.prototype);
|
||||
EventEmitter.call(app as any);
|
||||
|
||||
Object.assign(app, {
|
||||
commandLine: {
|
||||
@@ -24,99 +24,99 @@ Object.assign(app, {
|
||||
appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
|
||||
appendArgument: (arg: string) => commandLine.appendArgument(String(arg))
|
||||
} as Electron.CommandLine
|
||||
})
|
||||
});
|
||||
|
||||
// we define this here because it'd be overly complicated to
|
||||
// do in native land
|
||||
Object.defineProperty(app, 'applicationMenu', {
|
||||
get () {
|
||||
return Menu.getApplicationMenu()
|
||||
return Menu.getApplicationMenu();
|
||||
},
|
||||
set (menu: Electron.Menu | null) {
|
||||
return Menu.setApplicationMenu(menu)
|
||||
return Menu.setApplicationMenu(menu);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
App.prototype.isPackaged = (() => {
|
||||
const execFile = path.basename(process.execPath).toLowerCase()
|
||||
const execFile = path.basename(process.execPath).toLowerCase();
|
||||
if (process.platform === 'win32') {
|
||||
return execFile !== 'electron.exe'
|
||||
return execFile !== 'electron.exe';
|
||||
}
|
||||
return execFile !== 'electron'
|
||||
})()
|
||||
return execFile !== 'electron';
|
||||
})();
|
||||
|
||||
app._setDefaultAppPaths = (packagePath) => {
|
||||
// Set the user path according to application's name.
|
||||
app.setPath('userData', path.join(app.getPath('appData'), app.name!))
|
||||
app.setPath('userCache', path.join(app.getPath('cache'), app.name!))
|
||||
app.setAppPath(packagePath)
|
||||
app.setPath('userData', path.join(app.getPath('appData'), app.name!));
|
||||
app.setPath('userCache', path.join(app.getPath('cache'), app.name!));
|
||||
app.setAppPath(packagePath);
|
||||
|
||||
// Add support for --user-data-dir=
|
||||
if (app.commandLine.hasSwitch('user-data-dir')) {
|
||||
const userDataDir = app.commandLine.getSwitchValue('user-data-dir')
|
||||
if (path.isAbsolute(userDataDir)) app.setPath('userData', userDataDir)
|
||||
const userDataDir = app.commandLine.getSwitchValue('user-data-dir');
|
||||
if (path.isAbsolute(userDataDir)) app.setPath('userData', userDataDir);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const setDockMenu = app.dock!.setMenu
|
||||
const setDockMenu = app.dock!.setMenu;
|
||||
app.dock!.setMenu = (menu) => {
|
||||
dockMenu = menu
|
||||
setDockMenu(menu)
|
||||
}
|
||||
app.dock!.getMenu = () => dockMenu
|
||||
dockMenu = menu;
|
||||
setDockMenu(menu);
|
||||
};
|
||||
app.dock!.getMenu = () => dockMenu;
|
||||
}
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
const patternVmRSS = /^VmRSS:\s*(\d+) kB$/m
|
||||
const patternVmHWM = /^VmHWM:\s*(\d+) kB$/m
|
||||
const patternVmRSS = /^VmRSS:\s*(\d+) kB$/m;
|
||||
const patternVmHWM = /^VmHWM:\s*(\d+) kB$/m;
|
||||
|
||||
const getStatus = (pid: number) => {
|
||||
try {
|
||||
return fs.readFileSync(`/proc/${pid}/status`, 'utf8')
|
||||
return fs.readFileSync(`/proc/${pid}/status`, 'utf8');
|
||||
} catch {
|
||||
return ''
|
||||
return '';
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const getEntry = (file: string, pattern: RegExp) => {
|
||||
const match = file.match(pattern)
|
||||
return match ? parseInt(match[1], 10) : 0
|
||||
}
|
||||
const match = file.match(pattern);
|
||||
return match ? parseInt(match[1], 10) : 0;
|
||||
};
|
||||
|
||||
const getProcessMemoryInfo = (pid: number) => {
|
||||
const file = getStatus(pid)
|
||||
const file = getStatus(pid);
|
||||
|
||||
return {
|
||||
workingSetSize: getEntry(file, patternVmRSS),
|
||||
peakWorkingSetSize: getEntry(file, patternVmHWM)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const nativeFn = app.getAppMetrics
|
||||
const nativeFn = app.getAppMetrics;
|
||||
app.getAppMetrics = () => {
|
||||
const metrics = nativeFn.call(app)
|
||||
const metrics = nativeFn.call(app);
|
||||
for (const metric of metrics) {
|
||||
metric.memory = getProcessMemoryInfo(metric.pid)
|
||||
metric.memory = getProcessMemoryInfo(metric.pid);
|
||||
}
|
||||
|
||||
return metrics
|
||||
}
|
||||
return metrics;
|
||||
};
|
||||
}
|
||||
|
||||
// Routes the events to webContents.
|
||||
const events = ['certificate-error', 'select-client-certificate']
|
||||
const events = ['certificate-error', 'select-client-certificate'];
|
||||
for (const name of events) {
|
||||
app.on(name as 'certificate-error', (event, webContents, ...args: any[]) => {
|
||||
webContents.emit(name, event, ...args)
|
||||
})
|
||||
webContents.emit(name, event, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
// Property Deprecations
|
||||
deprecate.fnToProperty(App.prototype, 'accessibilitySupportEnabled', '_isAccessibilitySupportEnabled', '_setAccessibilitySupportEnabled')
|
||||
deprecate.fnToProperty(App.prototype, 'badgeCount', '_getBadgeCount', '_setBadgeCount')
|
||||
deprecate.fnToProperty(App.prototype, 'name', '_getName', '_setName')
|
||||
deprecate.fnToProperty(App.prototype, 'accessibilitySupportEnabled', '_isAccessibilitySupportEnabled', '_setAccessibilitySupportEnabled');
|
||||
deprecate.fnToProperty(App.prototype, 'badgeCount', '_getBadgeCount', '_setBadgeCount');
|
||||
deprecate.fnToProperty(App.prototype, 'name', '_getName', '_setName');
|
||||
|
||||
// Wrappers for native classes.
|
||||
const { DownloadItem } = process.electronBinding('download_item')
|
||||
Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype)
|
||||
const { DownloadItem } = process.electronBinding('download_item');
|
||||
Object.setPrototypeOf(DownloadItem.prototype, EventEmitter.prototype);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win')
|
||||
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-win');
|
||||
} else {
|
||||
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native')
|
||||
module.exports = require('@electron/internal/browser/api/auto-updater/auto-updater-native');
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater')
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const { autoUpdater, AutoUpdater } = process.electronBinding('auto_updater');
|
||||
|
||||
// AutoUpdater is an EventEmitter.
|
||||
Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(autoUpdater)
|
||||
Object.setPrototypeOf(AutoUpdater.prototype, EventEmitter.prototype);
|
||||
EventEmitter.call(autoUpdater);
|
||||
|
||||
module.exports = autoUpdater
|
||||
module.exports = autoUpdater;
|
||||
|
||||
@@ -1,74 +1,74 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { app } = require('electron')
|
||||
const { EventEmitter } = require('events')
|
||||
const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win')
|
||||
const { app } = require('electron');
|
||||
const { EventEmitter } = require('events');
|
||||
const squirrelUpdate = require('@electron/internal/browser/api/auto-updater/squirrel-update-win');
|
||||
|
||||
class AutoUpdater extends EventEmitter {
|
||||
quitAndInstall () {
|
||||
if (!this.updateAvailable) {
|
||||
return this.emitError('No update available, can\'t quit and install')
|
||||
return this.emitError('No update available, can\'t quit and install');
|
||||
}
|
||||
squirrelUpdate.processStart()
|
||||
app.quit()
|
||||
squirrelUpdate.processStart();
|
||||
app.quit();
|
||||
}
|
||||
|
||||
getFeedURL () {
|
||||
return this.updateURL
|
||||
return this.updateURL;
|
||||
}
|
||||
|
||||
setFeedURL (options) {
|
||||
let updateURL
|
||||
let updateURL;
|
||||
if (typeof options === 'object') {
|
||||
if (typeof options.url === 'string') {
|
||||
updateURL = options.url
|
||||
updateURL = options.url;
|
||||
} else {
|
||||
throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call')
|
||||
throw new Error('Expected options object to contain a \'url\' string property in setFeedUrl call');
|
||||
}
|
||||
} else if (typeof options === 'string') {
|
||||
updateURL = options
|
||||
updateURL = options;
|
||||
} else {
|
||||
throw new Error('Expected an options object with a \'url\' property to be provided')
|
||||
throw new Error('Expected an options object with a \'url\' property to be provided');
|
||||
}
|
||||
this.updateURL = updateURL
|
||||
this.updateURL = updateURL;
|
||||
}
|
||||
|
||||
checkForUpdates () {
|
||||
if (!this.updateURL) {
|
||||
return this.emitError('Update URL is not set')
|
||||
return this.emitError('Update URL is not set');
|
||||
}
|
||||
if (!squirrelUpdate.supported()) {
|
||||
return this.emitError('Can not find Squirrel')
|
||||
return this.emitError('Can not find Squirrel');
|
||||
}
|
||||
this.emit('checking-for-update')
|
||||
this.emit('checking-for-update');
|
||||
squirrelUpdate.checkForUpdate(this.updateURL, (error, update) => {
|
||||
if (error != null) {
|
||||
return this.emitError(error)
|
||||
return this.emitError(error);
|
||||
}
|
||||
if (update == null) {
|
||||
return this.emit('update-not-available')
|
||||
return this.emit('update-not-available');
|
||||
}
|
||||
this.updateAvailable = true
|
||||
this.emit('update-available')
|
||||
this.updateAvailable = true;
|
||||
this.emit('update-available');
|
||||
squirrelUpdate.update(this.updateURL, (error) => {
|
||||
if (error != null) {
|
||||
return this.emitError(error)
|
||||
return this.emitError(error);
|
||||
}
|
||||
const { releaseNotes, version } = update
|
||||
const { releaseNotes, version } = update;
|
||||
// Date is not available on Windows, so fake it.
|
||||
const date = new Date()
|
||||
const date = new Date();
|
||||
this.emit('update-downloaded', {}, releaseNotes, version, date, this.updateURL, () => {
|
||||
this.quitAndInstall()
|
||||
})
|
||||
})
|
||||
})
|
||||
this.quitAndInstall();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Private: Emit both error object and message, this is to keep compatibility
|
||||
// with Old APIs.
|
||||
emitError (message) {
|
||||
this.emit('error', new Error(message), message)
|
||||
this.emit('error', new Error(message), message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new AutoUpdater()
|
||||
module.exports = new AutoUpdater();
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const spawn = require('child_process').spawn
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const spawn = require('child_process').spawn;
|
||||
|
||||
// i.e. my-app/app-0.1.13/
|
||||
const appFolder = path.dirname(process.execPath)
|
||||
const appFolder = path.dirname(process.execPath);
|
||||
|
||||
// i.e. my-app/Update.exe
|
||||
const updateExe = path.resolve(appFolder, '..', 'Update.exe')
|
||||
const exeName = path.basename(process.execPath)
|
||||
let spawnedArgs = []
|
||||
let spawnedProcess
|
||||
const updateExe = path.resolve(appFolder, '..', 'Update.exe');
|
||||
const exeName = path.basename(process.execPath);
|
||||
let spawnedArgs = [];
|
||||
let spawnedProcess;
|
||||
|
||||
const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i])
|
||||
const isSameArgs = (args) => args.length === spawnedArgs.length && args.every((e, i) => e === spawnedArgs[i]);
|
||||
|
||||
// Spawn a command and invoke the callback when it completes with an error
|
||||
// and the output from standard out.
|
||||
const spawnUpdate = function (args, detached, callback) {
|
||||
let error, errorEmitted, stderr, stdout
|
||||
let error, errorEmitted, stderr, stdout;
|
||||
|
||||
try {
|
||||
// Ensure we don't spawn multiple squirrel processes
|
||||
@@ -28,92 +28,92 @@ const spawnUpdate = function (args, detached, callback) {
|
||||
if (spawnedProcess && !isSameArgs(args)) {
|
||||
// Disabled for backwards compatibility:
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return callback(`AutoUpdater process with arguments ${args} is already running`)
|
||||
return callback(`AutoUpdater process with arguments ${args} is already running`);
|
||||
} else if (!spawnedProcess) {
|
||||
spawnedProcess = spawn(updateExe, args, {
|
||||
detached: detached,
|
||||
windowsHide: true
|
||||
})
|
||||
spawnedArgs = args || []
|
||||
});
|
||||
spawnedArgs = args || [];
|
||||
}
|
||||
} catch (error1) {
|
||||
error = error1
|
||||
error = error1;
|
||||
|
||||
// Shouldn't happen, but still guard it.
|
||||
process.nextTick(function () {
|
||||
return callback(error)
|
||||
})
|
||||
return
|
||||
return callback(error);
|
||||
});
|
||||
return;
|
||||
}
|
||||
stdout = ''
|
||||
stderr = ''
|
||||
stdout = '';
|
||||
stderr = '';
|
||||
|
||||
spawnedProcess.stdout.on('data', (data) => { stdout += data })
|
||||
spawnedProcess.stderr.on('data', (data) => { stderr += data })
|
||||
spawnedProcess.stdout.on('data', (data) => { stdout += data; });
|
||||
spawnedProcess.stderr.on('data', (data) => { stderr += data; });
|
||||
|
||||
errorEmitted = false
|
||||
errorEmitted = false;
|
||||
spawnedProcess.on('error', (error) => {
|
||||
errorEmitted = true
|
||||
callback(error)
|
||||
})
|
||||
errorEmitted = true;
|
||||
callback(error);
|
||||
});
|
||||
|
||||
return spawnedProcess.on('exit', function (code, signal) {
|
||||
spawnedProcess = undefined
|
||||
spawnedArgs = []
|
||||
spawnedProcess = undefined;
|
||||
spawnedArgs = [];
|
||||
|
||||
// We may have already emitted an error.
|
||||
if (errorEmitted) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Process terminated with error.
|
||||
if (code !== 0) {
|
||||
// Disabled for backwards compatibility:
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`)
|
||||
return callback(`Command failed: ${signal != null ? signal : code}\n${stderr}`);
|
||||
}
|
||||
|
||||
// Success.
|
||||
callback(null, stdout)
|
||||
})
|
||||
}
|
||||
callback(null, stdout);
|
||||
});
|
||||
};
|
||||
|
||||
// Start an instance of the installed app.
|
||||
exports.processStart = function () {
|
||||
return spawnUpdate(['--processStartAndWait', exeName], true, function () {})
|
||||
}
|
||||
return spawnUpdate(['--processStartAndWait', exeName], true, function () {});
|
||||
};
|
||||
|
||||
// Download the releases specified by the URL and write new results to stdout.
|
||||
exports.checkForUpdate = function (updateURL, callback) {
|
||||
return spawnUpdate(['--checkForUpdate', updateURL], false, function (error, stdout) {
|
||||
let ref, ref1, update
|
||||
let ref, ref1, update;
|
||||
if (error != null) {
|
||||
return callback(error)
|
||||
return callback(error);
|
||||
}
|
||||
try {
|
||||
// Last line of output is the JSON details about the releases
|
||||
const json = stdout.trim().split('\n').pop()
|
||||
update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0
|
||||
const json = stdout.trim().split('\n').pop();
|
||||
update = (ref = JSON.parse(json)) != null ? (ref1 = ref.releasesToApply) != null ? typeof ref1.pop === 'function' ? ref1.pop() : void 0 : void 0 : void 0;
|
||||
} catch {
|
||||
// Disabled for backwards compatibility:
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return callback(`Invalid result:\n${stdout}`)
|
||||
return callback(`Invalid result:\n${stdout}`);
|
||||
}
|
||||
return callback(null, update)
|
||||
})
|
||||
}
|
||||
return callback(null, update);
|
||||
});
|
||||
};
|
||||
|
||||
// Update the application to the latest remote version specified by URL.
|
||||
exports.update = function (updateURL, callback) {
|
||||
return spawnUpdate(['--update', updateURL], false, callback)
|
||||
}
|
||||
return spawnUpdate(['--update', updateURL], false, callback);
|
||||
};
|
||||
|
||||
// Is the Update.exe installed with the current application?
|
||||
exports.supported = function () {
|
||||
try {
|
||||
fs.accessSync(updateExe, fs.R_OK)
|
||||
return true
|
||||
fs.accessSync(updateExe, fs.R_OK);
|
||||
return true;
|
||||
} catch {
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { BrowserView } = process.electronBinding('browser_view')
|
||||
const { EventEmitter } = require('events');
|
||||
const { BrowserView } = process.electronBinding('browser_view');
|
||||
|
||||
Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(BrowserView.prototype, EventEmitter.prototype);
|
||||
|
||||
BrowserView.fromWebContents = (webContents) => {
|
||||
for (const view of BrowserView.getAllViews()) {
|
||||
if (view.webContents.equal(webContents)) return view
|
||||
if (view.webContents.equal(webContents)) return view;
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
module.exports = BrowserView
|
||||
module.exports = BrowserView;
|
||||
|
||||
@@ -1,49 +1,49 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const { WebContentsView, TopLevelWindow, deprecate } = electron
|
||||
const { BrowserWindow } = process.electronBinding('window')
|
||||
const electron = require('electron');
|
||||
const { WebContentsView, TopLevelWindow, deprecate } = electron;
|
||||
const { BrowserWindow } = process.electronBinding('window');
|
||||
|
||||
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype)
|
||||
Object.setPrototypeOf(BrowserWindow.prototype, TopLevelWindow.prototype);
|
||||
|
||||
BrowserWindow.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
TopLevelWindow.prototype._init.call(this)
|
||||
TopLevelWindow.prototype._init.call(this);
|
||||
|
||||
// Avoid recursive require.
|
||||
const { app } = electron
|
||||
const { app } = electron;
|
||||
|
||||
// Create WebContentsView.
|
||||
this.setContentView(new WebContentsView(this.webContents))
|
||||
this.setContentView(new WebContentsView(this.webContents));
|
||||
|
||||
const nativeSetBounds = this.setBounds
|
||||
const nativeSetBounds = this.setBounds;
|
||||
this.setBounds = (bounds, ...opts) => {
|
||||
bounds = {
|
||||
...this.getBounds(),
|
||||
...bounds
|
||||
}
|
||||
nativeSetBounds.call(this, bounds, ...opts)
|
||||
}
|
||||
};
|
||||
nativeSetBounds.call(this, bounds, ...opts);
|
||||
};
|
||||
|
||||
// window.resizeTo(...)
|
||||
// window.moveTo(...)
|
||||
this.webContents.on('move', (event, size) => {
|
||||
this.setBounds(size)
|
||||
})
|
||||
this.setBounds(size);
|
||||
});
|
||||
|
||||
// Hide the auto-hide menu when webContents is focused.
|
||||
this.webContents.on('activate', () => {
|
||||
if (process.platform !== 'darwin' && this.autoHideMenuBar && this.isMenuBarVisible()) {
|
||||
this.setMenuBarVisibility(false)
|
||||
this.setMenuBarVisibility(false);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Change window title to page title.
|
||||
this.webContents.on('page-title-updated', (event, title, ...args) => {
|
||||
// Route the event to BrowserWindow.
|
||||
this.emit('page-title-updated', event, title, ...args)
|
||||
if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title)
|
||||
})
|
||||
this.emit('page-title-updated', event, title, ...args);
|
||||
if (!this.isDestroyed() && !event.defaultPrevented) this.setTitle(title);
|
||||
});
|
||||
|
||||
// Sometimes the webContents doesn't get focus when window is shown, so we
|
||||
// have to force focusing on webContents in this case. The safest way is to
|
||||
@@ -54,142 +54,142 @@ BrowserWindow.prototype._init = function () {
|
||||
// Finder, we still do it on all platforms in case of other bugs we don't
|
||||
// know.
|
||||
this.webContents.once('load-url', function () {
|
||||
this.focus()
|
||||
})
|
||||
this.focus();
|
||||
});
|
||||
|
||||
// Redirect focus/blur event to app instance too.
|
||||
this.on('blur', (event) => {
|
||||
app.emit('browser-window-blur', event, this)
|
||||
})
|
||||
app.emit('browser-window-blur', event, this);
|
||||
});
|
||||
this.on('focus', (event) => {
|
||||
app.emit('browser-window-focus', event, this)
|
||||
})
|
||||
app.emit('browser-window-focus', event, this);
|
||||
});
|
||||
|
||||
// Subscribe to visibilityState changes and pass to renderer process.
|
||||
let isVisible = this.isVisible() && !this.isMinimized()
|
||||
let isVisible = this.isVisible() && !this.isMinimized();
|
||||
const visibilityChanged = () => {
|
||||
const newState = this.isVisible() && !this.isMinimized()
|
||||
const newState = this.isVisible() && !this.isMinimized();
|
||||
if (isVisible !== newState) {
|
||||
isVisible = newState
|
||||
const visibilityState = isVisible ? 'visible' : 'hidden'
|
||||
this.webContents.emit('-window-visibility-change', visibilityState)
|
||||
isVisible = newState;
|
||||
const visibilityState = isVisible ? 'visible' : 'hidden';
|
||||
this.webContents.emit('-window-visibility-change', visibilityState);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore']
|
||||
const visibilityEvents = ['show', 'hide', 'minimize', 'maximize', 'restore'];
|
||||
for (const event of visibilityEvents) {
|
||||
this.on(event, visibilityChanged)
|
||||
this.on(event, visibilityChanged);
|
||||
}
|
||||
|
||||
// Notify the creation of the window.
|
||||
const event = process.electronBinding('event').createEmpty()
|
||||
app.emit('browser-window-created', event, this)
|
||||
const event = process.electronBinding('event').createEmpty();
|
||||
app.emit('browser-window-created', event, this);
|
||||
|
||||
Object.defineProperty(this, 'devToolsWebContents', {
|
||||
enumerable: true,
|
||||
configurable: false,
|
||||
get () {
|
||||
return this.webContents.devToolsWebContents
|
||||
return this.webContents.devToolsWebContents;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const isBrowserWindow = (win) => {
|
||||
return win && win.constructor.name === 'BrowserWindow'
|
||||
}
|
||||
return win && win.constructor.name === 'BrowserWindow';
|
||||
};
|
||||
|
||||
BrowserWindow.fromId = (id) => {
|
||||
const win = TopLevelWindow.fromId(id)
|
||||
return isBrowserWindow(win) ? win : null
|
||||
}
|
||||
const win = TopLevelWindow.fromId(id);
|
||||
return isBrowserWindow(win) ? win : null;
|
||||
};
|
||||
|
||||
BrowserWindow.getAllWindows = () => {
|
||||
return TopLevelWindow.getAllWindows().filter(isBrowserWindow)
|
||||
}
|
||||
return TopLevelWindow.getAllWindows().filter(isBrowserWindow);
|
||||
};
|
||||
|
||||
BrowserWindow.getFocusedWindow = () => {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (window.isFocused() || window.isDevToolsFocused()) return window
|
||||
if (window.isFocused() || window.isDevToolsFocused()) return window;
|
||||
}
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
BrowserWindow.fromWebContents = (webContents) => {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (window.webContents.equal(webContents)) return window
|
||||
if (window.webContents && window.webContents.equal(webContents)) return window;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BrowserWindow.fromBrowserView = (browserView) => {
|
||||
for (const window of BrowserWindow.getAllWindows()) {
|
||||
if (window.getBrowserView() === browserView) return window
|
||||
if (window.getBrowserView() === browserView) return window;
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
// Helpers.
|
||||
Object.assign(BrowserWindow.prototype, {
|
||||
loadURL (...args) {
|
||||
return this.webContents.loadURL(...args)
|
||||
return this.webContents.loadURL(...args);
|
||||
},
|
||||
getURL (...args) {
|
||||
return this.webContents.getURL()
|
||||
return this.webContents.getURL();
|
||||
},
|
||||
loadFile (...args) {
|
||||
return this.webContents.loadFile(...args)
|
||||
return this.webContents.loadFile(...args);
|
||||
},
|
||||
reload (...args) {
|
||||
return this.webContents.reload(...args)
|
||||
return this.webContents.reload(...args);
|
||||
},
|
||||
send (...args) {
|
||||
return this.webContents.send(...args)
|
||||
return this.webContents.send(...args);
|
||||
},
|
||||
openDevTools (...args) {
|
||||
return this.webContents.openDevTools(...args)
|
||||
return this.webContents.openDevTools(...args);
|
||||
},
|
||||
closeDevTools () {
|
||||
return this.webContents.closeDevTools()
|
||||
return this.webContents.closeDevTools();
|
||||
},
|
||||
isDevToolsOpened () {
|
||||
return this.webContents.isDevToolsOpened()
|
||||
return this.webContents.isDevToolsOpened();
|
||||
},
|
||||
isDevToolsFocused () {
|
||||
return this.webContents.isDevToolsFocused()
|
||||
return this.webContents.isDevToolsFocused();
|
||||
},
|
||||
toggleDevTools () {
|
||||
return this.webContents.toggleDevTools()
|
||||
return this.webContents.toggleDevTools();
|
||||
},
|
||||
inspectElement (...args) {
|
||||
return this.webContents.inspectElement(...args)
|
||||
return this.webContents.inspectElement(...args);
|
||||
},
|
||||
inspectSharedWorker () {
|
||||
return this.webContents.inspectSharedWorker()
|
||||
return this.webContents.inspectSharedWorker();
|
||||
},
|
||||
inspectServiceWorker () {
|
||||
return this.webContents.inspectServiceWorker()
|
||||
return this.webContents.inspectServiceWorker();
|
||||
},
|
||||
showDefinitionForSelection () {
|
||||
return this.webContents.showDefinitionForSelection()
|
||||
return this.webContents.showDefinitionForSelection();
|
||||
},
|
||||
capturePage (...args) {
|
||||
return this.webContents.capturePage(...args)
|
||||
return this.webContents.capturePage(...args);
|
||||
},
|
||||
setTouchBar (touchBar) {
|
||||
electron.TouchBar._setOnWindow(touchBar, this)
|
||||
electron.TouchBar._setOnWindow(touchBar, this);
|
||||
},
|
||||
setBackgroundThrottling (allowed) {
|
||||
this.webContents.setBackgroundThrottling(allowed)
|
||||
this.webContents.setBackgroundThrottling(allowed);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Deprecations
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'autoHideMenuBar', '_isMenuBarAutoHide', '_setAutoHideMenuBar')
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'minimizable', '_isMinimizable', '_setMinimizable')
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'maximizable', '_isMaximizable', '_setMaximizable')
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'resizable', '_isResizable', '_setResizable')
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'fullScreenable', '_isFullScreenable', '_setFullScreenable')
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'closable', '_isClosable', '_setClosable')
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'movable', '_isMovable', '_setMovable')
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'autoHideMenuBar', '_isMenuBarAutoHide', '_setAutoHideMenuBar');
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'minimizable', '_isMinimizable', '_setMinimizable');
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'maximizable', '_isMaximizable', '_setMaximizable');
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'resizable', '_isResizable', '_setResizable');
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'fullScreenable', '_isFullScreenable', '_setFullScreenable');
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'closable', '_isClosable', '_setClosable');
|
||||
deprecate.fnToProperty(BrowserWindow.prototype, 'movable', '_isMovable', '_setMovable');
|
||||
|
||||
module.exports = BrowserWindow
|
||||
module.exports = BrowserWindow;
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
'use strict'
|
||||
module.exports = process.electronBinding('content_tracing')
|
||||
'use strict';
|
||||
module.exports = process.electronBinding('content_tracing');
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const CrashReporter = require('@electron/internal/common/crash-reporter')
|
||||
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init')
|
||||
const CrashReporter = require('@electron/internal/common/crash-reporter');
|
||||
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
|
||||
|
||||
class CrashReporterMain extends CrashReporter {
|
||||
init (options) {
|
||||
return crashReporterInit(options)
|
||||
return crashReporterInit(options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CrashReporterMain()
|
||||
module.exports = new CrashReporterMain();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { app, BrowserWindow, deprecate } = require('electron')
|
||||
const binding = process.electronBinding('dialog')
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const { app, BrowserWindow, deprecate } = require('electron');
|
||||
const binding = process.electronBinding('dialog');
|
||||
const v8Util = process.electronBinding('v8_util');
|
||||
|
||||
const fileDialogProperties = {
|
||||
openFile: 1 << 0,
|
||||
@@ -13,15 +13,15 @@ const fileDialogProperties = {
|
||||
promptToCreate: 1 << 5,
|
||||
noResolveAliases: 1 << 6,
|
||||
treatPackageAsDirectory: 1 << 7
|
||||
}
|
||||
};
|
||||
|
||||
const normalizeAccessKey = (text) => {
|
||||
if (typeof text !== 'string') return text
|
||||
if (typeof text !== 'string') return text;
|
||||
|
||||
// macOS does not have access keys so remove single ampersands
|
||||
// and replace double ampersands with a single ampersand
|
||||
if (process.platform === 'darwin') {
|
||||
return text.replace(/&(&?)/g, '$1')
|
||||
return text.replace(/&(&?)/g, '$1');
|
||||
}
|
||||
|
||||
// Linux uses a single underscore as an access key prefix so escape
|
||||
@@ -30,29 +30,29 @@ const normalizeAccessKey = (text) => {
|
||||
// a single underscore
|
||||
if (process.platform === 'linux') {
|
||||
return text.replace(/_/g, '__').replace(/&(.?)/g, (match, after) => {
|
||||
if (after === '&') return after
|
||||
return `_${after}`
|
||||
})
|
||||
if (after === '&') return after;
|
||||
return `_${after}`;
|
||||
});
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
return text;
|
||||
};
|
||||
|
||||
const checkAppInitialized = function () {
|
||||
if (!app.isReady()) {
|
||||
throw new Error('dialog module can only be used after app is ready')
|
||||
throw new Error('dialog module can only be used after app is ready');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const saveDialog = (sync, window, options) => {
|
||||
checkAppInitialized()
|
||||
checkAppInitialized();
|
||||
|
||||
if (window && window.constructor !== BrowserWindow) {
|
||||
options = window
|
||||
window = null
|
||||
options = window;
|
||||
window = null;
|
||||
}
|
||||
|
||||
if (options == null) options = { title: 'Save' }
|
||||
if (options == null) options = { title: 'Save' };
|
||||
|
||||
const {
|
||||
buttonLabel = '',
|
||||
@@ -63,31 +63,31 @@ const saveDialog = (sync, window, options) => {
|
||||
securityScopedBookmarks = false,
|
||||
nameFieldLabel = '',
|
||||
showsTagField = true
|
||||
} = options
|
||||
} = options;
|
||||
|
||||
if (typeof title !== 'string') throw new TypeError('Title must be a string')
|
||||
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string')
|
||||
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string')
|
||||
if (typeof message !== 'string') throw new TypeError('Message must be a string')
|
||||
if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string')
|
||||
if (typeof title !== 'string') throw new TypeError('Title must be a string');
|
||||
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
|
||||
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
|
||||
if (typeof message !== 'string') throw new TypeError('Message must be a string');
|
||||
if (typeof nameFieldLabel !== 'string') throw new TypeError('Name field label must be a string');
|
||||
|
||||
const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window }
|
||||
return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings)
|
||||
}
|
||||
const settings = { buttonLabel, defaultPath, filters, title, message, securityScopedBookmarks, nameFieldLabel, showsTagField, window };
|
||||
return (sync) ? binding.showSaveDialogSync(settings) : binding.showSaveDialog(settings);
|
||||
};
|
||||
|
||||
const openDialog = (sync, window, options) => {
|
||||
checkAppInitialized()
|
||||
checkAppInitialized();
|
||||
|
||||
if (window && window.constructor !== BrowserWindow) {
|
||||
options = window
|
||||
window = null
|
||||
options = window;
|
||||
window = null;
|
||||
}
|
||||
|
||||
if (options == null) {
|
||||
options = {
|
||||
title: 'Open',
|
||||
properties: ['openFile']
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const {
|
||||
@@ -98,40 +98,40 @@ const openDialog = (sync, window, options) => {
|
||||
title = '',
|
||||
message = '',
|
||||
securityScopedBookmarks = false
|
||||
} = options
|
||||
} = options;
|
||||
|
||||
if (!Array.isArray(properties)) throw new TypeError('Properties must be an array')
|
||||
if (!Array.isArray(properties)) throw new TypeError('Properties must be an array');
|
||||
|
||||
let dialogProperties = 0
|
||||
let dialogProperties = 0;
|
||||
for (const prop in fileDialogProperties) {
|
||||
if (properties.includes(prop)) {
|
||||
dialogProperties |= fileDialogProperties[prop]
|
||||
dialogProperties |= fileDialogProperties[prop];
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof title !== 'string') throw new TypeError('Title must be a string')
|
||||
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string')
|
||||
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string')
|
||||
if (typeof message !== 'string') throw new TypeError('Message must be a string')
|
||||
if (typeof title !== 'string') throw new TypeError('Title must be a string');
|
||||
if (typeof buttonLabel !== 'string') throw new TypeError('Button label must be a string');
|
||||
if (typeof defaultPath !== 'string') throw new TypeError('Default path must be a string');
|
||||
if (typeof message !== 'string') throw new TypeError('Message must be a string');
|
||||
|
||||
const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window }
|
||||
settings.properties = dialogProperties
|
||||
const settings = { title, buttonLabel, defaultPath, filters, message, securityScopedBookmarks, window };
|
||||
settings.properties = dialogProperties;
|
||||
|
||||
return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings)
|
||||
}
|
||||
return (sync) ? binding.showOpenDialogSync(settings) : binding.showOpenDialog(settings);
|
||||
};
|
||||
|
||||
const messageBox = (sync, window, options) => {
|
||||
checkAppInitialized()
|
||||
checkAppInitialized();
|
||||
|
||||
if (window && window.constructor !== BrowserWindow) {
|
||||
options = window
|
||||
window = null
|
||||
options = window;
|
||||
window = null;
|
||||
}
|
||||
|
||||
if (options == null) options = { type: 'none' }
|
||||
if (options == null) options = { type: 'none' };
|
||||
|
||||
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question']
|
||||
const messageBoxOptions = { noLink: 1 << 0 }
|
||||
const messageBoxTypes = ['none', 'info', 'warning', 'error', 'question'];
|
||||
const messageBoxOptions = { noLink: 1 << 0 };
|
||||
|
||||
let {
|
||||
buttons = [],
|
||||
@@ -145,32 +145,32 @@ const messageBox = (sync, window, options) => {
|
||||
message = '',
|
||||
title = '',
|
||||
type = 'none'
|
||||
} = options
|
||||
} = options;
|
||||
|
||||
const messageBoxType = messageBoxTypes.indexOf(type)
|
||||
if (messageBoxType === -1) throw new TypeError('Invalid message box type')
|
||||
if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array')
|
||||
if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey)
|
||||
if (typeof title !== 'string') throw new TypeError('Title must be a string')
|
||||
if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean')
|
||||
if (typeof message !== 'string') throw new TypeError('Message must be a string')
|
||||
if (typeof detail !== 'string') throw new TypeError('Detail must be a string')
|
||||
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string')
|
||||
const messageBoxType = messageBoxTypes.indexOf(type);
|
||||
if (messageBoxType === -1) throw new TypeError('Invalid message box type');
|
||||
if (!Array.isArray(buttons)) throw new TypeError('Buttons must be an array');
|
||||
if (options.normalizeAccessKeys) buttons = buttons.map(normalizeAccessKey);
|
||||
if (typeof title !== 'string') throw new TypeError('Title must be a string');
|
||||
if (typeof noLink !== 'boolean') throw new TypeError('noLink must be a boolean');
|
||||
if (typeof message !== 'string') throw new TypeError('Message must be a string');
|
||||
if (typeof detail !== 'string') throw new TypeError('Detail must be a string');
|
||||
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string');
|
||||
|
||||
checkboxChecked = !!checkboxChecked
|
||||
checkboxChecked = !!checkboxChecked;
|
||||
if (checkboxChecked && !checkboxLabel) {
|
||||
throw new Error('checkboxChecked requires that checkboxLabel also be passed')
|
||||
throw new Error('checkboxChecked requires that checkboxLabel also be passed');
|
||||
}
|
||||
|
||||
// Choose a default button to get selected when dialog is cancelled.
|
||||
if (cancelId == null) {
|
||||
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
|
||||
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0
|
||||
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
|
||||
for (let i = 0; i < buttons.length; i++) {
|
||||
const text = buttons[i].toLowerCase()
|
||||
const text = buttons[i].toLowerCase();
|
||||
if (text === 'cancel' || text === 'no') {
|
||||
cancelId = i
|
||||
break
|
||||
cancelId = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -188,57 +188,57 @@ const messageBox = (sync, window, options) => {
|
||||
checkboxLabel,
|
||||
checkboxChecked,
|
||||
icon
|
||||
}
|
||||
};
|
||||
|
||||
if (sync) {
|
||||
return binding.showMessageBoxSync(settings)
|
||||
return binding.showMessageBoxSync(settings);
|
||||
} else {
|
||||
return binding.showMessageBox(settings)
|
||||
return binding.showMessageBox(settings);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
showOpenDialog: function (window, options) {
|
||||
return openDialog(false, window, options)
|
||||
return openDialog(false, window, options);
|
||||
},
|
||||
|
||||
showOpenDialogSync: function (window, options) {
|
||||
return openDialog(true, window, options)
|
||||
return openDialog(true, window, options);
|
||||
},
|
||||
|
||||
showSaveDialog: function (window, options) {
|
||||
return saveDialog(false, window, options)
|
||||
return saveDialog(false, window, options);
|
||||
},
|
||||
|
||||
showSaveDialogSync: function (window, options) {
|
||||
return saveDialog(true, window, options)
|
||||
return saveDialog(true, window, options);
|
||||
},
|
||||
|
||||
showMessageBox: function (window, options) {
|
||||
return messageBox(false, window, options)
|
||||
return messageBox(false, window, options);
|
||||
},
|
||||
|
||||
showMessageBoxSync: function (window, options) {
|
||||
return messageBox(true, window, options)
|
||||
return messageBox(true, window, options);
|
||||
},
|
||||
|
||||
showErrorBox: function (...args) {
|
||||
return binding.showErrorBox(...args)
|
||||
return binding.showErrorBox(...args);
|
||||
},
|
||||
|
||||
showCertificateTrustDialog: function (window, options) {
|
||||
if (window && window.constructor !== BrowserWindow) options = window
|
||||
if (window && window.constructor !== BrowserWindow) options = window;
|
||||
if (options == null || typeof options !== 'object') {
|
||||
throw new TypeError('options must be an object')
|
||||
throw new TypeError('options must be an object');
|
||||
}
|
||||
|
||||
const { certificate, message = '' } = options
|
||||
const { certificate, message = '' } = options;
|
||||
if (certificate == null || typeof certificate !== 'object') {
|
||||
throw new TypeError('certificate must be an object')
|
||||
throw new TypeError('certificate must be an object');
|
||||
}
|
||||
|
||||
if (typeof message !== 'string') throw new TypeError('message must be a string')
|
||||
if (typeof message !== 'string') throw new TypeError('message must be a string');
|
||||
|
||||
return binding.showCertificateTrustDialog(window, certificate, message)
|
||||
return binding.showCertificateTrustDialog(window, certificate, message);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const common = require('@electron/internal/common/api/exports/electron')
|
||||
const common = require('@electron/internal/common/api/exports/electron');
|
||||
// since browser module list is also used in renderer, keep it separate.
|
||||
const moduleList = require('@electron/internal/browser/api/module-list')
|
||||
const moduleList = require('@electron/internal/browser/api/module-list');
|
||||
|
||||
// Import common modules.
|
||||
common.defineProperties(exports)
|
||||
common.defineProperties(exports);
|
||||
|
||||
for (const module of moduleList) {
|
||||
Object.defineProperty(exports, module.name, {
|
||||
enumerable: !module.private,
|
||||
get: common.handleESModule(module.loader)
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
module.exports = process.electronBinding('global_shortcut').globalShortcut
|
||||
module.exports = process.electronBinding('global_shortcut').globalShortcut;
|
||||
|
||||
@@ -1,22 +1,22 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { deprecate } = require('electron')
|
||||
const { deprecate } = require('electron');
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
const { EventEmitter } = require('events')
|
||||
const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase')
|
||||
const { EventEmitter } = require('events');
|
||||
const { inAppPurchase, InAppPurchase } = process.electronBinding('in_app_purchase');
|
||||
|
||||
// inAppPurchase is an EventEmitter.
|
||||
Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(inAppPurchase)
|
||||
Object.setPrototypeOf(InAppPurchase.prototype, EventEmitter.prototype);
|
||||
EventEmitter.call(inAppPurchase);
|
||||
|
||||
module.exports = inAppPurchase
|
||||
module.exports = inAppPurchase;
|
||||
} else {
|
||||
module.exports = {
|
||||
purchaseProduct: (productID, quantity, callback) => {
|
||||
throw new Error('The inAppPurchase module can only be used on macOS')
|
||||
throw new Error('The inAppPurchase module can only be used on macOS');
|
||||
},
|
||||
canMakePayments: () => false,
|
||||
getReceiptURL: () => ''
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,40 +1,40 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { IpcMainInvokeEvent } from 'electron'
|
||||
import { EventEmitter } from 'events';
|
||||
import { IpcMainInvokeEvent } from 'electron';
|
||||
|
||||
class IpcMain extends EventEmitter {
|
||||
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
|
||||
|
||||
handle: Electron.IpcMain['handle'] = (method, fn) => {
|
||||
if (this._invokeHandlers.has(method)) {
|
||||
throw new Error(`Attempted to register a second handler for '${method}'`)
|
||||
throw new Error(`Attempted to register a second handler for '${method}'`);
|
||||
}
|
||||
if (typeof fn !== 'function') {
|
||||
throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`)
|
||||
throw new Error(`Expected handler to be a function, but found type '${typeof fn}'`);
|
||||
}
|
||||
this._invokeHandlers.set(method, async (e, ...args) => {
|
||||
try {
|
||||
(e as any)._reply(await Promise.resolve(fn(e, ...args)))
|
||||
(e as any)._reply(await Promise.resolve(fn(e, ...args)));
|
||||
} catch (err) {
|
||||
(e as any)._throw(err)
|
||||
(e as any)._throw(err);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
handleOnce: Electron.IpcMain['handleOnce'] = (method, fn) => {
|
||||
this.handle(method, (e, ...args) => {
|
||||
this.removeHandler(method)
|
||||
return fn(e, ...args)
|
||||
})
|
||||
this.removeHandler(method);
|
||||
return fn(e, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
removeHandler (method: string) {
|
||||
this._invokeHandlers.delete(method)
|
||||
this._invokeHandlers.delete(method);
|
||||
}
|
||||
}
|
||||
|
||||
const ipcMain = new IpcMain()
|
||||
const ipcMain = new IpcMain();
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
ipcMain.on('error', () => {})
|
||||
ipcMain.on('error', () => {});
|
||||
|
||||
export default ipcMain
|
||||
export default ipcMain;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { app } = require('electron')
|
||||
const { app } = require('electron');
|
||||
|
||||
const isMac = process.platform === 'darwin'
|
||||
const isWindows = process.platform === 'win32'
|
||||
const isLinux = process.platform === 'linux'
|
||||
const isMac = process.platform === 'darwin';
|
||||
const isWindows = process.platform === 'win32';
|
||||
const isLinux = process.platform === 'linux';
|
||||
|
||||
const roles = {
|
||||
about: {
|
||||
get label () {
|
||||
return isLinux ? 'About' : `About ${app.name}`
|
||||
return isLinux ? 'About' : `About ${app.name}`;
|
||||
}
|
||||
},
|
||||
close: {
|
||||
@@ -38,7 +38,7 @@ const roles = {
|
||||
accelerator: 'Shift+CmdOrCtrl+R',
|
||||
nonNativeMacOSRole: true,
|
||||
windowMethod: (window) => {
|
||||
window.webContents.reloadIgnoringCache()
|
||||
window.webContents.reloadIgnoringCache();
|
||||
}
|
||||
},
|
||||
front: {
|
||||
@@ -49,7 +49,7 @@ const roles = {
|
||||
},
|
||||
hide: {
|
||||
get label () {
|
||||
return `Hide ${app.name}`
|
||||
return `Hide ${app.name}`;
|
||||
},
|
||||
accelerator: 'Command+H'
|
||||
},
|
||||
@@ -77,9 +77,9 @@ const roles = {
|
||||
quit: {
|
||||
get label () {
|
||||
switch (process.platform) {
|
||||
case 'darwin': return `Quit ${app.name}`
|
||||
case 'win32': return 'Exit'
|
||||
default: return 'Quit'
|
||||
case 'darwin': return `Quit ${app.name}`;
|
||||
case 'win32': return 'Exit';
|
||||
default: return 'Quit';
|
||||
}
|
||||
},
|
||||
accelerator: isWindows ? undefined : 'CommandOrControl+Q',
|
||||
@@ -101,7 +101,7 @@ const roles = {
|
||||
accelerator: 'CommandOrControl+0',
|
||||
nonNativeMacOSRole: true,
|
||||
webContentsMethod: (webContents) => {
|
||||
webContents.zoomLevel = 0
|
||||
webContents.zoomLevel = 0;
|
||||
}
|
||||
},
|
||||
selectall: {
|
||||
@@ -134,7 +134,7 @@ const roles = {
|
||||
label: 'Toggle Full Screen',
|
||||
accelerator: isMac ? 'Control+Command+F' : 'F11',
|
||||
windowMethod: (window) => {
|
||||
window.setFullScreen(!window.isFullScreen())
|
||||
window.setFullScreen(!window.isFullScreen());
|
||||
}
|
||||
},
|
||||
undo: {
|
||||
@@ -156,7 +156,7 @@ const roles = {
|
||||
accelerator: 'CommandOrControl+Plus',
|
||||
nonNativeMacOSRole: true,
|
||||
webContentsMethod: (webContents) => {
|
||||
webContents.zoomLevel += 0.5
|
||||
webContents.zoomLevel += 0.5;
|
||||
}
|
||||
},
|
||||
zoomout: {
|
||||
@@ -164,13 +164,13 @@ const roles = {
|
||||
accelerator: 'CommandOrControl+-',
|
||||
nonNativeMacOSRole: true,
|
||||
webContentsMethod: (webContents) => {
|
||||
webContents.zoomLevel -= 0.5
|
||||
webContents.zoomLevel -= 0.5;
|
||||
}
|
||||
},
|
||||
// App submenu should be used for Mac only
|
||||
appmenu: {
|
||||
get label () {
|
||||
return app.name
|
||||
return app.name;
|
||||
},
|
||||
submenu: [
|
||||
{ role: 'about' },
|
||||
@@ -249,71 +249,71 @@ const roles = {
|
||||
])
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.roleList = roles
|
||||
exports.roleList = roles;
|
||||
|
||||
const canExecuteRole = (role) => {
|
||||
if (!roles.hasOwnProperty(role)) return false
|
||||
if (!isMac) return true
|
||||
if (!roles.hasOwnProperty(role)) return false;
|
||||
if (!isMac) return true;
|
||||
|
||||
// macOS handles all roles natively except for a few
|
||||
return roles[role].nonNativeMacOSRole
|
||||
}
|
||||
return roles[role].nonNativeMacOSRole;
|
||||
};
|
||||
|
||||
exports.getDefaultLabel = (role) => {
|
||||
return roles.hasOwnProperty(role) ? roles[role].label : ''
|
||||
}
|
||||
return roles.hasOwnProperty(role) ? roles[role].label : '';
|
||||
};
|
||||
|
||||
exports.getDefaultAccelerator = (role) => {
|
||||
if (roles.hasOwnProperty(role)) return roles[role].accelerator
|
||||
}
|
||||
if (roles.hasOwnProperty(role)) return roles[role].accelerator;
|
||||
};
|
||||
|
||||
exports.shouldRegisterAccelerator = (role) => {
|
||||
const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined
|
||||
return hasRoleRegister ? roles[role].registerAccelerator : true
|
||||
}
|
||||
const hasRoleRegister = roles.hasOwnProperty(role) && roles[role].registerAccelerator !== undefined;
|
||||
return hasRoleRegister ? roles[role].registerAccelerator : true;
|
||||
};
|
||||
|
||||
exports.getDefaultSubmenu = (role) => {
|
||||
if (!roles.hasOwnProperty(role)) return
|
||||
if (!roles.hasOwnProperty(role)) return;
|
||||
|
||||
let { submenu } = roles[role]
|
||||
let { submenu } = roles[role];
|
||||
|
||||
// remove null items from within the submenu
|
||||
if (Array.isArray(submenu)) {
|
||||
submenu = submenu.filter((item) => item != null)
|
||||
submenu = submenu.filter((item) => item != null);
|
||||
}
|
||||
|
||||
return submenu
|
||||
}
|
||||
return submenu;
|
||||
};
|
||||
|
||||
exports.execute = (role, focusedWindow, focusedWebContents) => {
|
||||
if (!canExecuteRole(role)) return false
|
||||
if (!canExecuteRole(role)) return false;
|
||||
|
||||
const { appMethod, webContentsMethod, windowMethod } = roles[role]
|
||||
const { appMethod, webContentsMethod, windowMethod } = roles[role];
|
||||
|
||||
if (appMethod) {
|
||||
app[appMethod]()
|
||||
return true
|
||||
app[appMethod]();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (windowMethod && focusedWindow != null) {
|
||||
if (typeof windowMethod === 'function') {
|
||||
windowMethod(focusedWindow)
|
||||
windowMethod(focusedWindow);
|
||||
} else {
|
||||
focusedWindow[windowMethod]()
|
||||
focusedWindow[windowMethod]();
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
if (webContentsMethod && focusedWebContents != null) {
|
||||
if (typeof webContentsMethod === 'function') {
|
||||
webContentsMethod(focusedWebContents)
|
||||
webContentsMethod(focusedWebContents);
|
||||
} else {
|
||||
focusedWebContents[webContentsMethod]()
|
||||
focusedWebContents[webContentsMethod]();
|
||||
}
|
||||
return true
|
||||
return true;
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
@@ -1,87 +1,87 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const roles = require('@electron/internal/browser/api/menu-item-roles')
|
||||
const roles = require('@electron/internal/browser/api/menu-item-roles');
|
||||
|
||||
let nextCommandId = 0
|
||||
let nextCommandId = 0;
|
||||
|
||||
const MenuItem = function (options) {
|
||||
const { Menu } = require('electron')
|
||||
const { Menu } = require('electron');
|
||||
|
||||
// Preserve extra fields specified by user
|
||||
for (const key in options) {
|
||||
if (!(key in this)) this[key] = options[key]
|
||||
if (!(key in this)) this[key] = options[key];
|
||||
}
|
||||
if (typeof this.role === 'string' || this.role instanceof String) {
|
||||
this.role = this.role.toLowerCase()
|
||||
this.role = this.role.toLowerCase();
|
||||
}
|
||||
this.submenu = this.submenu || roles.getDefaultSubmenu(this.role)
|
||||
this.submenu = this.submenu || roles.getDefaultSubmenu(this.role);
|
||||
if (this.submenu != null && this.submenu.constructor !== Menu) {
|
||||
this.submenu = Menu.buildFromTemplate(this.submenu)
|
||||
this.submenu = Menu.buildFromTemplate(this.submenu);
|
||||
}
|
||||
if (this.type == null && this.submenu != null) {
|
||||
this.type = 'submenu'
|
||||
this.type = 'submenu';
|
||||
}
|
||||
if (this.type === 'submenu' && (this.submenu == null || this.submenu.constructor !== Menu)) {
|
||||
throw new Error('Invalid submenu')
|
||||
throw new Error('Invalid submenu');
|
||||
}
|
||||
|
||||
this.overrideReadOnlyProperty('type', 'normal')
|
||||
this.overrideReadOnlyProperty('role')
|
||||
this.overrideReadOnlyProperty('accelerator')
|
||||
this.overrideReadOnlyProperty('icon')
|
||||
this.overrideReadOnlyProperty('submenu')
|
||||
this.overrideReadOnlyProperty('type', 'normal');
|
||||
this.overrideReadOnlyProperty('role');
|
||||
this.overrideReadOnlyProperty('accelerator');
|
||||
this.overrideReadOnlyProperty('icon');
|
||||
this.overrideReadOnlyProperty('submenu');
|
||||
|
||||
this.overrideProperty('label', roles.getDefaultLabel(this.role))
|
||||
this.overrideProperty('sublabel', '')
|
||||
this.overrideProperty('toolTip', '')
|
||||
this.overrideProperty('enabled', true)
|
||||
this.overrideProperty('visible', true)
|
||||
this.overrideProperty('checked', false)
|
||||
this.overrideProperty('acceleratorWorksWhenHidden', true)
|
||||
this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role))
|
||||
this.overrideProperty('label', roles.getDefaultLabel(this.role));
|
||||
this.overrideProperty('sublabel', '');
|
||||
this.overrideProperty('toolTip', '');
|
||||
this.overrideProperty('enabled', true);
|
||||
this.overrideProperty('visible', true);
|
||||
this.overrideProperty('checked', false);
|
||||
this.overrideProperty('acceleratorWorksWhenHidden', true);
|
||||
this.overrideProperty('registerAccelerator', roles.shouldRegisterAccelerator(this.role));
|
||||
|
||||
if (!MenuItem.types.includes(this.type)) {
|
||||
throw new Error(`Unknown menu item type: ${this.type}`)
|
||||
throw new Error(`Unknown menu item type: ${this.type}`);
|
||||
}
|
||||
|
||||
this.overrideReadOnlyProperty('commandId', ++nextCommandId)
|
||||
this.overrideReadOnlyProperty('commandId', ++nextCommandId);
|
||||
|
||||
const click = options.click
|
||||
const click = options.click;
|
||||
this.click = (event, focusedWindow, focusedWebContents) => {
|
||||
// Manually flip the checked flags when clicked.
|
||||
if (this.type === 'checkbox' || this.type === 'radio') {
|
||||
this.checked = !this.checked
|
||||
this.checked = !this.checked;
|
||||
}
|
||||
|
||||
if (!roles.execute(this.role, focusedWindow, focusedWebContents)) {
|
||||
if (typeof click === 'function') {
|
||||
click(this, focusedWindow, event)
|
||||
click(this, focusedWindow, event);
|
||||
} else if (typeof this.selector === 'string' && process.platform === 'darwin') {
|
||||
Menu.sendActionToFirstResponder(this.selector)
|
||||
Menu.sendActionToFirstResponder(this.selector);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio']
|
||||
MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'];
|
||||
|
||||
MenuItem.prototype.getDefaultRoleAccelerator = function () {
|
||||
return roles.getDefaultAccelerator(this.role)
|
||||
}
|
||||
return roles.getDefaultAccelerator(this.role);
|
||||
};
|
||||
|
||||
MenuItem.prototype.overrideProperty = function (name, defaultValue = null) {
|
||||
if (this[name] == null) {
|
||||
this[name] = defaultValue
|
||||
this[name] = defaultValue;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
MenuItem.prototype.overrideReadOnlyProperty = function (name, defaultValue) {
|
||||
this.overrideProperty(name, defaultValue)
|
||||
this.overrideProperty(name, defaultValue);
|
||||
Object.defineProperty(this, name, {
|
||||
enumerable: true,
|
||||
writable: false,
|
||||
value: this[name]
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = MenuItem
|
||||
module.exports = MenuItem;
|
||||
|
||||
@@ -1,41 +1,41 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
function splitArray (arr, predicate) {
|
||||
const result = arr.reduce((multi, item) => {
|
||||
const current = multi[multi.length - 1]
|
||||
const current = multi[multi.length - 1];
|
||||
if (predicate(item)) {
|
||||
if (current.length > 0) multi.push([])
|
||||
if (current.length > 0) multi.push([]);
|
||||
} else {
|
||||
current.push(item)
|
||||
current.push(item);
|
||||
}
|
||||
return multi
|
||||
}, [[]])
|
||||
return multi;
|
||||
}, [[]]);
|
||||
|
||||
if (result[result.length - 1].length === 0) {
|
||||
return result.slice(0, result.length - 1)
|
||||
return result.slice(0, result.length - 1);
|
||||
}
|
||||
return result
|
||||
return result;
|
||||
}
|
||||
|
||||
function joinArrays (arrays, joinIDs) {
|
||||
return arrays.reduce((joined, arr, i) => {
|
||||
if (i > 0 && arr.length) {
|
||||
if (joinIDs.length > 0) {
|
||||
joined.push(joinIDs[0])
|
||||
joinIDs.splice(0, 1)
|
||||
joined.push(joinIDs[0]);
|
||||
joinIDs.splice(0, 1);
|
||||
} else {
|
||||
joined.push({ type: 'separator' })
|
||||
joined.push({ type: 'separator' });
|
||||
}
|
||||
}
|
||||
return joined.concat(arr)
|
||||
}, [])
|
||||
return joined.concat(arr);
|
||||
}, []);
|
||||
}
|
||||
|
||||
function pushOntoMultiMap (map, key, value) {
|
||||
if (!map.has(key)) {
|
||||
map.set(key, [])
|
||||
map.set(key, []);
|
||||
}
|
||||
map.get(key).push(value)
|
||||
map.get(key).push(value);
|
||||
}
|
||||
|
||||
function indexOfGroupContainingID (groups, id, ignoreGroup) {
|
||||
@@ -45,102 +45,102 @@ function indexOfGroupContainingID (groups, id, ignoreGroup) {
|
||||
candidateGroup.some(
|
||||
candidateItem => candidateItem.id === id
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Sort nodes topologically using a depth-first approach. Encountered cycles
|
||||
// are broken.
|
||||
function sortTopologically (originalOrder, edgesById) {
|
||||
const sorted = []
|
||||
const marked = new Set()
|
||||
const sorted = [];
|
||||
const marked = new Set();
|
||||
|
||||
const visit = (mark) => {
|
||||
if (marked.has(mark)) return
|
||||
marked.add(mark)
|
||||
const edges = edgesById.get(mark)
|
||||
if (marked.has(mark)) return;
|
||||
marked.add(mark);
|
||||
const edges = edgesById.get(mark);
|
||||
if (edges != null) {
|
||||
edges.forEach(visit)
|
||||
edges.forEach(visit);
|
||||
}
|
||||
sorted.push(mark)
|
||||
}
|
||||
sorted.push(mark);
|
||||
};
|
||||
|
||||
originalOrder.forEach(visit)
|
||||
return sorted
|
||||
originalOrder.forEach(visit);
|
||||
return sorted;
|
||||
}
|
||||
|
||||
function attemptToMergeAGroup (groups) {
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
const group = groups[i]
|
||||
const group = groups[i];
|
||||
for (const item of group) {
|
||||
const toIDs = [...(item.before || []), ...(item.after || [])]
|
||||
const toIDs = [...(item.before || []), ...(item.after || [])];
|
||||
for (const id of toIDs) {
|
||||
const index = indexOfGroupContainingID(groups, id, group)
|
||||
if (index === -1) continue
|
||||
const mergeTarget = groups[index]
|
||||
const index = indexOfGroupContainingID(groups, id, group);
|
||||
if (index === -1) continue;
|
||||
const mergeTarget = groups[index];
|
||||
|
||||
mergeTarget.push(...group)
|
||||
groups.splice(i, 1)
|
||||
return true
|
||||
mergeTarget.push(...group);
|
||||
groups.splice(i, 1);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
function mergeGroups (groups) {
|
||||
let merged = true
|
||||
let merged = true;
|
||||
while (merged) {
|
||||
merged = attemptToMergeAGroup(groups)
|
||||
merged = attemptToMergeAGroup(groups);
|
||||
}
|
||||
return groups
|
||||
return groups;
|
||||
}
|
||||
|
||||
function sortItemsInGroup (group) {
|
||||
const originalOrder = group.map((node, i) => i)
|
||||
const edges = new Map()
|
||||
const idToIndex = new Map(group.map((item, i) => [item.id, i]))
|
||||
const originalOrder = group.map((node, i) => i);
|
||||
const edges = new Map();
|
||||
const idToIndex = new Map(group.map((item, i) => [item.id, i]));
|
||||
|
||||
group.forEach((item, i) => {
|
||||
if (item.before) {
|
||||
item.before.forEach(toID => {
|
||||
const to = idToIndex.get(toID)
|
||||
const to = idToIndex.get(toID);
|
||||
if (to != null) {
|
||||
pushOntoMultiMap(edges, to, i)
|
||||
pushOntoMultiMap(edges, to, i);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
if (item.after) {
|
||||
item.after.forEach(toID => {
|
||||
const to = idToIndex.get(toID)
|
||||
const to = idToIndex.get(toID);
|
||||
if (to != null) {
|
||||
pushOntoMultiMap(edges, i, to)
|
||||
pushOntoMultiMap(edges, i, to);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const sortedNodes = sortTopologically(originalOrder, edges)
|
||||
return sortedNodes.map(i => group[i])
|
||||
const sortedNodes = sortTopologically(originalOrder, edges);
|
||||
return sortedNodes.map(i => group[i]);
|
||||
}
|
||||
|
||||
function findEdgesInGroup (groups, i, edges) {
|
||||
const group = groups[i]
|
||||
const group = groups[i];
|
||||
for (const item of group) {
|
||||
if (item.beforeGroupContaining) {
|
||||
for (const id of item.beforeGroupContaining) {
|
||||
const to = indexOfGroupContainingID(groups, id, group)
|
||||
const to = indexOfGroupContainingID(groups, id, group);
|
||||
if (to !== -1) {
|
||||
pushOntoMultiMap(edges, to, i)
|
||||
return
|
||||
pushOntoMultiMap(edges, to, i);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (item.afterGroupContaining) {
|
||||
for (const id of item.afterGroupContaining) {
|
||||
const to = indexOfGroupContainingID(groups, id, group)
|
||||
const to = indexOfGroupContainingID(groups, id, group);
|
||||
if (to !== -1) {
|
||||
pushOntoMultiMap(edges, i, to)
|
||||
return
|
||||
pushOntoMultiMap(edges, i, to);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -148,29 +148,29 @@ function findEdgesInGroup (groups, i, edges) {
|
||||
}
|
||||
|
||||
function sortGroups (groups) {
|
||||
const originalOrder = groups.map((item, i) => i)
|
||||
const edges = new Map()
|
||||
const originalOrder = groups.map((item, i) => i);
|
||||
const edges = new Map();
|
||||
|
||||
for (let i = 0; i < groups.length; i++) {
|
||||
findEdgesInGroup(groups, i, edges)
|
||||
findEdgesInGroup(groups, i, edges);
|
||||
}
|
||||
|
||||
const sortedGroupIndexes = sortTopologically(originalOrder, edges)
|
||||
return sortedGroupIndexes.map(i => groups[i])
|
||||
const sortedGroupIndexes = sortTopologically(originalOrder, edges);
|
||||
return sortedGroupIndexes.map(i => groups[i]);
|
||||
}
|
||||
|
||||
function sortMenuItems (menuItems) {
|
||||
const isSeparator = (item) => item.type === 'separator'
|
||||
const separators = menuItems.filter(i => i.type === 'separator')
|
||||
const isSeparator = (item) => item.type === 'separator';
|
||||
const separators = menuItems.filter(i => i.type === 'separator');
|
||||
|
||||
// Split the items into their implicit groups based upon separators.
|
||||
const groups = splitArray(menuItems, isSeparator)
|
||||
const mergedGroups = mergeGroups(groups)
|
||||
const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup)
|
||||
const sortedGroups = sortGroups(mergedGroupsWithSortedItems)
|
||||
const groups = splitArray(menuItems, isSeparator);
|
||||
const mergedGroups = mergeGroups(groups);
|
||||
const mergedGroupsWithSortedItems = mergedGroups.map(sortItemsInGroup);
|
||||
const sortedGroups = sortGroups(mergedGroupsWithSortedItems);
|
||||
|
||||
const joined = joinArrays(sortedGroups, separators)
|
||||
return joined
|
||||
const joined = joinArrays(sortedGroups, separators);
|
||||
return joined;
|
||||
}
|
||||
|
||||
module.exports = { sortMenuItems }
|
||||
module.exports = { sortMenuItems };
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { TopLevelWindow, MenuItem, webContents } = require('electron')
|
||||
const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils')
|
||||
const EventEmitter = require('events').EventEmitter
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const bindings = process.electronBinding('menu')
|
||||
const { TopLevelWindow, MenuItem, webContents } = require('electron');
|
||||
const { sortMenuItems } = require('@electron/internal/browser/api/menu-utils');
|
||||
const EventEmitter = require('events').EventEmitter;
|
||||
const v8Util = process.electronBinding('v8_util');
|
||||
const bindings = process.electronBinding('menu');
|
||||
|
||||
const { Menu } = bindings
|
||||
let applicationMenu = null
|
||||
let groupIdIndex = 0
|
||||
const { Menu } = bindings;
|
||||
let applicationMenu = null;
|
||||
let groupIdIndex = 0;
|
||||
|
||||
Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(Menu.prototype, EventEmitter.prototype);
|
||||
|
||||
// Menu Delegate.
|
||||
// This object should hold no reference to |Menu| to avoid cyclic reference.
|
||||
@@ -20,172 +20,172 @@ const delegate = {
|
||||
shouldCommandIdWorkWhenHidden: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].acceleratorWorksWhenHidden : undefined,
|
||||
isCommandIdVisible: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].visible : undefined,
|
||||
getAcceleratorForCommandId: (menu, id, useDefaultAccelerator) => {
|
||||
const command = menu.commandsMap[id]
|
||||
if (!command) return
|
||||
if (command.accelerator != null) return command.accelerator
|
||||
if (useDefaultAccelerator) return command.getDefaultRoleAccelerator()
|
||||
const command = menu.commandsMap[id];
|
||||
if (!command) return;
|
||||
if (command.accelerator != null) return command.accelerator;
|
||||
if (useDefaultAccelerator) return command.getDefaultRoleAccelerator();
|
||||
},
|
||||
shouldRegisterAcceleratorForCommandId: (menu, id) => menu.commandsMap[id] ? menu.commandsMap[id].registerAccelerator : undefined,
|
||||
executeCommand: (menu, event, id) => {
|
||||
const command = menu.commandsMap[id]
|
||||
if (!command) return
|
||||
command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents())
|
||||
const command = menu.commandsMap[id];
|
||||
if (!command) return;
|
||||
command.click(event, TopLevelWindow.getFocusedWindow(), webContents.getFocusedWebContents());
|
||||
},
|
||||
menuWillShow: (menu) => {
|
||||
// Ensure radio groups have at least one menu item selected
|
||||
for (const id in menu.groupsMap) {
|
||||
const found = menu.groupsMap[id].find(item => item.checked) || null
|
||||
if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true)
|
||||
for (const id of Object.keys(menu.groupsMap)) {
|
||||
const found = menu.groupsMap[id].find(item => item.checked) || null;
|
||||
if (!found) v8Util.setHiddenValue(menu.groupsMap[id][0], 'checked', true);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* Instance Methods */
|
||||
|
||||
Menu.prototype._init = function () {
|
||||
this.commandsMap = {}
|
||||
this.groupsMap = {}
|
||||
this.items = []
|
||||
this.delegate = delegate
|
||||
}
|
||||
this.commandsMap = {};
|
||||
this.groupsMap = {};
|
||||
this.items = [];
|
||||
this.delegate = delegate;
|
||||
};
|
||||
|
||||
Menu.prototype.popup = function (options = {}) {
|
||||
if (options == null || typeof options !== 'object') {
|
||||
throw new TypeError('Options must be an object')
|
||||
throw new TypeError('Options must be an object');
|
||||
}
|
||||
let { window, x, y, positioningItem, callback } = options
|
||||
let { window, x, y, positioningItem, callback } = options;
|
||||
|
||||
// no callback passed
|
||||
if (!callback || typeof callback !== 'function') callback = () => {}
|
||||
if (!callback || typeof callback !== 'function') callback = () => {};
|
||||
|
||||
// set defaults
|
||||
if (typeof x !== 'number') x = -1
|
||||
if (typeof y !== 'number') y = -1
|
||||
if (typeof positioningItem !== 'number') positioningItem = -1
|
||||
if (typeof x !== 'number') x = -1;
|
||||
if (typeof y !== 'number') y = -1;
|
||||
if (typeof positioningItem !== 'number') positioningItem = -1;
|
||||
|
||||
// find which window to use
|
||||
const wins = TopLevelWindow.getAllWindows()
|
||||
const wins = TopLevelWindow.getAllWindows();
|
||||
if (!wins || wins.indexOf(window) === -1) {
|
||||
window = TopLevelWindow.getFocusedWindow()
|
||||
window = TopLevelWindow.getFocusedWindow();
|
||||
if (!window && wins && wins.length > 0) {
|
||||
window = wins[0]
|
||||
window = wins[0];
|
||||
}
|
||||
if (!window) {
|
||||
throw new Error(`Cannot open Menu without a TopLevelWindow present`)
|
||||
throw new Error(`Cannot open Menu without a TopLevelWindow present`);
|
||||
}
|
||||
}
|
||||
|
||||
this.popupAt(window, x, y, positioningItem, callback)
|
||||
return { browserWindow: window, x, y, position: positioningItem }
|
||||
}
|
||||
this.popupAt(window, x, y, positioningItem, callback);
|
||||
return { browserWindow: window, x, y, position: positioningItem };
|
||||
};
|
||||
|
||||
Menu.prototype.closePopup = function (window) {
|
||||
if (window instanceof TopLevelWindow) {
|
||||
this.closePopupAt(window.id)
|
||||
this.closePopupAt(window.id);
|
||||
} else {
|
||||
// Passing -1 (invalid) would make closePopupAt close the all menu runners
|
||||
// belong to this menu.
|
||||
this.closePopupAt(-1)
|
||||
this.closePopupAt(-1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Menu.prototype.getMenuItemById = function (id) {
|
||||
const items = this.items
|
||||
const items = this.items;
|
||||
|
||||
let found = items.find(item => item.id === id) || null
|
||||
let found = items.find(item => item.id === id) || null;
|
||||
for (let i = 0; !found && i < items.length; i++) {
|
||||
if (items[i].submenu) {
|
||||
found = items[i].submenu.getMenuItemById(id)
|
||||
found = items[i].submenu.getMenuItemById(id);
|
||||
}
|
||||
}
|
||||
return found
|
||||
}
|
||||
return found;
|
||||
};
|
||||
|
||||
Menu.prototype.append = function (item) {
|
||||
return this.insert(this.getItemCount(), item)
|
||||
}
|
||||
return this.insert(this.getItemCount(), item);
|
||||
};
|
||||
|
||||
Menu.prototype.insert = function (pos, item) {
|
||||
if ((item ? item.constructor : void 0) !== MenuItem) {
|
||||
throw new TypeError('Invalid item')
|
||||
throw new TypeError('Invalid item');
|
||||
}
|
||||
|
||||
if (pos < 0) {
|
||||
throw new RangeError(`Position ${pos} cannot be less than 0`)
|
||||
throw new RangeError(`Position ${pos} cannot be less than 0`);
|
||||
} else if (pos > this.getItemCount()) {
|
||||
throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`)
|
||||
throw new RangeError(`Position ${pos} cannot be greater than the total MenuItem count`);
|
||||
}
|
||||
|
||||
// insert item depending on its type
|
||||
insertItemByType.call(this, item, pos)
|
||||
insertItemByType.call(this, item, pos);
|
||||
|
||||
// set item properties
|
||||
if (item.sublabel) this.setSublabel(pos, item.sublabel)
|
||||
if (item.toolTip) this.setToolTip(pos, item.toolTip)
|
||||
if (item.icon) this.setIcon(pos, item.icon)
|
||||
if (item.role) this.setRole(pos, item.role)
|
||||
if (item.sublabel) this.setSublabel(pos, item.sublabel);
|
||||
if (item.toolTip) this.setToolTip(pos, item.toolTip);
|
||||
if (item.icon) this.setIcon(pos, item.icon);
|
||||
if (item.role) this.setRole(pos, item.role);
|
||||
|
||||
// Make menu accessable to items.
|
||||
item.overrideReadOnlyProperty('menu', this)
|
||||
item.overrideReadOnlyProperty('menu', this);
|
||||
|
||||
// Remember the items.
|
||||
this.items.splice(pos, 0, item)
|
||||
this.commandsMap[item.commandId] = item
|
||||
}
|
||||
this.items.splice(pos, 0, item);
|
||||
this.commandsMap[item.commandId] = item;
|
||||
};
|
||||
|
||||
Menu.prototype._callMenuWillShow = function () {
|
||||
if (this.delegate) this.delegate.menuWillShow(this)
|
||||
if (this.delegate) this.delegate.menuWillShow(this);
|
||||
this.items.forEach(item => {
|
||||
if (item.submenu) item.submenu._callMenuWillShow()
|
||||
})
|
||||
}
|
||||
if (item.submenu) item.submenu._callMenuWillShow();
|
||||
});
|
||||
};
|
||||
|
||||
/* Static Methods */
|
||||
|
||||
Menu.getApplicationMenu = () => applicationMenu
|
||||
Menu.getApplicationMenu = () => applicationMenu;
|
||||
|
||||
Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder
|
||||
Menu.sendActionToFirstResponder = bindings.sendActionToFirstResponder;
|
||||
|
||||
// set application menu with a preexisting menu
|
||||
Menu.setApplicationMenu = function (menu) {
|
||||
if (menu && menu.constructor !== Menu) {
|
||||
throw new TypeError('Invalid menu')
|
||||
throw new TypeError('Invalid menu');
|
||||
}
|
||||
|
||||
applicationMenu = menu
|
||||
v8Util.setHiddenValue(global, 'applicationMenuSet', true)
|
||||
applicationMenu = menu;
|
||||
v8Util.setHiddenValue(global, 'applicationMenuSet', true);
|
||||
|
||||
if (process.platform === 'darwin') {
|
||||
if (!menu) return
|
||||
menu._callMenuWillShow()
|
||||
bindings.setApplicationMenu(menu)
|
||||
if (!menu) return;
|
||||
menu._callMenuWillShow();
|
||||
bindings.setApplicationMenu(menu);
|
||||
} else {
|
||||
const windows = TopLevelWindow.getAllWindows()
|
||||
return windows.map(w => w.setMenu(menu))
|
||||
const windows = TopLevelWindow.getAllWindows();
|
||||
return windows.map(w => w.setMenu(menu));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Menu.buildFromTemplate = function (template) {
|
||||
if (!Array.isArray(template)) {
|
||||
throw new TypeError('Invalid template for Menu: Menu template must be an array')
|
||||
throw new TypeError('Invalid template for Menu: Menu template must be an array');
|
||||
}
|
||||
if (!areValidTemplateItems(template)) {
|
||||
throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type')
|
||||
throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type');
|
||||
}
|
||||
const filtered = removeExtraSeparators(template)
|
||||
const sorted = sortTemplate(filtered)
|
||||
const filtered = removeExtraSeparators(template);
|
||||
const sorted = sortTemplate(filtered);
|
||||
|
||||
const menu = new Menu()
|
||||
const menu = new Menu();
|
||||
sorted.forEach(item => {
|
||||
if (item instanceof MenuItem) {
|
||||
menu.append(item)
|
||||
menu.append(item);
|
||||
} else {
|
||||
menu.append(new MenuItem(item))
|
||||
menu.append(new MenuItem(item));
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return menu
|
||||
}
|
||||
return menu;
|
||||
};
|
||||
|
||||
/* Helper Functions */
|
||||
|
||||
@@ -196,51 +196,50 @@ function areValidTemplateItems (template) {
|
||||
typeof item === 'object' &&
|
||||
(item.hasOwnProperty('label') ||
|
||||
item.hasOwnProperty('role') ||
|
||||
item.type === 'separator'))
|
||||
item.type === 'separator'));
|
||||
}
|
||||
|
||||
function sortTemplate (template) {
|
||||
const sorted = sortMenuItems(template)
|
||||
for (const id in sorted) {
|
||||
const item = sorted[id]
|
||||
const sorted = sortMenuItems(template);
|
||||
for (const item of sorted) {
|
||||
if (Array.isArray(item.submenu)) {
|
||||
item.submenu = sortTemplate(item.submenu)
|
||||
item.submenu = sortTemplate(item.submenu);
|
||||
}
|
||||
}
|
||||
return sorted
|
||||
return sorted;
|
||||
}
|
||||
|
||||
// Search between separators to find a radio menu item and return its group id
|
||||
function generateGroupId (items, pos) {
|
||||
if (pos > 0) {
|
||||
for (let idx = pos - 1; idx >= 0; idx--) {
|
||||
if (items[idx].type === 'radio') return items[idx].groupId
|
||||
if (items[idx].type === 'separator') break
|
||||
if (items[idx].type === 'radio') return items[idx].groupId;
|
||||
if (items[idx].type === 'separator') break;
|
||||
}
|
||||
} else if (pos < items.length) {
|
||||
for (let idx = pos; idx <= items.length - 1; idx++) {
|
||||
if (items[idx].type === 'radio') return items[idx].groupId
|
||||
if (items[idx].type === 'separator') break
|
||||
if (items[idx].type === 'radio') return items[idx].groupId;
|
||||
if (items[idx].type === 'separator') break;
|
||||
}
|
||||
}
|
||||
groupIdIndex += 1
|
||||
return groupIdIndex
|
||||
groupIdIndex += 1;
|
||||
return groupIdIndex;
|
||||
}
|
||||
|
||||
function removeExtraSeparators (items) {
|
||||
// fold adjacent separators together
|
||||
let ret = items.filter((e, idx, arr) => {
|
||||
if (e.visible === false) return true
|
||||
return e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator'
|
||||
})
|
||||
if (e.visible === false) return true;
|
||||
return e.type !== 'separator' || idx === 0 || arr[idx - 1].type !== 'separator';
|
||||
});
|
||||
|
||||
// remove edge separators
|
||||
ret = ret.filter((e, idx, arr) => {
|
||||
if (e.visible === false) return true
|
||||
return e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1)
|
||||
})
|
||||
if (e.visible === false) return true;
|
||||
return e.type !== 'separator' || (idx !== 0 && idx !== arr.length - 1);
|
||||
});
|
||||
|
||||
return ret
|
||||
return ret;
|
||||
}
|
||||
|
||||
function insertItemByType (item, pos) {
|
||||
@@ -251,28 +250,28 @@ function insertItemByType (item, pos) {
|
||||
submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
|
||||
radio: () => {
|
||||
// Grouping radio menu items
|
||||
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos))
|
||||
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos));
|
||||
if (this.groupsMap[item.groupId] == null) {
|
||||
this.groupsMap[item.groupId] = []
|
||||
this.groupsMap[item.groupId] = [];
|
||||
}
|
||||
this.groupsMap[item.groupId].push(item)
|
||||
this.groupsMap[item.groupId].push(item);
|
||||
|
||||
// Setting a radio menu item should flip other items in the group.
|
||||
v8Util.setHiddenValue(item, 'checked', item.checked)
|
||||
v8Util.setHiddenValue(item, 'checked', item.checked);
|
||||
Object.defineProperty(item, 'checked', {
|
||||
enumerable: true,
|
||||
get: () => v8Util.getHiddenValue(item, 'checked'),
|
||||
set: () => {
|
||||
this.groupsMap[item.groupId].forEach(other => {
|
||||
if (other !== item) v8Util.setHiddenValue(other, 'checked', false)
|
||||
})
|
||||
v8Util.setHiddenValue(item, 'checked', true)
|
||||
if (other !== item) v8Util.setHiddenValue(other, 'checked', false);
|
||||
});
|
||||
v8Util.setHiddenValue(item, 'checked', true);
|
||||
}
|
||||
})
|
||||
this.insertRadioItem(pos, item.commandId, item.label, item.groupId)
|
||||
});
|
||||
this.insertRadioItem(pos, item.commandId, item.label, item.groupId);
|
||||
}
|
||||
}
|
||||
types[item.type]()
|
||||
};
|
||||
types[item.type]();
|
||||
}
|
||||
|
||||
module.exports = Menu
|
||||
module.exports = Menu;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
// TODO: Figure out a way to not duplicate this information between here and module-list
|
||||
// It is currently duplicated as module-list "require"s all the browser API file and the
|
||||
// remote module in the renderer process depends on that file. As a result webpack
|
||||
// includes all the browser API files in the renderer process as well and we want to avoid that
|
||||
|
||||
const features = process.electronBinding('features')
|
||||
const features = process.electronBinding('features');
|
||||
|
||||
// Browser side modules, please sort alphabetically.
|
||||
module.exports = [
|
||||
@@ -37,7 +37,7 @@ module.exports = [
|
||||
{ name: 'View' },
|
||||
{ name: 'webContents' },
|
||||
{ name: 'WebContentsView' }
|
||||
]
|
||||
];
|
||||
|
||||
if (features.isViewApiEnabled()) {
|
||||
module.exports.push(
|
||||
@@ -48,5 +48,5 @@ if (features.isViewApiEnabled()) {
|
||||
{ name: 'MdTextButton' },
|
||||
{ name: 'ResizeArea' },
|
||||
{ name: 'TextField' }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
// TODO: Updating this file also required updating the module-keys file
|
||||
|
||||
const features = process.electronBinding('features')
|
||||
const features = process.electronBinding('features');
|
||||
|
||||
// Browser side modules, please sort alphabetically.
|
||||
module.exports = [
|
||||
@@ -34,7 +34,7 @@ module.exports = [
|
||||
{ name: 'View', loader: () => require('./view') },
|
||||
{ name: 'webContents', loader: () => require('./web-contents') },
|
||||
{ name: 'WebContentsView', loader: () => require('./web-contents-view') }
|
||||
]
|
||||
];
|
||||
|
||||
if (features.isViewApiEnabled()) {
|
||||
module.exports.push(
|
||||
@@ -45,5 +45,5 @@ if (features.isViewApiEnabled()) {
|
||||
{ name: 'MdTextButton', loader: () => require('./views/md-text-button') },
|
||||
{ name: 'ResizeArea', loader: () => require('./views/resize-area') },
|
||||
{ name: 'TextField', loader: () => require('./views/text-field') }
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const { NativeTheme, nativeTheme } = process.electronBinding('native_theme')
|
||||
const { NativeTheme, nativeTheme } = process.electronBinding('native_theme');
|
||||
|
||||
Object.setPrototypeOf(NativeTheme.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(nativeTheme as any)
|
||||
Object.setPrototypeOf(NativeTheme.prototype, EventEmitter.prototype);
|
||||
EventEmitter.call(nativeTheme as any);
|
||||
|
||||
module.exports = nativeTheme
|
||||
module.exports = nativeTheme;
|
||||
|
||||
@@ -1,32 +1,32 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
// TODO(deepak1556): Deprecate and remove standalone netLog module,
|
||||
// it is now a property of session module.
|
||||
const { app, session } = require('electron')
|
||||
const { app, session } = require('electron');
|
||||
|
||||
// Fallback to default session.
|
||||
Object.setPrototypeOf(module.exports, new Proxy({}, {
|
||||
get (target, property) {
|
||||
if (!app.isReady()) return
|
||||
if (!app.isReady()) return;
|
||||
|
||||
const netLog = session.defaultSession.netLog
|
||||
const netLog = session.defaultSession.netLog;
|
||||
|
||||
if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return
|
||||
if (!Object.getPrototypeOf(netLog).hasOwnProperty(property)) return;
|
||||
|
||||
// check for properties on the prototype chain that aren't functions
|
||||
if (typeof netLog[property] !== 'function') return netLog[property]
|
||||
if (typeof netLog[property] !== 'function') return netLog[property];
|
||||
|
||||
// Returning a native function directly would throw error.
|
||||
return (...args) => netLog[property](...args)
|
||||
return (...args) => netLog[property](...args);
|
||||
},
|
||||
|
||||
ownKeys () {
|
||||
if (!app.isReady()) return []
|
||||
if (!app.isReady()) return [];
|
||||
|
||||
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog))
|
||||
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog));
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor (target) {
|
||||
return { configurable: true, enumerable: true }
|
||||
return { configurable: true, enumerable: true };
|
||||
}
|
||||
}))
|
||||
}));
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const url = require('url')
|
||||
const { EventEmitter } = require('events')
|
||||
const { Readable, Writable } = require('stream')
|
||||
const { app } = require('electron')
|
||||
const { Session } = process.electronBinding('session')
|
||||
const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net')
|
||||
const { URLLoader } = net
|
||||
const url = require('url');
|
||||
const { EventEmitter } = require('events');
|
||||
const { Readable, Writable } = require('stream');
|
||||
const { app } = require('electron');
|
||||
const { Session } = process.electronBinding('session');
|
||||
const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net');
|
||||
const { URLLoader } = net;
|
||||
|
||||
Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype);
|
||||
|
||||
const kSupportedProtocols = new Set(['http:', 'https:'])
|
||||
const kSupportedProtocols = new Set(['http:', 'https:']);
|
||||
|
||||
// set of headers that Node.js discards duplicates for
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
@@ -33,166 +33,175 @@ const discardableDuplicateHeaders = new Set([
|
||||
'server',
|
||||
'age',
|
||||
'expires'
|
||||
])
|
||||
]);
|
||||
|
||||
class IncomingMessage extends Readable {
|
||||
constructor (responseHead) {
|
||||
super()
|
||||
this._shouldPush = false
|
||||
this._data = []
|
||||
this._responseHead = responseHead
|
||||
super();
|
||||
this._shouldPush = false;
|
||||
this._data = [];
|
||||
this._responseHead = responseHead;
|
||||
}
|
||||
|
||||
get statusCode () {
|
||||
return this._responseHead.statusCode
|
||||
return this._responseHead.statusCode;
|
||||
}
|
||||
|
||||
get statusMessage () {
|
||||
return this._responseHead.statusMessage
|
||||
return this._responseHead.statusMessage;
|
||||
}
|
||||
|
||||
get headers () {
|
||||
const filteredHeaders = {}
|
||||
const rawHeaders = this._responseHead.headers
|
||||
Object.keys(rawHeaders).forEach(header => {
|
||||
if (header in filteredHeaders && discardableDuplicateHeaders.has(header)) {
|
||||
const filteredHeaders = {};
|
||||
const { rawHeaders } = this._responseHead;
|
||||
rawHeaders.forEach(header => {
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key) &&
|
||||
discardableDuplicateHeaders.has(header.key)) {
|
||||
// do nothing with discardable duplicate headers
|
||||
} else {
|
||||
if (header === 'set-cookie') {
|
||||
if (header.key === 'set-cookie') {
|
||||
// keep set-cookie as an array per Node.js rules
|
||||
// see https://nodejs.org/api/http.html#http_message_headers
|
||||
filteredHeaders[header] = rawHeaders[header]
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
|
||||
filteredHeaders[header.key].push(header.value);
|
||||
} else {
|
||||
filteredHeaders[header.key] = [header.value];
|
||||
}
|
||||
} else {
|
||||
// for non-cookie headers, the values are joined together with ', '
|
||||
filteredHeaders[header] = rawHeaders[header].join(', ')
|
||||
if (Object.prototype.hasOwnProperty.call(filteredHeaders, header.key)) {
|
||||
filteredHeaders[header.key] += `, ${header.value}`;
|
||||
} else {
|
||||
filteredHeaders[header.key] = header.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
return filteredHeaders
|
||||
});
|
||||
return filteredHeaders;
|
||||
}
|
||||
|
||||
get httpVersion () {
|
||||
return `${this.httpVersionMajor}.${this.httpVersionMinor}`
|
||||
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
|
||||
}
|
||||
|
||||
get httpVersionMajor () {
|
||||
return this._responseHead.httpVersion.major
|
||||
return this._responseHead.httpVersion.major;
|
||||
}
|
||||
|
||||
get httpVersionMinor () {
|
||||
return this._responseHead.httpVersion.minor
|
||||
return this._responseHead.httpVersion.minor;
|
||||
}
|
||||
|
||||
get rawTrailers () {
|
||||
throw new Error('HTTP trailers are not supported')
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
get trailers () {
|
||||
throw new Error('HTTP trailers are not supported')
|
||||
throw new Error('HTTP trailers are not supported');
|
||||
}
|
||||
|
||||
_storeInternalData (chunk) {
|
||||
this._data.push(chunk)
|
||||
this._pushInternalData()
|
||||
this._data.push(chunk);
|
||||
this._pushInternalData();
|
||||
}
|
||||
|
||||
_pushInternalData () {
|
||||
while (this._shouldPush && this._data.length > 0) {
|
||||
const chunk = this._data.shift()
|
||||
this._shouldPush = this.push(chunk)
|
||||
const chunk = this._data.shift();
|
||||
this._shouldPush = this.push(chunk);
|
||||
}
|
||||
}
|
||||
|
||||
_read () {
|
||||
this._shouldPush = true
|
||||
this._pushInternalData()
|
||||
this._shouldPush = true;
|
||||
this._pushInternalData();
|
||||
}
|
||||
}
|
||||
|
||||
/** Writable stream that buffers up everything written to it. */
|
||||
class SlurpStream extends Writable {
|
||||
constructor () {
|
||||
super()
|
||||
this._data = Buffer.alloc(0)
|
||||
super();
|
||||
this._data = Buffer.alloc(0);
|
||||
}
|
||||
_write (chunk, encoding, callback) {
|
||||
this._data = Buffer.concat([this._data, chunk])
|
||||
callback()
|
||||
this._data = Buffer.concat([this._data, chunk]);
|
||||
callback();
|
||||
}
|
||||
data () { return this._data }
|
||||
data () { return this._data; }
|
||||
}
|
||||
|
||||
class ChunkedBodyStream extends Writable {
|
||||
constructor (clientRequest) {
|
||||
super()
|
||||
this._clientRequest = clientRequest
|
||||
super();
|
||||
this._clientRequest = clientRequest;
|
||||
}
|
||||
|
||||
_write (chunk, encoding, callback) {
|
||||
if (this._downstream) {
|
||||
this._downstream.write(chunk).then(callback, callback)
|
||||
this._downstream.write(chunk).then(callback, callback);
|
||||
} else {
|
||||
// the contract of _write is that we won't be called again until we call
|
||||
// the callback, so we're good to just save a single chunk.
|
||||
this._pendingChunk = chunk
|
||||
this._pendingCallback = callback
|
||||
this._pendingChunk = chunk;
|
||||
this._pendingCallback = callback;
|
||||
|
||||
// The first write to a chunked body stream begins the request.
|
||||
this._clientRequest._startRequest()
|
||||
this._clientRequest._startRequest();
|
||||
}
|
||||
}
|
||||
|
||||
_final (callback) {
|
||||
this._downstream.done()
|
||||
callback()
|
||||
this._downstream.done();
|
||||
callback();
|
||||
}
|
||||
|
||||
startReading (pipe) {
|
||||
if (this._downstream) {
|
||||
throw new Error('two startReading calls???')
|
||||
throw new Error('two startReading calls???');
|
||||
}
|
||||
this._downstream = pipe
|
||||
this._downstream = pipe;
|
||||
if (this._pendingChunk) {
|
||||
const doneWriting = (maybeError) => {
|
||||
const cb = this._pendingCallback
|
||||
delete this._pendingCallback
|
||||
delete this._pendingChunk
|
||||
cb(maybeError)
|
||||
}
|
||||
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting)
|
||||
const cb = this._pendingCallback;
|
||||
delete this._pendingCallback;
|
||||
delete this._pendingChunk;
|
||||
cb(maybeError);
|
||||
};
|
||||
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseOptions (options) {
|
||||
if (typeof options === 'string') {
|
||||
options = url.parse(options)
|
||||
options = url.parse(options);
|
||||
} else {
|
||||
options = { ...options }
|
||||
options = { ...options };
|
||||
}
|
||||
|
||||
const method = (options.method || 'GET').toUpperCase()
|
||||
let urlStr = options.url
|
||||
const method = (options.method || 'GET').toUpperCase();
|
||||
let urlStr = options.url;
|
||||
|
||||
if (!urlStr) {
|
||||
const urlObj = {}
|
||||
const protocol = options.protocol || 'http:'
|
||||
const urlObj = {};
|
||||
const protocol = options.protocol || 'http:';
|
||||
if (!kSupportedProtocols.has(protocol)) {
|
||||
throw new Error('Protocol "' + protocol + '" not supported')
|
||||
throw new Error('Protocol "' + protocol + '" not supported');
|
||||
}
|
||||
urlObj.protocol = protocol
|
||||
urlObj.protocol = protocol;
|
||||
|
||||
if (options.host) {
|
||||
urlObj.host = options.host
|
||||
urlObj.host = options.host;
|
||||
} else {
|
||||
if (options.hostname) {
|
||||
urlObj.hostname = options.hostname
|
||||
urlObj.hostname = options.hostname;
|
||||
} else {
|
||||
urlObj.hostname = 'localhost'
|
||||
urlObj.hostname = 'localhost';
|
||||
}
|
||||
|
||||
if (options.port) {
|
||||
urlObj.port = options.port
|
||||
urlObj.port = options.port;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -203,203 +212,204 @@ function parseOptions (options) {
|
||||
// well, and b) possibly too restrictive for real-world usage. That's
|
||||
// why it only scans for spaces because those are guaranteed to create
|
||||
// an invalid request.
|
||||
throw new TypeError('Request path contains unescaped characters')
|
||||
throw new TypeError('Request path contains unescaped characters');
|
||||
}
|
||||
const pathObj = url.parse(options.path || '/')
|
||||
urlObj.pathname = pathObj.pathname
|
||||
urlObj.search = pathObj.search
|
||||
urlObj.hash = pathObj.hash
|
||||
urlStr = url.format(urlObj)
|
||||
const pathObj = url.parse(options.path || '/');
|
||||
urlObj.pathname = pathObj.pathname;
|
||||
urlObj.search = pathObj.search;
|
||||
urlObj.hash = pathObj.hash;
|
||||
urlStr = url.format(urlObj);
|
||||
}
|
||||
|
||||
const redirectPolicy = options.redirect || 'follow'
|
||||
const redirectPolicy = options.redirect || 'follow';
|
||||
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
|
||||
throw new Error('redirect mode should be one of follow, error or manual')
|
||||
throw new Error('redirect mode should be one of follow, error or manual');
|
||||
}
|
||||
|
||||
if (options.headers != null && typeof options.headers !== 'object') {
|
||||
throw new TypeError('headers must be an object')
|
||||
throw new TypeError('headers must be an object');
|
||||
}
|
||||
|
||||
const urlLoaderOptions = {
|
||||
method: method,
|
||||
url: urlStr,
|
||||
redirectPolicy,
|
||||
extraHeaders: options.headers || {}
|
||||
}
|
||||
extraHeaders: options.headers || {},
|
||||
useSessionCookies: options.useSessionCookies || false
|
||||
};
|
||||
for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) {
|
||||
if (!_isValidHeaderName(name)) {
|
||||
throw new Error(`Invalid header name: '${name}'`)
|
||||
throw new Error(`Invalid header name: '${name}'`);
|
||||
}
|
||||
if (!_isValidHeaderValue(value.toString())) {
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`)
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`);
|
||||
}
|
||||
}
|
||||
if (options.session) {
|
||||
if (options.session instanceof Session) {
|
||||
urlLoaderOptions.session = options.session
|
||||
urlLoaderOptions.session = options.session;
|
||||
} else {
|
||||
throw new TypeError('`session` should be an instance of the Session class')
|
||||
throw new TypeError('`session` should be an instance of the Session class');
|
||||
}
|
||||
} else if (options.partition) {
|
||||
if (typeof options.partition === 'string') {
|
||||
urlLoaderOptions.partition = options.partition
|
||||
urlLoaderOptions.partition = options.partition;
|
||||
} else {
|
||||
throw new TypeError('`partition` should be a string')
|
||||
throw new TypeError('`partition` should be a string');
|
||||
}
|
||||
}
|
||||
return urlLoaderOptions
|
||||
return urlLoaderOptions;
|
||||
}
|
||||
|
||||
class ClientRequest extends Writable {
|
||||
constructor (options, callback) {
|
||||
super({ autoDestroy: true })
|
||||
super({ autoDestroy: true });
|
||||
|
||||
if (!app.isReady()) {
|
||||
throw new Error('net module can only be used after app is ready')
|
||||
throw new Error('net module can only be used after app is ready');
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
this.once('response', callback)
|
||||
this.once('response', callback);
|
||||
}
|
||||
|
||||
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options)
|
||||
this._urlLoaderOptions = urlLoaderOptions
|
||||
this._redirectPolicy = redirectPolicy
|
||||
this._started = false
|
||||
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options);
|
||||
this._urlLoaderOptions = urlLoaderOptions;
|
||||
this._redirectPolicy = redirectPolicy;
|
||||
this._started = false;
|
||||
}
|
||||
|
||||
set chunkedEncoding (value) {
|
||||
if (this._started) {
|
||||
throw new Error('chunkedEncoding can only be set before the request is started')
|
||||
throw new Error('chunkedEncoding can only be set before the request is started');
|
||||
}
|
||||
if (typeof this._chunkedEncoding !== 'undefined') {
|
||||
throw new Error('chunkedEncoding can only be set once')
|
||||
throw new Error('chunkedEncoding can only be set once');
|
||||
}
|
||||
this._chunkedEncoding = !!value
|
||||
this._chunkedEncoding = !!value;
|
||||
if (this._chunkedEncoding) {
|
||||
this._body = new ChunkedBodyStream(this)
|
||||
this._body = new ChunkedBodyStream(this);
|
||||
this._urlLoaderOptions.body = (pipe) => {
|
||||
this._body.startReading(pipe)
|
||||
}
|
||||
this._body.startReading(pipe);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
setHeader (name, value) {
|
||||
if (typeof name !== 'string') {
|
||||
throw new TypeError('`name` should be a string in setHeader(name, value)')
|
||||
throw new TypeError('`name` should be a string in setHeader(name, value)');
|
||||
}
|
||||
if (value == null) {
|
||||
throw new Error('`value` required in setHeader("' + name + '", value)')
|
||||
throw new Error('`value` required in setHeader("' + name + '", value)');
|
||||
}
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error('Can\'t set headers after they are sent')
|
||||
throw new Error('Can\'t set headers after they are sent');
|
||||
}
|
||||
if (!_isValidHeaderName(name)) {
|
||||
throw new Error(`Invalid header name: '${name}'`)
|
||||
throw new Error(`Invalid header name: '${name}'`);
|
||||
}
|
||||
if (!_isValidHeaderValue(value.toString())) {
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`)
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`);
|
||||
}
|
||||
|
||||
const key = name.toLowerCase()
|
||||
this._urlLoaderOptions.extraHeaders[key] = value
|
||||
const key = name.toLowerCase();
|
||||
this._urlLoaderOptions.extraHeaders[key] = value;
|
||||
}
|
||||
|
||||
getHeader (name) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for getHeader(name)')
|
||||
throw new Error('`name` is required for getHeader(name)');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase()
|
||||
return this._urlLoaderOptions.extraHeaders[key]
|
||||
const key = name.toLowerCase();
|
||||
return this._urlLoaderOptions.extraHeaders[key];
|
||||
}
|
||||
|
||||
removeHeader (name) {
|
||||
if (name == null) {
|
||||
throw new Error('`name` is required for removeHeader(name)')
|
||||
throw new Error('`name` is required for removeHeader(name)');
|
||||
}
|
||||
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error('Can\'t remove headers after they are sent')
|
||||
throw new Error('Can\'t remove headers after they are sent');
|
||||
}
|
||||
|
||||
const key = name.toLowerCase()
|
||||
delete this._urlLoaderOptions.extraHeaders[key]
|
||||
const key = name.toLowerCase();
|
||||
delete this._urlLoaderOptions.extraHeaders[key];
|
||||
}
|
||||
|
||||
_write (chunk, encoding, callback) {
|
||||
this._firstWrite = true
|
||||
this._firstWrite = true;
|
||||
if (!this._body) {
|
||||
this._body = new SlurpStream()
|
||||
this._body = new SlurpStream();
|
||||
this._body.on('finish', () => {
|
||||
this._urlLoaderOptions.body = this._body.data()
|
||||
this._startRequest()
|
||||
})
|
||||
this._urlLoaderOptions.body = this._body.data();
|
||||
this._startRequest();
|
||||
});
|
||||
}
|
||||
// TODO: is this the right way to forward to another stream?
|
||||
this._body.write(chunk, encoding, callback)
|
||||
this._body.write(chunk, encoding, callback);
|
||||
}
|
||||
|
||||
_final (callback) {
|
||||
if (this._body) {
|
||||
// TODO: is this the right way to forward to another stream?
|
||||
this._body.end(callback)
|
||||
this._body.end(callback);
|
||||
} else {
|
||||
// end() called without a body, go ahead and start the request
|
||||
this._startRequest()
|
||||
callback()
|
||||
this._startRequest();
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
_startRequest () {
|
||||
this._started = true
|
||||
this._started = true;
|
||||
const stringifyValues = (obj) => {
|
||||
const ret = {}
|
||||
for (const k in obj) {
|
||||
ret[k] = obj[k].toString()
|
||||
const ret = {};
|
||||
for (const k of Object.keys(obj)) {
|
||||
ret[k] = obj[k].toString();
|
||||
}
|
||||
return ret
|
||||
}
|
||||
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) }
|
||||
this._urlLoader = new URLLoader(opts)
|
||||
return ret;
|
||||
};
|
||||
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) };
|
||||
this._urlLoader = new URLLoader(opts);
|
||||
this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
|
||||
const response = this._response = new IncomingMessage(responseHead)
|
||||
this.emit('response', response)
|
||||
})
|
||||
const response = this._response = new IncomingMessage(responseHead);
|
||||
this.emit('response', response);
|
||||
});
|
||||
this._urlLoader.on('data', (event, data) => {
|
||||
this._response._storeInternalData(Buffer.from(data))
|
||||
})
|
||||
this._response._storeInternalData(Buffer.from(data));
|
||||
});
|
||||
this._urlLoader.on('complete', () => {
|
||||
if (this._response) { this._response._storeInternalData(null) }
|
||||
})
|
||||
if (this._response) { this._response._storeInternalData(null); }
|
||||
});
|
||||
this._urlLoader.on('error', (event, netErrorString) => {
|
||||
const error = new Error(netErrorString)
|
||||
if (this._response) this._response.destroy(error)
|
||||
this._die(error)
|
||||
})
|
||||
const error = new Error(netErrorString);
|
||||
if (this._response) this._response.destroy(error);
|
||||
this._die(error);
|
||||
});
|
||||
|
||||
this._urlLoader.on('login', (event, authInfo, callback) => {
|
||||
const handled = this.emit('login', authInfo, callback)
|
||||
const handled = this.emit('login', authInfo, callback);
|
||||
if (!handled) {
|
||||
// If there were no listeners, cancel the authentication request.
|
||||
callback()
|
||||
callback();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
|
||||
const { statusCode, newMethod, newUrl } = redirectInfo
|
||||
const { statusCode, newMethod, newUrl } = redirectInfo;
|
||||
if (this._redirectPolicy === 'error') {
|
||||
this._die(new Error(`Attempted to redirect, but redirect policy was 'error'`))
|
||||
this._die(new Error(`Attempted to redirect, but redirect policy was 'error'`));
|
||||
} else if (this._redirectPolicy === 'manual') {
|
||||
let _followRedirect = false
|
||||
this._followRedirectCb = () => { _followRedirect = true }
|
||||
let _followRedirect = false;
|
||||
this._followRedirectCb = () => { _followRedirect = true; };
|
||||
try {
|
||||
this.emit('redirect', statusCode, newMethod, newUrl, headers)
|
||||
this.emit('redirect', statusCode, newMethod, newUrl, headers);
|
||||
} finally {
|
||||
this._followRedirectCb = null
|
||||
if (!_followRedirect) {
|
||||
this._die(new Error('Redirect was cancelled'))
|
||||
this._followRedirectCb = null;
|
||||
if (!_followRedirect && !this._aborted) {
|
||||
this._die(new Error('Redirect was cancelled'));
|
||||
}
|
||||
}
|
||||
} else if (this._redirectPolicy === 'follow') {
|
||||
@@ -407,61 +417,61 @@ class ClientRequest extends Writable {
|
||||
// allowed but does nothing. (Perhaps it should throw an error
|
||||
// though...? Since the redirect will happen regardless.)
|
||||
try {
|
||||
this._followRedirectCb = () => {}
|
||||
this.emit('redirect', statusCode, newMethod, newUrl, headers)
|
||||
this._followRedirectCb = () => {};
|
||||
this.emit('redirect', statusCode, newMethod, newUrl, headers);
|
||||
} finally {
|
||||
this._followRedirectCb = null
|
||||
this._followRedirectCb = null;
|
||||
}
|
||||
} else {
|
||||
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`))
|
||||
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`));
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this._urlLoader.on('upload-progress', (event, position, total) => {
|
||||
this._uploadProgress = { active: true, started: true, current: position, total }
|
||||
this.emit('upload-progress', position, total) // Undocumented, for now
|
||||
})
|
||||
this._uploadProgress = { active: true, started: true, current: position, total };
|
||||
this.emit('upload-progress', position, total); // Undocumented, for now
|
||||
});
|
||||
|
||||
this._urlLoader.on('download-progress', (event, current) => {
|
||||
if (this._response) {
|
||||
this._response.emit('download-progress', current) // Undocumented, for now
|
||||
this._response.emit('download-progress', current); // Undocumented, for now
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
followRedirect () {
|
||||
if (this._followRedirectCb) {
|
||||
this._followRedirectCb()
|
||||
this._followRedirectCb();
|
||||
} else {
|
||||
throw new Error('followRedirect() called, but was not waiting for a redirect')
|
||||
throw new Error('followRedirect() called, but was not waiting for a redirect');
|
||||
}
|
||||
}
|
||||
|
||||
abort () {
|
||||
if (!this._aborted) {
|
||||
process.nextTick(() => { this.emit('abort') })
|
||||
process.nextTick(() => { this.emit('abort'); });
|
||||
}
|
||||
this._aborted = true
|
||||
this._die()
|
||||
this._aborted = true;
|
||||
this._die();
|
||||
}
|
||||
|
||||
_die (err) {
|
||||
this.destroy(err)
|
||||
this.destroy(err);
|
||||
if (this._urlLoader) {
|
||||
this._urlLoader.cancel()
|
||||
if (this._response) this._response.destroy(err)
|
||||
this._urlLoader.cancel();
|
||||
if (this._response) this._response.destroy(err);
|
||||
}
|
||||
}
|
||||
|
||||
getUploadProgress () {
|
||||
return this._uploadProgress ? { ...this._uploadProgress } : { active: false }
|
||||
return this._uploadProgress ? { ...this._uploadProgress } : { active: false };
|
||||
}
|
||||
}
|
||||
|
||||
Net.prototype.request = function (options, callback) {
|
||||
return new ClientRequest(options, callback)
|
||||
}
|
||||
return new ClientRequest(options, callback);
|
||||
};
|
||||
|
||||
net.ClientRequest = ClientRequest
|
||||
net.ClientRequest = ClientRequest;
|
||||
|
||||
module.exports = net
|
||||
module.exports = net;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { Notification, isSupported } = process.electronBinding('notification')
|
||||
const { EventEmitter } = require('events');
|
||||
const { Notification, isSupported } = process.electronBinding('notification');
|
||||
|
||||
Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(Notification.prototype, EventEmitter.prototype);
|
||||
|
||||
Notification.isSupported = isSupported
|
||||
Notification.isSupported = isSupported;
|
||||
|
||||
module.exports = Notification
|
||||
module.exports = Notification;
|
||||
|
||||
@@ -1,28 +1,38 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
import { createLazyInstance } from '../utils'
|
||||
import { createLazyInstance } from '../utils';
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { createPowerMonitor, PowerMonitor } = process.electronBinding('power_monitor')
|
||||
const { EventEmitter } = require('events');
|
||||
const { createPowerMonitor, PowerMonitor } = process.electronBinding('power_monitor');
|
||||
|
||||
// PowerMonitor is an EventEmitter.
|
||||
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(PowerMonitor.prototype, EventEmitter.prototype);
|
||||
|
||||
const powerMonitor = createLazyInstance(createPowerMonitor, PowerMonitor, true)
|
||||
const powerMonitor = createLazyInstance(createPowerMonitor, PowerMonitor, true);
|
||||
|
||||
// On Linux we need to call blockShutdown() to subscribe to shutdown event.
|
||||
if (process.platform === 'linux') {
|
||||
powerMonitor.on('newListener', (event:string) => {
|
||||
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
|
||||
powerMonitor.blockShutdown()
|
||||
}
|
||||
})
|
||||
|
||||
powerMonitor.on('removeListener', (event: string) => {
|
||||
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
|
||||
powerMonitor.unblockShutdown()
|
||||
}
|
||||
})
|
||||
// In order to delay system shutdown when e.preventDefault() is invoked
|
||||
// on a powerMonitor 'shutdown' event, we need an org.freedesktop.login1
|
||||
// shutdown delay lock. For more details see the "Taking Delay Locks"
|
||||
// section of https://www.freedesktop.org/wiki/Software/systemd/inhibit/
|
||||
//
|
||||
// So here we watch for 'shutdown' listeners to be added or removed and
|
||||
// set or unset our shutdown delay lock accordingly.
|
||||
const { app } = require('electron');
|
||||
app.whenReady().then(() => {
|
||||
powerMonitor.on('newListener', (event: string) => {
|
||||
// whenever the listener count is incremented to one...
|
||||
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
|
||||
powerMonitor.blockShutdown();
|
||||
}
|
||||
});
|
||||
powerMonitor.on('removeListener', (event: string) => {
|
||||
// whenever the listener count is decremented to zero...
|
||||
if (event === 'shutdown' && powerMonitor.listenerCount('shutdown') === 0) {
|
||||
powerMonitor.unblockShutdown();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = powerMonitor
|
||||
module.exports = powerMonitor;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker
|
||||
module.exports = process.electronBinding('power_save_blocker').powerSaveBlocker;
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
import { app, session } from 'electron'
|
||||
import { app, session } from 'electron';
|
||||
|
||||
// Global protocol APIs.
|
||||
const protocol = process.electronBinding('protocol')
|
||||
const protocol = process.electronBinding('protocol');
|
||||
|
||||
// Fallback protocol APIs of default session.
|
||||
Object.setPrototypeOf(protocol, new Proxy({}, {
|
||||
get (_target, property) {
|
||||
if (!app.isReady()) return
|
||||
if (!app.isReady()) return;
|
||||
|
||||
const protocol = session.defaultSession!.protocol
|
||||
if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return
|
||||
const protocol = session.defaultSession!.protocol;
|
||||
if (!Object.getPrototypeOf(protocol).hasOwnProperty(property)) return;
|
||||
|
||||
// Returning a native function directly would throw error.
|
||||
return (...args: any[]) => (protocol[property as keyof Electron.Protocol] as Function)(...args)
|
||||
return (...args: any[]) => (protocol[property as keyof Electron.Protocol] as Function)(...args);
|
||||
},
|
||||
|
||||
ownKeys () {
|
||||
if (!app.isReady()) return []
|
||||
if (!app.isReady()) return [];
|
||||
|
||||
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession!.protocol))
|
||||
return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession!.protocol));
|
||||
},
|
||||
|
||||
getOwnPropertyDescriptor () {
|
||||
return { configurable: true, enumerable: true }
|
||||
return { configurable: true, enumerable: true };
|
||||
}
|
||||
}))
|
||||
}));
|
||||
|
||||
export default protocol
|
||||
export default protocol;
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
import { createLazyInstance } from '../utils'
|
||||
const { EventEmitter } = require('events')
|
||||
const { Screen, createScreen } = process.electronBinding('screen')
|
||||
import { createLazyInstance } from '../utils';
|
||||
const { EventEmitter } = require('events');
|
||||
const { Screen, createScreen } = process.electronBinding('screen');
|
||||
|
||||
// Screen is an EventEmitter.
|
||||
Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(Screen.prototype, EventEmitter.prototype);
|
||||
|
||||
module.exports = createLazyInstance(createScreen, Screen, true)
|
||||
module.exports = createLazyInstance(createScreen, Screen, true);
|
||||
|
||||
@@ -1,52 +1,53 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { app, deprecate } = require('electron')
|
||||
const { fromPartition, Session, Cookies, NetLog, Protocol } = process.electronBinding('session')
|
||||
const { EventEmitter } = require('events');
|
||||
const { app, deprecate } = require('electron');
|
||||
const { fromPartition, Session, Cookies, NetLog, Protocol } = process.electronBinding('session');
|
||||
|
||||
// Public API.
|
||||
Object.defineProperties(exports, {
|
||||
defaultSession: {
|
||||
enumerable: true,
|
||||
get () { return fromPartition('') }
|
||||
get () { return fromPartition(''); }
|
||||
},
|
||||
fromPartition: {
|
||||
enumerable: true,
|
||||
value: fromPartition
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
Object.setPrototypeOf(Session.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(Session.prototype, EventEmitter.prototype);
|
||||
Object.setPrototypeOf(Cookies.prototype, EventEmitter.prototype);
|
||||
|
||||
Session.prototype._init = function () {
|
||||
app.emit('session-created', this)
|
||||
}
|
||||
app.emit('session-created', this);
|
||||
};
|
||||
|
||||
const _originalStartLogging = NetLog.prototype.startLogging
|
||||
const _originalStartLogging = NetLog.prototype.startLogging;
|
||||
NetLog.prototype.startLogging = function (path, ...args) {
|
||||
this._currentlyLoggingPath = path
|
||||
this._currentlyLoggingPath = path;
|
||||
try {
|
||||
return _originalStartLogging.call(this, path, ...args)
|
||||
return _originalStartLogging.call(this, path, ...args);
|
||||
} catch (e) {
|
||||
this._currentlyLoggingPath = null
|
||||
throw e
|
||||
this._currentlyLoggingPath = null;
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const _originalStopLogging = NetLog.prototype.stopLogging
|
||||
const _originalStopLogging = NetLog.prototype.stopLogging;
|
||||
NetLog.prototype.stopLogging = function () {
|
||||
this._currentlyLoggingPath = null
|
||||
return _originalStopLogging.call(this)
|
||||
}
|
||||
const logPath = this._currentlyLoggingPath;
|
||||
this._currentlyLoggingPath = null;
|
||||
return _originalStopLogging.call(this).then(() => logPath);
|
||||
};
|
||||
|
||||
const currentlyLoggingPathDeprecated = deprecate.warnOnce('currentlyLoggingPath')
|
||||
const currentlyLoggingPathDeprecated = deprecate.warnOnce('currentlyLoggingPath');
|
||||
Object.defineProperties(NetLog.prototype, {
|
||||
currentlyLoggingPath: {
|
||||
enumerable: true,
|
||||
get () {
|
||||
currentlyLoggingPathDeprecated()
|
||||
return this._currentlyLoggingPath == null ? '' : this._currentlyLoggingPath
|
||||
currentlyLoggingPathDeprecated();
|
||||
return this._currentlyLoggingPath == null ? '' : this._currentlyLoggingPath;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { deprecate } from 'electron'
|
||||
const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences')
|
||||
import { EventEmitter } from 'events';
|
||||
import { deprecate } from 'electron';
|
||||
const { systemPreferences, SystemPreferences } = process.electronBinding('system_preferences');
|
||||
|
||||
// SystemPreferences is an EventEmitter.
|
||||
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(systemPreferences)
|
||||
Object.setPrototypeOf(SystemPreferences.prototype, EventEmitter.prototype);
|
||||
EventEmitter.call(systemPreferences);
|
||||
|
||||
if ('appLevelAppearance' in systemPreferences) {
|
||||
deprecate.fnToProperty(
|
||||
@@ -12,7 +12,7 @@ if ('appLevelAppearance' in systemPreferences) {
|
||||
'appLevelAppearance',
|
||||
'_getAppLevelAppearance',
|
||||
'_setAppLevelAppearance'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if ('effectiveAppearance' in systemPreferences) {
|
||||
@@ -20,23 +20,23 @@ if ('effectiveAppearance' in systemPreferences) {
|
||||
SystemPreferences.prototype,
|
||||
'effectiveAppearance',
|
||||
'_getEffectiveAppearance'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
SystemPreferences.prototype.isDarkMode = deprecate.moveAPI(
|
||||
SystemPreferences.prototype.isDarkMode,
|
||||
'systemPreferences.isDarkMode()',
|
||||
'nativeTheme.shouldUseDarkColors'
|
||||
)
|
||||
);
|
||||
SystemPreferences.prototype.isInvertedColorScheme = deprecate.moveAPI(
|
||||
SystemPreferences.prototype.isInvertedColorScheme,
|
||||
'systemPreferences.isInvertedColorScheme()',
|
||||
'nativeTheme.shouldUseInvertedColorScheme'
|
||||
)
|
||||
);
|
||||
SystemPreferences.prototype.isHighContrastColorScheme = deprecate.moveAPI(
|
||||
SystemPreferences.prototype.isHighContrastColorScheme,
|
||||
'systemPreferences.isHighContrastColorScheme()',
|
||||
'nativeTheme.shouldUseHighContrastColors'
|
||||
)
|
||||
);
|
||||
|
||||
module.exports = systemPreferences
|
||||
module.exports = systemPreferences;
|
||||
|
||||
@@ -1,24 +1,24 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const { EventEmitter } = require('events')
|
||||
const { TopLevelWindow } = process.electronBinding('top_level_window')
|
||||
const electron = require('electron');
|
||||
const { EventEmitter } = require('events');
|
||||
const { TopLevelWindow } = process.electronBinding('top_level_window');
|
||||
|
||||
Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(TopLevelWindow.prototype, EventEmitter.prototype);
|
||||
|
||||
TopLevelWindow.prototype._init = function () {
|
||||
// Avoid recursive require.
|
||||
const { app } = electron
|
||||
const { app } = electron;
|
||||
|
||||
// Simulate the application menu on platforms other than macOS.
|
||||
if (process.platform !== 'darwin') {
|
||||
const menu = app.applicationMenu
|
||||
if (menu) this.setMenu(menu)
|
||||
const menu = app.applicationMenu;
|
||||
if (menu) this.setMenu(menu);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TopLevelWindow.getFocusedWindow = () => {
|
||||
return TopLevelWindow.getAllWindows().find((win) => win.isFocused())
|
||||
}
|
||||
return TopLevelWindow.getAllWindows().find((win) => win.isFocused());
|
||||
};
|
||||
|
||||
module.exports = TopLevelWindow
|
||||
module.exports = TopLevelWindow;
|
||||
|
||||
@@ -1,337 +1,337 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { EventEmitter } = require('events');
|
||||
|
||||
let nextItemID = 1
|
||||
let nextItemID = 1;
|
||||
|
||||
class TouchBar extends EventEmitter {
|
||||
// Bind a touch bar to a window
|
||||
static _setOnWindow (touchBar, window) {
|
||||
if (window._touchBar != null) {
|
||||
window._touchBar._removeFromWindow(window)
|
||||
window._touchBar._removeFromWindow(window);
|
||||
}
|
||||
|
||||
if (touchBar == null) {
|
||||
window._setTouchBarItems([])
|
||||
return
|
||||
window._setTouchBarItems([]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (Array.isArray(touchBar)) {
|
||||
touchBar = new TouchBar(touchBar)
|
||||
touchBar = new TouchBar(touchBar);
|
||||
}
|
||||
touchBar._addToWindow(window)
|
||||
touchBar._addToWindow(window);
|
||||
}
|
||||
|
||||
constructor (options) {
|
||||
super()
|
||||
super();
|
||||
|
||||
if (options == null) {
|
||||
throw new Error('Must specify options object as first argument')
|
||||
throw new Error('Must specify options object as first argument');
|
||||
}
|
||||
|
||||
let { items, escapeItem } = options
|
||||
let { items, escapeItem } = options;
|
||||
|
||||
if (!Array.isArray(items)) {
|
||||
items = []
|
||||
items = [];
|
||||
}
|
||||
|
||||
this.changeListener = (item) => {
|
||||
this.emit('change', item.id, item.type)
|
||||
}
|
||||
this.emit('change', item.id, item.type);
|
||||
};
|
||||
|
||||
this.windowListeners = {}
|
||||
this.items = {}
|
||||
this.ordereredItems = []
|
||||
this.escapeItem = escapeItem
|
||||
this.windowListeners = {};
|
||||
this.items = {};
|
||||
this.ordereredItems = [];
|
||||
this.escapeItem = escapeItem;
|
||||
|
||||
const registerItem = (item) => {
|
||||
this.items[item.id] = item
|
||||
item.on('change', this.changeListener)
|
||||
this.items[item.id] = item;
|
||||
item.on('change', this.changeListener);
|
||||
if (item.child instanceof TouchBar) {
|
||||
item.child.ordereredItems.forEach(registerItem)
|
||||
item.child.ordereredItems.forEach(registerItem);
|
||||
}
|
||||
}
|
||||
};
|
||||
items.forEach((item) => {
|
||||
if (!(item instanceof TouchBarItem)) {
|
||||
throw new Error('Each item must be an instance of TouchBarItem')
|
||||
throw new Error('Each item must be an instance of TouchBarItem');
|
||||
}
|
||||
this.ordereredItems.push(item)
|
||||
registerItem(item)
|
||||
})
|
||||
this.ordereredItems.push(item);
|
||||
registerItem(item);
|
||||
});
|
||||
}
|
||||
|
||||
set escapeItem (item) {
|
||||
if (item != null && !(item instanceof TouchBarItem)) {
|
||||
throw new Error('Escape item must be an instance of TouchBarItem')
|
||||
throw new Error('Escape item must be an instance of TouchBarItem');
|
||||
}
|
||||
if (this.escapeItem != null) {
|
||||
this.escapeItem.removeListener('change', this.changeListener)
|
||||
this.escapeItem.removeListener('change', this.changeListener);
|
||||
}
|
||||
this._escapeItem = item
|
||||
this._escapeItem = item;
|
||||
if (this.escapeItem != null) {
|
||||
this.escapeItem.on('change', this.changeListener)
|
||||
this.escapeItem.on('change', this.changeListener);
|
||||
}
|
||||
this.emit('escape-item-change', item)
|
||||
this.emit('escape-item-change', item);
|
||||
}
|
||||
|
||||
get escapeItem () {
|
||||
return this._escapeItem
|
||||
return this._escapeItem;
|
||||
}
|
||||
|
||||
_addToWindow (window) {
|
||||
const { id } = window
|
||||
const { id } = window;
|
||||
|
||||
// Already added to window
|
||||
if (this.windowListeners.hasOwnProperty(id)) return
|
||||
if (this.windowListeners.hasOwnProperty(id)) return;
|
||||
|
||||
window._touchBar = this
|
||||
window._touchBar = this;
|
||||
|
||||
const changeListener = (itemID) => {
|
||||
window._refreshTouchBarItem(itemID)
|
||||
}
|
||||
this.on('change', changeListener)
|
||||
window._refreshTouchBarItem(itemID);
|
||||
};
|
||||
this.on('change', changeListener);
|
||||
|
||||
const escapeItemListener = (item) => {
|
||||
window._setEscapeTouchBarItem(item != null ? item : {})
|
||||
}
|
||||
this.on('escape-item-change', escapeItemListener)
|
||||
window._setEscapeTouchBarItem(item != null ? item : {});
|
||||
};
|
||||
this.on('escape-item-change', escapeItemListener);
|
||||
|
||||
const interactionListener = (event, itemID, details) => {
|
||||
let item = this.items[itemID]
|
||||
let item = this.items[itemID];
|
||||
if (item == null && this.escapeItem != null && this.escapeItem.id === itemID) {
|
||||
item = this.escapeItem
|
||||
item = this.escapeItem;
|
||||
}
|
||||
if (item != null && item.onInteraction != null) {
|
||||
item.onInteraction(details)
|
||||
item.onInteraction(details);
|
||||
}
|
||||
}
|
||||
window.on('-touch-bar-interaction', interactionListener)
|
||||
};
|
||||
window.on('-touch-bar-interaction', interactionListener);
|
||||
|
||||
const removeListeners = () => {
|
||||
this.removeListener('change', changeListener)
|
||||
this.removeListener('escape-item-change', escapeItemListener)
|
||||
window.removeListener('-touch-bar-interaction', interactionListener)
|
||||
window.removeListener('closed', removeListeners)
|
||||
window._touchBar = null
|
||||
delete this.windowListeners[id]
|
||||
this.removeListener('change', changeListener);
|
||||
this.removeListener('escape-item-change', escapeItemListener);
|
||||
window.removeListener('-touch-bar-interaction', interactionListener);
|
||||
window.removeListener('closed', removeListeners);
|
||||
window._touchBar = null;
|
||||
delete this.windowListeners[id];
|
||||
const unregisterItems = (items) => {
|
||||
for (const item of items) {
|
||||
item.removeListener('change', this.changeListener)
|
||||
item.removeListener('change', this.changeListener);
|
||||
if (item.child instanceof TouchBar) {
|
||||
unregisterItems(item.child.ordereredItems)
|
||||
unregisterItems(item.child.ordereredItems);
|
||||
}
|
||||
}
|
||||
}
|
||||
unregisterItems(this.ordereredItems)
|
||||
};
|
||||
unregisterItems(this.ordereredItems);
|
||||
if (this.escapeItem) {
|
||||
this.escapeItem.removeListener('change', this.changeListener)
|
||||
this.escapeItem.removeListener('change', this.changeListener);
|
||||
}
|
||||
}
|
||||
window.once('closed', removeListeners)
|
||||
this.windowListeners[id] = removeListeners
|
||||
};
|
||||
window.once('closed', removeListeners);
|
||||
this.windowListeners[id] = removeListeners;
|
||||
|
||||
window._setTouchBarItems(this.ordereredItems)
|
||||
escapeItemListener(this.escapeItem)
|
||||
window._setTouchBarItems(this.ordereredItems);
|
||||
escapeItemListener(this.escapeItem);
|
||||
}
|
||||
|
||||
_removeFromWindow (window) {
|
||||
const removeListeners = this.windowListeners[window.id]
|
||||
if (removeListeners != null) removeListeners()
|
||||
const removeListeners = this.windowListeners[window.id];
|
||||
if (removeListeners != null) removeListeners();
|
||||
}
|
||||
}
|
||||
|
||||
class TouchBarItem extends EventEmitter {
|
||||
constructor () {
|
||||
super()
|
||||
this._addImmutableProperty('id', `${nextItemID++}`)
|
||||
this._parents = []
|
||||
super();
|
||||
this._addImmutableProperty('id', `${nextItemID++}`);
|
||||
this._parents = [];
|
||||
}
|
||||
|
||||
_addImmutableProperty (name, value) {
|
||||
Object.defineProperty(this, name, {
|
||||
get: function () {
|
||||
return value
|
||||
return value;
|
||||
},
|
||||
set: function () {
|
||||
throw new Error(`Cannot override property ${name}`)
|
||||
throw new Error(`Cannot override property ${name}`);
|
||||
},
|
||||
enumerable: true,
|
||||
configurable: false
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_addLiveProperty (name, initialValue) {
|
||||
const privateName = `_${name}`
|
||||
this[privateName] = initialValue
|
||||
const privateName = `_${name}`;
|
||||
this[privateName] = initialValue;
|
||||
Object.defineProperty(this, name, {
|
||||
get: function () {
|
||||
return this[privateName]
|
||||
return this[privateName];
|
||||
},
|
||||
set: function (value) {
|
||||
this[privateName] = value
|
||||
this.emit('change', this)
|
||||
this[privateName] = value;
|
||||
this.emit('change', this);
|
||||
},
|
||||
enumerable: true
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
_addParent (item) {
|
||||
const existing = this._parents.some(test => test.id === item.id)
|
||||
const existing = this._parents.some(test => test.id === item.id);
|
||||
if (!existing) {
|
||||
this._parents.push({
|
||||
id: item.id,
|
||||
type: item.type
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TouchBar.TouchBarButton = class TouchBarButton extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
this._addImmutableProperty('type', 'button')
|
||||
const { click, icon, iconPosition, label, backgroundColor } = config
|
||||
this._addLiveProperty('label', label)
|
||||
this._addLiveProperty('backgroundColor', backgroundColor)
|
||||
this._addLiveProperty('icon', icon)
|
||||
this._addLiveProperty('iconPosition', iconPosition)
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
this._addImmutableProperty('type', 'button');
|
||||
const { click, icon, iconPosition, label, backgroundColor } = config;
|
||||
this._addLiveProperty('label', label);
|
||||
this._addLiveProperty('backgroundColor', backgroundColor);
|
||||
this._addLiveProperty('icon', icon);
|
||||
this._addLiveProperty('iconPosition', iconPosition);
|
||||
if (typeof click === 'function') {
|
||||
this._addImmutableProperty('onInteraction', () => {
|
||||
config.click()
|
||||
})
|
||||
config.click();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarColorPicker = class TouchBarColorPicker extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
this._addImmutableProperty('type', 'colorpicker')
|
||||
const { availableColors, change, selectedColor } = config
|
||||
this._addLiveProperty('availableColors', availableColors)
|
||||
this._addLiveProperty('selectedColor', selectedColor)
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
this._addImmutableProperty('type', 'colorpicker');
|
||||
const { availableColors, change, selectedColor } = config;
|
||||
this._addLiveProperty('availableColors', availableColors);
|
||||
this._addLiveProperty('selectedColor', selectedColor);
|
||||
|
||||
if (typeof change === 'function') {
|
||||
this._addImmutableProperty('onInteraction', (details) => {
|
||||
this._selectedColor = details.color
|
||||
change(details.color)
|
||||
})
|
||||
this._selectedColor = details.color;
|
||||
change(details.color);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarGroup = class TouchBarGroup extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
this._addImmutableProperty('type', 'group')
|
||||
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items)
|
||||
this._addLiveProperty('child', defaultChild)
|
||||
this.child.ordereredItems.forEach((item) => item._addParent(this))
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
this._addImmutableProperty('type', 'group');
|
||||
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items);
|
||||
this._addLiveProperty('child', defaultChild);
|
||||
this.child.ordereredItems.forEach((item) => item._addParent(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarLabel = class TouchBarLabel extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
this._addImmutableProperty('type', 'label')
|
||||
this._addLiveProperty('label', config.label)
|
||||
this._addLiveProperty('textColor', config.textColor)
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
this._addImmutableProperty('type', 'label');
|
||||
this._addLiveProperty('label', config.label);
|
||||
this._addLiveProperty('textColor', config.textColor);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarPopover = class TouchBarPopover extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
this._addImmutableProperty('type', 'popover')
|
||||
this._addLiveProperty('label', config.label)
|
||||
this._addLiveProperty('icon', config.icon)
|
||||
this._addLiveProperty('showCloseButton', config.showCloseButton)
|
||||
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items)
|
||||
this._addLiveProperty('child', defaultChild)
|
||||
this.child.ordereredItems.forEach((item) => item._addParent(this))
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
this._addImmutableProperty('type', 'popover');
|
||||
this._addLiveProperty('label', config.label);
|
||||
this._addLiveProperty('icon', config.icon);
|
||||
this._addLiveProperty('showCloseButton', config.showCloseButton);
|
||||
const defaultChild = (config.items instanceof TouchBar) ? config.items : new TouchBar(config.items);
|
||||
this._addLiveProperty('child', defaultChild);
|
||||
this.child.ordereredItems.forEach((item) => item._addParent(this));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarSlider = class TouchBarSlider extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
this._addImmutableProperty('type', 'slider')
|
||||
const { change, label, minValue, maxValue, value } = config
|
||||
this._addLiveProperty('label', label)
|
||||
this._addLiveProperty('minValue', minValue)
|
||||
this._addLiveProperty('maxValue', maxValue)
|
||||
this._addLiveProperty('value', value)
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
this._addImmutableProperty('type', 'slider');
|
||||
const { change, label, minValue, maxValue, value } = config;
|
||||
this._addLiveProperty('label', label);
|
||||
this._addLiveProperty('minValue', minValue);
|
||||
this._addLiveProperty('maxValue', maxValue);
|
||||
this._addLiveProperty('value', value);
|
||||
|
||||
if (typeof change === 'function') {
|
||||
this._addImmutableProperty('onInteraction', (details) => {
|
||||
this._value = details.value
|
||||
change(details.value)
|
||||
})
|
||||
this._value = details.value;
|
||||
change(details.value);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarSpacer = class TouchBarSpacer extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
this._addImmutableProperty('type', 'spacer')
|
||||
this._addImmutableProperty('size', config.size)
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
this._addImmutableProperty('type', 'spacer');
|
||||
this._addImmutableProperty('size', config.size);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarSegmentedControl = class TouchBarSegmentedControl extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
const { segmentStyle, segments, selectedIndex, change, mode } = config
|
||||
this._addImmutableProperty('type', 'segmented_control')
|
||||
this._addLiveProperty('segmentStyle', segmentStyle)
|
||||
this._addLiveProperty('segments', segments || [])
|
||||
this._addLiveProperty('selectedIndex', selectedIndex)
|
||||
this._addLiveProperty('mode', mode)
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
const { segmentStyle, segments, selectedIndex, change, mode } = config;
|
||||
this._addImmutableProperty('type', 'segmented_control');
|
||||
this._addLiveProperty('segmentStyle', segmentStyle);
|
||||
this._addLiveProperty('segments', segments || []);
|
||||
this._addLiveProperty('selectedIndex', selectedIndex);
|
||||
this._addLiveProperty('mode', mode);
|
||||
|
||||
if (typeof change === 'function') {
|
||||
this._addImmutableProperty('onInteraction', (details) => {
|
||||
this._selectedIndex = details.selectedIndex
|
||||
change(details.selectedIndex, details.isSelected)
|
||||
})
|
||||
this._selectedIndex = details.selectedIndex;
|
||||
change(details.selectedIndex, details.isSelected);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
TouchBar.TouchBarScrubber = class TouchBarScrubber extends TouchBarItem {
|
||||
constructor (config) {
|
||||
super()
|
||||
if (config == null) config = {}
|
||||
const { items, selectedStyle, overlayStyle, showArrowButtons, continuous, mode } = config
|
||||
let { select, highlight } = config
|
||||
this._addImmutableProperty('type', 'scrubber')
|
||||
this._addLiveProperty('items', items)
|
||||
this._addLiveProperty('selectedStyle', selectedStyle || null)
|
||||
this._addLiveProperty('overlayStyle', overlayStyle || null)
|
||||
this._addLiveProperty('showArrowButtons', showArrowButtons || false)
|
||||
this._addLiveProperty('mode', mode || 'free')
|
||||
this._addLiveProperty('continuous', typeof continuous === 'undefined' ? true : continuous)
|
||||
super();
|
||||
if (config == null) config = {};
|
||||
const { items, selectedStyle, overlayStyle, showArrowButtons, continuous, mode } = config;
|
||||
let { select, highlight } = config;
|
||||
this._addImmutableProperty('type', 'scrubber');
|
||||
this._addLiveProperty('items', items);
|
||||
this._addLiveProperty('selectedStyle', selectedStyle || null);
|
||||
this._addLiveProperty('overlayStyle', overlayStyle || null);
|
||||
this._addLiveProperty('showArrowButtons', showArrowButtons || false);
|
||||
this._addLiveProperty('mode', mode || 'free');
|
||||
this._addLiveProperty('continuous', typeof continuous === 'undefined' ? true : continuous);
|
||||
|
||||
if (typeof select === 'function' || typeof highlight === 'function') {
|
||||
if (select == null) select = () => {}
|
||||
if (highlight == null) highlight = () => {}
|
||||
if (select == null) select = () => {};
|
||||
if (highlight == null) highlight = () => {};
|
||||
this._addImmutableProperty('onInteraction', (details) => {
|
||||
if (details.type === 'select' && typeof select === 'function') {
|
||||
select(details.selectedIndex)
|
||||
select(details.selectedIndex);
|
||||
} else if (details.type === 'highlight' && typeof highlight === 'function') {
|
||||
highlight(details.highlightedIndex)
|
||||
highlight(details.highlightedIndex);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = TouchBar
|
||||
module.exports = TouchBar;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { deprecate } = require('electron')
|
||||
const { Tray } = process.electronBinding('tray')
|
||||
const { EventEmitter } = require('events');
|
||||
const { deprecate } = require('electron');
|
||||
const { Tray } = process.electronBinding('tray');
|
||||
|
||||
Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(Tray.prototype, EventEmitter.prototype);
|
||||
|
||||
module.exports = Tray
|
||||
module.exports = Tray;
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { View } = process.electronBinding('view')
|
||||
const { EventEmitter } = require('events');
|
||||
const { View } = process.electronBinding('view');
|
||||
|
||||
Object.setPrototypeOf(View.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(View.prototype, EventEmitter.prototype);
|
||||
|
||||
View.prototype._init = function () {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = View
|
||||
module.exports = View;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
const { LayoutManager } = electron
|
||||
const { BoxLayout } = process.electronBinding('box_layout')
|
||||
const { LayoutManager } = electron;
|
||||
const { BoxLayout } = process.electronBinding('box_layout');
|
||||
|
||||
Object.setPrototypeOf(BoxLayout.prototype, LayoutManager.prototype)
|
||||
Object.setPrototypeOf(BoxLayout.prototype, LayoutManager.prototype);
|
||||
|
||||
BoxLayout.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
LayoutManager.prototype._init.call(this)
|
||||
}
|
||||
LayoutManager.prototype._init.call(this);
|
||||
};
|
||||
|
||||
module.exports = BoxLayout
|
||||
module.exports = BoxLayout;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
const { View } = electron
|
||||
const { Button } = process.electronBinding('button')
|
||||
const { View } = electron;
|
||||
const { Button } = process.electronBinding('button');
|
||||
|
||||
Object.setPrototypeOf(Button.prototype, View.prototype)
|
||||
Object.setPrototypeOf(Button.prototype, View.prototype);
|
||||
|
||||
Button.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
View.prototype._init.call(this)
|
||||
}
|
||||
View.prototype._init.call(this);
|
||||
};
|
||||
|
||||
module.exports = Button
|
||||
module.exports = Button;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
const { Button } = electron
|
||||
const { LabelButton } = process.electronBinding('label_button')
|
||||
const { Button } = electron;
|
||||
const { LabelButton } = process.electronBinding('label_button');
|
||||
|
||||
Object.setPrototypeOf(LabelButton.prototype, Button.prototype)
|
||||
Object.setPrototypeOf(LabelButton.prototype, Button.prototype);
|
||||
|
||||
LabelButton.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
Button.prototype._init.call(this)
|
||||
}
|
||||
Button.prototype._init.call(this);
|
||||
};
|
||||
|
||||
module.exports = LabelButton
|
||||
module.exports = LabelButton;
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { LayoutManager } = process.electronBinding('layout_manager')
|
||||
const { LayoutManager } = process.electronBinding('layout_manager');
|
||||
|
||||
LayoutManager.prototype._init = function () {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = LayoutManager
|
||||
module.exports = LayoutManager;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
const { LabelButton } = electron
|
||||
const { MdTextButton } = process.electronBinding('md_text_button')
|
||||
const { LabelButton } = electron;
|
||||
const { MdTextButton } = process.electronBinding('md_text_button');
|
||||
|
||||
Object.setPrototypeOf(MdTextButton.prototype, LabelButton.prototype)
|
||||
Object.setPrototypeOf(MdTextButton.prototype, LabelButton.prototype);
|
||||
|
||||
MdTextButton.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
LabelButton.prototype._init.call(this)
|
||||
}
|
||||
LabelButton.prototype._init.call(this);
|
||||
};
|
||||
|
||||
module.exports = MdTextButton
|
||||
module.exports = MdTextButton;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
const { View } = electron
|
||||
const { ResizeArea } = process.electronBinding('resize_area')
|
||||
const { View } = electron;
|
||||
const { ResizeArea } = process.electronBinding('resize_area');
|
||||
|
||||
Object.setPrototypeOf(ResizeArea.prototype, View.prototype)
|
||||
Object.setPrototypeOf(ResizeArea.prototype, View.prototype);
|
||||
|
||||
ResizeArea.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
View.prototype._init.call(this)
|
||||
}
|
||||
View.prototype._init.call(this);
|
||||
};
|
||||
|
||||
module.exports = ResizeArea
|
||||
module.exports = ResizeArea;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
const { View } = electron
|
||||
const { TextField } = process.electronBinding('text_field')
|
||||
const { View } = electron;
|
||||
const { TextField } = process.electronBinding('text_field');
|
||||
|
||||
Object.setPrototypeOf(TextField.prototype, View.prototype)
|
||||
Object.setPrototypeOf(TextField.prototype, View.prototype);
|
||||
|
||||
TextField.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
View.prototype._init.call(this)
|
||||
}
|
||||
View.prototype._init.call(this);
|
||||
};
|
||||
|
||||
module.exports = TextField
|
||||
module.exports = TextField;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const electron = require('electron');
|
||||
|
||||
const { View } = electron
|
||||
const { WebContentsView } = process.electronBinding('web_contents_view')
|
||||
const { View } = electron;
|
||||
const { WebContentsView } = process.electronBinding('web_contents_view');
|
||||
|
||||
Object.setPrototypeOf(WebContentsView.prototype, View.prototype)
|
||||
Object.setPrototypeOf(WebContentsView.prototype, View.prototype);
|
||||
|
||||
WebContentsView.prototype._init = function () {
|
||||
// Call parent class's _init.
|
||||
View.prototype._init.call(this)
|
||||
}
|
||||
View.prototype._init.call(this);
|
||||
};
|
||||
|
||||
module.exports = WebContentsView
|
||||
module.exports = WebContentsView;
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const features = process.electronBinding('features')
|
||||
const { EventEmitter } = require('events')
|
||||
const electron = require('electron')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
const { app, ipcMain, session, deprecate } = electron
|
||||
const features = process.electronBinding('features');
|
||||
const { EventEmitter } = require('events');
|
||||
const electron = require('electron');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const { app, ipcMain, session, deprecate } = electron;
|
||||
|
||||
const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager')
|
||||
const NavigationController = require('@electron/internal/browser/navigation-controller')
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
const { internalWindowOpen } = require('@electron/internal/browser/guest-window-manager');
|
||||
const NavigationController = require('@electron/internal/browser/navigation-controller');
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
|
||||
const errorUtils = require('@electron/internal/common/error-utils');
|
||||
|
||||
// session is not used here, the purpose is to make sure session is initalized
|
||||
// before the webContents module.
|
||||
// eslint-disable-next-line
|
||||
session
|
||||
|
||||
let nextId = 0
|
||||
let nextId = 0;
|
||||
const getNextId = function () {
|
||||
return ++nextId
|
||||
}
|
||||
return ++nextId;
|
||||
};
|
||||
|
||||
// Stock page sizes
|
||||
const PDFPageSizes = {
|
||||
@@ -62,7 +62,7 @@ const PDFPageSizes = {
|
||||
width_microns: 279400,
|
||||
custom_display_name: 'Tabloid'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Default printing setting
|
||||
const defaultPrintingSetting = {
|
||||
@@ -92,83 +92,83 @@ const defaultPrintingSetting = {
|
||||
collate: true,
|
||||
shouldPrintBackgrounds: false,
|
||||
shouldPrintSelectionOnly: false
|
||||
}
|
||||
};
|
||||
|
||||
// JavaScript implementations of WebContents.
|
||||
const binding = process.electronBinding('web_contents')
|
||||
const { WebContents } = binding
|
||||
const binding = process.electronBinding('web_contents');
|
||||
const { WebContents } = binding;
|
||||
|
||||
Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype)
|
||||
Object.setPrototypeOf(NavigationController.prototype, EventEmitter.prototype);
|
||||
Object.setPrototypeOf(WebContents.prototype, NavigationController.prototype);
|
||||
|
||||
// WebContents::send(channel, args..)
|
||||
// WebContents::sendToAll(channel, args..)
|
||||
WebContents.prototype.send = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = false
|
||||
const sendToAll = false
|
||||
const internal = false;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args)
|
||||
}
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
};
|
||||
|
||||
WebContents.prototype.sendToAll = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = false
|
||||
const sendToAll = true
|
||||
const internal = false;
|
||||
const sendToAll = true;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args)
|
||||
}
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
};
|
||||
|
||||
WebContents.prototype._sendInternal = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = true
|
||||
const sendToAll = false
|
||||
const internal = true;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args)
|
||||
}
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
};
|
||||
WebContents.prototype._sendInternalToAll = function (channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
throw new Error('Missing required channel argument');
|
||||
}
|
||||
|
||||
const internal = true
|
||||
const sendToAll = true
|
||||
const internal = true;
|
||||
const sendToAll = true;
|
||||
|
||||
return this._send(internal, sendToAll, channel, args)
|
||||
}
|
||||
return this._send(internal, sendToAll, channel, args);
|
||||
};
|
||||
WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
throw new Error('Missing required channel argument');
|
||||
} else if (typeof frameId !== 'number') {
|
||||
throw new Error('Missing required frameId argument')
|
||||
throw new Error('Missing required frameId argument');
|
||||
}
|
||||
|
||||
const internal = false
|
||||
const sendToAll = false
|
||||
const internal = false;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args)
|
||||
}
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
|
||||
};
|
||||
WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) {
|
||||
if (typeof channel !== 'string') {
|
||||
throw new Error('Missing required channel argument')
|
||||
throw new Error('Missing required channel argument');
|
||||
} else if (typeof frameId !== 'number') {
|
||||
throw new Error('Missing required frameId argument')
|
||||
throw new Error('Missing required frameId argument');
|
||||
}
|
||||
|
||||
const internal = true
|
||||
const sendToAll = false
|
||||
const internal = true;
|
||||
const sendToAll = false;
|
||||
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args)
|
||||
}
|
||||
return this._sendToFrame(internal, sendToAll, frameId, channel, args);
|
||||
};
|
||||
|
||||
// Following methods are mapped to webFrame.
|
||||
const webFrameMethods = [
|
||||
@@ -177,56 +177,56 @@ const webFrameMethods = [
|
||||
'removeInsertedCSS',
|
||||
'setLayoutZoomLevelLimits',
|
||||
'setVisualZoomLevelLimits'
|
||||
]
|
||||
];
|
||||
|
||||
for (const method of webFrameMethods) {
|
||||
WebContents.prototype[method] = function (...args) {
|
||||
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args)
|
||||
}
|
||||
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', method, ...args);
|
||||
};
|
||||
}
|
||||
|
||||
const executeJavaScript = (contents, code, hasUserGesture) => {
|
||||
return ipcMainUtils.invokeInWebContents(contents, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture)
|
||||
}
|
||||
return ipcMainUtils.invokeInWebContents(contents, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture);
|
||||
};
|
||||
|
||||
// Make sure WebContents::executeJavaScript would run the code only when the
|
||||
// WebContents has been loaded.
|
||||
WebContents.prototype.executeJavaScript = function (code, hasUserGesture) {
|
||||
if (this.getURL() && !this.isLoadingMainFrame()) {
|
||||
return executeJavaScript(this, code, hasUserGesture)
|
||||
return executeJavaScript(this, code, hasUserGesture);
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.once('did-stop-loading', () => {
|
||||
executeJavaScript(this, code, hasUserGesture).then(resolve, reject)
|
||||
})
|
||||
})
|
||||
executeJavaScript(this, code, hasUserGesture).then(resolve, reject);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Translate the options of printToPDF.
|
||||
WebContents.prototype.printToPDF = function (options) {
|
||||
const printingSetting = {
|
||||
...defaultPrintingSetting,
|
||||
requestID: getNextId()
|
||||
}
|
||||
};
|
||||
if (options.landscape) {
|
||||
printingSetting.landscape = options.landscape
|
||||
printingSetting.landscape = options.landscape;
|
||||
}
|
||||
if (options.marginsType) {
|
||||
printingSetting.marginsType = options.marginsType
|
||||
printingSetting.marginsType = options.marginsType;
|
||||
}
|
||||
if (options.printSelectionOnly) {
|
||||
printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly
|
||||
printingSetting.shouldPrintSelectionOnly = options.printSelectionOnly;
|
||||
}
|
||||
if (options.printBackground) {
|
||||
printingSetting.shouldPrintBackgrounds = options.printBackground
|
||||
printingSetting.shouldPrintBackgrounds = options.printBackground;
|
||||
}
|
||||
|
||||
if (options.pageSize) {
|
||||
const pageSize = options.pageSize
|
||||
const pageSize = options.pageSize;
|
||||
if (typeof pageSize === 'object') {
|
||||
if (!pageSize.height || !pageSize.width) {
|
||||
return Promise.reject(new Error('Must define height and width for pageSize'))
|
||||
return Promise.reject(new Error('Must define height and width for pageSize'));
|
||||
}
|
||||
// Dimensions in Microns
|
||||
// 1 meter = 10^6 microns
|
||||
@@ -235,47 +235,47 @@ WebContents.prototype.printToPDF = function (options) {
|
||||
custom_display_name: 'Custom',
|
||||
height_microns: Math.ceil(pageSize.height),
|
||||
width_microns: Math.ceil(pageSize.width)
|
||||
}
|
||||
};
|
||||
} else if (PDFPageSizes[pageSize]) {
|
||||
printingSetting.mediaSize = PDFPageSizes[pageSize]
|
||||
printingSetting.mediaSize = PDFPageSizes[pageSize];
|
||||
} else {
|
||||
return Promise.reject(new Error(`Does not support pageSize with ${pageSize}`))
|
||||
return Promise.reject(new Error(`Does not support pageSize with ${pageSize}`));
|
||||
}
|
||||
} else {
|
||||
printingSetting.mediaSize = PDFPageSizes['A4']
|
||||
printingSetting.mediaSize = PDFPageSizes['A4'];
|
||||
}
|
||||
|
||||
// Chromium expects this in a 0-100 range number, not as float
|
||||
printingSetting.scaleFactor *= 100
|
||||
printingSetting.scaleFactor *= 100;
|
||||
if (features.isPrintingEnabled()) {
|
||||
return this._printToPDF(printingSetting)
|
||||
return this._printToPDF(printingSetting);
|
||||
} else {
|
||||
return Promise.reject(new Error('Printing feature is disabled'))
|
||||
return Promise.reject(new Error('Printing feature is disabled'));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebContents.prototype.print = function (...args) {
|
||||
if (features.isPrintingEnabled()) {
|
||||
this._print(...args)
|
||||
this._print(...args);
|
||||
} else {
|
||||
console.error('Error: Printing feature is disabled.')
|
||||
console.error('Error: Printing feature is disabled.');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebContents.prototype.getPrinters = function () {
|
||||
if (features.isPrintingEnabled()) {
|
||||
return this._getPrinters()
|
||||
return this._getPrinters();
|
||||
} else {
|
||||
console.error('Error: Printing feature is disabled.')
|
||||
return []
|
||||
console.error('Error: Printing feature is disabled.');
|
||||
return [];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
WebContents.prototype.loadFile = function (filePath, options = {}) {
|
||||
if (typeof filePath !== 'string') {
|
||||
throw new Error('Must pass filePath as a string')
|
||||
throw new Error('Must pass filePath as a string');
|
||||
}
|
||||
const { query, search, hash } = options
|
||||
const { query, search, hash } = options;
|
||||
|
||||
return this.loadURL(url.format({
|
||||
protocol: 'file',
|
||||
@@ -284,98 +284,98 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
||||
query,
|
||||
search,
|
||||
hash
|
||||
}))
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
const addReplyToEvent = (event) => {
|
||||
event.reply = (...args) => {
|
||||
event.sender.sendToFrame(event.frameId, ...args)
|
||||
}
|
||||
}
|
||||
event.sender.sendToFrame(event.frameId, ...args);
|
||||
};
|
||||
};
|
||||
|
||||
const addReplyInternalToEvent = (event) => {
|
||||
Object.defineProperty(event, '_replyInternal', {
|
||||
configurable: false,
|
||||
enumerable: false,
|
||||
value: (...args) => {
|
||||
event.sender._sendToFrameInternal(event.frameId, ...args)
|
||||
event.sender._sendToFrameInternal(event.frameId, ...args);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const addReturnValueToEvent = (event) => {
|
||||
Object.defineProperty(event, 'returnValue', {
|
||||
set: (value) => event.sendReply([value]),
|
||||
get: () => {}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// Add JavaScript wrappers for WebContents class.
|
||||
WebContents.prototype._init = function () {
|
||||
// The navigation controller.
|
||||
NavigationController.call(this, this)
|
||||
NavigationController.call(this, this);
|
||||
|
||||
// Every remote callback from renderer process would add a listener to the
|
||||
// render-view-deleted event, so ignore the listeners warning.
|
||||
this.setMaxListeners(0)
|
||||
this.setMaxListeners(0);
|
||||
|
||||
// Dispatch IPC messages to the ipc module.
|
||||
this.on('-ipc-message', function (event, internal, channel, args) {
|
||||
if (internal) {
|
||||
addReplyInternalToEvent(event)
|
||||
ipcMainInternal.emit(channel, event, ...args)
|
||||
addReplyInternalToEvent(event);
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event)
|
||||
this.emit('ipc-message', event, channel, ...args)
|
||||
ipcMain.emit(channel, event, ...args)
|
||||
addReplyToEvent(event);
|
||||
this.emit('ipc-message', event, channel, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.on('-ipc-invoke', function (event, channel, args) {
|
||||
event._reply = (result) => event.sendReply({ result })
|
||||
event._reply = (result) => event.sendReply({ result });
|
||||
event._throw = (error) => {
|
||||
console.error(`Error occurred in handler for '${channel}':`, error)
|
||||
event.sendReply({ error: error.toString() })
|
||||
}
|
||||
console.error(`Error occurred in handler for '${channel}':`, error);
|
||||
event.sendReply({ error: error.toString() });
|
||||
};
|
||||
if (ipcMain._invokeHandlers.has(channel)) {
|
||||
ipcMain._invokeHandlers.get(channel)(event, ...args)
|
||||
ipcMain._invokeHandlers.get(channel)(event, ...args);
|
||||
} else {
|
||||
event._throw(`No handler registered for '${channel}'`)
|
||||
event._throw(`No handler registered for '${channel}'`);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
this.on('-ipc-message-sync', function (event, internal, channel, args) {
|
||||
addReturnValueToEvent(event)
|
||||
addReturnValueToEvent(event);
|
||||
if (internal) {
|
||||
addReplyInternalToEvent(event)
|
||||
ipcMainInternal.emit(channel, event, ...args)
|
||||
addReplyInternalToEvent(event);
|
||||
ipcMainInternal.emit(channel, event, ...args);
|
||||
} else {
|
||||
addReplyToEvent(event)
|
||||
this.emit('ipc-message-sync', event, channel, ...args)
|
||||
ipcMain.emit(channel, event, ...args)
|
||||
addReplyToEvent(event);
|
||||
this.emit('ipc-message-sync', event, channel, ...args);
|
||||
ipcMain.emit(channel, event, ...args);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Handle context menu action request from pepper plugin.
|
||||
this.on('pepper-context-menu', function (event, params, callback) {
|
||||
// Access Menu via electron.Menu to prevent circular require.
|
||||
const menu = electron.Menu.buildFromTemplate(params.menu)
|
||||
const menu = electron.Menu.buildFromTemplate(params.menu);
|
||||
menu.popup({
|
||||
window: event.sender.getOwnerBrowserWindow(),
|
||||
x: params.x,
|
||||
y: params.y,
|
||||
callback
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
this.on('crashed', (event, ...args) => {
|
||||
app.emit('renderer-process-crashed', event, this, ...args)
|
||||
})
|
||||
app.emit('renderer-process-crashed', event, this, ...args);
|
||||
});
|
||||
|
||||
// The devtools requests the webContents to reload.
|
||||
this.on('devtools-reload-page', function () {
|
||||
this.reload()
|
||||
})
|
||||
this.reload();
|
||||
});
|
||||
|
||||
// Handle window.open for BrowserWindow and BrowserView.
|
||||
if (['browserView', 'window'].includes(this.getType())) {
|
||||
@@ -387,9 +387,9 @@ WebContents.prototype._init = function () {
|
||||
show: true,
|
||||
width: 800,
|
||||
height: 600
|
||||
}
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData)
|
||||
})
|
||||
};
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures, postData);
|
||||
});
|
||||
|
||||
// Create a new browser window for the native implementation of
|
||||
// "window.open", used in sandbox and nativeWindowOpen mode.
|
||||
@@ -397,8 +397,8 @@ WebContents.prototype._init = function () {
|
||||
userGesture, left, top, width, height, url, frameName) => {
|
||||
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
|
||||
disposition !== 'background-tab')) {
|
||||
event.preventDefault()
|
||||
return
|
||||
event.preventDefault();
|
||||
return;
|
||||
}
|
||||
|
||||
const options = {
|
||||
@@ -408,54 +408,54 @@ WebContents.prototype._init = function () {
|
||||
width: width || 800,
|
||||
height: height || 600,
|
||||
webContents
|
||||
}
|
||||
const referrer = { url: '', policy: 'default' }
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options)
|
||||
})
|
||||
};
|
||||
const referrer = { url: '', policy: 'default' };
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options);
|
||||
});
|
||||
}
|
||||
|
||||
this.on('login', (event, ...args) => {
|
||||
app.emit('login', event, this, ...args)
|
||||
})
|
||||
app.emit('login', event, this, ...args);
|
||||
});
|
||||
|
||||
const event = process.electronBinding('event').createEmpty()
|
||||
app.emit('web-contents-created', event, this)
|
||||
}
|
||||
const event = process.electronBinding('event').createEmpty();
|
||||
app.emit('web-contents-created', event, this);
|
||||
};
|
||||
|
||||
// Deprecations
|
||||
deprecate.fnToProperty(WebContents.prototype, 'audioMuted', '_isAudioMuted', '_setAudioMuted')
|
||||
deprecate.fnToProperty(WebContents.prototype, 'userAgent', '_getUserAgent', '_setUserAgent')
|
||||
deprecate.fnToProperty(WebContents.prototype, 'zoomLevel', '_getZoomLevel', '_setZoomLevel')
|
||||
deprecate.fnToProperty(WebContents.prototype, 'zoomFactor', '_getZoomFactor', '_setZoomFactor')
|
||||
deprecate.fnToProperty(WebContents.prototype, 'frameRate', '_getFrameRate', '_setFrameRate')
|
||||
deprecate.fnToProperty(WebContents.prototype, 'audioMuted', '_isAudioMuted', '_setAudioMuted');
|
||||
deprecate.fnToProperty(WebContents.prototype, 'userAgent', '_getUserAgent', '_setUserAgent');
|
||||
deprecate.fnToProperty(WebContents.prototype, 'zoomLevel', '_getZoomLevel', '_setZoomLevel');
|
||||
deprecate.fnToProperty(WebContents.prototype, 'zoomFactor', '_getZoomFactor', '_setZoomFactor');
|
||||
deprecate.fnToProperty(WebContents.prototype, 'frameRate', '_getFrameRate', '_setFrameRate');
|
||||
|
||||
// JavaScript wrapper of Debugger.
|
||||
const { Debugger } = process.electronBinding('debugger')
|
||||
Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype)
|
||||
const { Debugger } = process.electronBinding('debugger');
|
||||
Object.setPrototypeOf(Debugger.prototype, EventEmitter.prototype);
|
||||
|
||||
// Public APIs.
|
||||
module.exports = {
|
||||
create (options = {}) {
|
||||
return binding.create(options)
|
||||
return binding.create(options);
|
||||
},
|
||||
|
||||
fromId (id) {
|
||||
return binding.fromId(id)
|
||||
return binding.fromId(id);
|
||||
},
|
||||
|
||||
getFocusedWebContents () {
|
||||
let focused = null
|
||||
let focused = null;
|
||||
for (const contents of binding.getAllWebContents()) {
|
||||
if (!contents.isFocused()) continue
|
||||
if (focused == null) focused = contents
|
||||
if (!contents.isFocused()) continue;
|
||||
if (focused == null) focused = contents;
|
||||
// Return webview web contents which may be embedded inside another
|
||||
// web contents that is also reporting as focused
|
||||
if (contents.getType() === 'webview') return contents
|
||||
if (contents.getType() === 'webview') return contents;
|
||||
}
|
||||
return focused
|
||||
return focused;
|
||||
},
|
||||
|
||||
getAllWebContents () {
|
||||
return binding.getAllWebContents()
|
||||
return binding.getAllWebContents();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,57 +1,57 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { app, webContents, BrowserWindow } = require('electron')
|
||||
const { getAllWebContents } = process.electronBinding('web_contents')
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||
const { app, webContents, BrowserWindow } = require('electron');
|
||||
const { getAllWebContents } = process.electronBinding('web_contents');
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const url = require('url')
|
||||
const util = require('util')
|
||||
const { Buffer } = require('buffer');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const url = require('url');
|
||||
const util = require('util');
|
||||
|
||||
// Mapping between extensionId(hostname) and manifest.
|
||||
const manifestMap = {} // extensionId => manifest
|
||||
const manifestNameMap = {} // name => manifest
|
||||
const devToolsExtensionNames = new Set()
|
||||
const manifestMap = {}; // extensionId => manifest
|
||||
const manifestNameMap = {}; // name => manifest
|
||||
const devToolsExtensionNames = new Set();
|
||||
|
||||
const generateExtensionIdFromName = function (name) {
|
||||
return name.replace(/[\W_]+/g, '-').toLowerCase()
|
||||
}
|
||||
return name.replace(/[\W_]+/g, '-').toLowerCase();
|
||||
};
|
||||
|
||||
const isWindowOrWebView = function (webContents) {
|
||||
const type = webContents.getType()
|
||||
return type === 'window' || type === 'webview'
|
||||
}
|
||||
const type = webContents.getType();
|
||||
return type === 'window' || type === 'webview';
|
||||
};
|
||||
|
||||
const isBackgroundPage = function (webContents) {
|
||||
return webContents.getType() === 'backgroundPage'
|
||||
}
|
||||
return webContents.getType() === 'backgroundPage';
|
||||
};
|
||||
|
||||
// Create or get manifest object from |srcDirectory|.
|
||||
const getManifestFromPath = function (srcDirectory) {
|
||||
let manifest
|
||||
let manifestContent
|
||||
let manifest;
|
||||
let manifestContent;
|
||||
|
||||
try {
|
||||
manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'))
|
||||
manifestContent = fs.readFileSync(path.join(srcDirectory, 'manifest.json'));
|
||||
} catch (readError) {
|
||||
console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`)
|
||||
console.warn(readError.stack || readError)
|
||||
throw readError
|
||||
console.warn(`Reading ${path.join(srcDirectory, 'manifest.json')} failed.`);
|
||||
console.warn(readError.stack || readError);
|
||||
throw readError;
|
||||
}
|
||||
|
||||
try {
|
||||
manifest = JSON.parse(manifestContent)
|
||||
manifest = JSON.parse(manifestContent);
|
||||
} catch (parseError) {
|
||||
console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`)
|
||||
console.warn(parseError.stack || parseError)
|
||||
throw parseError
|
||||
console.warn(`Parsing ${path.join(srcDirectory, 'manifest.json')} failed.`);
|
||||
console.warn(parseError.stack || parseError);
|
||||
throw parseError;
|
||||
}
|
||||
|
||||
if (!manifestNameMap[manifest.name]) {
|
||||
const extensionId = generateExtensionIdFromName(manifest.name)
|
||||
manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest
|
||||
const extensionId = generateExtensionIdFromName(manifest.name);
|
||||
manifestMap[extensionId] = manifestNameMap[manifest.name] = manifest;
|
||||
Object.assign(manifest, {
|
||||
srcDirectory: srcDirectory,
|
||||
extensionId: extensionId,
|
||||
@@ -63,31 +63,31 @@ const getManifestFromPath = function (srcDirectory) {
|
||||
hostname: extensionId,
|
||||
pathname: manifest.devtools_page
|
||||
})
|
||||
})
|
||||
return manifest
|
||||
});
|
||||
return manifest;
|
||||
} else if (manifest && manifest.name) {
|
||||
console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`)
|
||||
return manifest
|
||||
console.warn(`Attempted to load extension "${manifest.name}" that has already been loaded.`);
|
||||
return manifest;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Manage the background pages.
|
||||
const backgroundPages = {}
|
||||
const backgroundPages = {};
|
||||
|
||||
const startBackgroundPages = function (manifest) {
|
||||
if (backgroundPages[manifest.extensionId] || !manifest.background) return
|
||||
if (backgroundPages[manifest.extensionId] || !manifest.background) return;
|
||||
|
||||
let html
|
||||
let name
|
||||
let html;
|
||||
let name;
|
||||
if (manifest.background.page) {
|
||||
name = manifest.background.page
|
||||
html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page))
|
||||
name = manifest.background.page;
|
||||
html = fs.readFileSync(path.join(manifest.srcDirectory, manifest.background.page));
|
||||
} else {
|
||||
name = '_generated_background_page.html'
|
||||
name = '_generated_background_page.html';
|
||||
const scripts = manifest.background.scripts.map((name) => {
|
||||
return `<script src="${name}"></script>`
|
||||
}).join('')
|
||||
html = Buffer.from(`<html><body>${scripts}</body></html>`)
|
||||
return `<script src="${name}"></script>`;
|
||||
}).join('');
|
||||
html = Buffer.from(`<html><body>${scripts}</body></html>`);
|
||||
}
|
||||
|
||||
const contents = webContents.create({
|
||||
@@ -95,36 +95,36 @@ const startBackgroundPages = function (manifest) {
|
||||
type: 'backgroundPage',
|
||||
sandbox: true,
|
||||
enableRemoteModule: false
|
||||
})
|
||||
backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name }
|
||||
});
|
||||
backgroundPages[manifest.extensionId] = { html: html, webContents: contents, name: name };
|
||||
contents.loadURL(url.format({
|
||||
protocol: 'chrome-extension',
|
||||
slashes: true,
|
||||
hostname: manifest.extensionId,
|
||||
pathname: name
|
||||
}))
|
||||
}
|
||||
}));
|
||||
};
|
||||
|
||||
const removeBackgroundPages = function (manifest) {
|
||||
if (!backgroundPages[manifest.extensionId]) return
|
||||
if (!backgroundPages[manifest.extensionId]) return;
|
||||
|
||||
backgroundPages[manifest.extensionId].webContents.destroy()
|
||||
delete backgroundPages[manifest.extensionId]
|
||||
}
|
||||
backgroundPages[manifest.extensionId].webContents.destroy();
|
||||
delete backgroundPages[manifest.extensionId];
|
||||
};
|
||||
|
||||
const sendToBackgroundPages = function (...args) {
|
||||
for (const page of Object.values(backgroundPages)) {
|
||||
if (!page.webContents.isDestroyed()) {
|
||||
page.webContents._sendInternalToAll(...args)
|
||||
page.webContents._sendInternalToAll(...args);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Dispatch web contents events to Chrome APIs
|
||||
const hookWebContentsEvents = function (webContents) {
|
||||
const tabId = webContents.id
|
||||
const tabId = webContents.id;
|
||||
|
||||
sendToBackgroundPages('CHROME_TABS_ONCREATED')
|
||||
sendToBackgroundPages('CHROME_TABS_ONCREATED');
|
||||
|
||||
webContents.on('will-navigate', (event, url) => {
|
||||
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', {
|
||||
@@ -134,8 +134,8 @@ const hookWebContentsEvents = function (webContents) {
|
||||
tabId: tabId,
|
||||
timeStamp: Date.now(),
|
||||
url: url
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
webContents.on('did-navigate', (event, url) => {
|
||||
sendToBackgroundPages('CHROME_WEBNAVIGATION_ONCOMPLETED', {
|
||||
@@ -145,189 +145,189 @@ const hookWebContentsEvents = function (webContents) {
|
||||
tabId: tabId,
|
||||
timeStamp: Date.now(),
|
||||
url: url
|
||||
})
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
webContents.once('destroyed', () => {
|
||||
sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId)
|
||||
})
|
||||
}
|
||||
sendToBackgroundPages('CHROME_TABS_ONREMOVED', tabId);
|
||||
});
|
||||
};
|
||||
|
||||
// Handle the chrome.* API messages.
|
||||
let nextId = 0
|
||||
let nextId = 0;
|
||||
|
||||
ipcMainUtils.handle('CHROME_RUNTIME_CONNECT', function (event, extensionId, connectInfo) {
|
||||
if (isBackgroundPage(event.sender)) {
|
||||
throw new Error('chrome.runtime.connect is not supported in background page')
|
||||
throw new Error('chrome.runtime.connect is not supported in background page');
|
||||
}
|
||||
|
||||
const page = backgroundPages[extensionId]
|
||||
const page = backgroundPages[extensionId];
|
||||
if (!page || page.webContents.isDestroyed()) {
|
||||
throw new Error(`Connect to unknown extension ${extensionId}`)
|
||||
throw new Error(`Connect to unknown extension ${extensionId}`);
|
||||
}
|
||||
|
||||
const tabId = page.webContents.id
|
||||
const portId = ++nextId
|
||||
const tabId = page.webContents.id;
|
||||
const portId = ++nextId;
|
||||
|
||||
event.sender.once('render-view-deleted', () => {
|
||||
if (page.webContents.isDestroyed()) return
|
||||
page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`)
|
||||
})
|
||||
page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo)
|
||||
if (page.webContents.isDestroyed()) return;
|
||||
page.webContents._sendInternalToAll(`CHROME_PORT_DISCONNECT_${portId}`);
|
||||
});
|
||||
page.webContents._sendInternalToAll(`CHROME_RUNTIME_ONCONNECT_${extensionId}`, event.sender.id, portId, connectInfo);
|
||||
|
||||
return { tabId, portId }
|
||||
})
|
||||
return { tabId, portId };
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('CHROME_EXTENSION_MANIFEST', function (event, extensionId) {
|
||||
const manifest = manifestMap[extensionId]
|
||||
const manifest = manifestMap[extensionId];
|
||||
if (!manifest) {
|
||||
throw new Error(`Invalid extensionId: ${extensionId}`)
|
||||
throw new Error(`Invalid extensionId: ${extensionId}`);
|
||||
}
|
||||
return manifest
|
||||
})
|
||||
return manifest;
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('CHROME_RUNTIME_SEND_MESSAGE', async function (event, extensionId, message) {
|
||||
if (isBackgroundPage(event.sender)) {
|
||||
throw new Error('chrome.runtime.sendMessage is not supported in background page')
|
||||
throw new Error('chrome.runtime.sendMessage is not supported in background page');
|
||||
}
|
||||
|
||||
const page = backgroundPages[extensionId]
|
||||
const page = backgroundPages[extensionId];
|
||||
if (!page || page.webContents.isDestroyed()) {
|
||||
throw new Error(`Connect to unknown extension ${extensionId}`)
|
||||
throw new Error(`Connect to unknown extension ${extensionId}`);
|
||||
}
|
||||
|
||||
return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message)
|
||||
})
|
||||
return ipcMainUtils.invokeInWebContents(page.webContents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, event.sender.id, message);
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('CHROME_TABS_SEND_MESSAGE', async function (event, tabId, extensionId, message) {
|
||||
const contents = webContents.fromId(tabId)
|
||||
const contents = webContents.fromId(tabId);
|
||||
if (!contents) {
|
||||
throw new Error(`Sending message to unknown tab ${tabId}`)
|
||||
throw new Error(`Sending message to unknown tab ${tabId}`);
|
||||
}
|
||||
|
||||
const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id
|
||||
const senderTabId = isBackgroundPage(event.sender) ? null : event.sender.id;
|
||||
|
||||
return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message)
|
||||
})
|
||||
return ipcMainUtils.invokeInWebContents(contents, true, `CHROME_RUNTIME_ONMESSAGE_${extensionId}`, senderTabId, message);
|
||||
});
|
||||
|
||||
const getLanguage = () => {
|
||||
return app.getLocale().replace(/-.*$/, '').toLowerCase()
|
||||
}
|
||||
return app.getLocale().replace(/-.*$/, '').toLowerCase();
|
||||
};
|
||||
|
||||
const getMessagesPath = (extensionId) => {
|
||||
const metadata = manifestMap[extensionId]
|
||||
const metadata = manifestMap[extensionId];
|
||||
if (!metadata) {
|
||||
throw new Error(`Invalid extensionId: ${extensionId}`)
|
||||
throw new Error(`Invalid extensionId: ${extensionId}`);
|
||||
}
|
||||
|
||||
const localesDirectory = path.join(metadata.srcDirectory, '_locales')
|
||||
const language = getLanguage()
|
||||
const localesDirectory = path.join(metadata.srcDirectory, '_locales');
|
||||
const language = getLanguage();
|
||||
|
||||
try {
|
||||
const filename = path.join(localesDirectory, language, 'messages.json')
|
||||
fs.accessSync(filename, fs.constants.R_OK)
|
||||
return filename
|
||||
const filename = path.join(localesDirectory, language, 'messages.json');
|
||||
fs.accessSync(filename, fs.constants.R_OK);
|
||||
return filename;
|
||||
} catch {
|
||||
const defaultLocale = metadata.default_locale || 'en'
|
||||
return path.join(localesDirectory, defaultLocale, 'messages.json')
|
||||
const defaultLocale = metadata.default_locale || 'en';
|
||||
return path.join(localesDirectory, defaultLocale, 'messages.json');
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ipcMainUtils.handle('CHROME_GET_MESSAGES', async function (event, extensionId) {
|
||||
const messagesPath = getMessagesPath(extensionId)
|
||||
return fs.promises.readFile(messagesPath)
|
||||
})
|
||||
const messagesPath = getMessagesPath(extensionId);
|
||||
return fs.promises.readFile(messagesPath);
|
||||
});
|
||||
|
||||
const validStorageTypes = new Set(['sync', 'local'])
|
||||
const validStorageTypes = new Set(['sync', 'local']);
|
||||
|
||||
const getChromeStoragePath = (storageType, extensionId) => {
|
||||
if (!validStorageTypes.has(storageType)) {
|
||||
throw new Error(`Invalid storageType: ${storageType}`)
|
||||
throw new Error(`Invalid storageType: ${storageType}`);
|
||||
}
|
||||
|
||||
if (!manifestMap[extensionId]) {
|
||||
throw new Error(`Invalid extensionId: ${extensionId}`)
|
||||
throw new Error(`Invalid extensionId: ${extensionId}`);
|
||||
}
|
||||
|
||||
return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`)
|
||||
}
|
||||
return path.join(app.getPath('userData'), `/Chrome Storage/${extensionId}-${storageType}.json`);
|
||||
};
|
||||
|
||||
ipcMainUtils.handle('CHROME_STORAGE_READ', async function (event, storageType, extensionId) {
|
||||
const filePath = getChromeStoragePath(storageType, extensionId)
|
||||
const filePath = getChromeStoragePath(storageType, extensionId);
|
||||
|
||||
try {
|
||||
return await fs.promises.readFile(filePath, 'utf8')
|
||||
return await fs.promises.readFile(filePath, 'utf8');
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return null
|
||||
return null;
|
||||
} else {
|
||||
throw error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('CHROME_STORAGE_WRITE', async function (event, storageType, extensionId, data) {
|
||||
const filePath = getChromeStoragePath(storageType, extensionId)
|
||||
const filePath = getChromeStoragePath(storageType, extensionId);
|
||||
|
||||
try {
|
||||
await fs.promises.mkdir(path.dirname(filePath), { recursive: true })
|
||||
await fs.promises.mkdir(path.dirname(filePath), { recursive: true });
|
||||
} catch {
|
||||
// we just ignore the errors of mkdir
|
||||
}
|
||||
|
||||
return fs.promises.writeFile(filePath, data, 'utf8')
|
||||
})
|
||||
return fs.promises.writeFile(filePath, data, 'utf8');
|
||||
});
|
||||
|
||||
const isChromeExtension = function (pageURL) {
|
||||
const { protocol } = url.parse(pageURL)
|
||||
return protocol === 'chrome-extension:'
|
||||
}
|
||||
const { protocol } = url.parse(pageURL);
|
||||
return protocol === 'chrome-extension:';
|
||||
};
|
||||
|
||||
const assertChromeExtension = function (contents, api) {
|
||||
const pageURL = contents._getURL()
|
||||
const pageURL = contents._getURL();
|
||||
if (!isChromeExtension(pageURL)) {
|
||||
console.error(`Blocked ${pageURL} from calling ${api}`)
|
||||
throw new Error(`Blocked ${api}`)
|
||||
console.error(`Blocked ${pageURL} from calling ${api}`);
|
||||
throw new Error(`Blocked ${api}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ipcMainUtils.handle('CHROME_TABS_EXECUTE_SCRIPT', async function (event, tabId, extensionId, details) {
|
||||
assertChromeExtension(event.sender, 'chrome.tabs.executeScript()')
|
||||
assertChromeExtension(event.sender, 'chrome.tabs.executeScript()');
|
||||
|
||||
const contents = webContents.fromId(tabId)
|
||||
const contents = webContents.fromId(tabId);
|
||||
if (!contents) {
|
||||
throw new Error(`Sending message to unknown tab ${tabId}`)
|
||||
throw new Error(`Sending message to unknown tab ${tabId}`);
|
||||
}
|
||||
|
||||
let code, url
|
||||
let code, url;
|
||||
if (details.file) {
|
||||
const manifest = manifestMap[extensionId]
|
||||
code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)))
|
||||
url = `chrome-extension://${extensionId}${details.file}`
|
||||
const manifest = manifestMap[extensionId];
|
||||
code = String(fs.readFileSync(path.join(manifest.srcDirectory, details.file)));
|
||||
url = `chrome-extension://${extensionId}${details.file}`;
|
||||
} else {
|
||||
code = details.code
|
||||
url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`
|
||||
code = details.code;
|
||||
url = `chrome-extension://${extensionId}/${String(Math.random()).substr(2, 8)}.js`;
|
||||
}
|
||||
|
||||
return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code)
|
||||
})
|
||||
return ipcMainUtils.invokeInWebContents(contents, false, 'CHROME_TABS_EXECUTE_SCRIPT', extensionId, url, code);
|
||||
});
|
||||
|
||||
exports.getContentScripts = () => {
|
||||
return Object.values(contentScripts)
|
||||
}
|
||||
return Object.values(contentScripts);
|
||||
};
|
||||
|
||||
// Transfer the content scripts to renderer.
|
||||
const contentScripts = {}
|
||||
const contentScripts = {};
|
||||
|
||||
const injectContentScripts = function (manifest) {
|
||||
if (contentScripts[manifest.name] || !manifest.content_scripts) return
|
||||
if (contentScripts[manifest.name] || !manifest.content_scripts) return;
|
||||
|
||||
const readArrayOfFiles = function (relativePath) {
|
||||
return {
|
||||
url: `chrome-extension://${manifest.extensionId}/${relativePath}`,
|
||||
code: String(fs.readFileSync(path.join(manifest.srcDirectory, relativePath)))
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const contentScriptToEntry = function (script) {
|
||||
return {
|
||||
@@ -336,25 +336,25 @@ const injectContentScripts = function (manifest) {
|
||||
css: script.css ? script.css.map(readArrayOfFiles) : [],
|
||||
runAt: script.run_at || 'document_idle',
|
||||
allFrames: script.all_frames || false
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
try {
|
||||
const entry = {
|
||||
extensionId: manifest.extensionId,
|
||||
contentScripts: manifest.content_scripts.map(contentScriptToEntry)
|
||||
}
|
||||
contentScripts[manifest.name] = entry
|
||||
};
|
||||
contentScripts[manifest.name] = entry;
|
||||
} catch (e) {
|
||||
console.error('Failed to read content scripts', e)
|
||||
console.error('Failed to read content scripts', e);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const removeContentScripts = function (manifest) {
|
||||
if (!contentScripts[manifest.name]) return
|
||||
if (!contentScripts[manifest.name]) return;
|
||||
|
||||
delete contentScripts[manifest.name]
|
||||
}
|
||||
delete contentScripts[manifest.name];
|
||||
};
|
||||
|
||||
// Transfer the |manifest| to a format that can be recognized by the
|
||||
// |DevToolsAPI.addExtensions|.
|
||||
@@ -364,166 +364,166 @@ const manifestToExtensionInfo = function (manifest) {
|
||||
srcDirectory: manifest.srcDirectory,
|
||||
name: manifest.name,
|
||||
exposeExperimentalAPIs: true
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Load the extensions for the window.
|
||||
const loadExtension = function (manifest) {
|
||||
startBackgroundPages(manifest)
|
||||
injectContentScripts(manifest)
|
||||
}
|
||||
startBackgroundPages(manifest);
|
||||
injectContentScripts(manifest);
|
||||
};
|
||||
|
||||
const loadDevToolsExtensions = function (win, manifests) {
|
||||
if (!win.devToolsWebContents) return
|
||||
if (!win.devToolsWebContents) return;
|
||||
|
||||
manifests.forEach(loadExtension)
|
||||
manifests.forEach(loadExtension);
|
||||
|
||||
const extensionInfoArray = manifests.map(manifestToExtensionInfo)
|
||||
const extensionInfoArray = manifests.map(manifestToExtensionInfo);
|
||||
extensionInfoArray.forEach((extension) => {
|
||||
win.devToolsWebContents._grantOriginAccess(extension.startPage)
|
||||
})
|
||||
win.devToolsWebContents._grantOriginAccess(extension.startPage);
|
||||
});
|
||||
|
||||
extensionInfoArray.forEach((extensionInfo) => {
|
||||
win.devToolsWebContents.executeJavaScript(`Extensions.extensionServer._addExtension(${JSON.stringify(extensionInfo)})`)
|
||||
})
|
||||
}
|
||||
win.devToolsWebContents.executeJavaScript(`Extensions.extensionServer._addExtension(${JSON.stringify(extensionInfo)})`);
|
||||
});
|
||||
};
|
||||
|
||||
app.on('web-contents-created', function (event, webContents) {
|
||||
if (!isWindowOrWebView(webContents)) return
|
||||
if (!isWindowOrWebView(webContents)) return;
|
||||
|
||||
hookWebContentsEvents(webContents)
|
||||
hookWebContentsEvents(webContents);
|
||||
webContents.on('devtools-opened', function () {
|
||||
loadDevToolsExtensions(webContents, Object.values(manifestMap))
|
||||
})
|
||||
})
|
||||
loadDevToolsExtensions(webContents, Object.values(manifestMap));
|
||||
});
|
||||
});
|
||||
|
||||
// The chrome-extension: can map a extension URL request to real file path.
|
||||
const chromeExtensionHandler = function (request, callback) {
|
||||
const parsed = url.parse(request.url)
|
||||
if (!parsed.hostname || !parsed.path) return callback()
|
||||
const parsed = url.parse(request.url);
|
||||
if (!parsed.hostname || !parsed.path) return callback();
|
||||
|
||||
const manifest = manifestMap[parsed.hostname]
|
||||
if (!manifest) return callback()
|
||||
const manifest = manifestMap[parsed.hostname];
|
||||
if (!manifest) return callback();
|
||||
|
||||
const page = backgroundPages[parsed.hostname]
|
||||
const page = backgroundPages[parsed.hostname];
|
||||
if (page && parsed.path === `/${page.name}`) {
|
||||
// Disabled due to false positive in StandardJS
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return callback({
|
||||
mimeType: 'text/html',
|
||||
data: page.html
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fs.readFile(path.join(manifest.srcDirectory, parsed.path), function (err, content) {
|
||||
if (err) {
|
||||
// Disabled due to false positive in StandardJS
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
return callback(-6) // FILE_NOT_FOUND
|
||||
return callback(-6); // FILE_NOT_FOUND
|
||||
} else {
|
||||
return callback(content)
|
||||
return callback(content);
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
app.on('session-created', function (ses) {
|
||||
ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler)
|
||||
})
|
||||
ses.protocol.registerBufferProtocol('chrome-extension', chromeExtensionHandler);
|
||||
});
|
||||
|
||||
// The persistent path of "DevTools Extensions" preference file.
|
||||
let loadedDevToolsExtensionsPath = null
|
||||
let loadedDevToolsExtensionsPath = null;
|
||||
|
||||
app.on('will-quit', function () {
|
||||
try {
|
||||
const loadedDevToolsExtensions = Array.from(devToolsExtensionNames)
|
||||
.map(name => manifestNameMap[name].srcDirectory)
|
||||
.map(name => manifestNameMap[name].srcDirectory);
|
||||
if (loadedDevToolsExtensions.length > 0) {
|
||||
try {
|
||||
fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath))
|
||||
fs.mkdirSync(path.dirname(loadedDevToolsExtensionsPath));
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions))
|
||||
fs.writeFileSync(loadedDevToolsExtensionsPath, JSON.stringify(loadedDevToolsExtensions));
|
||||
} else {
|
||||
fs.unlinkSync(loadedDevToolsExtensionsPath)
|
||||
fs.unlinkSync(loadedDevToolsExtensionsPath);
|
||||
}
|
||||
} catch {
|
||||
// Ignore error
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// We can not use protocol or BrowserWindow until app is ready.
|
||||
app.once('ready', function () {
|
||||
// The public API to add/remove extensions.
|
||||
BrowserWindow.addExtension = function (srcDirectory) {
|
||||
const manifest = getManifestFromPath(srcDirectory)
|
||||
const manifest = getManifestFromPath(srcDirectory);
|
||||
if (manifest) {
|
||||
loadExtension(manifest)
|
||||
loadExtension(manifest);
|
||||
for (const webContents of getAllWebContents()) {
|
||||
if (isWindowOrWebView(webContents)) {
|
||||
loadDevToolsExtensions(webContents, [manifest])
|
||||
loadDevToolsExtensions(webContents, [manifest]);
|
||||
}
|
||||
}
|
||||
return manifest.name
|
||||
return manifest.name;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
BrowserWindow.removeExtension = function (name) {
|
||||
const manifest = manifestNameMap[name]
|
||||
if (!manifest) return
|
||||
const manifest = manifestNameMap[name];
|
||||
if (!manifest) return;
|
||||
|
||||
removeBackgroundPages(manifest)
|
||||
removeContentScripts(manifest)
|
||||
delete manifestMap[manifest.extensionId]
|
||||
delete manifestNameMap[name]
|
||||
}
|
||||
removeBackgroundPages(manifest);
|
||||
removeContentScripts(manifest);
|
||||
delete manifestMap[manifest.extensionId];
|
||||
delete manifestNameMap[name];
|
||||
};
|
||||
|
||||
BrowserWindow.getExtensions = function () {
|
||||
const extensions = {}
|
||||
const extensions = {};
|
||||
Object.keys(manifestNameMap).forEach(function (name) {
|
||||
const manifest = manifestNameMap[name]
|
||||
extensions[name] = { name: manifest.name, version: manifest.version }
|
||||
})
|
||||
return extensions
|
||||
}
|
||||
const manifest = manifestNameMap[name];
|
||||
extensions[name] = { name: manifest.name, version: manifest.version };
|
||||
});
|
||||
return extensions;
|
||||
};
|
||||
|
||||
BrowserWindow.addDevToolsExtension = function (srcDirectory) {
|
||||
const manifestName = BrowserWindow.addExtension(srcDirectory)
|
||||
const manifestName = BrowserWindow.addExtension(srcDirectory);
|
||||
if (manifestName) {
|
||||
devToolsExtensionNames.add(manifestName)
|
||||
devToolsExtensionNames.add(manifestName);
|
||||
}
|
||||
return manifestName
|
||||
}
|
||||
return manifestName;
|
||||
};
|
||||
|
||||
BrowserWindow.removeDevToolsExtension = function (name) {
|
||||
BrowserWindow.removeExtension(name)
|
||||
devToolsExtensionNames.delete(name)
|
||||
}
|
||||
BrowserWindow.removeExtension(name);
|
||||
devToolsExtensionNames.delete(name);
|
||||
};
|
||||
|
||||
BrowserWindow.getDevToolsExtensions = function () {
|
||||
const extensions = BrowserWindow.getExtensions()
|
||||
const devExtensions = {}
|
||||
const extensions = BrowserWindow.getExtensions();
|
||||
const devExtensions = {};
|
||||
Array.from(devToolsExtensionNames).forEach(function (name) {
|
||||
if (!extensions[name]) return
|
||||
devExtensions[name] = extensions[name]
|
||||
})
|
||||
return devExtensions
|
||||
}
|
||||
if (!extensions[name]) return;
|
||||
devExtensions[name] = extensions[name];
|
||||
});
|
||||
return devExtensions;
|
||||
};
|
||||
|
||||
// Load persisted extensions.
|
||||
loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions')
|
||||
loadedDevToolsExtensionsPath = path.join(app.getPath('userData'), 'DevTools Extensions');
|
||||
try {
|
||||
const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath))
|
||||
const loadedDevToolsExtensions = JSON.parse(fs.readFileSync(loadedDevToolsExtensionsPath));
|
||||
if (Array.isArray(loadedDevToolsExtensions)) {
|
||||
for (const srcDirectory of loadedDevToolsExtensions) {
|
||||
// Start background pages and set content scripts.
|
||||
BrowserWindow.addDevToolsExtension(srcDirectory)
|
||||
BrowserWindow.addDevToolsExtension(srcDirectory);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
if (process.env.ELECTRON_ENABLE_LOGGING && error.code !== 'ENOENT') {
|
||||
console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath)
|
||||
console.error(error)
|
||||
console.error('Failed to load browser extensions from directory:', loadedDevToolsExtensionsPath);
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
@@ -1,25 +1,25 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { app } = require('electron')
|
||||
const path = require('path')
|
||||
const { app } = require('electron');
|
||||
const path = require('path');
|
||||
|
||||
const getTempDirectory = function () {
|
||||
try {
|
||||
return app.getPath('temp')
|
||||
return app.getPath('temp');
|
||||
} catch {
|
||||
// Delibrately laze-load the os module, this file is on the hot
|
||||
// path when booting Electron and os takes between 5 - 8ms to load and we do not need it yet
|
||||
return require('os').tmpdir()
|
||||
return require('os').tmpdir();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
exports.crashReporterInit = function (options) {
|
||||
const productName = options.productName || app.name
|
||||
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`)
|
||||
const productName = options.productName || app.name;
|
||||
const crashesDirectory = path.join(getTempDirectory(), `${productName} Crashes`);
|
||||
|
||||
return {
|
||||
productName,
|
||||
crashesDirectory,
|
||||
appVersion: app.getVersion()
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { shell, Menu } from 'electron'
|
||||
import { shell, Menu } from 'electron';
|
||||
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const v8Util = process.electronBinding('v8_util');
|
||||
|
||||
const isMac = process.platform === 'darwin'
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
export const setDefaultApplicationMenu = () => {
|
||||
if (v8Util.getHiddenValue<boolean>(global, 'applicationMenuSet')) return
|
||||
if (v8Util.getHiddenValue<boolean>(global, 'applicationMenuSet')) return;
|
||||
|
||||
const helpMenu: Electron.MenuItemConstructorOptions = {
|
||||
role: 'help',
|
||||
@@ -13,32 +13,32 @@ export const setDefaultApplicationMenu = () => {
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://electronjs.org')
|
||||
await shell.openExternal('https://electronjs.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: async () => {
|
||||
const version = process.versions.electron
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`)
|
||||
const version = process.versions.electron;
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://discuss.atom.io/c/electron')
|
||||
await shell.openExternal('https://discuss.atom.io/c/electron');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://github.com/electron/electron/issues')
|
||||
await shell.openExternal('https://github.com/electron/electron/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' }
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [macAppMenu] : []),
|
||||
{ role: 'fileMenu' },
|
||||
@@ -46,8 +46,8 @@ export const setDefaultApplicationMenu = () => {
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' },
|
||||
helpMenu
|
||||
]
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template)
|
||||
Menu.setApplicationMenu(menu)
|
||||
}
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
Menu.setApplicationMenu(menu);
|
||||
};
|
||||
|
||||
@@ -1,66 +1,66 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const { createDesktopCapturer } = process.electronBinding('desktop_capturer')
|
||||
const { createDesktopCapturer } = process.electronBinding('desktop_capturer');
|
||||
|
||||
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b)
|
||||
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
|
||||
|
||||
let currentlyRunning: {
|
||||
options: ElectronInternal.GetSourcesOptions;
|
||||
getSources: Promise<ElectronInternal.GetSourcesResult[]>;
|
||||
}[] = []
|
||||
}[] = [];
|
||||
|
||||
export const getSources = (event: Electron.IpcMainEvent, options: ElectronInternal.GetSourcesOptions) => {
|
||||
for (const running of currentlyRunning) {
|
||||
if (deepEqual(running.options, options)) {
|
||||
// If a request is currently running for the same options
|
||||
// return that promise
|
||||
return running.getSources
|
||||
return running.getSources;
|
||||
}
|
||||
}
|
||||
|
||||
const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
|
||||
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer()
|
||||
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
|
||||
|
||||
const stopRunning = () => {
|
||||
if (capturer) {
|
||||
capturer.emit = null
|
||||
capturer = null
|
||||
capturer.emit = null;
|
||||
capturer = null;
|
||||
}
|
||||
// Remove from currentlyRunning once we resolve or reject
|
||||
currentlyRunning = currentlyRunning.filter(running => running.options !== options)
|
||||
}
|
||||
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
|
||||
};
|
||||
|
||||
const emitter = new EventEmitter()
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
emitter.once('error', (event, error: string) => {
|
||||
stopRunning()
|
||||
reject(error)
|
||||
})
|
||||
stopRunning();
|
||||
reject(error);
|
||||
});
|
||||
|
||||
emitter.once('finished', (event, sources: Electron.DesktopCapturerSource[], fetchWindowIcons: boolean) => {
|
||||
stopRunning()
|
||||
stopRunning();
|
||||
resolve(sources.map(source => ({
|
||||
id: source.id,
|
||||
name: source.name,
|
||||
thumbnail: source.thumbnail.toDataURL(),
|
||||
display_id: source.display_id,
|
||||
appIcon: (fetchWindowIcons && source.appIcon) ? source.appIcon.toDataURL() : null
|
||||
})))
|
||||
})
|
||||
})));
|
||||
});
|
||||
|
||||
capturer.emit = emitter.emit.bind(emitter)
|
||||
capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons)
|
||||
capturer.emit = emitter.emit.bind(emitter);
|
||||
capturer.startHandling(options.captureWindow, options.captureScreen, options.thumbnailSize, options.fetchWindowIcons);
|
||||
|
||||
// If the WebContents is destroyed before receiving result, just remove the
|
||||
// reference to emit and the capturer itself so that it never dispatches
|
||||
// back to the renderer
|
||||
event.sender.once('destroyed', () => stopRunning())
|
||||
})
|
||||
event.sender.once('destroyed', () => stopRunning());
|
||||
});
|
||||
|
||||
currentlyRunning.push({
|
||||
options,
|
||||
getSources
|
||||
})
|
||||
});
|
||||
|
||||
return getSources
|
||||
}
|
||||
return getSources;
|
||||
};
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { dialog, Menu } from 'electron'
|
||||
import * as fs from 'fs'
|
||||
import * as url from 'url'
|
||||
import { dialog, Menu } from 'electron';
|
||||
import * as fs from 'fs';
|
||||
import * as url from 'url';
|
||||
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
|
||||
|
||||
const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
|
||||
return items.map(function (item) {
|
||||
@@ -22,15 +22,15 @@ const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id:
|
||||
type: 'normal',
|
||||
label: item.label,
|
||||
enabled: item.enabled
|
||||
}
|
||||
};
|
||||
|
||||
if (item.id != null) {
|
||||
transformed.click = () => handler(item.id)
|
||||
transformed.click = () => handler(item.id);
|
||||
}
|
||||
|
||||
return transformed
|
||||
})
|
||||
}
|
||||
return transformed;
|
||||
});
|
||||
};
|
||||
|
||||
const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] {
|
||||
return [
|
||||
@@ -43,56 +43,56 @@ const getEditMenuItems = function (): Electron.MenuItemConstructorOptions[] {
|
||||
{ role: 'pasteAndMatchStyle' },
|
||||
{ role: 'delete' },
|
||||
{ role: 'selectAll' }
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const isChromeDevTools = function (pageURL: string) {
|
||||
const { protocol } = url.parse(pageURL)
|
||||
return protocol === 'devtools:'
|
||||
}
|
||||
const { protocol } = url.parse(pageURL);
|
||||
return protocol === 'devtools:';
|
||||
};
|
||||
|
||||
const assertChromeDevTools = function (contents: Electron.WebContents, api: string) {
|
||||
const pageURL = contents._getURL()
|
||||
const pageURL = contents._getURL();
|
||||
if (!isChromeDevTools(pageURL)) {
|
||||
console.error(`Blocked ${pageURL} from calling ${api}`)
|
||||
throw new Error(`Blocked ${api}`)
|
||||
console.error(`Blocked ${pageURL} from calling ${api}`);
|
||||
throw new Error(`Blocked ${api}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_INSPECTOR_CONTEXT_MENU', function (event: Electron.IpcMainEvent, items: ContextMenuItem[], isEditMenu: boolean) {
|
||||
return new Promise(resolve => {
|
||||
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()')
|
||||
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
|
||||
|
||||
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve)
|
||||
const menu = Menu.buildFromTemplate(template)
|
||||
const window = event.sender.getOwnerBrowserWindow()
|
||||
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
const window = event.sender.getOwnerBrowserWindow();
|
||||
|
||||
menu.popup({ window, callback: () => resolve() })
|
||||
})
|
||||
})
|
||||
menu.popup({ window, callback: () => resolve() });
|
||||
});
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_INSPECTOR_SELECT_FILE', async function (event: Electron.IpcMainEvent) {
|
||||
assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()')
|
||||
assertChromeDevTools(event.sender, 'window.UI.createFileSelectorElement()');
|
||||
|
||||
const result = await dialog.showOpenDialog({})
|
||||
if (result.canceled) return []
|
||||
const result = await dialog.showOpenDialog({});
|
||||
if (result.canceled) return [];
|
||||
|
||||
const path = result.filePaths[0]
|
||||
const data = await fs.promises.readFile(path)
|
||||
const path = result.filePaths[0];
|
||||
const data = await fs.promises.readFile(path);
|
||||
|
||||
return [path, data]
|
||||
})
|
||||
return [path, data];
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_INSPECTOR_CONFIRM', async function (event: Electron.IpcMainEvent, message: string = '', title: string = '') {
|
||||
assertChromeDevTools(event.sender, 'window.confirm()')
|
||||
assertChromeDevTools(event.sender, 'window.confirm()');
|
||||
|
||||
const options = {
|
||||
message: String(message),
|
||||
title: String(title),
|
||||
buttons: ['OK', 'Cancel'],
|
||||
cancelId: 1
|
||||
}
|
||||
const window = event.sender.getOwnerBrowserWindow()
|
||||
const { response } = await dialog.showMessageBox(window, options)
|
||||
return response === 0
|
||||
})
|
||||
};
|
||||
const window = event.sender.getOwnerBrowserWindow();
|
||||
const { response } = await dialog.showMessageBox(window, options);
|
||||
return response === 0;
|
||||
});
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { webContents } = require('electron')
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
|
||||
const { syncMethods, asyncMethods } = require('@electron/internal/common/web-view-methods')
|
||||
const { serialize } = require('@electron/internal/common/type-utils')
|
||||
const { webContents } = require('electron');
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
|
||||
const parseFeaturesString = require('@electron/internal/common/parse-features-string');
|
||||
const { syncMethods, asyncMethods, properties } = require('@electron/internal/common/web-view-methods');
|
||||
const { serialize } = require('@electron/internal/common/type-utils');
|
||||
|
||||
// Doesn't exist in early initialization.
|
||||
let webViewManager = null
|
||||
let webViewManager = null;
|
||||
|
||||
const supportedWebViewEvents = [
|
||||
'load-commit',
|
||||
@@ -44,142 +44,142 @@ const supportedWebViewEvents = [
|
||||
'found-in-page',
|
||||
'did-change-theme-color',
|
||||
'update-target-url'
|
||||
]
|
||||
];
|
||||
|
||||
const guestInstances = {}
|
||||
const embedderElementsMap = {}
|
||||
const guestInstances = {};
|
||||
const embedderElementsMap = {};
|
||||
|
||||
// Create a new guest instance.
|
||||
const createGuest = function (embedder, params) {
|
||||
if (webViewManager == null) {
|
||||
webViewManager = process.electronBinding('web_view_manager')
|
||||
webViewManager = process.electronBinding('web_view_manager');
|
||||
}
|
||||
|
||||
const guest = webContents.create({
|
||||
type: 'webview',
|
||||
partition: params.partition,
|
||||
embedder: embedder
|
||||
})
|
||||
const guestInstanceId = guest.id
|
||||
});
|
||||
const guestInstanceId = guest.id;
|
||||
guestInstances[guestInstanceId] = {
|
||||
guest: guest,
|
||||
embedder: embedder
|
||||
}
|
||||
};
|
||||
|
||||
// Clear the guest from map when it is destroyed.
|
||||
guest.once('destroyed', () => {
|
||||
if (guestInstanceId in guestInstances) {
|
||||
detachGuest(embedder, guestInstanceId)
|
||||
if (Object.prototype.hasOwnProperty.call(guestInstances, guestInstanceId)) {
|
||||
detachGuest(embedder, guestInstanceId);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Init guest web view after attached.
|
||||
guest.once('did-attach', function (event) {
|
||||
params = this.attachParams
|
||||
delete this.attachParams
|
||||
params = this.attachParams;
|
||||
delete this.attachParams;
|
||||
|
||||
const previouslyAttached = this.viewInstanceId != null
|
||||
this.viewInstanceId = params.instanceId
|
||||
const previouslyAttached = this.viewInstanceId != null;
|
||||
this.viewInstanceId = params.instanceId;
|
||||
|
||||
// Only load URL and set size on first attach
|
||||
if (previouslyAttached) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
if (params.src) {
|
||||
const opts = {}
|
||||
const opts = {};
|
||||
if (params.httpreferrer) {
|
||||
opts.httpReferrer = params.httpreferrer
|
||||
opts.httpReferrer = params.httpreferrer;
|
||||
}
|
||||
if (params.useragent) {
|
||||
opts.userAgent = params.useragent
|
||||
opts.userAgent = params.useragent;
|
||||
}
|
||||
this.loadURL(params.src, opts)
|
||||
this.loadURL(params.src, opts);
|
||||
}
|
||||
embedder.emit('did-attach-webview', event, guest)
|
||||
})
|
||||
embedder.emit('did-attach-webview', event, guest);
|
||||
});
|
||||
|
||||
const sendToEmbedder = (channel, ...args) => {
|
||||
if (!embedder.isDestroyed()) {
|
||||
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args)
|
||||
embedder._sendInternal(`${channel}-${guest.viewInstanceId}`, ...args);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Dispatch events to embedder.
|
||||
const fn = function (event) {
|
||||
guest.on(event, function (_, ...args) {
|
||||
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args)
|
||||
})
|
||||
}
|
||||
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT', event, ...args);
|
||||
});
|
||||
};
|
||||
for (const event of supportedWebViewEvents) {
|
||||
fn(event)
|
||||
fn(event);
|
||||
}
|
||||
|
||||
// Dispatch guest's IPC messages to embedder.
|
||||
guest.on('ipc-message-host', function (_, channel, args) {
|
||||
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args)
|
||||
})
|
||||
sendToEmbedder('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE', channel, ...args);
|
||||
});
|
||||
|
||||
// Notify guest of embedder window visibility when it is ready
|
||||
// FIXME Remove once https://github.com/electron/electron/issues/6828 is fixed
|
||||
guest.on('dom-ready', function () {
|
||||
const guestInstance = guestInstances[guestInstanceId]
|
||||
const guestInstance = guestInstances[guestInstanceId];
|
||||
if (guestInstance != null && guestInstance.visibilityState != null) {
|
||||
guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState)
|
||||
guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', guestInstance.visibilityState);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// Forward internal web contents event to embedder to handle
|
||||
// native window.open setup
|
||||
guest.on('-add-new-contents', (...args) => {
|
||||
if (guest.getLastWebPreferences().nativeWindowOpen === true) {
|
||||
const embedder = getEmbedder(guestInstanceId)
|
||||
const embedder = getEmbedder(guestInstanceId);
|
||||
if (embedder != null) {
|
||||
embedder.emit('-add-new-contents', ...args)
|
||||
embedder.emit('-add-new-contents', ...args);
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
return guestInstanceId
|
||||
}
|
||||
return guestInstanceId;
|
||||
};
|
||||
|
||||
// Attach the guest to an element of embedder.
|
||||
const attachGuest = function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
|
||||
const embedder = event.sender
|
||||
const embedder = event.sender;
|
||||
// Destroy the old guest when attaching.
|
||||
const key = `${embedder.id}-${elementInstanceId}`
|
||||
const oldGuestInstanceId = embedderElementsMap[key]
|
||||
const key = `${embedder.id}-${elementInstanceId}`;
|
||||
const oldGuestInstanceId = embedderElementsMap[key];
|
||||
if (oldGuestInstanceId != null) {
|
||||
// Reattachment to the same guest is just a no-op.
|
||||
if (oldGuestInstanceId === guestInstanceId) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
const oldGuestInstance = guestInstances[oldGuestInstanceId]
|
||||
const oldGuestInstance = guestInstances[oldGuestInstanceId];
|
||||
if (oldGuestInstance) {
|
||||
oldGuestInstance.guest.detachFromOuterFrame()
|
||||
oldGuestInstance.guest.detachFromOuterFrame();
|
||||
}
|
||||
}
|
||||
|
||||
const guestInstance = guestInstances[guestInstanceId]
|
||||
const guestInstance = guestInstances[guestInstanceId];
|
||||
// If this isn't a valid guest instance then do nothing.
|
||||
if (!guestInstance) {
|
||||
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`)
|
||||
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
|
||||
}
|
||||
const { guest } = guestInstance
|
||||
const { guest } = guestInstance;
|
||||
if (guest.hostWebContents !== event.sender) {
|
||||
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`)
|
||||
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}`
|
||||
delete embedderElementsMap[oldKey]
|
||||
const oldKey = `${guestInstance.embedder.id}-${guestInstance.elementInstanceId}`;
|
||||
delete embedderElementsMap[oldKey];
|
||||
|
||||
// Remove guest from embedder if moving across web views
|
||||
if (guest.viewInstanceId !== params.instanceId) {
|
||||
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId)
|
||||
guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`)
|
||||
webViewManager.removeGuest(guestInstance.embedder, guestInstanceId);
|
||||
guestInstance.embedder._sendInternal(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${guest.viewInstanceId}`);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
|
||||
webSecurity: !params.disablewebsecurity,
|
||||
enableBlinkFeatures: params.blinkfeatures,
|
||||
disableBlinkFeatures: params.disableblinkfeatures
|
||||
}
|
||||
};
|
||||
|
||||
// parse the 'webpreferences' attribute string, if set
|
||||
// this uses the same parsing rules as window.open uses for its features
|
||||
@@ -202,14 +202,14 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
|
||||
parseFeaturesString(params.webpreferences, function (key, value) {
|
||||
if (value === undefined) {
|
||||
// no value was specified, default it to true
|
||||
value = true
|
||||
value = true;
|
||||
}
|
||||
webPreferences[key] = value
|
||||
})
|
||||
webPreferences[key] = value;
|
||||
});
|
||||
}
|
||||
|
||||
if (params.preload) {
|
||||
webPreferences.preloadURL = params.preload
|
||||
webPreferences.preloadURL = params.preload;
|
||||
}
|
||||
|
||||
// Security options that guest will always inherit from embedder
|
||||
@@ -221,171 +221,189 @@ const attachGuest = function (event, embedderFrameId, elementInstanceId, guestIn
|
||||
['enableRemoteModule', false],
|
||||
['sandbox', true],
|
||||
['nodeIntegrationInSubFrames', false]
|
||||
])
|
||||
]);
|
||||
|
||||
// Inherit certain option values from embedder
|
||||
const lastWebPreferences = embedder.getLastWebPreferences()
|
||||
const lastWebPreferences = embedder.getLastWebPreferences();
|
||||
for (const [name, value] of inheritedWebPreferences) {
|
||||
if (lastWebPreferences[name] === value) {
|
||||
webPreferences[name] = value
|
||||
webPreferences[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
embedder.emit('will-attach-webview', event, webPreferences, params)
|
||||
embedder.emit('will-attach-webview', event, webPreferences, params);
|
||||
if (event.defaultPrevented) {
|
||||
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId
|
||||
guest.destroy()
|
||||
return
|
||||
if (guest.viewInstanceId == null) guest.viewInstanceId = params.instanceId;
|
||||
guest.destroy();
|
||||
return;
|
||||
}
|
||||
|
||||
guest.attachParams = params
|
||||
embedderElementsMap[key] = guestInstanceId
|
||||
guest.attachParams = params;
|
||||
embedderElementsMap[key] = guestInstanceId;
|
||||
|
||||
guest.setEmbedder(embedder)
|
||||
guestInstance.embedder = embedder
|
||||
guestInstance.elementInstanceId = elementInstanceId
|
||||
guest.setEmbedder(embedder);
|
||||
guestInstance.embedder = embedder;
|
||||
guestInstance.elementInstanceId = elementInstanceId;
|
||||
|
||||
watchEmbedder(embedder)
|
||||
watchEmbedder(embedder);
|
||||
|
||||
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences)
|
||||
guest.attachToIframe(embedder, embedderFrameId)
|
||||
}
|
||||
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences);
|
||||
guest.attachToIframe(embedder, embedderFrameId);
|
||||
};
|
||||
|
||||
// Remove an guest-embedder relationship.
|
||||
const detachGuest = function (embedder, guestInstanceId) {
|
||||
const guestInstance = guestInstances[guestInstanceId]
|
||||
const guestInstance = guestInstances[guestInstanceId];
|
||||
if (embedder !== guestInstance.embedder) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
webViewManager.removeGuest(embedder, guestInstanceId)
|
||||
delete guestInstances[guestInstanceId]
|
||||
webViewManager.removeGuest(embedder, guestInstanceId);
|
||||
delete guestInstances[guestInstanceId];
|
||||
|
||||
const key = `${embedder.id}-${guestInstance.elementInstanceId}`
|
||||
delete embedderElementsMap[key]
|
||||
}
|
||||
const key = `${embedder.id}-${guestInstance.elementInstanceId}`;
|
||||
delete embedderElementsMap[key];
|
||||
};
|
||||
|
||||
// Once an embedder has had a guest attached we watch it for destruction to
|
||||
// destroy any remaining guests.
|
||||
const watchedEmbedders = new Set()
|
||||
const watchedEmbedders = new Set();
|
||||
const watchEmbedder = function (embedder) {
|
||||
if (watchedEmbedders.has(embedder)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
watchedEmbedders.add(embedder)
|
||||
watchedEmbedders.add(embedder);
|
||||
|
||||
// Forward embedder window visiblity change events to guest
|
||||
const onVisibilityChange = function (visibilityState) {
|
||||
for (const guestInstanceId in guestInstances) {
|
||||
const guestInstance = guestInstances[guestInstanceId]
|
||||
guestInstance.visibilityState = visibilityState
|
||||
for (const guestInstanceId of Object.keys(guestInstances)) {
|
||||
const guestInstance = guestInstances[guestInstanceId];
|
||||
guestInstance.visibilityState = visibilityState;
|
||||
if (guestInstance.embedder === embedder) {
|
||||
guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState)
|
||||
guestInstance.guest._sendInternal('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', visibilityState);
|
||||
}
|
||||
}
|
||||
}
|
||||
embedder.on('-window-visibility-change', onVisibilityChange)
|
||||
};
|
||||
embedder.on('-window-visibility-change', onVisibilityChange);
|
||||
|
||||
embedder.once('will-destroy', () => {
|
||||
// Usually the guestInstances is cleared when guest is destroyed, but it
|
||||
// may happen that the embedder gets manually destroyed earlier than guest,
|
||||
// and the embedder will be invalid in the usual code path.
|
||||
for (const guestInstanceId in guestInstances) {
|
||||
const guestInstance = guestInstances[guestInstanceId]
|
||||
for (const guestInstanceId of Object.keys(guestInstances)) {
|
||||
const guestInstance = guestInstances[guestInstanceId];
|
||||
if (guestInstance.embedder === embedder) {
|
||||
detachGuest(embedder, parseInt(guestInstanceId))
|
||||
detachGuest(embedder, parseInt(guestInstanceId));
|
||||
}
|
||||
}
|
||||
// Clear the listeners.
|
||||
embedder.removeListener('-window-visibility-change', onVisibilityChange)
|
||||
watchedEmbedders.delete(embedder)
|
||||
})
|
||||
}
|
||||
embedder.removeListener('-window-visibility-change', onVisibilityChange);
|
||||
watchedEmbedders.delete(embedder);
|
||||
});
|
||||
};
|
||||
|
||||
const isWebViewTagEnabledCache = new WeakMap()
|
||||
const isWebViewTagEnabledCache = new WeakMap();
|
||||
|
||||
const isWebViewTagEnabled = function (contents) {
|
||||
if (!isWebViewTagEnabledCache.has(contents)) {
|
||||
const webPreferences = contents.getLastWebPreferences() || {}
|
||||
isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag)
|
||||
const webPreferences = contents.getLastWebPreferences() || {};
|
||||
isWebViewTagEnabledCache.set(contents, !!webPreferences.webviewTag);
|
||||
}
|
||||
|
||||
return isWebViewTagEnabledCache.get(contents)
|
||||
}
|
||||
return isWebViewTagEnabledCache.get(contents);
|
||||
};
|
||||
|
||||
const handleMessage = function (channel, handler) {
|
||||
ipcMainUtils.handle(channel, (event, ...args) => {
|
||||
if (isWebViewTagEnabled(event.sender)) {
|
||||
return handler(event, ...args)
|
||||
return handler(event, ...args);
|
||||
} else {
|
||||
console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`)
|
||||
throw new Error('<webview> disabled')
|
||||
console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`);
|
||||
throw new Error('<webview> disabled');
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CREATE_GUEST', function (event, params) {
|
||||
return createGuest(event.sender, params)
|
||||
})
|
||||
return createGuest(event.sender, params);
|
||||
});
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, embedderFrameId, elementInstanceId, guestInstanceId, params) {
|
||||
try {
|
||||
attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params)
|
||||
attachGuest(event, embedderFrameId, elementInstanceId, guestInstanceId, params);
|
||||
} catch (error) {
|
||||
console.error(`Guest attach failed: ${error}`)
|
||||
console.error(`Guest attach failed: ${error}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
// this message is sent by the actual <webview>
|
||||
ipcMainInternal.on('ELECTRON_GUEST_VIEW_MANAGER_FOCUS_CHANGE', function (event, focus, guestInstanceId) {
|
||||
const guest = getGuest(guestInstanceId)
|
||||
const guest = getGuest(guestInstanceId);
|
||||
if (guest === event.sender) {
|
||||
event.sender.emit('focus-change', {}, focus, guestInstanceId)
|
||||
event.sender.emit('focus-change', {}, focus, guestInstanceId);
|
||||
} else {
|
||||
console.error(`focus-change for guestInstanceId: ${guestInstanceId}`)
|
||||
console.error(`focus-change for guestInstanceId: ${guestInstanceId}`);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const allMethods = new Set([ ...syncMethods, ...asyncMethods ])
|
||||
const allMethods = new Set([ ...syncMethods, ...asyncMethods ]);
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstanceId, method, args) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender)
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!allMethods.has(method)) {
|
||||
throw new Error(`Invalid method: ${method}`)
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
|
||||
return guest[method](...args)
|
||||
})
|
||||
return guest[method](...args);
|
||||
});
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_GET', function (event, guestInstanceId, property) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!properties.has(property)) {
|
||||
throw new Error(`Invalid property: ${property}`);
|
||||
}
|
||||
|
||||
return guest[property];
|
||||
});
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_PROPERTY_SET', function (event, guestInstanceId, property, val) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
if (!properties.has(property)) {
|
||||
throw new Error(`Invalid property: ${property}`);
|
||||
}
|
||||
|
||||
guest[property] = val;
|
||||
});
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender)
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||
|
||||
return serialize(await guest.capturePage(...args))
|
||||
})
|
||||
return serialize(await guest.capturePage(...args));
|
||||
});
|
||||
|
||||
// Returns WebContents from its guest id hosted in given webContents.
|
||||
const getGuestForWebContents = function (guestInstanceId, contents) {
|
||||
const guest = getGuest(guestInstanceId)
|
||||
const guest = getGuest(guestInstanceId);
|
||||
if (!guest) {
|
||||
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`)
|
||||
throw new Error(`Invalid guestInstanceId: ${guestInstanceId}`);
|
||||
}
|
||||
if (guest.hostWebContents !== contents) {
|
||||
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`)
|
||||
throw new Error(`Access denied to guestInstanceId: ${guestInstanceId}`);
|
||||
}
|
||||
return guest
|
||||
}
|
||||
return guest;
|
||||
};
|
||||
|
||||
// Returns WebContents from its guest id.
|
||||
const getGuest = function (guestInstanceId) {
|
||||
const guestInstance = guestInstances[guestInstanceId]
|
||||
if (guestInstance != null) return guestInstance.guest
|
||||
}
|
||||
const guestInstance = guestInstances[guestInstanceId];
|
||||
if (guestInstance != null) return guestInstance.guest;
|
||||
};
|
||||
|
||||
// Returns the embedder of the guest.
|
||||
const getEmbedder = function (guestInstanceId) {
|
||||
const guestInstance = guestInstances[guestInstanceId]
|
||||
if (guestInstance != null) return guestInstance.embedder
|
||||
}
|
||||
const guestInstance = guestInstances[guestInstanceId];
|
||||
if (guestInstance != null) return guestInstance.embedder;
|
||||
};
|
||||
|
||||
exports.getGuestForWebContents = getGuestForWebContents
|
||||
exports.isWebViewTagEnabled = isWebViewTagEnabled
|
||||
exports.getGuestForWebContents = getGuestForWebContents;
|
||||
exports.isWebViewTagEnabled = isWebViewTagEnabled;
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const { BrowserWindow } = electron
|
||||
const { isSameOrigin } = process.electronBinding('v8_util')
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
|
||||
const electron = require('electron');
|
||||
const { BrowserWindow } = electron;
|
||||
const { isSameOrigin } = process.electronBinding('v8_util');
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
|
||||
const parseFeaturesString = require('@electron/internal/common/parse-features-string');
|
||||
|
||||
const hasProp = {}.hasOwnProperty
|
||||
const frameToGuest = new Map()
|
||||
const hasProp = {}.hasOwnProperty;
|
||||
const frameToGuest = new Map();
|
||||
|
||||
// Security options that child windows will always inherit from parent windows
|
||||
const inheritedWebPreferences = new Map([
|
||||
@@ -20,107 +20,109 @@ const inheritedWebPreferences = new Map([
|
||||
['sandbox', true],
|
||||
['webviewTag', false],
|
||||
['nodeIntegrationInSubFrames', false]
|
||||
])
|
||||
]);
|
||||
|
||||
// Copy attribute of |parent| to |child| if it is not defined in |child|.
|
||||
const mergeOptions = function (child, parent, visited) {
|
||||
// Check for circular reference.
|
||||
if (visited == null) visited = new Set()
|
||||
if (visited.has(parent)) return
|
||||
if (visited == null) visited = new Set();
|
||||
if (visited.has(parent)) return;
|
||||
|
||||
visited.add(parent)
|
||||
visited.add(parent);
|
||||
for (const key in parent) {
|
||||
if (key === 'type') continue
|
||||
if (!hasProp.call(parent, key)) continue
|
||||
if (key in child && key !== 'webPreferences') continue
|
||||
if (key === 'type') continue;
|
||||
if (!hasProp.call(parent, key)) continue;
|
||||
if (key in child && key !== 'webPreferences') continue;
|
||||
|
||||
const value = parent[key]
|
||||
const value = parent[key];
|
||||
if (typeof value === 'object' && !Array.isArray(value)) {
|
||||
child[key] = mergeOptions(child[key] || {}, value, visited)
|
||||
child[key] = mergeOptions(child[key] || {}, value, visited);
|
||||
} else {
|
||||
child[key] = value
|
||||
child[key] = value;
|
||||
}
|
||||
}
|
||||
visited.delete(parent)
|
||||
visited.delete(parent);
|
||||
|
||||
return child
|
||||
}
|
||||
return child;
|
||||
};
|
||||
|
||||
// Merge |options| with the |embedder|'s window's options.
|
||||
const mergeBrowserWindowOptions = function (embedder, options) {
|
||||
if (options.webPreferences == null) {
|
||||
options.webPreferences = {}
|
||||
options.webPreferences = {};
|
||||
}
|
||||
if (embedder.browserWindowOptions != null) {
|
||||
let parentOptions = embedder.browserWindowOptions
|
||||
let parentOptions = embedder.browserWindowOptions;
|
||||
|
||||
// if parent's visibility is available, that overrides 'show' flag (#12125)
|
||||
const win = BrowserWindow.fromWebContents(embedder.webContents)
|
||||
const win = BrowserWindow.fromWebContents(embedder.webContents);
|
||||
if (win != null) {
|
||||
parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() }
|
||||
parentOptions = { ...embedder.browserWindowOptions, show: win.isVisible() };
|
||||
}
|
||||
|
||||
// Inherit the original options if it is a BrowserWindow.
|
||||
mergeOptions(options, parentOptions)
|
||||
mergeOptions(options, parentOptions);
|
||||
} else {
|
||||
// Or only inherit webPreferences if it is a webview.
|
||||
mergeOptions(options.webPreferences, embedder.getLastWebPreferences())
|
||||
mergeOptions(options.webPreferences, embedder.getLastWebPreferences());
|
||||
}
|
||||
|
||||
// Inherit certain option values from parent window
|
||||
const webPreferences = embedder.getLastWebPreferences()
|
||||
const webPreferences = embedder.getLastWebPreferences();
|
||||
for (const [name, value] of inheritedWebPreferences) {
|
||||
if (webPreferences[name] === value) {
|
||||
options.webPreferences[name] = value
|
||||
options.webPreferences[name] = value;
|
||||
}
|
||||
}
|
||||
|
||||
// Sets correct openerId here to give correct options to 'new-window' event handler
|
||||
options.webPreferences.openerId = embedder.id
|
||||
if (!webPreferences.nativeWindowOpen) {
|
||||
// Sets correct openerId here to give correct options to 'new-window' event handler
|
||||
options.webPreferences.openerId = embedder.id;
|
||||
}
|
||||
|
||||
return options
|
||||
}
|
||||
return options;
|
||||
};
|
||||
|
||||
// Setup a new guest with |embedder|
|
||||
const setupGuest = function (embedder, frameName, guest, options) {
|
||||
// When |embedder| is destroyed we should also destroy attached guest, and if
|
||||
// guest is closed by user then we should prevent |embedder| from double
|
||||
// closing guest.
|
||||
const guestId = guest.webContents.id
|
||||
const guestId = guest.webContents.id;
|
||||
const closedByEmbedder = function () {
|
||||
guest.removeListener('closed', closedByUser)
|
||||
guest.destroy()
|
||||
}
|
||||
guest.removeListener('closed', closedByUser);
|
||||
guest.destroy();
|
||||
};
|
||||
const closedByUser = function () {
|
||||
embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId)
|
||||
embedder.removeListener('current-render-view-deleted', closedByEmbedder)
|
||||
}
|
||||
embedder.once('current-render-view-deleted', closedByEmbedder)
|
||||
guest.once('closed', closedByUser)
|
||||
embedder._sendInternal('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_' + guestId);
|
||||
embedder.removeListener('current-render-view-deleted', closedByEmbedder);
|
||||
};
|
||||
embedder.once('current-render-view-deleted', closedByEmbedder);
|
||||
guest.once('closed', closedByUser);
|
||||
if (frameName) {
|
||||
frameToGuest.set(frameName, guest)
|
||||
guest.frameName = frameName
|
||||
frameToGuest.set(frameName, guest);
|
||||
guest.frameName = frameName;
|
||||
guest.once('closed', function () {
|
||||
frameToGuest.delete(frameName)
|
||||
})
|
||||
frameToGuest.delete(frameName);
|
||||
});
|
||||
}
|
||||
return guestId
|
||||
}
|
||||
return guestId;
|
||||
};
|
||||
|
||||
// Create a new guest created by |embedder| with |options|.
|
||||
const createGuest = function (embedder, url, referrer, frameName, options, postData) {
|
||||
let guest = frameToGuest.get(frameName)
|
||||
let guest = frameToGuest.get(frameName);
|
||||
if (frameName && (guest != null)) {
|
||||
guest.loadURL(url)
|
||||
return guest.webContents.id
|
||||
guest.loadURL(url);
|
||||
return guest.webContents.id;
|
||||
}
|
||||
|
||||
// Remember the embedder window's id.
|
||||
if (options.webPreferences == null) {
|
||||
options.webPreferences = {}
|
||||
options.webPreferences = {};
|
||||
}
|
||||
|
||||
guest = new BrowserWindow(options)
|
||||
guest = new BrowserWindow(options);
|
||||
if (!options.webContents) {
|
||||
// We should not call `loadURL` if the window was constructed from an
|
||||
// existing webContents (window.open in a sandboxed renderer).
|
||||
@@ -129,214 +131,214 @@ const createGuest = function (embedder, url, referrer, frameName, options, postD
|
||||
// webContents is not necessary (it will navigate there anyway).
|
||||
const loadOptions = {
|
||||
httpReferrer: referrer
|
||||
}
|
||||
};
|
||||
if (postData != null) {
|
||||
loadOptions.postData = postData
|
||||
loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded'
|
||||
loadOptions.postData = postData;
|
||||
loadOptions.extraHeaders = 'content-type: application/x-www-form-urlencoded';
|
||||
if (postData.length > 0) {
|
||||
const postDataFront = postData[0].bytes.toString()
|
||||
const boundary = /^--.*[^-\r\n]/.exec(postDataFront)
|
||||
const postDataFront = postData[0].bytes.toString();
|
||||
const boundary = /^--.*[^-\r\n]/.exec(postDataFront);
|
||||
if (boundary != null) {
|
||||
loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`
|
||||
loadOptions.extraHeaders = `content-type: multipart/form-data; boundary=${boundary[0].substr(2)}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
guest.loadURL(url, loadOptions)
|
||||
guest.loadURL(url, loadOptions);
|
||||
}
|
||||
|
||||
return setupGuest(embedder, frameName, guest, options)
|
||||
}
|
||||
return setupGuest(embedder, frameName, guest, options);
|
||||
};
|
||||
|
||||
const getGuestWindow = function (guestContents) {
|
||||
let guestWindow = BrowserWindow.fromWebContents(guestContents)
|
||||
let guestWindow = BrowserWindow.fromWebContents(guestContents);
|
||||
if (guestWindow == null) {
|
||||
const hostContents = guestContents.hostWebContents
|
||||
const hostContents = guestContents.hostWebContents;
|
||||
if (hostContents != null) {
|
||||
guestWindow = BrowserWindow.fromWebContents(hostContents)
|
||||
guestWindow = BrowserWindow.fromWebContents(hostContents);
|
||||
}
|
||||
}
|
||||
if (!guestWindow) {
|
||||
throw new Error('getGuestWindow failed')
|
||||
throw new Error('getGuestWindow failed');
|
||||
}
|
||||
return guestWindow
|
||||
}
|
||||
return guestWindow;
|
||||
};
|
||||
|
||||
const isChildWindow = function (sender, target) {
|
||||
return target.getLastWebPreferences().openerId === sender.id
|
||||
}
|
||||
return target.getLastWebPreferences().openerId === sender.id;
|
||||
};
|
||||
|
||||
const isRelatedWindow = function (sender, target) {
|
||||
return isChildWindow(sender, target) || isChildWindow(target, sender)
|
||||
}
|
||||
return isChildWindow(sender, target) || isChildWindow(target, sender);
|
||||
};
|
||||
|
||||
const isScriptableWindow = function (sender, target) {
|
||||
return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL())
|
||||
}
|
||||
return isRelatedWindow(sender, target) && isSameOrigin(sender.getURL(), target.getURL());
|
||||
};
|
||||
|
||||
const isNodeIntegrationEnabled = function (sender) {
|
||||
return sender.getLastWebPreferences().nodeIntegration === true
|
||||
}
|
||||
return sender.getLastWebPreferences().nodeIntegration === true;
|
||||
};
|
||||
|
||||
// Checks whether |sender| can access the |target|:
|
||||
const canAccessWindow = function (sender, target) {
|
||||
return isChildWindow(sender, target) ||
|
||||
isScriptableWindow(sender, target) ||
|
||||
isNodeIntegrationEnabled(sender)
|
||||
}
|
||||
isNodeIntegrationEnabled(sender);
|
||||
};
|
||||
|
||||
// Routed window.open messages with raw options
|
||||
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, frameName, features) => {
|
||||
if (url == null || url === '') url = 'about:blank'
|
||||
if (frameName == null) frameName = ''
|
||||
if (features == null) features = ''
|
||||
if (url == null || url === '') url = 'about:blank';
|
||||
if (frameName == null) frameName = '';
|
||||
if (features == null) features = '';
|
||||
|
||||
const options = {}
|
||||
const options = {};
|
||||
|
||||
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
|
||||
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag']
|
||||
const disposition = 'new-window'
|
||||
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor'];
|
||||
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag'];
|
||||
const disposition = 'new-window';
|
||||
|
||||
// Used to store additional features
|
||||
const additionalFeatures = []
|
||||
const additionalFeatures = [];
|
||||
|
||||
// Parse the features
|
||||
parseFeaturesString(features, function (key, value) {
|
||||
if (value === undefined) {
|
||||
additionalFeatures.push(key)
|
||||
additionalFeatures.push(key);
|
||||
} else {
|
||||
// Don't allow webPreferences to be set since it must be an object
|
||||
// that cannot be directly overridden
|
||||
if (key === 'webPreferences') return
|
||||
if (key === 'webPreferences') return;
|
||||
|
||||
if (webPreferences.includes(key)) {
|
||||
if (options.webPreferences == null) {
|
||||
options.webPreferences = {}
|
||||
options.webPreferences = {};
|
||||
}
|
||||
options.webPreferences[key] = value
|
||||
options.webPreferences[key] = value;
|
||||
} else {
|
||||
options[key] = value
|
||||
options[key] = value;
|
||||
}
|
||||
}
|
||||
})
|
||||
});
|
||||
if (options.left) {
|
||||
if (options.x == null) {
|
||||
options.x = options.left
|
||||
options.x = options.left;
|
||||
}
|
||||
}
|
||||
if (options.top) {
|
||||
if (options.y == null) {
|
||||
options.y = options.top
|
||||
options.y = options.top;
|
||||
}
|
||||
}
|
||||
if (options.title == null) {
|
||||
options.title = frameName
|
||||
options.title = frameName;
|
||||
}
|
||||
if (options.width == null) {
|
||||
options.width = 800
|
||||
options.width = 800;
|
||||
}
|
||||
if (options.height == null) {
|
||||
options.height = 600
|
||||
options.height = 600;
|
||||
}
|
||||
|
||||
for (const name of ints) {
|
||||
if (options[name] != null) {
|
||||
options[name] = parseInt(options[name], 10)
|
||||
options[name] = parseInt(options[name], 10);
|
||||
}
|
||||
}
|
||||
|
||||
const referrer = { url: '', policy: 'default' }
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures)
|
||||
})
|
||||
const referrer = { url: '', policy: 'default' };
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures);
|
||||
});
|
||||
|
||||
// Routed window.open messages with fully parsed options
|
||||
function internalWindowOpen (event, url, referrer, frameName, disposition, options, additionalFeatures, postData) {
|
||||
options = mergeBrowserWindowOptions(event.sender, options)
|
||||
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer)
|
||||
const { newGuest } = event
|
||||
options = mergeBrowserWindowOptions(event.sender, options);
|
||||
event.sender.emit('new-window', event, url, frameName, disposition, options, additionalFeatures, referrer);
|
||||
const { newGuest } = event;
|
||||
if ((event.sender.getType() === 'webview' && event.sender.getLastWebPreferences().disablePopups) || event.defaultPrevented) {
|
||||
if (newGuest != null) {
|
||||
if (options.webContents === newGuest.webContents) {
|
||||
// the webContents is not changed, so set defaultPrevented to false to
|
||||
// stop the callers of this event from destroying the webContents.
|
||||
event.defaultPrevented = false
|
||||
event.defaultPrevented = false;
|
||||
}
|
||||
event.returnValue = setupGuest(event.sender, frameName, newGuest, options)
|
||||
event.returnValue = setupGuest(event.sender, frameName, newGuest, options);
|
||||
} else {
|
||||
event.returnValue = null
|
||||
event.returnValue = null;
|
||||
}
|
||||
} else {
|
||||
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData)
|
||||
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData);
|
||||
}
|
||||
}
|
||||
|
||||
const handleMessage = function (channel, handler) {
|
||||
ipcMainUtils.handle(channel, (event, guestId, ...args) => {
|
||||
// Access webContents via electron to prevent circular require.
|
||||
const guestContents = electron.webContents.fromId(guestId)
|
||||
const guestContents = electron.webContents.fromId(guestId);
|
||||
if (!guestContents) {
|
||||
throw new Error(`Invalid guestId: ${guestId}`)
|
||||
throw new Error(`Invalid guestId: ${guestId}`);
|
||||
}
|
||||
|
||||
return handler(event, guestContents, ...args)
|
||||
})
|
||||
}
|
||||
return handler(event, guestContents, ...args);
|
||||
});
|
||||
};
|
||||
|
||||
const securityCheck = function (contents, guestContents, check) {
|
||||
if (!check(contents, guestContents)) {
|
||||
console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`)
|
||||
throw new Error(`Access denied to guestId: ${guestContents.id}`)
|
||||
console.error(`Blocked ${contents.getURL()} from accessing guestId: ${guestContents.id}`);
|
||||
throw new Error(`Access denied to guestId: ${guestContents.id}`);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const windowMethods = new Set([
|
||||
'destroy',
|
||||
'focus',
|
||||
'blur'
|
||||
])
|
||||
]);
|
||||
|
||||
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', (event, guestContents, method, ...args) => {
|
||||
securityCheck(event.sender, guestContents, canAccessWindow)
|
||||
securityCheck(event.sender, guestContents, canAccessWindow);
|
||||
|
||||
if (!windowMethods.has(method)) {
|
||||
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`)
|
||||
throw new Error(`Invalid method: ${method}`)
|
||||
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
|
||||
return getGuestWindow(guestContents)[method](...args)
|
||||
})
|
||||
return getGuestWindow(guestContents)[method](...args);
|
||||
});
|
||||
|
||||
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', (event, guestContents, message, targetOrigin, sourceOrigin) => {
|
||||
if (targetOrigin == null) {
|
||||
targetOrigin = '*'
|
||||
targetOrigin = '*';
|
||||
}
|
||||
|
||||
// The W3C does not seem to have word on how postMessage should work when the
|
||||
// origins do not match, so we do not do |canAccessWindow| check here since
|
||||
// postMessage across origins is useful and not harmful.
|
||||
securityCheck(event.sender, guestContents, isRelatedWindow)
|
||||
securityCheck(event.sender, guestContents, isRelatedWindow);
|
||||
|
||||
if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
|
||||
const sourceId = event.sender.id
|
||||
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
|
||||
const sourceId = event.sender.id;
|
||||
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const webContentsMethods = new Set([
|
||||
'getURL',
|
||||
'loadURL',
|
||||
'executeJavaScript',
|
||||
'print'
|
||||
])
|
||||
]);
|
||||
|
||||
handleMessage('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', (event, guestContents, method, ...args) => {
|
||||
securityCheck(event.sender, guestContents, canAccessWindow)
|
||||
securityCheck(event.sender, guestContents, canAccessWindow);
|
||||
|
||||
if (!webContentsMethods.has(method)) {
|
||||
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`)
|
||||
throw new Error(`Invalid method: ${method}`)
|
||||
console.error(`Blocked ${event.sender.getURL()} from calling method: ${method}`);
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
|
||||
return guestContents[method](...args)
|
||||
})
|
||||
return guestContents[method](...args);
|
||||
});
|
||||
|
||||
exports.internalWindowOpen = internalWindowOpen
|
||||
exports.internalWindowOpen = internalWindowOpen;
|
||||
|
||||
@@ -1,45 +1,47 @@
|
||||
import { Buffer } from 'buffer'
|
||||
import * as fs from 'fs'
|
||||
import * as path from 'path'
|
||||
import * as util from 'util'
|
||||
import { Buffer } from 'buffer';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import * as util from 'util';
|
||||
|
||||
const Module = require('module')
|
||||
const Module = require('module');
|
||||
|
||||
// We modified the original process.argv to let node.js load the init.js,
|
||||
// we need to restore it here.
|
||||
process.argv.splice(1, 1)
|
||||
process.argv.splice(1, 1);
|
||||
|
||||
// Clear search paths.
|
||||
require('../common/reset-search-paths')
|
||||
require('../common/reset-search-paths');
|
||||
|
||||
// Import common settings.
|
||||
require('@electron/internal/common/init')
|
||||
require('@electron/internal/common/init');
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Redirect node's console to use our own implementations, since node can not
|
||||
// handle console output when running as GUI program.
|
||||
const consoleLog = (format: any, ...args: any[]) => {
|
||||
return process.log(util.format(format, ...args) + '\n')
|
||||
}
|
||||
const consoleLog = (...args: any[]) => {
|
||||
// @ts-ignore this typing is incorrect; 'format' is an optional parameter
|
||||
// See https://nodejs.org/api/util.html#util_util_format_format_args
|
||||
return process.log(util.format(...args) + '\n');
|
||||
};
|
||||
const streamWrite: NodeJS.WritableStream['write'] = function (chunk: Buffer | string, encoding?: any, callback?: Function) {
|
||||
if (Buffer.isBuffer(chunk)) {
|
||||
chunk = chunk.toString(encoding)
|
||||
chunk = chunk.toString(encoding);
|
||||
}
|
||||
process.log(chunk)
|
||||
process.log(chunk);
|
||||
if (callback) {
|
||||
callback()
|
||||
callback();
|
||||
}
|
||||
return true
|
||||
}
|
||||
console.log = console.error = console.warn = consoleLog
|
||||
process.stdout.write = process.stderr.write = streamWrite
|
||||
return true;
|
||||
};
|
||||
console.log = console.error = console.warn = consoleLog;
|
||||
process.stdout.write = process.stderr.write = streamWrite;
|
||||
}
|
||||
|
||||
// Don't quit on fatal error.
|
||||
process.on('uncaughtException', function (error) {
|
||||
// Do nothing if the user has a custom uncaught exception handler.
|
||||
if (process.listenerCount('uncaughtException') > 1) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
|
||||
// Show error in GUI.
|
||||
@@ -48,18 +50,18 @@ process.on('uncaughtException', function (error) {
|
||||
// so we import it inside the handler down here
|
||||
import('electron')
|
||||
.then(({ dialog }) => {
|
||||
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`
|
||||
const message = 'Uncaught Exception:\n' + stack
|
||||
dialog.showErrorBox('A JavaScript error occurred in the main process', message)
|
||||
})
|
||||
})
|
||||
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
|
||||
const message = 'Uncaught Exception:\n' + stack;
|
||||
dialog.showErrorBox('A JavaScript error occurred in the main process', message);
|
||||
});
|
||||
});
|
||||
|
||||
// Emit 'exit' event on quit.
|
||||
const { app } = require('electron')
|
||||
const { app } = require('electron');
|
||||
|
||||
app.on('quit', function (event, exitCode) {
|
||||
process.emit('exit', exitCode)
|
||||
})
|
||||
process.emit('exit', exitCode);
|
||||
});
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// If we are a Squirrel.Windows-installed app, set app user model ID
|
||||
@@ -76,131 +78,131 @@ if (process.platform === 'win32') {
|
||||
// form `com.squirrel.PACKAGE-NAME.OUREXE`. We need to call
|
||||
// app.setAppUserModelId with a matching identifier so that renderer processes
|
||||
// will inherit this value.
|
||||
const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe')
|
||||
const updateDotExe = path.join(path.dirname(process.execPath), '..', 'update.exe');
|
||||
|
||||
if (fs.existsSync(updateDotExe)) {
|
||||
const packageDir = path.dirname(path.resolve(updateDotExe))
|
||||
const packageName = path.basename(packageDir).replace(/\s/g, '')
|
||||
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '')
|
||||
const packageDir = path.dirname(path.resolve(updateDotExe));
|
||||
const packageName = path.basename(packageDir).replace(/\s/g, '');
|
||||
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replace(/\s/g, '');
|
||||
|
||||
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`)
|
||||
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`);
|
||||
}
|
||||
}
|
||||
|
||||
// Map process.exit to app.exit, which quits gracefully.
|
||||
process.exit = app.exit as () => never
|
||||
process.exit = app.exit as () => never;
|
||||
|
||||
// Load the RPC server.
|
||||
require('@electron/internal/browser/rpc-server')
|
||||
require('@electron/internal/browser/rpc-server');
|
||||
|
||||
// Load the guest view manager.
|
||||
require('@electron/internal/browser/guest-view-manager')
|
||||
require('@electron/internal/browser/guest-window-manager')
|
||||
require('@electron/internal/browser/guest-view-manager');
|
||||
require('@electron/internal/browser/guest-window-manager');
|
||||
|
||||
// Now we try to load app's package.json.
|
||||
let packagePath = null
|
||||
let packageJson = null
|
||||
const searchPaths = ['app', 'app.asar', 'default_app.asar']
|
||||
let packagePath = null;
|
||||
let packageJson = null;
|
||||
const searchPaths = ['app', 'app.asar', 'default_app.asar'];
|
||||
|
||||
if (process.resourcesPath) {
|
||||
for (packagePath of searchPaths) {
|
||||
try {
|
||||
packagePath = path.join(process.resourcesPath, packagePath)
|
||||
packageJson = Module._load(path.join(packagePath, 'package.json'))
|
||||
break
|
||||
packagePath = path.join(process.resourcesPath, packagePath);
|
||||
packageJson = Module._load(path.join(packagePath, 'package.json'));
|
||||
break;
|
||||
} catch {
|
||||
continue
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (packageJson == null) {
|
||||
process.nextTick(function () {
|
||||
return process.exit(1)
|
||||
})
|
||||
throw new Error('Unable to find a valid app')
|
||||
return process.exit(1);
|
||||
});
|
||||
throw new Error('Unable to find a valid app');
|
||||
}
|
||||
|
||||
// Set application's version.
|
||||
if (packageJson.version != null) {
|
||||
app.setVersion(packageJson.version)
|
||||
app.setVersion(packageJson.version);
|
||||
}
|
||||
|
||||
// Set application's name.
|
||||
if (packageJson.productName != null) {
|
||||
app.name = `${packageJson.productName}`.trim()
|
||||
app.name = `${packageJson.productName}`.trim();
|
||||
} else if (packageJson.name != null) {
|
||||
app.name = `${packageJson.name}`.trim()
|
||||
app.name = `${packageJson.name}`.trim();
|
||||
}
|
||||
|
||||
// Set application's desktop name.
|
||||
if (packageJson.desktopName != null) {
|
||||
app.setDesktopName(packageJson.desktopName)
|
||||
app.setDesktopName(packageJson.desktopName);
|
||||
} else {
|
||||
app.setDesktopName(`${app.name}.desktop`)
|
||||
app.setDesktopName(`${app.name}.desktop`);
|
||||
}
|
||||
|
||||
// Set v8 flags, delibrately lazy load so that apps that do not use this
|
||||
// feature do not pay the price
|
||||
if (packageJson.v8Flags != null) {
|
||||
require('v8').setFlagsFromString(packageJson.v8Flags)
|
||||
require('v8').setFlagsFromString(packageJson.v8Flags);
|
||||
}
|
||||
|
||||
app._setDefaultAppPaths(packagePath)
|
||||
app._setDefaultAppPaths(packagePath);
|
||||
|
||||
// Load the chrome devtools support.
|
||||
require('@electron/internal/browser/devtools')
|
||||
require('@electron/internal/browser/devtools');
|
||||
|
||||
// Load the chrome extension support.
|
||||
require('@electron/internal/browser/chrome-extension')
|
||||
require('@electron/internal/browser/chrome-extension');
|
||||
|
||||
// Load protocol module to ensure it is populated on app ready
|
||||
require('@electron/internal/browser/api/protocol')
|
||||
require('@electron/internal/browser/api/protocol');
|
||||
|
||||
// Set main startup script of the app.
|
||||
const mainStartupScript = packageJson.main || 'index.js'
|
||||
const mainStartupScript = packageJson.main || 'index.js';
|
||||
|
||||
const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME']
|
||||
const KNOWN_XDG_DESKTOP_VALUES = ['Pantheon', 'Unity:Unity7', 'pop:GNOME'];
|
||||
|
||||
function currentPlatformSupportsAppIndicator () {
|
||||
if (process.platform !== 'linux') return false
|
||||
const currentDesktop = process.env.XDG_CURRENT_DESKTOP
|
||||
if (process.platform !== 'linux') return false;
|
||||
const currentDesktop = process.env.XDG_CURRENT_DESKTOP;
|
||||
|
||||
if (!currentDesktop) return false
|
||||
if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true
|
||||
if (!currentDesktop) return false;
|
||||
if (KNOWN_XDG_DESKTOP_VALUES.includes(currentDesktop)) return true;
|
||||
// ubuntu based or derived session (default ubuntu one, communitheme…) supports
|
||||
// indicator too.
|
||||
if (/ubuntu/ig.test(currentDesktop)) return true
|
||||
if (/ubuntu/ig.test(currentDesktop)) return true;
|
||||
|
||||
return false
|
||||
return false;
|
||||
}
|
||||
|
||||
// Workaround for electron/electron#5050 and electron/electron#9046
|
||||
if (currentPlatformSupportsAppIndicator()) {
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity'
|
||||
process.env.XDG_CURRENT_DESKTOP = 'Unity';
|
||||
}
|
||||
|
||||
// Quit when all windows are closed and no other one is listening to this.
|
||||
app.on('window-all-closed', () => {
|
||||
if (app.listenerCount('window-all-closed') === 1) {
|
||||
app.quit()
|
||||
app.quit();
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu')
|
||||
const { setDefaultApplicationMenu } = require('@electron/internal/browser/default-menu');
|
||||
|
||||
// Create default menu.
|
||||
//
|
||||
// Note that the task must be added before loading any app, so we can make sure
|
||||
// the call is maded before any user window is created, otherwise the default
|
||||
// menu may show even when user explicitly hides the menu.
|
||||
app.once('ready', setDefaultApplicationMenu)
|
||||
app.once('ready', setDefaultApplicationMenu);
|
||||
|
||||
if (packagePath) {
|
||||
// Finally load app's main.js and transfer control to C++.
|
||||
process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false)
|
||||
Module._load(path.join(packagePath, mainStartupScript), Module, true)
|
||||
process._firstFileName = Module._resolveFilename(path.join(packagePath, mainStartupScript), null, false);
|
||||
Module._load(path.join(packagePath, mainStartupScript), Module, true);
|
||||
} else {
|
||||
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)')
|
||||
console.error('This normally means you\'ve damaged the Electron package somehow')
|
||||
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)');
|
||||
console.error('This normally means you\'ve damaged the Electron package somehow');
|
||||
}
|
||||
|
||||
@@ -1,56 +1,56 @@
|
||||
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal'
|
||||
import * as errorUtils from '@electron/internal/common/error-utils'
|
||||
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
|
||||
import * as errorUtils from '@electron/internal/common/error-utils';
|
||||
|
||||
type IPCHandler = (event: ElectronInternal.IpcMainInternalEvent, ...args: any[]) => any
|
||||
|
||||
const callHandler = async function (handler: IPCHandler, event: ElectronInternal.IpcMainInternalEvent, args: any[], reply: (args: any[]) => void) {
|
||||
try {
|
||||
const result = await handler(event, ...args)
|
||||
reply([null, result])
|
||||
const result = await handler(event, ...args);
|
||||
reply([null, result]);
|
||||
} catch (error) {
|
||||
reply([errorUtils.serialize(error)])
|
||||
reply([errorUtils.serialize(error)]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const handle = function <T extends IPCHandler> (channel: string, handler: T) {
|
||||
ipcMainInternal.on(channel, (event, requestId, ...args) => {
|
||||
callHandler(handler, event, args, responseArgs => {
|
||||
if (requestId) {
|
||||
event._replyInternal(`${channel}_RESPONSE_${requestId}`, ...responseArgs)
|
||||
event._replyInternal(`${channel}_RESPONSE_${requestId}`, ...responseArgs);
|
||||
} else {
|
||||
event.returnValue = responseArgs
|
||||
event.returnValue = responseArgs;
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
let nextId = 0
|
||||
let nextId = 0;
|
||||
|
||||
export function invokeInWebContents<T> (sender: Electron.WebContentsInternal, sendToAll: boolean, command: string, ...args: any[]) {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
const requestId = ++nextId
|
||||
const channel = `${command}_RESPONSE_${requestId}`
|
||||
const requestId = ++nextId;
|
||||
const channel = `${command}_RESPONSE_${requestId}`;
|
||||
ipcMainInternal.on(channel, function handler (
|
||||
event, error: Electron.SerializedError, result: any
|
||||
) {
|
||||
if (event.sender !== sender) {
|
||||
console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`)
|
||||
return
|
||||
console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`);
|
||||
return;
|
||||
}
|
||||
|
||||
ipcMainInternal.removeListener(channel, handler)
|
||||
ipcMainInternal.removeListener(channel, handler);
|
||||
|
||||
if (error) {
|
||||
reject(errorUtils.deserialize(error))
|
||||
reject(errorUtils.deserialize(error));
|
||||
} else {
|
||||
resolve(result)
|
||||
resolve(result);
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (sendToAll) {
|
||||
sender._sendInternalToAll(command, requestId, ...args)
|
||||
sender._sendInternalToAll(command, requestId, ...args);
|
||||
} else {
|
||||
sender._sendInternal(command, requestId, ...args)
|
||||
sender._sendInternal(command, requestId, ...args);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
const emitter = new EventEmitter()
|
||||
const emitter = new EventEmitter();
|
||||
|
||||
// Do not throw exception when channel name is "error".
|
||||
emitter.on('error', () => {})
|
||||
emitter.on('error', () => {});
|
||||
|
||||
export const ipcMainInternal = emitter as ElectronInternal.IpcMainInternal
|
||||
export const ipcMainInternal = emitter as ElectronInternal.IpcMainInternal;
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
|
||||
|
||||
// The history operation in renderer is redirected to browser.
|
||||
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) {
|
||||
event.sender.goBack()
|
||||
})
|
||||
event.sender.goBack();
|
||||
});
|
||||
|
||||
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD', function (event) {
|
||||
event.sender.goForward()
|
||||
})
|
||||
event.sender.goForward();
|
||||
});
|
||||
|
||||
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', function (event, offset) {
|
||||
event.sender.goToOffset(offset)
|
||||
})
|
||||
event.sender.goToOffset(offset);
|
||||
});
|
||||
|
||||
ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) {
|
||||
event.returnValue = event.sender.length()
|
||||
})
|
||||
event.returnValue = event.sender.length();
|
||||
});
|
||||
|
||||
// JavaScript implementation of Chromium's NavigationController.
|
||||
// Instead of relying on Chromium for history control, we compeletely do history
|
||||
@@ -26,64 +26,64 @@ ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_LENGTH', function (event) {
|
||||
// process is restarted everytime.
|
||||
const NavigationController = (function () {
|
||||
function NavigationController (webContents) {
|
||||
this.webContents = webContents
|
||||
this.clearHistory()
|
||||
this.webContents = webContents;
|
||||
this.clearHistory();
|
||||
|
||||
// webContents may have already navigated to a page.
|
||||
if (this.webContents._getURL()) {
|
||||
this.currentIndex++
|
||||
this.history.push(this.webContents._getURL())
|
||||
this.currentIndex++;
|
||||
this.history.push(this.webContents._getURL());
|
||||
}
|
||||
this.webContents.on('navigation-entry-commited', (event, url, inPage, replaceEntry) => {
|
||||
if (this.inPageIndex > -1 && !inPage) {
|
||||
// Navigated to a new page, clear in-page mark.
|
||||
this.inPageIndex = -1
|
||||
this.inPageIndex = -1;
|
||||
} else if (this.inPageIndex === -1 && inPage && !replaceEntry) {
|
||||
// Started in-page navigations.
|
||||
this.inPageIndex = this.currentIndex
|
||||
this.inPageIndex = this.currentIndex;
|
||||
}
|
||||
if (this.pendingIndex >= 0) {
|
||||
// Go to index.
|
||||
this.currentIndex = this.pendingIndex
|
||||
this.pendingIndex = -1
|
||||
this.history[this.currentIndex] = url
|
||||
this.currentIndex = this.pendingIndex;
|
||||
this.pendingIndex = -1;
|
||||
this.history[this.currentIndex] = url;
|
||||
} else if (replaceEntry) {
|
||||
// Non-user initialized navigation.
|
||||
this.history[this.currentIndex] = url
|
||||
this.history[this.currentIndex] = url;
|
||||
} else {
|
||||
// Normal navigation. Clear history.
|
||||
this.history = this.history.slice(0, this.currentIndex + 1)
|
||||
this.currentIndex++
|
||||
this.history.push(url)
|
||||
this.history = this.history.slice(0, this.currentIndex + 1);
|
||||
this.currentIndex++;
|
||||
this.history.push(url);
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
NavigationController.prototype.loadURL = function (url, options) {
|
||||
if (options == null) {
|
||||
options = {}
|
||||
options = {};
|
||||
}
|
||||
const p = new Promise((resolve, reject) => {
|
||||
const resolveAndCleanup = () => {
|
||||
removeListeners()
|
||||
resolve()
|
||||
}
|
||||
removeListeners();
|
||||
resolve();
|
||||
};
|
||||
const rejectAndCleanup = (errorCode, errorDescription, url) => {
|
||||
const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`)
|
||||
Object.assign(err, { errno: errorCode, code: errorDescription, url })
|
||||
removeListeners()
|
||||
reject(err)
|
||||
}
|
||||
const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`);
|
||||
Object.assign(err, { errno: errorCode, code: errorDescription, url });
|
||||
removeListeners();
|
||||
reject(err);
|
||||
};
|
||||
const finishListener = () => {
|
||||
resolveAndCleanup()
|
||||
}
|
||||
resolveAndCleanup();
|
||||
};
|
||||
const failListener = (event, errorCode, errorDescription, validatedURL, isMainFrame, frameProcessId, frameRoutingId) => {
|
||||
if (isMainFrame) {
|
||||
rejectAndCleanup(errorCode, errorDescription, validatedURL)
|
||||
rejectAndCleanup(errorCode, errorDescription, validatedURL);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let navigationStarted = false
|
||||
let navigationStarted = false;
|
||||
const navigationListener = (event, url, isSameDocument, isMainFrame, frameProcessId, frameRoutingId, navigationId) => {
|
||||
if (isMainFrame) {
|
||||
if (navigationStarted && !isSameDocument) {
|
||||
@@ -96,11 +96,11 @@ const NavigationController = (function () {
|
||||
// considered navigation events but are triggered with isSameDocument.
|
||||
// We can ignore these to allow virtual routing on page load as long
|
||||
// as the routing does not leave the document
|
||||
return rejectAndCleanup(-3, 'ERR_ABORTED', url)
|
||||
return rejectAndCleanup(-3, 'ERR_ABORTED', url);
|
||||
}
|
||||
navigationStarted = true
|
||||
navigationStarted = true;
|
||||
}
|
||||
}
|
||||
};
|
||||
const stopLoadingListener = () => {
|
||||
// By the time we get here, either 'finish' or 'fail' should have fired
|
||||
// if the navigation occurred. However, in some situations (e.g. when
|
||||
@@ -110,134 +110,134 @@ const NavigationController = (function () {
|
||||
// TODO(jeremy): enumerate all the cases in which this can happen. If
|
||||
// the only one is with a bad scheme, perhaps ERR_INVALID_ARGUMENT
|
||||
// would be more appropriate.
|
||||
rejectAndCleanup(-2, 'ERR_FAILED', url)
|
||||
}
|
||||
rejectAndCleanup(-2, 'ERR_FAILED', url);
|
||||
};
|
||||
const removeListeners = () => {
|
||||
this.webContents.removeListener('did-finish-load', finishListener)
|
||||
this.webContents.removeListener('did-fail-load', failListener)
|
||||
this.webContents.removeListener('did-start-navigation', navigationListener)
|
||||
this.webContents.removeListener('did-stop-loading', stopLoadingListener)
|
||||
}
|
||||
this.webContents.on('did-finish-load', finishListener)
|
||||
this.webContents.on('did-fail-load', failListener)
|
||||
this.webContents.on('did-start-navigation', navigationListener)
|
||||
this.webContents.on('did-stop-loading', stopLoadingListener)
|
||||
})
|
||||
this.webContents.removeListener('did-finish-load', finishListener);
|
||||
this.webContents.removeListener('did-fail-load', failListener);
|
||||
this.webContents.removeListener('did-start-navigation', navigationListener);
|
||||
this.webContents.removeListener('did-stop-loading', stopLoadingListener);
|
||||
};
|
||||
this.webContents.on('did-finish-load', finishListener);
|
||||
this.webContents.on('did-fail-load', failListener);
|
||||
this.webContents.on('did-start-navigation', navigationListener);
|
||||
this.webContents.on('did-stop-loading', stopLoadingListener);
|
||||
});
|
||||
// Add a no-op rejection handler to silence the unhandled rejection error.
|
||||
p.catch(() => {})
|
||||
this.pendingIndex = -1
|
||||
this.webContents._loadURL(url, options)
|
||||
this.webContents.emit('load-url', url, options)
|
||||
return p
|
||||
}
|
||||
p.catch(() => {});
|
||||
this.pendingIndex = -1;
|
||||
this.webContents._loadURL(url, options);
|
||||
this.webContents.emit('load-url', url, options);
|
||||
return p;
|
||||
};
|
||||
|
||||
NavigationController.prototype.getURL = function () {
|
||||
if (this.currentIndex === -1) {
|
||||
return ''
|
||||
return '';
|
||||
} else {
|
||||
return this.history[this.currentIndex]
|
||||
return this.history[this.currentIndex];
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NavigationController.prototype.stop = function () {
|
||||
this.pendingIndex = -1
|
||||
return this.webContents._stop()
|
||||
}
|
||||
this.pendingIndex = -1;
|
||||
return this.webContents._stop();
|
||||
};
|
||||
|
||||
NavigationController.prototype.reload = function () {
|
||||
this.pendingIndex = this.currentIndex
|
||||
return this.webContents._loadURL(this.getURL(), {})
|
||||
}
|
||||
this.pendingIndex = this.currentIndex;
|
||||
return this.webContents._loadURL(this.getURL(), {});
|
||||
};
|
||||
|
||||
NavigationController.prototype.reloadIgnoringCache = function () {
|
||||
this.pendingIndex = this.currentIndex
|
||||
this.pendingIndex = this.currentIndex;
|
||||
return this.webContents._loadURL(this.getURL(), {
|
||||
extraHeaders: 'pragma: no-cache\n',
|
||||
reloadIgnoringCache: true
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
NavigationController.prototype.canGoBack = function () {
|
||||
return this.getActiveIndex() > 0
|
||||
}
|
||||
return this.getActiveIndex() > 0;
|
||||
};
|
||||
|
||||
NavigationController.prototype.canGoForward = function () {
|
||||
return this.getActiveIndex() < this.history.length - 1
|
||||
}
|
||||
return this.getActiveIndex() < this.history.length - 1;
|
||||
};
|
||||
|
||||
NavigationController.prototype.canGoToIndex = function (index) {
|
||||
return index >= 0 && index < this.history.length
|
||||
}
|
||||
return index >= 0 && index < this.history.length;
|
||||
};
|
||||
|
||||
NavigationController.prototype.canGoToOffset = function (offset) {
|
||||
return this.canGoToIndex(this.currentIndex + offset)
|
||||
}
|
||||
return this.canGoToIndex(this.currentIndex + offset);
|
||||
};
|
||||
|
||||
NavigationController.prototype.clearHistory = function () {
|
||||
this.history = []
|
||||
this.currentIndex = -1
|
||||
this.pendingIndex = -1
|
||||
this.inPageIndex = -1
|
||||
}
|
||||
this.history = [];
|
||||
this.currentIndex = -1;
|
||||
this.pendingIndex = -1;
|
||||
this.inPageIndex = -1;
|
||||
};
|
||||
|
||||
NavigationController.prototype.goBack = function () {
|
||||
if (!this.canGoBack()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.pendingIndex = this.getActiveIndex() - 1
|
||||
this.pendingIndex = this.getActiveIndex() - 1;
|
||||
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
|
||||
return this.webContents._goBack()
|
||||
return this.webContents._goBack();
|
||||
} else {
|
||||
return this.webContents._loadURL(this.history[this.pendingIndex], {})
|
||||
return this.webContents._loadURL(this.history[this.pendingIndex], {});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NavigationController.prototype.goForward = function () {
|
||||
if (!this.canGoForward()) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.pendingIndex = this.getActiveIndex() + 1
|
||||
this.pendingIndex = this.getActiveIndex() + 1;
|
||||
if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) {
|
||||
return this.webContents._goForward()
|
||||
return this.webContents._goForward();
|
||||
} else {
|
||||
return this.webContents._loadURL(this.history[this.pendingIndex], {})
|
||||
return this.webContents._loadURL(this.history[this.pendingIndex], {});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NavigationController.prototype.goToIndex = function (index) {
|
||||
if (!this.canGoToIndex(index)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
this.pendingIndex = index
|
||||
return this.webContents._loadURL(this.history[this.pendingIndex], {})
|
||||
}
|
||||
this.pendingIndex = index;
|
||||
return this.webContents._loadURL(this.history[this.pendingIndex], {});
|
||||
};
|
||||
|
||||
NavigationController.prototype.goToOffset = function (offset) {
|
||||
if (!this.canGoToOffset(offset)) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
const pendingIndex = this.currentIndex + offset
|
||||
const pendingIndex = this.currentIndex + offset;
|
||||
if (this.inPageIndex > -1 && pendingIndex >= this.inPageIndex) {
|
||||
this.pendingIndex = pendingIndex
|
||||
return this.webContents._goToOffset(offset)
|
||||
this.pendingIndex = pendingIndex;
|
||||
return this.webContents._goToOffset(offset);
|
||||
} else {
|
||||
return this.goToIndex(pendingIndex)
|
||||
return this.goToIndex(pendingIndex);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NavigationController.prototype.getActiveIndex = function () {
|
||||
if (this.pendingIndex === -1) {
|
||||
return this.currentIndex
|
||||
return this.currentIndex;
|
||||
} else {
|
||||
return this.pendingIndex
|
||||
return this.pendingIndex;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
NavigationController.prototype.length = function () {
|
||||
return this.history.length
|
||||
}
|
||||
return this.history.length;
|
||||
};
|
||||
|
||||
return NavigationController
|
||||
})()
|
||||
return NavigationController;
|
||||
})();
|
||||
|
||||
module.exports = NavigationController
|
||||
module.exports = NavigationController;
|
||||
|
||||
@@ -1,51 +1,51 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const v8Util = process.electronBinding('v8_util');
|
||||
|
||||
const getOwnerKey = (webContents, contextId) => {
|
||||
return `${webContents.id}-${contextId}`
|
||||
}
|
||||
return `${webContents.id}-${contextId}`;
|
||||
};
|
||||
|
||||
class ObjectsRegistry {
|
||||
constructor () {
|
||||
this.nextId = 0
|
||||
this.nextId = 0;
|
||||
|
||||
// Stores all objects by ref-counting.
|
||||
// (id) => {object, count}
|
||||
this.storage = {}
|
||||
this.storage = {};
|
||||
|
||||
// Stores the IDs + refCounts of objects referenced by WebContents.
|
||||
// (ownerKey) => { id: refCount }
|
||||
this.owners = {}
|
||||
this.owners = {};
|
||||
}
|
||||
|
||||
// Register a new object and return its assigned ID. If the object is already
|
||||
// registered then the already assigned ID would be returned.
|
||||
add (webContents, contextId, obj) {
|
||||
// Get or assign an ID to the object.
|
||||
const id = this.saveToStorage(obj)
|
||||
const id = this.saveToStorage(obj);
|
||||
|
||||
// Add object to the set of referenced objects.
|
||||
const ownerKey = getOwnerKey(webContents, contextId)
|
||||
let owner = this.owners[ownerKey]
|
||||
const ownerKey = getOwnerKey(webContents, contextId);
|
||||
let owner = this.owners[ownerKey];
|
||||
if (!owner) {
|
||||
owner = this.owners[ownerKey] = new Map()
|
||||
this.registerDeleteListener(webContents, contextId)
|
||||
owner = this.owners[ownerKey] = new Map();
|
||||
this.registerDeleteListener(webContents, contextId);
|
||||
}
|
||||
if (!owner.has(id)) {
|
||||
owner.set(id, 0)
|
||||
owner.set(id, 0);
|
||||
// Increase reference count if not referenced before.
|
||||
this.storage[id].count++
|
||||
this.storage[id].count++;
|
||||
}
|
||||
|
||||
owner.set(id, owner.get(id) + 1)
|
||||
return id
|
||||
owner.set(id, owner.get(id) + 1);
|
||||
return id;
|
||||
}
|
||||
|
||||
// Get an object according to its ID.
|
||||
get (id) {
|
||||
const pointer = this.storage[id]
|
||||
if (pointer != null) return pointer.object
|
||||
const pointer = this.storage[id];
|
||||
if (pointer != null) return pointer.object;
|
||||
}
|
||||
|
||||
// Dereference an object according to its ID.
|
||||
@@ -60,79 +60,79 @@ class ObjectsRegistry {
|
||||
// For more details on why we do renderer side ref counting see
|
||||
// https://github.com/electron/electron/pull/17464
|
||||
remove (webContents, contextId, id, rendererSideRefCount) {
|
||||
const ownerKey = getOwnerKey(webContents, contextId)
|
||||
const owner = this.owners[ownerKey]
|
||||
const ownerKey = getOwnerKey(webContents, contextId);
|
||||
const owner = this.owners[ownerKey];
|
||||
if (owner && owner.has(id)) {
|
||||
const newRefCount = owner.get(id) - rendererSideRefCount
|
||||
const newRefCount = owner.get(id) - rendererSideRefCount;
|
||||
|
||||
// Only completely remove if the number of references GCed in the
|
||||
// renderer is the same as the number of references we sent them
|
||||
if (newRefCount <= 0) {
|
||||
// Remove the reference in owner.
|
||||
owner.delete(id)
|
||||
owner.delete(id);
|
||||
// Dereference from the storage.
|
||||
this.dereference(id)
|
||||
this.dereference(id);
|
||||
} else {
|
||||
owner.set(id, newRefCount)
|
||||
owner.set(id, newRefCount);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Clear all references to objects refrenced by the WebContents.
|
||||
clear (webContents, contextId) {
|
||||
const ownerKey = getOwnerKey(webContents, contextId)
|
||||
const owner = this.owners[ownerKey]
|
||||
if (!owner) return
|
||||
const ownerKey = getOwnerKey(webContents, contextId);
|
||||
const owner = this.owners[ownerKey];
|
||||
if (!owner) return;
|
||||
|
||||
for (const id of owner.keys()) this.dereference(id)
|
||||
for (const id of owner.keys()) this.dereference(id);
|
||||
|
||||
delete this.owners[ownerKey]
|
||||
delete this.owners[ownerKey];
|
||||
}
|
||||
|
||||
// Private: Saves the object into storage and assigns an ID for it.
|
||||
saveToStorage (object) {
|
||||
let id = v8Util.getHiddenValue(object, 'atomId')
|
||||
let id = v8Util.getHiddenValue(object, 'atomId');
|
||||
if (!id) {
|
||||
id = ++this.nextId
|
||||
id = ++this.nextId;
|
||||
this.storage[id] = {
|
||||
count: 0,
|
||||
object: object
|
||||
}
|
||||
v8Util.setHiddenValue(object, 'atomId', id)
|
||||
};
|
||||
v8Util.setHiddenValue(object, 'atomId', id);
|
||||
}
|
||||
return id
|
||||
return id;
|
||||
}
|
||||
|
||||
// Private: Dereference the object from store.
|
||||
dereference (id) {
|
||||
const pointer = this.storage[id]
|
||||
const pointer = this.storage[id];
|
||||
if (pointer == null) {
|
||||
return
|
||||
return;
|
||||
}
|
||||
pointer.count -= 1
|
||||
pointer.count -= 1;
|
||||
if (pointer.count === 0) {
|
||||
v8Util.deleteHiddenValue(pointer.object, 'atomId')
|
||||
delete this.storage[id]
|
||||
v8Util.deleteHiddenValue(pointer.object, 'atomId');
|
||||
delete this.storage[id];
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Clear the storage when renderer process is destroyed.
|
||||
registerDeleteListener (webContents, contextId) {
|
||||
// contextId => ${processHostId}-${contextCount}
|
||||
const processHostId = contextId.split('-')[0]
|
||||
const processHostId = contextId.split('-')[0];
|
||||
const listener = (event, deletedProcessHostId) => {
|
||||
if (deletedProcessHostId &&
|
||||
deletedProcessHostId.toString() === processHostId) {
|
||||
webContents.removeListener('render-view-deleted', listener)
|
||||
this.clear(webContents, contextId)
|
||||
webContents.removeListener('render-view-deleted', listener);
|
||||
this.clear(webContents, contextId);
|
||||
}
|
||||
}
|
||||
};
|
||||
// Note that the "render-view-deleted" event may not be emitted on time when
|
||||
// the renderer process get destroyed because of navigation, we rely on the
|
||||
// renderer process to send "ELECTRON_BROWSER_CONTEXT_RELEASE" message to
|
||||
// guard this situation.
|
||||
webContents.on('render-view-deleted', listener)
|
||||
webContents.on('render-view-deleted', listener);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new ObjectsRegistry()
|
||||
module.exports = new ObjectsRegistry();
|
||||
|
||||
@@ -1,135 +1,135 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const electron = require('electron')
|
||||
const { EventEmitter } = require('events')
|
||||
const fs = require('fs')
|
||||
const electron = require('electron');
|
||||
const { EventEmitter } = require('events');
|
||||
const fs = require('fs');
|
||||
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const eventBinding = process.electronBinding('event')
|
||||
const clipboard = process.electronBinding('clipboard')
|
||||
const features = process.electronBinding('features')
|
||||
const v8Util = process.electronBinding('v8_util');
|
||||
const eventBinding = process.electronBinding('event');
|
||||
const clipboard = process.electronBinding('clipboard');
|
||||
const features = process.electronBinding('features');
|
||||
|
||||
const { getContentScripts } = require('@electron/internal/browser/chrome-extension')
|
||||
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init')
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils')
|
||||
const objectsRegistry = require('@electron/internal/browser/objects-registry')
|
||||
const guestViewManager = require('@electron/internal/browser/guest-view-manager')
|
||||
const bufferUtils = require('@electron/internal/common/buffer-utils')
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
const typeUtils = require('@electron/internal/common/type-utils')
|
||||
const { isPromise } = require('@electron/internal/common/is-promise')
|
||||
const { getContentScripts } = require('@electron/internal/browser/chrome-extension');
|
||||
const { crashReporterInit } = require('@electron/internal/browser/crash-reporter-init');
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal');
|
||||
const ipcMainUtils = require('@electron/internal/browser/ipc-main-internal-utils');
|
||||
const objectsRegistry = require('@electron/internal/browser/objects-registry');
|
||||
const guestViewManager = require('@electron/internal/browser/guest-view-manager');
|
||||
const bufferUtils = require('@electron/internal/common/buffer-utils');
|
||||
const errorUtils = require('@electron/internal/common/error-utils');
|
||||
const typeUtils = require('@electron/internal/common/type-utils');
|
||||
const { isPromise } = require('@electron/internal/common/is-promise');
|
||||
|
||||
const hasProp = {}.hasOwnProperty
|
||||
const hasProp = {}.hasOwnProperty;
|
||||
|
||||
// The internal properties of Function.
|
||||
const FUNCTION_PROPERTIES = [
|
||||
'length', 'name', 'arguments', 'caller', 'prototype'
|
||||
]
|
||||
];
|
||||
|
||||
// The remote functions in renderer processes.
|
||||
// id => Function
|
||||
const rendererFunctions = v8Util.createDoubleIDWeakMap()
|
||||
const rendererFunctions = v8Util.createDoubleIDWeakMap();
|
||||
|
||||
// Return the description of object's members:
|
||||
const getObjectMembers = function (object) {
|
||||
let names = Object.getOwnPropertyNames(object)
|
||||
let names = Object.getOwnPropertyNames(object);
|
||||
// For Function, we should not override following properties even though they
|
||||
// are "own" properties.
|
||||
if (typeof object === 'function') {
|
||||
names = names.filter((name) => {
|
||||
return !FUNCTION_PROPERTIES.includes(name)
|
||||
})
|
||||
return !FUNCTION_PROPERTIES.includes(name);
|
||||
});
|
||||
}
|
||||
// Map properties to descriptors.
|
||||
return names.map((name) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, name)
|
||||
const member = { name, enumerable: descriptor.enumerable, writable: false }
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, name);
|
||||
const member = { name, enumerable: descriptor.enumerable, writable: false };
|
||||
if (descriptor.get === undefined && typeof object[name] === 'function') {
|
||||
member.type = 'method'
|
||||
member.type = 'method';
|
||||
} else {
|
||||
if (descriptor.set || descriptor.writable) member.writable = true
|
||||
member.type = 'get'
|
||||
if (descriptor.set || descriptor.writable) member.writable = true;
|
||||
member.type = 'get';
|
||||
}
|
||||
return member
|
||||
})
|
||||
}
|
||||
return member;
|
||||
});
|
||||
};
|
||||
|
||||
// Return the description of object's prototype.
|
||||
const getObjectPrototype = function (object) {
|
||||
const proto = Object.getPrototypeOf(object)
|
||||
if (proto === null || proto === Object.prototype) return null
|
||||
const proto = Object.getPrototypeOf(object);
|
||||
if (proto === null || proto === Object.prototype) return null;
|
||||
return {
|
||||
members: getObjectMembers(proto),
|
||||
proto: getObjectPrototype(proto)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
// Convert a real value into meta data.
|
||||
const valueToMeta = function (sender, contextId, value, optimizeSimpleObject = false) {
|
||||
// Determine the type of value.
|
||||
const meta = { type: typeof value }
|
||||
const meta = { type: typeof value };
|
||||
if (meta.type === 'object') {
|
||||
// Recognize certain types of objects.
|
||||
if (value === null) {
|
||||
meta.type = 'value'
|
||||
meta.type = 'value';
|
||||
} else if (bufferUtils.isBuffer(value)) {
|
||||
meta.type = 'buffer'
|
||||
meta.type = 'buffer';
|
||||
} else if (Array.isArray(value)) {
|
||||
meta.type = 'array'
|
||||
meta.type = 'array';
|
||||
} else if (value instanceof Error) {
|
||||
meta.type = 'error'
|
||||
meta.type = 'error';
|
||||
} else if (value instanceof Date) {
|
||||
meta.type = 'date'
|
||||
meta.type = 'date';
|
||||
} else if (isPromise(value)) {
|
||||
meta.type = 'promise'
|
||||
meta.type = 'promise';
|
||||
} else if (hasProp.call(value, 'callee') && value.length != null) {
|
||||
// Treat the arguments object as array.
|
||||
meta.type = 'array'
|
||||
meta.type = 'array';
|
||||
} else if (optimizeSimpleObject && v8Util.getHiddenValue(value, 'simple')) {
|
||||
// Treat simple objects as value.
|
||||
meta.type = 'value'
|
||||
meta.type = 'value';
|
||||
}
|
||||
}
|
||||
|
||||
// Fill the meta object according to value's type.
|
||||
if (meta.type === 'array') {
|
||||
meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject))
|
||||
meta.members = value.map((el) => valueToMeta(sender, contextId, el, optimizeSimpleObject));
|
||||
} else if (meta.type === 'object' || meta.type === 'function') {
|
||||
meta.name = value.constructor ? value.constructor.name : ''
|
||||
meta.name = value.constructor ? value.constructor.name : '';
|
||||
|
||||
// Reference the original value if it's an object, because when it's
|
||||
// passed to renderer we would assume the renderer keeps a reference of
|
||||
// it.
|
||||
meta.id = objectsRegistry.add(sender, contextId, value)
|
||||
meta.members = getObjectMembers(value)
|
||||
meta.proto = getObjectPrototype(value)
|
||||
meta.id = objectsRegistry.add(sender, contextId, value);
|
||||
meta.members = getObjectMembers(value);
|
||||
meta.proto = getObjectPrototype(value);
|
||||
} else if (meta.type === 'buffer') {
|
||||
meta.value = bufferUtils.bufferToMeta(value)
|
||||
meta.value = bufferUtils.bufferToMeta(value);
|
||||
} else if (meta.type === 'promise') {
|
||||
// Add default handler to prevent unhandled rejections in main process
|
||||
// Instead they should appear in the renderer process
|
||||
value.then(function () {}, function () {})
|
||||
value.then(function () {}, function () {});
|
||||
|
||||
meta.then = valueToMeta(sender, contextId, function (onFulfilled, onRejected) {
|
||||
value.then(onFulfilled, onRejected)
|
||||
})
|
||||
value.then(onFulfilled, onRejected);
|
||||
});
|
||||
} else if (meta.type === 'error') {
|
||||
meta.members = plainObjectToMeta(value)
|
||||
meta.members = plainObjectToMeta(value);
|
||||
|
||||
// Error.name is not part of own properties.
|
||||
meta.members.push({
|
||||
name: 'name',
|
||||
value: value.name
|
||||
})
|
||||
});
|
||||
} else if (meta.type === 'date') {
|
||||
meta.value = value.getTime()
|
||||
meta.value = value.getTime();
|
||||
} else {
|
||||
meta.type = 'value'
|
||||
meta.value = value
|
||||
meta.type = 'value';
|
||||
meta.value = value;
|
||||
}
|
||||
return meta
|
||||
}
|
||||
return meta;
|
||||
};
|
||||
|
||||
// Convert object to meta by value.
|
||||
const plainObjectToMeta = function (obj) {
|
||||
@@ -137,404 +137,407 @@ const plainObjectToMeta = function (obj) {
|
||||
return {
|
||||
name: name,
|
||||
value: obj[name]
|
||||
}
|
||||
})
|
||||
}
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Convert Error into meta data.
|
||||
const exceptionToMeta = function (error) {
|
||||
return {
|
||||
type: 'exception',
|
||||
value: errorUtils.serialize(error)
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
const throwRPCError = function (message) {
|
||||
const error = new Error(message)
|
||||
error.code = 'EBADRPC'
|
||||
error.errno = -72
|
||||
throw error
|
||||
}
|
||||
const error = new Error(message);
|
||||
error.code = 'EBADRPC';
|
||||
error.errno = -72;
|
||||
throw error;
|
||||
};
|
||||
|
||||
const removeRemoteListenersAndLogWarning = (sender, callIntoRenderer) => {
|
||||
const location = v8Util.getHiddenValue(callIntoRenderer, 'location')
|
||||
const location = v8Util.getHiddenValue(callIntoRenderer, 'location');
|
||||
let message = `Attempting to call a function in a renderer window that has been closed or released.` +
|
||||
`\nFunction provided here: ${location}`
|
||||
`\nFunction provided here: ${location}`;
|
||||
|
||||
if (sender instanceof EventEmitter) {
|
||||
const remoteEvents = sender.eventNames().filter((eventName) => {
|
||||
return sender.listeners(eventName).includes(callIntoRenderer)
|
||||
})
|
||||
return sender.listeners(eventName).includes(callIntoRenderer);
|
||||
});
|
||||
|
||||
if (remoteEvents.length > 0) {
|
||||
message += `\nRemote event names: ${remoteEvents.join(', ')}`
|
||||
message += `\nRemote event names: ${remoteEvents.join(', ')}`;
|
||||
remoteEvents.forEach((eventName) => {
|
||||
sender.removeListener(eventName, callIntoRenderer)
|
||||
})
|
||||
sender.removeListener(eventName, callIntoRenderer);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
console.warn(message)
|
||||
}
|
||||
console.warn(message);
|
||||
};
|
||||
|
||||
// Convert array of meta data from renderer into array of real values.
|
||||
const unwrapArgs = function (sender, frameId, contextId, args) {
|
||||
const metaToValue = function (meta) {
|
||||
switch (meta.type) {
|
||||
case 'value':
|
||||
return meta.value
|
||||
return meta.value;
|
||||
case 'remote-object':
|
||||
return objectsRegistry.get(meta.id)
|
||||
return objectsRegistry.get(meta.id);
|
||||
case 'array':
|
||||
return unwrapArgs(sender, frameId, contextId, meta.value)
|
||||
return unwrapArgs(sender, frameId, contextId, meta.value);
|
||||
case 'buffer':
|
||||
return bufferUtils.metaToBuffer(meta.value)
|
||||
return bufferUtils.metaToBuffer(meta.value);
|
||||
case 'date':
|
||||
return new Date(meta.value)
|
||||
return new Date(meta.value);
|
||||
case 'promise':
|
||||
return Promise.resolve({
|
||||
then: metaToValue(meta.then)
|
||||
})
|
||||
});
|
||||
case 'object': {
|
||||
const ret = {}
|
||||
Object.defineProperty(ret.constructor, 'name', { value: meta.name })
|
||||
const ret = {};
|
||||
Object.defineProperty(ret.constructor, 'name', { value: meta.name });
|
||||
|
||||
for (const { name, value } of meta.members) {
|
||||
ret[name] = metaToValue(value)
|
||||
ret[name] = metaToValue(value);
|
||||
}
|
||||
return ret
|
||||
return ret;
|
||||
}
|
||||
case 'function-with-return-value':
|
||||
const returnValue = metaToValue(meta.value)
|
||||
const returnValue = metaToValue(meta.value);
|
||||
return function () {
|
||||
return returnValue
|
||||
}
|
||||
return returnValue;
|
||||
};
|
||||
case 'function': {
|
||||
// Merge contextId and meta.id, since meta.id can be the same in
|
||||
// different webContents.
|
||||
const objectId = [contextId, meta.id]
|
||||
const objectId = [contextId, meta.id];
|
||||
|
||||
// Cache the callbacks in renderer.
|
||||
if (rendererFunctions.has(objectId)) {
|
||||
return rendererFunctions.get(objectId)
|
||||
return rendererFunctions.get(objectId);
|
||||
}
|
||||
|
||||
const callIntoRenderer = function (...args) {
|
||||
let succeed = false
|
||||
let succeed = false;
|
||||
if (!sender.isDestroyed()) {
|
||||
succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args))
|
||||
succeed = sender._sendToFrameInternal(frameId, 'ELECTRON_RENDERER_CALLBACK', contextId, meta.id, valueToMeta(sender, contextId, args));
|
||||
}
|
||||
if (!succeed) {
|
||||
removeRemoteListenersAndLogWarning(this, callIntoRenderer)
|
||||
removeRemoteListenersAndLogWarning(this, callIntoRenderer);
|
||||
}
|
||||
}
|
||||
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
|
||||
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
|
||||
};
|
||||
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location);
|
||||
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length });
|
||||
|
||||
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender)
|
||||
rendererFunctions.set(objectId, callIntoRenderer)
|
||||
return callIntoRenderer
|
||||
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender);
|
||||
rendererFunctions.set(objectId, callIntoRenderer);
|
||||
return callIntoRenderer;
|
||||
}
|
||||
default:
|
||||
throw new TypeError(`Unknown type: ${meta.type}`)
|
||||
throw new TypeError(`Unknown type: ${meta.type}`);
|
||||
}
|
||||
}
|
||||
return args.map(metaToValue)
|
||||
}
|
||||
};
|
||||
return args.map(metaToValue);
|
||||
};
|
||||
|
||||
const isRemoteModuleEnabledCache = new WeakMap()
|
||||
const isRemoteModuleEnabledCache = new WeakMap();
|
||||
|
||||
const isRemoteModuleEnabled = function (contents) {
|
||||
if (!isRemoteModuleEnabledCache.has(contents)) {
|
||||
isRemoteModuleEnabledCache.set(contents, contents._isRemoteModuleEnabled())
|
||||
isRemoteModuleEnabledCache.set(contents, contents._isRemoteModuleEnabled());
|
||||
}
|
||||
|
||||
return isRemoteModuleEnabledCache.get(contents)
|
||||
}
|
||||
return isRemoteModuleEnabledCache.get(contents);
|
||||
};
|
||||
|
||||
const handleRemoteCommand = function (channel, handler) {
|
||||
ipcMainInternal.on(channel, (event, contextId, ...args) => {
|
||||
let returnValue
|
||||
let returnValue;
|
||||
if (!isRemoteModuleEnabled(event.sender)) {
|
||||
event.returnValue = null
|
||||
return
|
||||
event.returnValue = null;
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
returnValue = handler(event, contextId, ...args)
|
||||
returnValue = handler(event, contextId, ...args);
|
||||
} catch (error) {
|
||||
returnValue = exceptionToMeta(error)
|
||||
returnValue = exceptionToMeta(error);
|
||||
}
|
||||
|
||||
if (returnValue !== undefined) {
|
||||
event.returnValue = returnValue
|
||||
event.returnValue = returnValue;
|
||||
}
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const emitCustomEvent = function (contents, eventName, ...args) {
|
||||
const event = eventBinding.createWithSender(contents)
|
||||
const event = eventBinding.createWithSender(contents);
|
||||
|
||||
electron.app.emit(eventName, event, contents, ...args)
|
||||
contents.emit(eventName, event, ...args)
|
||||
electron.app.emit(eventName, event, contents, ...args);
|
||||
contents.emit(eventName, event, ...args);
|
||||
|
||||
return event
|
||||
}
|
||||
return event;
|
||||
};
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
|
||||
const objectId = [passedContextId, id]
|
||||
const objectId = [passedContextId, id];
|
||||
if (!rendererFunctions.has(objectId)) {
|
||||
// Do nothing if the error has already been reported before.
|
||||
return
|
||||
return;
|
||||
}
|
||||
removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
|
||||
})
|
||||
removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId));
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName) {
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName)
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-require', moduleName);
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.require('${moduleName}')`)
|
||||
throw new Error(`Blocked remote.require('${moduleName}')`);
|
||||
} else {
|
||||
customEvent.returnValue = process.mainModule.require(moduleName)
|
||||
customEvent.returnValue = process.mainModule.require(moduleName);
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GET_BUILTIN', function (event, contextId, moduleName) {
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName)
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-builtin', moduleName);
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.getBuiltin('${moduleName}')`)
|
||||
throw new Error(`Blocked remote.getBuiltin('${moduleName}')`);
|
||||
} else {
|
||||
customEvent.returnValue = electron[moduleName]
|
||||
customEvent.returnValue = electron[moduleName];
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GLOBAL', function (event, contextId, globalName) {
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName)
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-global', globalName);
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.getGlobal('${globalName}')`)
|
||||
throw new Error(`Blocked remote.getGlobal('${globalName}')`);
|
||||
} else {
|
||||
customEvent.returnValue = global[globalName]
|
||||
customEvent.returnValue = global[globalName];
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WINDOW', function (event, contextId) {
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window')
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-window');
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error('Blocked remote.getCurrentWindow()')
|
||||
throw new Error('Blocked remote.getCurrentWindow()');
|
||||
} else {
|
||||
customEvent.returnValue = event.sender.getOwnerBrowserWindow()
|
||||
customEvent.returnValue = event.sender.getOwnerBrowserWindow();
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, contextId) {
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents')
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-current-web-contents');
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error('Blocked remote.getCurrentWebContents()')
|
||||
throw new Error('Blocked remote.getCurrentWebContents()');
|
||||
} else {
|
||||
customEvent.returnValue = event.sender
|
||||
customEvent.returnValue = event.sender;
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args)
|
||||
const constructor = objectsRegistry.get(id)
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
const constructor = objectsRegistry.get(id);
|
||||
|
||||
if (constructor == null) {
|
||||
throwRPCError(`Cannot call constructor on missing remote object ${id}`)
|
||||
throwRPCError(`Cannot call constructor on missing remote object ${id}`);
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, new constructor(...args))
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, new constructor(...args));
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args)
|
||||
const func = objectsRegistry.get(id)
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
const func = objectsRegistry.get(id);
|
||||
|
||||
if (func == null) {
|
||||
throwRPCError(`Cannot call function on missing remote object ${id}`)
|
||||
throwRPCError(`Cannot call function on missing remote object ${id}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return valueToMeta(event.sender, contextId, func(...args), true)
|
||||
return valueToMeta(event.sender, contextId, func(...args), true);
|
||||
} catch (error) {
|
||||
const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
|
||||
err.cause = error
|
||||
throw err
|
||||
const err = new Error(`Could not call remote function '${func.name || 'anonymous'}'. Check that the function signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
|
||||
err.cause = error;
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args)
|
||||
const object = objectsRegistry.get(id)
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
const object = objectsRegistry.get(id);
|
||||
|
||||
if (object == null) {
|
||||
throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`)
|
||||
throwRPCError(`Cannot call constructor '${method}' on missing remote object ${id}`);
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, new object[method](...args))
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, new object[method](...args));
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args)
|
||||
const object = objectsRegistry.get(id)
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
const object = objectsRegistry.get(id);
|
||||
|
||||
if (object == null) {
|
||||
throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`)
|
||||
throwRPCError(`Cannot call method '${method}' on missing remote object ${id}`);
|
||||
}
|
||||
|
||||
try {
|
||||
return valueToMeta(event.sender, contextId, object[method](...args), true)
|
||||
return valueToMeta(event.sender, contextId, object[method](...args), true);
|
||||
} catch (error) {
|
||||
const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`)
|
||||
err.cause = error
|
||||
throw err
|
||||
const err = new Error(`Could not call remote method '${method}'. Check that the method signature is correct. Underlying error: ${error.message}\nUnderlying stack: ${error.stack}\n`);
|
||||
err.cause = error;
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) {
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args)
|
||||
const obj = objectsRegistry.get(id)
|
||||
args = unwrapArgs(event.sender, event.frameId, contextId, args);
|
||||
const obj = objectsRegistry.get(id);
|
||||
|
||||
if (obj == null) {
|
||||
throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`)
|
||||
throwRPCError(`Cannot set property '${name}' on missing remote object ${id}`);
|
||||
}
|
||||
|
||||
obj[name] = args[0]
|
||||
return null
|
||||
})
|
||||
obj[name] = args[0];
|
||||
return null;
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_MEMBER_GET', function (event, contextId, id, name) {
|
||||
const obj = objectsRegistry.get(id)
|
||||
const obj = objectsRegistry.get(id);
|
||||
|
||||
if (obj == null) {
|
||||
throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`)
|
||||
throwRPCError(`Cannot get property '${name}' on missing remote object ${id}`);
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, obj[name])
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, obj[name]);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId, id, rendererSideRefCount) {
|
||||
objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount)
|
||||
})
|
||||
objectsRegistry.remove(event.sender, contextId, id, rendererSideRefCount);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
|
||||
objectsRegistry.clear(event.sender, contextId)
|
||||
})
|
||||
objectsRegistry.clear(event.sender, contextId);
|
||||
});
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
|
||||
const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender)
|
||||
const guest = guestViewManager.getGuestForWebContents(guestInstanceId, event.sender);
|
||||
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-guest-web-contents', guest)
|
||||
const customEvent = emitCustomEvent(event.sender, 'remote-get-guest-web-contents', guest);
|
||||
|
||||
if (customEvent.returnValue === undefined) {
|
||||
if (customEvent.defaultPrevented) {
|
||||
throw new Error(`Blocked remote.getGuestForWebContents()`)
|
||||
throw new Error(`Blocked remote.getGuestForWebContents()`);
|
||||
} else {
|
||||
customEvent.returnValue = guest
|
||||
customEvent.returnValue = guest;
|
||||
}
|
||||
}
|
||||
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue)
|
||||
})
|
||||
return valueToMeta(event.sender, contextId, customEvent.returnValue);
|
||||
});
|
||||
|
||||
// Implements window.close()
|
||||
ipcMainInternal.on('ELECTRON_BROWSER_WINDOW_CLOSE', function (event) {
|
||||
const window = event.sender.getOwnerBrowserWindow()
|
||||
const window = event.sender.getOwnerBrowserWindow();
|
||||
if (window) {
|
||||
window.close()
|
||||
window.close();
|
||||
}
|
||||
event.returnValue = null
|
||||
})
|
||||
event.returnValue = null;
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_CRASH_REPORTER_INIT', function (event, options) {
|
||||
return crashReporterInit(options)
|
||||
})
|
||||
return crashReporterInit(options);
|
||||
});
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_BROWSER_GET_LAST_WEB_PREFERENCES', function (event) {
|
||||
return event.sender.getLastWebPreferences()
|
||||
})
|
||||
return event.sender.getLastWebPreferences();
|
||||
});
|
||||
|
||||
// Methods not listed in this set are called directly in the renderer process.
|
||||
const allowedClipboardMethods = (() => {
|
||||
switch (process.platform) {
|
||||
case 'darwin':
|
||||
return new Set(['readFindText', 'writeFindText'])
|
||||
return new Set(['readFindText', 'writeFindText']);
|
||||
case 'linux':
|
||||
return new Set(Object.keys(clipboard))
|
||||
return new Set(Object.keys(clipboard));
|
||||
default:
|
||||
return new Set()
|
||||
return new Set();
|
||||
}
|
||||
})()
|
||||
})();
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...args) {
|
||||
if (!allowedClipboardMethods.has(method)) {
|
||||
throw new Error(`Invalid method: ${method}`)
|
||||
throw new Error(`Invalid method: ${method}`);
|
||||
}
|
||||
|
||||
return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)))
|
||||
})
|
||||
return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)));
|
||||
});
|
||||
|
||||
if (features.isDesktopCapturerEnabled()) {
|
||||
const desktopCapturer = require('@electron/internal/browser/desktop-capturer')
|
||||
const desktopCapturer = require('@electron/internal/browser/desktop-capturer');
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_BROWSER_DESKTOP_CAPTURER_GET_SOURCES', function (event, ...args) {
|
||||
const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources')
|
||||
const customEvent = emitCustomEvent(event.sender, 'desktop-capturer-get-sources');
|
||||
|
||||
if (customEvent.defaultPrevented) {
|
||||
console.error('Blocked desktopCapturer.getSources()')
|
||||
return []
|
||||
console.error('Blocked desktopCapturer.getSources()');
|
||||
return [];
|
||||
}
|
||||
|
||||
return desktopCapturer.getSources(event, ...args)
|
||||
})
|
||||
return desktopCapturer.getSources(event, ...args);
|
||||
});
|
||||
}
|
||||
|
||||
const getPreloadScript = async function (preloadPath) {
|
||||
let preloadSrc = null
|
||||
let preloadError = null
|
||||
let preloadSrc = null;
|
||||
let preloadError = null;
|
||||
try {
|
||||
preloadSrc = (await fs.promises.readFile(preloadPath)).toString()
|
||||
preloadSrc = (await fs.promises.readFile(preloadPath)).toString();
|
||||
} catch (err) {
|
||||
preloadError = errorUtils.serialize(err)
|
||||
preloadError = errorUtils.serialize(err);
|
||||
}
|
||||
return { preloadPath, preloadSrc, preloadError }
|
||||
}
|
||||
return { preloadPath, preloadSrc, preloadError };
|
||||
};
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts())
|
||||
ipcMainUtils.handle('ELECTRON_GET_CONTENT_SCRIPTS', () => getContentScripts());
|
||||
|
||||
ipcMainUtils.handle('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) {
|
||||
const preloadPaths = event.sender._getPreloadPaths()
|
||||
const preloadPaths = event.sender._getPreloadPaths();
|
||||
const webPreferences = event.sender.getLastWebPreferences() || {};
|
||||
|
||||
return {
|
||||
contentScripts: getContentScripts(),
|
||||
preloadScripts: await Promise.all(preloadPaths.map(path => getPreloadScript(path))),
|
||||
isRemoteModuleEnabled: isRemoteModuleEnabled(event.sender),
|
||||
isWebViewTagEnabled: guestViewManager.isWebViewTagEnabled(event.sender),
|
||||
guestInstanceId: webPreferences.guestInstanceId,
|
||||
openerId: webPreferences.openerId,
|
||||
process: {
|
||||
arch: process.arch,
|
||||
platform: process.platform,
|
||||
@@ -543,9 +546,9 @@ ipcMainUtils.handle('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) {
|
||||
versions: process.versions,
|
||||
execPath: process.helperExecPath
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
ipcMainInternal.on('ELECTRON_BROWSER_PRELOAD_ERROR', function (event, preloadPath, error) {
|
||||
event.sender.emit('preload-error', event, preloadPath, errorUtils.deserialize(error))
|
||||
})
|
||||
event.sender.emit('preload-error', event, preloadPath, errorUtils.deserialize(error));
|
||||
});
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { EventEmitter } from 'events'
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
/**
|
||||
* Creates a lazy instance of modules that can't be required before the
|
||||
@@ -16,23 +16,23 @@ export function createLazyInstance (
|
||||
holder: Object,
|
||||
isEventEmitter: Boolean
|
||||
) {
|
||||
let lazyModule: Object
|
||||
const module: any = {}
|
||||
for (const method in (holder as any).prototype) {
|
||||
let lazyModule: Object;
|
||||
const module: any = {};
|
||||
for (const method in (holder as any).prototype) { // eslint-disable-line guard-for-in
|
||||
module[method] = (...args: any) => {
|
||||
// create new instance of module at runtime if none exists
|
||||
if (!lazyModule) {
|
||||
lazyModule = creator()
|
||||
if (isEventEmitter) EventEmitter.call(lazyModule as any)
|
||||
lazyModule = creator();
|
||||
if (isEventEmitter) EventEmitter.call(lazyModule as any);
|
||||
}
|
||||
|
||||
// check for properties on the prototype chain that aren't functions
|
||||
if (typeof (lazyModule as any)[method] !== 'function') {
|
||||
return (lazyModule as any)[method]
|
||||
return (lazyModule as any)[method];
|
||||
}
|
||||
|
||||
return (lazyModule as any)[method](...args)
|
||||
}
|
||||
return (lazyModule as any)[method](...args);
|
||||
};
|
||||
}
|
||||
return module
|
||||
return module;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,29 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const clipboard = process.electronBinding('clipboard')
|
||||
const clipboard = process.electronBinding('clipboard');
|
||||
|
||||
if (process.type === 'renderer') {
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
|
||||
const typeUtils = require('@electron/internal/common/type-utils')
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
|
||||
const typeUtils = require('@electron/internal/common/type-utils');
|
||||
|
||||
const makeRemoteMethod = function (method) {
|
||||
return (...args) => {
|
||||
args = typeUtils.serialize(args)
|
||||
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args)
|
||||
return typeUtils.deserialize(result)
|
||||
}
|
||||
}
|
||||
args = typeUtils.serialize(args);
|
||||
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args);
|
||||
return typeUtils.deserialize(result);
|
||||
};
|
||||
};
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
// On Linux we could not access clipboard in renderer process.
|
||||
for (const method of Object.keys(clipboard)) {
|
||||
clipboard[method] = makeRemoteMethod(method)
|
||||
clipboard[method] = makeRemoteMethod(method);
|
||||
}
|
||||
} else if (process.platform === 'darwin') {
|
||||
// Read/write to find pasteboard over IPC since only main process is notified of changes
|
||||
clipboard.readFindText = makeRemoteMethod('readFindText')
|
||||
clipboard.writeFindText = makeRemoteMethod('writeFindText')
|
||||
clipboard.readFindText = makeRemoteMethod('readFindText');
|
||||
clipboard.writeFindText = makeRemoteMethod('writeFindText');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = clipboard
|
||||
module.exports = clipboard;
|
||||
|
||||
@@ -1,95 +1,95 @@
|
||||
let deprecationHandler: ElectronInternal.DeprecationHandler | null = null
|
||||
let deprecationHandler: ElectronInternal.DeprecationHandler | null = null;
|
||||
|
||||
function warnOnce (oldName: string, newName?: string) {
|
||||
let warned = false
|
||||
let warned = false;
|
||||
const msg = newName
|
||||
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
|
||||
: `'${oldName}' is deprecated and will be removed.`
|
||||
: `'${oldName}' is deprecated and will be removed.`;
|
||||
return () => {
|
||||
if (!warned && !process.noDeprecation) {
|
||||
warned = true
|
||||
deprecate.log(msg)
|
||||
warned = true;
|
||||
deprecate.log(msg);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const deprecate: ElectronInternal.DeprecationUtil = {
|
||||
warnOnce,
|
||||
setHandler: (handler) => { deprecationHandler = handler },
|
||||
setHandler: (handler) => { deprecationHandler = handler; },
|
||||
getHandler: () => deprecationHandler,
|
||||
warn: (oldName, newName) => {
|
||||
if (!process.noDeprecation) {
|
||||
deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`)
|
||||
deprecate.log(`'${oldName}' is deprecated. Use '${newName}' instead.`);
|
||||
}
|
||||
},
|
||||
log: (message) => {
|
||||
if (typeof deprecationHandler === 'function') {
|
||||
deprecationHandler(message)
|
||||
deprecationHandler(message);
|
||||
} else if (process.throwDeprecation) {
|
||||
throw new Error(message)
|
||||
throw new Error(message);
|
||||
} else if (process.traceDeprecation) {
|
||||
return console.trace(message)
|
||||
return console.trace(message);
|
||||
} else {
|
||||
return console.warn(`(electron) ${message}`)
|
||||
return console.warn(`(electron) ${message}`);
|
||||
}
|
||||
},
|
||||
|
||||
// remove a function with no replacement
|
||||
removeFunction: (fn, removedName) => {
|
||||
if (!fn) { throw Error(`'${removedName} function' is invalid or does not exist.`) }
|
||||
if (!fn) { throw Error(`'${removedName} function' is invalid or does not exist.`); }
|
||||
|
||||
// wrap the deprecated function to warn user
|
||||
const warn = warnOnce(`${fn.name} function`)
|
||||
const warn = warnOnce(`${fn.name} function`);
|
||||
return function (this: any) {
|
||||
warn()
|
||||
fn.apply(this, arguments)
|
||||
}
|
||||
warn();
|
||||
fn.apply(this, arguments);
|
||||
};
|
||||
},
|
||||
|
||||
// change the name of a function
|
||||
renameFunction: (fn, newName) => {
|
||||
const warn = warnOnce(`${fn.name} function`, `${newName} function`)
|
||||
const warn = warnOnce(`${fn.name} function`, `${newName} function`);
|
||||
return function (this: any) {
|
||||
warn()
|
||||
return fn.apply(this, arguments)
|
||||
}
|
||||
warn();
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
},
|
||||
|
||||
moveAPI: (fn: Function, oldUsage: string, newUsage: string) => {
|
||||
const warn = warnOnce(oldUsage, newUsage)
|
||||
const warn = warnOnce(oldUsage, newUsage);
|
||||
return function (this: any) {
|
||||
warn()
|
||||
return fn.apply(this, arguments)
|
||||
}
|
||||
warn();
|
||||
return fn.apply(this, arguments);
|
||||
};
|
||||
},
|
||||
|
||||
// change the name of an event
|
||||
event: (emitter, oldName, newName) => {
|
||||
const warn = newName.startsWith('-') /* internal event */
|
||||
? warnOnce(`${oldName} event`)
|
||||
: warnOnce(`${oldName} event`, `${newName} event`)
|
||||
: warnOnce(`${oldName} event`, `${newName} event`);
|
||||
return emitter.on(newName, function (this: NodeJS.EventEmitter, ...args) {
|
||||
if (this.listenerCount(oldName) !== 0) {
|
||||
warn()
|
||||
this.emit(oldName, ...args)
|
||||
warn();
|
||||
this.emit(oldName, ...args);
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// deprecate a getter/setter function pair in favor of a property
|
||||
fnToProperty: (prototype: any, prop: string, getter: string, setter?: string) => {
|
||||
const withWarnOnce = function (obj: any, key: any, oldName: string, newName: string) {
|
||||
const warn = warnOnce(oldName, newName)
|
||||
const method = obj[key]
|
||||
const warn = warnOnce(oldName, newName);
|
||||
const method = obj[key];
|
||||
return function (this: any, ...args: any) {
|
||||
warn()
|
||||
return method.apply(this, args)
|
||||
}
|
||||
}
|
||||
warn();
|
||||
return method.apply(this, args);
|
||||
};
|
||||
};
|
||||
|
||||
prototype[getter.substr(1)] = withWarnOnce(prototype, getter, `${getter.substr(1)} function`, `${prop} property`)
|
||||
prototype[getter.substr(1)] = withWarnOnce(prototype, getter, `${getter.substr(1)} function`, `${prop} property`);
|
||||
if (setter) {
|
||||
prototype[setter.substr(1)] = withWarnOnce(prototype, setter, `${setter.substr(1)} function`, `${prop} property`)
|
||||
prototype[setter.substr(1)] = withWarnOnce(prototype, setter, `${setter.substr(1)} function`, `${prop} property`);
|
||||
}
|
||||
},
|
||||
|
||||
@@ -97,107 +97,107 @@ const deprecate: ElectronInternal.DeprecationUtil = {
|
||||
removeProperty: (o, removedName) => {
|
||||
// if the property's already been removed, warn about it
|
||||
if (!(removedName in o)) {
|
||||
deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`)
|
||||
deprecate.log(`Unable to remove property '${removedName}' from an object that lacks it.`);
|
||||
}
|
||||
|
||||
// wrap the deprecated property in an accessor to warn
|
||||
const warn = warnOnce(removedName)
|
||||
let val = o[removedName]
|
||||
const warn = warnOnce(removedName);
|
||||
let val = o[removedName];
|
||||
return Object.defineProperty(o, removedName, {
|
||||
configurable: true,
|
||||
get: () => {
|
||||
warn()
|
||||
return val
|
||||
warn();
|
||||
return val;
|
||||
},
|
||||
set: newVal => {
|
||||
warn()
|
||||
val = newVal
|
||||
warn();
|
||||
val = newVal;
|
||||
}
|
||||
})
|
||||
});
|
||||
},
|
||||
|
||||
// deprecate a callback-based function in favor of one returning a Promise
|
||||
promisify: <T extends (...args: any[]) => any>(fn: T): T => {
|
||||
const fnName = fn.name || 'function'
|
||||
const oldName = `${fnName} with callbacks`
|
||||
const newName = `${fnName} with Promises`
|
||||
const warn = warnOnce(oldName, newName)
|
||||
const fnName = fn.name || 'function';
|
||||
const oldName = `${fnName} with callbacks`;
|
||||
const newName = `${fnName} with Promises`;
|
||||
const warn = warnOnce(oldName, newName);
|
||||
|
||||
return function (this: any, ...params: any[]) {
|
||||
let cb: Function | undefined
|
||||
let cb: Function | undefined;
|
||||
if (params.length > 0 && typeof params[params.length - 1] === 'function') {
|
||||
cb = params.pop()
|
||||
cb = params.pop();
|
||||
}
|
||||
const promise = fn.apply(this, params)
|
||||
if (!cb) return promise
|
||||
if (process.enablePromiseAPIs) warn()
|
||||
const promise = fn.apply(this, params);
|
||||
if (!cb) return promise;
|
||||
if (process.enablePromiseAPIs) warn();
|
||||
return promise
|
||||
.then((res: any) => {
|
||||
process.nextTick(() => {
|
||||
cb!.length === 2 ? cb!(null, res) : cb!(res)
|
||||
})
|
||||
return res
|
||||
cb!.length === 2 ? cb!(null, res) : cb!(res);
|
||||
});
|
||||
return res;
|
||||
}, (err: Error) => {
|
||||
process.nextTick(() => {
|
||||
cb!.length === 2 ? cb!(err) : cb!()
|
||||
})
|
||||
throw err
|
||||
})
|
||||
} as T
|
||||
cb!.length === 2 ? cb!(err) : cb!();
|
||||
});
|
||||
throw err;
|
||||
});
|
||||
} as T;
|
||||
},
|
||||
|
||||
// convertPromiseValue: Temporarily disabled until it's used
|
||||
// deprecate a callback-based function in favor of one returning a Promise
|
||||
promisifyMultiArg: <T extends (...args: any[]) => any>(fn: T /* convertPromiseValue: (v: any) => any */): T => {
|
||||
const fnName = fn.name || 'function'
|
||||
const oldName = `${fnName} with callbacks`
|
||||
const newName = `${fnName} with Promises`
|
||||
const warn = warnOnce(oldName, newName)
|
||||
const fnName = fn.name || 'function';
|
||||
const oldName = `${fnName} with callbacks`;
|
||||
const newName = `${fnName} with Promises`;
|
||||
const warn = warnOnce(oldName, newName);
|
||||
|
||||
return function (this: any, ...params) {
|
||||
let cb: Function | undefined
|
||||
let cb: Function | undefined;
|
||||
if (params.length > 0 && typeof params[params.length - 1] === 'function') {
|
||||
cb = params.pop()
|
||||
cb = params.pop();
|
||||
}
|
||||
const promise = fn.apply(this, params)
|
||||
if (!cb) return promise
|
||||
if (process.enablePromiseAPIs) warn()
|
||||
const promise = fn.apply(this, params);
|
||||
if (!cb) return promise;
|
||||
if (process.enablePromiseAPIs) warn();
|
||||
return promise
|
||||
.then((res: any) => {
|
||||
process.nextTick(() => {
|
||||
// eslint-disable-next-line standard/no-callback-literal
|
||||
cb!.length > 2 ? cb!(null, ...res) : cb!(...res)
|
||||
})
|
||||
cb!.length > 2 ? cb!(null, ...res) : cb!(...res);
|
||||
});
|
||||
}, (err: Error) => {
|
||||
process.nextTick(() => cb!(err))
|
||||
})
|
||||
} as T
|
||||
process.nextTick(() => cb!(err));
|
||||
});
|
||||
} as T;
|
||||
},
|
||||
|
||||
// change the name of a property
|
||||
renameProperty: (o, oldName, newName) => {
|
||||
const warn = warnOnce(oldName, newName)
|
||||
const warn = warnOnce(oldName, newName);
|
||||
|
||||
// if the new property isn't there yet,
|
||||
// inject it and warn about it
|
||||
if ((oldName in o) && !(newName in o)) {
|
||||
warn()
|
||||
o[newName] = (o as any)[oldName]
|
||||
warn();
|
||||
o[newName] = (o as any)[oldName];
|
||||
}
|
||||
|
||||
// wrap the deprecated property in an accessor to warn
|
||||
// and redirect to the new property
|
||||
return Object.defineProperty(o, oldName, {
|
||||
get: () => {
|
||||
warn()
|
||||
return o[newName]
|
||||
warn();
|
||||
return o[newName];
|
||||
},
|
||||
set: value => {
|
||||
warn()
|
||||
o[newName] = value
|
||||
warn();
|
||||
o[newName] = value;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export default deprecate
|
||||
export default deprecate;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const moduleList = require('@electron/internal/common/api/module-list')
|
||||
const moduleList = require('@electron/internal/common/api/module-list');
|
||||
|
||||
exports.handleESModule = (loader) => () => {
|
||||
const value = loader()
|
||||
if (value.__esModule && value.default) return value.default
|
||||
return value
|
||||
}
|
||||
const value = loader();
|
||||
if (value.__esModule && value.default) return value.default;
|
||||
return value;
|
||||
};
|
||||
|
||||
exports.memoizedGetter = (getter) => {
|
||||
/*
|
||||
@@ -14,24 +14,24 @@ exports.memoizedGetter = (getter) => {
|
||||
* node module cache anyway at `Module._cache`. This memoization
|
||||
* is dramatically faster than relying on nodes module cache however
|
||||
*/
|
||||
let memoizedValue = null
|
||||
let memoizedValue = null;
|
||||
|
||||
return () => {
|
||||
if (memoizedValue === null) {
|
||||
memoizedValue = getter()
|
||||
memoizedValue = getter();
|
||||
}
|
||||
return memoizedValue
|
||||
}
|
||||
}
|
||||
return memoizedValue;
|
||||
};
|
||||
};
|
||||
|
||||
// Attaches properties to |targetExports|.
|
||||
exports.defineProperties = function (targetExports) {
|
||||
const descriptors = {}
|
||||
const descriptors = {};
|
||||
for (const module of moduleList) {
|
||||
descriptors[module.name] = {
|
||||
enumerable: !module.private,
|
||||
get: exports.handleESModule(module.loader)
|
||||
}
|
||||
};
|
||||
}
|
||||
return Object.defineProperties(targetExports, descriptors)
|
||||
}
|
||||
return Object.defineProperties(targetExports, descriptors);
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
// Common modules, please sort alphabetically
|
||||
module.exports = [
|
||||
@@ -7,4 +7,4 @@ module.exports = [
|
||||
{ name: 'shell', loader: () => require('./shell') },
|
||||
// The internal modules, invisible unless you know their names.
|
||||
{ name: 'deprecate', loader: () => require('./deprecate'), private: true }
|
||||
]
|
||||
];
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const { deprecate } = require('electron')
|
||||
const { NativeImage, nativeImage } = process.electronBinding('native_image')
|
||||
const { deprecate } = require('electron');
|
||||
const { NativeImage, nativeImage } = process.electronBinding('native_image');
|
||||
|
||||
deprecate.fnToProperty(NativeImage.prototype, 'isMacTemplateImage', '_isTemplateImage', '_setTemplateImage')
|
||||
deprecate.fnToProperty(NativeImage.prototype, 'isMacTemplateImage', '_isTemplateImage', '_setTemplateImage');
|
||||
|
||||
module.exports = nativeImage
|
||||
module.exports = nativeImage;
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
module.exports = process.electronBinding('shell')
|
||||
module.exports = process.electronBinding('shell');
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
/* global require */
|
||||
|
||||
// Monkey-patch the fs module.
|
||||
require('electron/js2c/asar').wrapFsWithAsar(require('fs'))
|
||||
require('electron/js2c/asar').wrapFsWithAsar(require('fs'));
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Buffer } from 'buffer'
|
||||
import { Buffer } from 'buffer';
|
||||
|
||||
const typedArrays: Record<string, Function> = {
|
||||
Buffer,
|
||||
@@ -12,31 +12,31 @@ const typedArrays: Record<string, Function> = {
|
||||
Uint32Array,
|
||||
Float32Array,
|
||||
Float64Array
|
||||
}
|
||||
};
|
||||
|
||||
type BufferLike = Buffer | ArrayBuffer | ArrayBufferView
|
||||
|
||||
function getType (value: BufferLike) {
|
||||
for (const type of Object.keys(typedArrays)) {
|
||||
if (value instanceof typedArrays[type]) {
|
||||
return type
|
||||
return type;
|
||||
}
|
||||
}
|
||||
throw new Error('Invalid buffer')
|
||||
throw new Error('Invalid buffer');
|
||||
}
|
||||
|
||||
function getBuffer (value: BufferLike) {
|
||||
if (value instanceof Buffer) {
|
||||
return value
|
||||
return value;
|
||||
} else if (value instanceof ArrayBuffer) {
|
||||
return Buffer.from(value)
|
||||
return Buffer.from(value);
|
||||
} else {
|
||||
return Buffer.from(value.buffer, value.byteOffset, value.byteLength)
|
||||
return Buffer.from(value.buffer, value.byteOffset, value.byteLength);
|
||||
}
|
||||
}
|
||||
|
||||
export function isBuffer (value: BufferLike) {
|
||||
return ArrayBuffer.isView(value) || value instanceof ArrayBuffer
|
||||
return ArrayBuffer.isView(value) || value instanceof ArrayBuffer;
|
||||
}
|
||||
|
||||
interface BufferMeta {
|
||||
@@ -52,20 +52,20 @@ export function bufferToMeta (value: BufferLike): BufferMeta {
|
||||
// NB. We only use length when decoding Int8Array and friends.
|
||||
// For other buffer-like types this is expected to be undefined.
|
||||
length: (value as Buffer).length
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function metaToBuffer (value: BufferMeta) {
|
||||
const constructor = typedArrays[value.type]
|
||||
const data = getBuffer(value.data)
|
||||
const constructor = typedArrays[value.type];
|
||||
const data = getBuffer(value.data);
|
||||
|
||||
if (constructor === Buffer) {
|
||||
return data
|
||||
return data;
|
||||
} else if (constructor === ArrayBuffer) {
|
||||
return data.buffer
|
||||
return data.buffer;
|
||||
} else if (constructor) {
|
||||
return new (constructor as any)(data.buffer, data.byteOffset, value.length)
|
||||
return new (constructor as any)(data.buffer, data.byteOffset, value.length);
|
||||
} else {
|
||||
return data
|
||||
return data;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const binding = process.electronBinding('crash_reporter')
|
||||
const binding = process.electronBinding('crash_reporter');
|
||||
|
||||
class CrashReporter {
|
||||
contructor () {
|
||||
this.productName = null
|
||||
this.crashesDirectory = null
|
||||
this.productName = null;
|
||||
this.crashesDirectory = null;
|
||||
}
|
||||
|
||||
init (options) {
|
||||
throw new Error('Not implemented')
|
||||
throw new Error('Not implemented');
|
||||
}
|
||||
|
||||
start (options) {
|
||||
if (options == null) options = {}
|
||||
if (options == null) options = {};
|
||||
|
||||
const {
|
||||
productName,
|
||||
@@ -22,76 +22,81 @@ class CrashReporter {
|
||||
ignoreSystemCrashHandler = false,
|
||||
submitURL,
|
||||
uploadToServer = true
|
||||
} = options
|
||||
} = options;
|
||||
|
||||
if (companyName == null) throw new Error('companyName is a required option to crashReporter.start')
|
||||
if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start')
|
||||
if (companyName == null) throw new Error('companyName is a required option to crashReporter.start');
|
||||
if (submitURL == null) throw new Error('submitURL is a required option to crashReporter.start');
|
||||
|
||||
const ret = this.init({
|
||||
submitURL,
|
||||
productName
|
||||
})
|
||||
});
|
||||
|
||||
this.productName = ret.productName
|
||||
this.crashesDirectory = ret.crashesDirectory
|
||||
this.productName = ret.productName;
|
||||
this.crashesDirectory = ret.crashesDirectory;
|
||||
|
||||
if (extra._productName == null) extra._productName = ret.productName
|
||||
if (extra._companyName == null) extra._companyName = companyName
|
||||
if (extra._version == null) extra._version = ret.appVersion
|
||||
if (extra._productName == null) extra._productName = ret.productName;
|
||||
if (extra._companyName == null) extra._companyName = companyName;
|
||||
if (extra._version == null) extra._version = ret.appVersion;
|
||||
|
||||
binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra)
|
||||
binding.start(ret.productName, companyName, submitURL, ret.crashesDirectory, uploadToServer, ignoreSystemCrashHandler, extra);
|
||||
}
|
||||
|
||||
getLastCrashReport () {
|
||||
const reports = this.getUploadedReports()
|
||||
.sort((a, b) => {
|
||||
const ats = (a && a.date) ? new Date(a.date).getTime() : 0
|
||||
const bts = (b && b.date) ? new Date(b.date).getTime() : 0
|
||||
return bts - ats
|
||||
})
|
||||
const ats = (a && a.date) ? new Date(a.date).getTime() : 0;
|
||||
const bts = (b && b.date) ? new Date(b.date).getTime() : 0;
|
||||
return bts - ats;
|
||||
});
|
||||
|
||||
return (reports.length > 0) ? reports[0] : null
|
||||
return (reports.length > 0) ? reports[0] : null;
|
||||
}
|
||||
|
||||
getUploadedReports () {
|
||||
return binding.getUploadedReports(this.getCrashesDirectory())
|
||||
const crashDir = this.getCrashesDirectory();
|
||||
if (!crashDir) {
|
||||
throw new Error('crashReporter has not been started');
|
||||
}
|
||||
|
||||
return binding.getUploadedReports(crashDir);
|
||||
}
|
||||
|
||||
getCrashesDirectory () {
|
||||
return this.crashesDirectory
|
||||
return this.crashesDirectory;
|
||||
}
|
||||
|
||||
getProductName () {
|
||||
return this.productName
|
||||
return this.productName;
|
||||
}
|
||||
|
||||
getUploadToServer () {
|
||||
if (process.type === 'browser') {
|
||||
return binding.getUploadToServer()
|
||||
return binding.getUploadToServer();
|
||||
} else {
|
||||
throw new Error('getUploadToServer can only be called from the main process')
|
||||
throw new Error('getUploadToServer can only be called from the main process');
|
||||
}
|
||||
}
|
||||
|
||||
setUploadToServer (uploadToServer) {
|
||||
if (process.type === 'browser') {
|
||||
return binding.setUploadToServer(uploadToServer)
|
||||
return binding.setUploadToServer(uploadToServer);
|
||||
} else {
|
||||
throw new Error('setUploadToServer can only be called from the main process')
|
||||
throw new Error('setUploadToServer can only be called from the main process');
|
||||
}
|
||||
}
|
||||
|
||||
addExtraParameter (key, value) {
|
||||
binding.addExtraParameter(key, value)
|
||||
binding.addExtraParameter(key, value);
|
||||
}
|
||||
|
||||
removeExtraParameter (key) {
|
||||
binding.removeExtraParameter(key)
|
||||
binding.removeExtraParameter(key);
|
||||
}
|
||||
|
||||
getParameters () {
|
||||
return binding.getParameters()
|
||||
return binding.getParameters();
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CrashReporter
|
||||
module.exports = CrashReporter;
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
export function electronBindingSetup (binding: typeof process['_linkedBinding'], processType: typeof process['type']): typeof process['electronBinding'] {
|
||||
return function electronBinding (name: string) {
|
||||
try {
|
||||
return binding(`atom_${processType}_${name}`)
|
||||
return binding(`atom_${processType}_${name}`);
|
||||
} catch (error) {
|
||||
if (/No such module/.test(error.message)) {
|
||||
return binding(`atom_common_${name}`)
|
||||
return binding(`atom_common_${name}`);
|
||||
} else {
|
||||
throw error
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -6,18 +6,18 @@ const constructors = new Map([
|
||||
[SyntaxError.name, SyntaxError],
|
||||
[TypeError.name, TypeError],
|
||||
[URIError.name, URIError]
|
||||
])
|
||||
]);
|
||||
|
||||
export function deserialize (error: Electron.SerializedError): Electron.ErrorWithCause {
|
||||
if (error && error.__ELECTRON_SERIALIZED_ERROR__ && constructors.has(error.name)) {
|
||||
const constructor = constructors.get(error.name)
|
||||
const deserializedError = new constructor!(error.message) as Electron.ErrorWithCause
|
||||
deserializedError.stack = error.stack
|
||||
deserializedError.from = error.from
|
||||
deserializedError.cause = exports.deserialize(error.cause)
|
||||
return deserializedError
|
||||
const constructor = constructors.get(error.name);
|
||||
const deserializedError = new constructor!(error.message) as Electron.ErrorWithCause;
|
||||
deserializedError.stack = error.stack;
|
||||
deserializedError.from = error.from;
|
||||
deserializedError.cause = exports.deserialize(error.cause);
|
||||
return deserializedError;
|
||||
}
|
||||
return error
|
||||
return error;
|
||||
}
|
||||
|
||||
export function serialize (error: Electron.ErrorWithCause): Electron.SerializedError {
|
||||
@@ -31,7 +31,7 @@ export function serialize (error: Electron.ErrorWithCause): Electron.SerializedE
|
||||
from: process.type as Electron.ProcessType,
|
||||
cause: exports.serialize(error.cause),
|
||||
__ELECTRON_SERIALIZED_ERROR__: true
|
||||
}
|
||||
};
|
||||
}
|
||||
return error
|
||||
return error;
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import * as util from 'util'
|
||||
import * as util from 'util';
|
||||
|
||||
import { electronBindingSetup } from '@electron/internal/common/electron-binding-setup'
|
||||
import { electronBindingSetup } from '@electron/internal/common/electron-binding-setup';
|
||||
|
||||
const timers = require('timers')
|
||||
const timers = require('timers');
|
||||
|
||||
process.electronBinding = electronBindingSetup(process._linkedBinding, process.type)
|
||||
process.electronBinding = electronBindingSetup(process._linkedBinding, process.type);
|
||||
|
||||
type AnyFn = (...args: any[]) => any
|
||||
|
||||
@@ -17,11 +17,11 @@ type AnyFn = (...args: any[]) => any
|
||||
const wrapWithActivateUvLoop = function <T extends AnyFn> (func: T): T {
|
||||
return wrap(func, function (func) {
|
||||
return function (this: any, ...args: any[]) {
|
||||
process.activateUvLoop()
|
||||
return func.apply(this, args)
|
||||
}
|
||||
}) as T
|
||||
}
|
||||
process.activateUvLoop();
|
||||
return func.apply(this, args);
|
||||
};
|
||||
}) as T;
|
||||
};
|
||||
|
||||
/**
|
||||
* Casts to any below for func are due to Typescript not supporting symbols
|
||||
@@ -30,41 +30,41 @@ const wrapWithActivateUvLoop = function <T extends AnyFn> (func: T): T {
|
||||
* Refs: https://github.com/Microsoft/TypeScript/issues/1863
|
||||
*/
|
||||
function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) {
|
||||
const wrapped = wrapper(func)
|
||||
const wrapped = wrapper(func);
|
||||
if ((func as any)[util.promisify.custom]) {
|
||||
(wrapped as any)[util.promisify.custom] = wrapper((func as any)[util.promisify.custom])
|
||||
(wrapped as any)[util.promisify.custom] = wrapper((func as any)[util.promisify.custom]);
|
||||
}
|
||||
return wrapped
|
||||
return wrapped;
|
||||
}
|
||||
|
||||
process.nextTick = wrapWithActivateUvLoop(process.nextTick)
|
||||
process.nextTick = wrapWithActivateUvLoop(process.nextTick);
|
||||
|
||||
global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate)
|
||||
global.clearImmediate = timers.clearImmediate
|
||||
global.setImmediate = timers.setImmediate = wrapWithActivateUvLoop(timers.setImmediate);
|
||||
global.clearImmediate = timers.clearImmediate;
|
||||
|
||||
// setTimeout needs to update the polling timeout of the event loop, when
|
||||
// called under Chromium's event loop the node's event loop won't get a chance
|
||||
// to update the timeout, so we have to force the node's event loop to
|
||||
// recalculate the timeout in browser process.
|
||||
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout)
|
||||
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval)
|
||||
timers.setTimeout = wrapWithActivateUvLoop(timers.setTimeout);
|
||||
timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
|
||||
|
||||
// Only override the global setTimeout/setInterval impls in the browser process
|
||||
if (process.type === 'browser') {
|
||||
global.setTimeout = timers.setTimeout
|
||||
global.setInterval = timers.setInterval
|
||||
global.setTimeout = timers.setTimeout;
|
||||
global.setInterval = timers.setInterval;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Always returns EOF for stdin stream.
|
||||
const { Readable } = require('stream')
|
||||
const stdin = new Readable()
|
||||
stdin.push(null)
|
||||
const { Readable } = require('stream');
|
||||
const stdin = new Readable();
|
||||
stdin.push(null);
|
||||
Object.defineProperty(process, 'stdin', {
|
||||
configurable: false,
|
||||
enumerable: true,
|
||||
get () {
|
||||
return stdin
|
||||
return stdin;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,5 +8,5 @@ export function isPromise (val: any) {
|
||||
val.constructor.reject instanceof Function &&
|
||||
val.constructor.resolve &&
|
||||
val.constructor.resolve instanceof Function
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
// parses a feature string that has the format used in window.open()
|
||||
// - `features` input string
|
||||
// - `emit` function(key, value) - called for each parsed KV
|
||||
module.exports = function parseFeaturesString (features, emit) {
|
||||
features = `${features}`.trim()
|
||||
features = `${features}`.trim();
|
||||
// split the string by ','
|
||||
features.split(/\s*,\s*/).forEach((feature) => {
|
||||
// expected form is either a key by itself or a key/value pair in the form of
|
||||
// 'key=value'
|
||||
let [key, value] = feature.split(/\s*=\s*/)
|
||||
if (!key) return
|
||||
let [key, value] = feature.split(/\s*=\s*/);
|
||||
if (!key) return;
|
||||
|
||||
// interpret the value as a boolean, if possible
|
||||
value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value
|
||||
value = (value === 'yes' || value === '1') ? true : (value === 'no' || value === '0') ? false : value;
|
||||
|
||||
// emit the parsed pair
|
||||
emit(key, value)
|
||||
})
|
||||
}
|
||||
emit(key, value);
|
||||
});
|
||||
};
|
||||
|
||||
@@ -1,42 +1,42 @@
|
||||
import * as path from 'path'
|
||||
import * as path from 'path';
|
||||
|
||||
const Module = require('module')
|
||||
const Module = require('module');
|
||||
|
||||
// Clear Node's global search paths.
|
||||
Module.globalPaths.length = 0
|
||||
Module.globalPaths.length = 0;
|
||||
|
||||
// Prevent Node from adding paths outside this app to search paths.
|
||||
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep
|
||||
const originalNodeModulePaths = Module._nodeModulePaths
|
||||
const resourcesPathWithTrailingSlash = process.resourcesPath + path.sep;
|
||||
const originalNodeModulePaths = Module._nodeModulePaths;
|
||||
Module._nodeModulePaths = function (from: string) {
|
||||
const paths: string[] = originalNodeModulePaths(from)
|
||||
const fromPath = path.resolve(from) + path.sep
|
||||
const paths: string[] = originalNodeModulePaths(from);
|
||||
const fromPath = path.resolve(from) + path.sep;
|
||||
// If "from" is outside the app then we do nothing.
|
||||
if (fromPath.startsWith(resourcesPathWithTrailingSlash)) {
|
||||
return paths.filter(function (candidate) {
|
||||
return candidate.startsWith(resourcesPathWithTrailingSlash)
|
||||
})
|
||||
return candidate.startsWith(resourcesPathWithTrailingSlash);
|
||||
});
|
||||
} else {
|
||||
return paths
|
||||
return paths;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Make a fake Electron module that we will insert into the module cache
|
||||
const electronModule = new Module('electron', null)
|
||||
electronModule.id = 'electron'
|
||||
electronModule.loaded = true
|
||||
electronModule.filename = 'electron'
|
||||
const electronModule = new Module('electron', null);
|
||||
electronModule.id = 'electron';
|
||||
electronModule.loaded = true;
|
||||
electronModule.filename = 'electron';
|
||||
Object.defineProperty(electronModule, 'exports', {
|
||||
get: () => require('electron')
|
||||
})
|
||||
});
|
||||
|
||||
Module._cache['electron'] = electronModule
|
||||
Module._cache['electron'] = electronModule;
|
||||
|
||||
const originalResolveFilename = Module._resolveFilename
|
||||
const originalResolveFilename = Module._resolveFilename;
|
||||
Module._resolveFilename = function (request: string, parent: NodeModule, isMain: boolean) {
|
||||
if (request === 'electron') {
|
||||
return 'electron'
|
||||
return 'electron';
|
||||
} else {
|
||||
return originalResolveFilename(request, parent, isMain)
|
||||
return originalResolveFilename(request, parent, isMain);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
const { nativeImage, NativeImage } = process.electronBinding('native_image')
|
||||
const { nativeImage, NativeImage } = process.electronBinding('native_image');
|
||||
|
||||
const objectMap = function (source: Object, mapper: (value: any) => any) {
|
||||
const sourceEntries = Object.entries(source)
|
||||
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)])
|
||||
return Object.fromEntries(targetEntries)
|
||||
}
|
||||
const sourceEntries = Object.entries(source);
|
||||
const targetEntries = sourceEntries.map(([key, val]) => [key, mapper(val)]);
|
||||
return Object.fromEntries(targetEntries);
|
||||
};
|
||||
|
||||
export function serialize (value: any): any {
|
||||
if (value instanceof NativeImage) {
|
||||
@@ -12,28 +12,28 @@ export function serialize (value: any): any {
|
||||
buffer: value.toBitmap(),
|
||||
size: value.getSize(),
|
||||
__ELECTRON_SERIALIZED_NativeImage__: true
|
||||
}
|
||||
};
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.map(serialize)
|
||||
return value.map(serialize);
|
||||
} else if (value instanceof Buffer) {
|
||||
return value
|
||||
return value;
|
||||
} else if (value instanceof Object) {
|
||||
return objectMap(value, serialize)
|
||||
return objectMap(value, serialize);
|
||||
} else {
|
||||
return value
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export function deserialize (value: any): any {
|
||||
if (value && value.__ELECTRON_SERIALIZED_NativeImage__) {
|
||||
return nativeImage.createFromBitmap(value.buffer, value.size)
|
||||
return nativeImage.createFromBitmap(value.buffer, value.size);
|
||||
} else if (Array.isArray(value)) {
|
||||
return value.map(deserialize)
|
||||
return value.map(deserialize);
|
||||
} else if (value instanceof Buffer) {
|
||||
return value
|
||||
return value;
|
||||
} else if (value instanceof Object) {
|
||||
return objectMap(value, deserialize)
|
||||
return objectMap(value, deserialize);
|
||||
} else {
|
||||
return value
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,7 +48,15 @@ export const syncMethods = new Set([
|
||||
'getZoomLevel',
|
||||
'setZoomFactor',
|
||||
'setZoomLevel'
|
||||
])
|
||||
]);
|
||||
|
||||
export const properties = new Set([
|
||||
'audioMuted',
|
||||
'userAgent',
|
||||
'zoomLevel',
|
||||
'zoomFactor',
|
||||
'frameRate'
|
||||
]);
|
||||
|
||||
export const asyncMethods = new Set([
|
||||
'loadURL',
|
||||
@@ -62,4 +70,4 @@ export const asyncMethods = new Set([
|
||||
'setVisualZoomLevelLimits',
|
||||
'print',
|
||||
'printToPDF'
|
||||
])
|
||||
]);
|
||||
|
||||
@@ -5,4 +5,4 @@
|
||||
//
|
||||
// Will mutate this captured one as well and that is OK.
|
||||
|
||||
export const Promise = global.Promise
|
||||
export const Promise = global.Promise;
|
||||
|
||||
@@ -1,37 +1,37 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
/* global nodeProcess, isolatedWorld, worldId */
|
||||
|
||||
const { EventEmitter } = require('events')
|
||||
const { EventEmitter } = require('events');
|
||||
|
||||
process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer')
|
||||
process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer');
|
||||
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const v8Util = process.electronBinding('v8_util');
|
||||
|
||||
// The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the
|
||||
// "ipc-internal" hidden value
|
||||
v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal'))
|
||||
v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal'));
|
||||
|
||||
// The process object created by webpack is not an event emitter, fix it so
|
||||
// the API is more compatible with non-sandboxed renderers.
|
||||
for (const prop of Object.keys(EventEmitter.prototype)) {
|
||||
if (process.hasOwnProperty(prop)) {
|
||||
delete process[prop]
|
||||
delete process[prop];
|
||||
}
|
||||
}
|
||||
Object.setPrototypeOf(process, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(process, EventEmitter.prototype);
|
||||
|
||||
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args')
|
||||
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args');
|
||||
|
||||
if (isolatedWorldArgs) {
|
||||
const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs
|
||||
const { windowSetup } = require('@electron/internal/renderer/window-setup')
|
||||
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
|
||||
const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs;
|
||||
const { windowSetup } = require('@electron/internal/renderer/window-setup');
|
||||
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen);
|
||||
}
|
||||
|
||||
const extensionId = v8Util.getHiddenValue(isolatedWorld, `extension-${worldId}`)
|
||||
const extensionId = v8Util.getHiddenValue(isolatedWorld, `extension-${worldId}`);
|
||||
|
||||
if (extensionId) {
|
||||
const chromeAPI = require('@electron/internal/renderer/chrome-api')
|
||||
chromeAPI.injectTo(extensionId, window)
|
||||
const chromeAPI = require('@electron/internal/renderer/chrome-api');
|
||||
chromeAPI.injectTo(extensionId, window);
|
||||
}
|
||||
|
||||
@@ -1,27 +1,27 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
/* global nodeProcess, isolatedWorld */
|
||||
|
||||
process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer')
|
||||
process.electronBinding = require('@electron/internal/common/electron-binding-setup').electronBindingSetup(nodeProcess._linkedBinding, 'renderer');
|
||||
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const v8Util = process.electronBinding('v8_util');
|
||||
|
||||
// The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the
|
||||
// "ipc-internal" hidden value
|
||||
v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal'))
|
||||
v8Util.setHiddenValue(global, 'ipc-internal', v8Util.getHiddenValue(isolatedWorld, 'ipc-internal'));
|
||||
|
||||
const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl')
|
||||
const webViewImpl = v8Util.getHiddenValue(isolatedWorld, 'web-view-impl');
|
||||
|
||||
if (webViewImpl) {
|
||||
// Must setup the WebView element in main world.
|
||||
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element')
|
||||
setupWebView(v8Util, webViewImpl)
|
||||
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element');
|
||||
setupWebView(v8Util, webViewImpl);
|
||||
}
|
||||
|
||||
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args')
|
||||
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args');
|
||||
|
||||
if (isolatedWorldArgs) {
|
||||
const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs
|
||||
const { windowSetup } = require('@electron/internal/renderer/window-setup')
|
||||
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
|
||||
const { guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs;
|
||||
const { windowSetup } = require('@electron/internal/renderer/window-setup');
|
||||
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen);
|
||||
}
|
||||
|
||||
@@ -1,20 +1,20 @@
|
||||
const { hasSwitch } = process.electronBinding('command_line')
|
||||
const binding = process.electronBinding('context_bridge')
|
||||
const { hasSwitch } = process.electronBinding('command_line');
|
||||
const binding = process.electronBinding('context_bridge');
|
||||
|
||||
const contextIsolationEnabled = hasSwitch('context-isolation')
|
||||
const contextIsolationEnabled = hasSwitch('context-isolation');
|
||||
|
||||
const checkContextIsolationEnabled = () => {
|
||||
if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled')
|
||||
}
|
||||
if (!contextIsolationEnabled) throw new Error('contextBridge API can only be used when contextIsolation is enabled');
|
||||
};
|
||||
|
||||
const contextBridge = {
|
||||
exposeInMainWorld: (key: string, api: Record<string, any>) => {
|
||||
checkContextIsolationEnabled()
|
||||
return binding.exposeAPIInMainWorld(key, api)
|
||||
checkContextIsolationEnabled();
|
||||
return binding.exposeAPIInMainWorld(key, api);
|
||||
},
|
||||
debugGC: () => binding._debugGCMaps({})
|
||||
}
|
||||
};
|
||||
|
||||
if (!binding._debugGCMaps) delete contextBridge.debugGC
|
||||
if (!binding._debugGCMaps) delete contextBridge.debugGC;
|
||||
|
||||
export default contextBridge
|
||||
export default contextBridge;
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
'use strict'
|
||||
'use strict';
|
||||
|
||||
const CrashReporter = require('@electron/internal/common/crash-reporter')
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
|
||||
const CrashReporter = require('@electron/internal/common/crash-reporter');
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils');
|
||||
|
||||
class CrashReporterRenderer extends CrashReporter {
|
||||
init (options) {
|
||||
return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options)
|
||||
return ipcRendererUtils.invokeSync('ELECTRON_CRASH_REPORTER_INIT', options);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = new CrashReporterRenderer()
|
||||
module.exports = new CrashReporterRenderer();
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user