mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
133 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ee20443741 | ||
|
|
c4b90afab8 | ||
|
|
d0a326c1d6 | ||
|
|
dac2137420 | ||
|
|
b355d722dc | ||
|
|
890bd47caf | ||
|
|
4c09496cf0 | ||
|
|
8af46e4ed7 | ||
|
|
cbc0112991 | ||
|
|
0f67fac1ad | ||
|
|
18877923b3 | ||
|
|
61137132ba | ||
|
|
16af7fdc20 | ||
|
|
9ca060df7f | ||
|
|
2ef57ce93d | ||
|
|
65eca25a63 | ||
|
|
bbed4b1f71 | ||
|
|
61d0fa5147 | ||
|
|
fb9b281515 | ||
|
|
9a058fb766 | ||
|
|
15c60bafe7 | ||
|
|
4ae22e003b | ||
|
|
a1d039c0a8 | ||
|
|
63b6f49d1f | ||
|
|
bdabef56d4 | ||
|
|
6c90f5c73a | ||
|
|
bc6cc1a1f9 | ||
|
|
4f8bc49ea0 | ||
|
|
b945793011 | ||
|
|
78d92f5349 | ||
|
|
19c0ff75fc | ||
|
|
bdd19d9b28 | ||
|
|
26ee9476de | ||
|
|
2c01ed5529 | ||
|
|
2b3d4b4cdf | ||
|
|
3b4c04dbaf | ||
|
|
3b1a452406 | ||
|
|
062c3ec700 | ||
|
|
e5966bad8a | ||
|
|
2156a6b23c | ||
|
|
38ed0e5a47 | ||
|
|
831e060caf | ||
|
|
a7ddb0ea45 | ||
|
|
9b52ee4e86 | ||
|
|
6773a51ec3 | ||
|
|
2327947ed6 | ||
|
|
dca6516c10 | ||
|
|
f27fde70ef | ||
|
|
1498f7c24b | ||
|
|
dde1ddade9 | ||
|
|
2ca6450c73 | ||
|
|
e020754249 | ||
|
|
15ea59bbb3 | ||
|
|
7a65aebcfd | ||
|
|
dffecfaf59 | ||
|
|
eff5cee15b | ||
|
|
03d5ae2f93 | ||
|
|
b142266292 | ||
|
|
7dd6a185db | ||
|
|
47ee65c66c | ||
|
|
530fc1e2a4 | ||
|
|
32aca15962 | ||
|
|
8c531ed424 | ||
|
|
cbe447e27a | ||
|
|
19c705ab80 | ||
|
|
42b4433fe4 | ||
|
|
8c8d3d2974 | ||
|
|
5c0319e00e | ||
|
|
9743d70131 | ||
|
|
ba2841ccee | ||
|
|
6f62f91822 | ||
|
|
469d1cc1d6 | ||
|
|
0b2c61a10f | ||
|
|
3e52002406 | ||
|
|
86755f4353 | ||
|
|
25c87c0c26 | ||
|
|
1f64aaf171 | ||
|
|
7a5e281ae1 | ||
|
|
511bfd226c | ||
|
|
73b97d6109 | ||
|
|
bd96771ea9 | ||
|
|
988c57369a | ||
|
|
3d10d4c7e0 | ||
|
|
503c86bf89 | ||
|
|
66ee417697 | ||
|
|
a5b474e824 | ||
|
|
1591e1320a | ||
|
|
4c25b619a6 | ||
|
|
c2c25f1ee3 | ||
|
|
8cf8c8fe63 | ||
|
|
61fdda3e8f | ||
|
|
4a5f89d5c7 | ||
|
|
36fd0e9b37 | ||
|
|
512736542b | ||
|
|
b144900c00 | ||
|
|
dabaa7557a | ||
|
|
c6a794d738 | ||
|
|
aa863bc323 | ||
|
|
e3a79d6c52 | ||
|
|
14ba3ac63e | ||
|
|
6a07825c47 | ||
|
|
70b5e673bb | ||
|
|
6359db712a | ||
|
|
268cd3939c | ||
|
|
3ca62d9432 | ||
|
|
9901700a91 | ||
|
|
d5b088bc26 | ||
|
|
a12de693b0 | ||
|
|
e257981a6d | ||
|
|
ffb96acab0 | ||
|
|
fd0b57f219 | ||
|
|
1e50380fab | ||
|
|
31ba6c203e | ||
|
|
03d16f37d2 | ||
|
|
e501930d38 | ||
|
|
28eb7b0532 | ||
|
|
15e611a0ff | ||
|
|
3176e323e4 | ||
|
|
99a0581d0d | ||
|
|
4e417e21b8 | ||
|
|
e472efbea2 | ||
|
|
886e636b13 | ||
|
|
0450ee9524 | ||
|
|
ce31dc3591 | ||
|
|
15c89c8262 | ||
|
|
dbd72b2265 | ||
|
|
10a9e9c043 | ||
|
|
690271e38c | ||
|
|
fc7ef4cc1c | ||
|
|
f2d1abd0e3 | ||
|
|
e3be323962 | ||
|
|
407747b48c | ||
|
|
17b8b551ac |
@@ -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.
|
||||
#
|
||||
@@ -87,22 +130,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
|
||||
@@ -139,7 +191,10 @@ step-depot-tools-get: &step-depot-tools-get
|
||||
run:
|
||||
name: Get depot tools
|
||||
command: |
|
||||
git clone --depth=1 https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
||||
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
|
||||
cd depot_tools
|
||||
git checkout 79d9e4b8508d5e9e93e37121295521223a5dd6ca
|
||||
cd ..
|
||||
|
||||
step-depot-tools-add-to-path: &step-depot-tools-add-to-path
|
||||
run:
|
||||
@@ -150,13 +205,34 @@ step-gclient-sync: &step-gclient-sync
|
||||
run:
|
||||
name: Gclient sync
|
||||
command: |
|
||||
gclient config \
|
||||
--name "src/electron" \
|
||||
--unmanaged \
|
||||
$GCLIENT_EXTRA_ARGS \
|
||||
"$CIRCLE_REPOSITORY_URL"
|
||||
# If we did not restore a complete sync then we need to sync for realz
|
||||
if [ ! -s "src/electron/.circle-sync-done" ]; then
|
||||
gclient config \
|
||||
--name "src/electron" \
|
||||
--unmanaged \
|
||||
$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/common/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
|
||||
run:
|
||||
@@ -171,7 +247,7 @@ step-setup-env-for-build: &step-setup-env-for-build
|
||||
echo 'export SCCACHE_PATH="'"$SCCACHE_PATH"'"' >> $BASH_ENV
|
||||
if [ "$CIRCLE_PR_NUMBER" != "" ]; then
|
||||
#if building a fork set readonly access to sccache
|
||||
echo 'export SCCACHE_BUCKET="electronjs-sccache"' >> $BASH_ENV
|
||||
echo 'export SCCACHE_BUCKET="electronjs-sccache-ci"' >> $BASH_ENV
|
||||
echo 'export SCCACHE_TWO_TIER=true' >> $BASH_ENV
|
||||
fi
|
||||
fi
|
||||
@@ -183,6 +259,13 @@ step-restore-brew-cache: &step-restore-brew-cache
|
||||
keys:
|
||||
- v1-brew-cache-{{ arch }}
|
||||
|
||||
step-save-brew-cache: &step-save-brew-cache
|
||||
save_cache:
|
||||
paths:
|
||||
- /usr/local/Homebrew
|
||||
key: v1-brew-cache-{{ arch }}
|
||||
name: Persisting brew cache
|
||||
|
||||
step-get-more-space-on-mac: &step-get-more-space-on-mac
|
||||
run:
|
||||
name: Free up space on MacOS
|
||||
@@ -222,18 +305,23 @@ step-fix-sync-on-mac: &step-fix-sync-on-mac
|
||||
# Fix Clang Install (wrong binary)
|
||||
rm -rf src/third_party/llvm-build
|
||||
python src/tools/clang/scripts/update.py
|
||||
# Fix Framework Header Installs (symlinks not retained)
|
||||
rm -rf src/electron/external_binaries
|
||||
python src/electron/script/update-external-binaries.py
|
||||
fi
|
||||
|
||||
# Manually run update-external-binaries.py with system python
|
||||
step-fixup-external-binaries: &step-fixup-external-binaries
|
||||
run:
|
||||
name: Update external binaries
|
||||
command: |
|
||||
rm -rf src/electron/external_binaries
|
||||
python src/electron/script/update-external-binaries.py
|
||||
|
||||
step-install-signing-cert-on-mac: &step-install-signing-cert-on-mac
|
||||
run:
|
||||
name: Import and trust self-signed codesigning cert on MacOS
|
||||
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
|
||||
@@ -275,9 +363,18 @@ step-maybe-electron-dist-strip: &step-maybe-electron-dist-strip
|
||||
run:
|
||||
name: Strip electron binaries
|
||||
command: |
|
||||
if [ "$STRIP_BINARIES" == "true" ] && [ "`uname`" != "Darwin" ]; then
|
||||
if [ "$STRIP_BINARIES" == "true" ] && [ "`uname`" == "Linux" ]; then
|
||||
if [ x"$TARGET_ARCH" == x ]; then
|
||||
target_cpu=x64
|
||||
elif [ "$TARGET_ARCH" == "ia32" ]; then
|
||||
target_cpu=x86
|
||||
else
|
||||
target_cpu="$TARGET_ARCH"
|
||||
fi
|
||||
cd src
|
||||
electron/script/strip-binaries.py --target-cpu="$TARGET_ARCH"
|
||||
electron/script/copy-debug-symbols.py --target-cpu="$target_cpu" --out-dir=out/Default/debug --compress
|
||||
electron/script/strip-binaries.py --target-cpu="$target_cpu"
|
||||
electron/script/add-debug-link.py --target-cpu="$target_cpu" --debug-dir=out/Default/debug
|
||||
fi
|
||||
|
||||
step-electron-dist-build: &step-electron-dist-build
|
||||
@@ -320,7 +417,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
|
||||
@@ -440,6 +539,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
|
||||
@@ -447,6 +547,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
|
||||
@@ -526,6 +627,93 @@ step-fix-known-hosts-linux: &step-fix-known-hosts-linux
|
||||
./src/electron/.circleci/fix-known-hosts.sh
|
||||
fi
|
||||
|
||||
# Checkout Steps
|
||||
step-generate-deps-hash: &step-generate-deps-hash
|
||||
run:
|
||||
name: Generate DEPS Hash
|
||||
command: node src/electron/script/generate-deps-hash.js
|
||||
|
||||
step-touch-sync-done: &step-touch-sync-done
|
||||
run:
|
||||
name: Touch Sync Done
|
||||
command: touch src/electron/.circle-sync-done
|
||||
|
||||
# Restore exact src cache based on the hash of DEPS and patches/*
|
||||
# If no cache is matched EXACTLY then the .circle-sync-done file is empty
|
||||
# If a cache is matched EXACTLY then the .circle-sync-done file contains "done"
|
||||
step-maybe-restore-src-cache: &step-maybe-restore-src-cache
|
||||
restore_cache:
|
||||
paths:
|
||||
- ./src
|
||||
keys:
|
||||
- v5-src-cache-{{ arch }}-{{ checksum "src/electron/.depshash" }}
|
||||
name: Restoring src cache
|
||||
|
||||
# Restore exact or closest git cache based on the hash of DEPS and .circle-sync-done
|
||||
# If the src cache was restored above then this will match an empty cache
|
||||
# If the src cache was not restored above then this will match a close git cache
|
||||
step-maybe-restore-git-cache: &step-maybe-restore-git-cache
|
||||
restore_cache:
|
||||
paths:
|
||||
- ~/.gclient-cache
|
||||
keys:
|
||||
- v2-gclient-cache-{{ arch }}-{{ checksum "src/electron/.circle-sync-done" }}-{{ checksum "src/electron/DEPS" }}
|
||||
- v2-gclient-cache-{{ arch }}-{{ checksum "src/electron/.circle-sync-done" }}
|
||||
name: Conditionally restoring git cache
|
||||
|
||||
step-set-git-cache-path: &step-set-git-cache-path
|
||||
run:
|
||||
name: Set GIT_CACHE_PATH to make gclient to use the cache
|
||||
command: |
|
||||
# CircleCI does not support interpolation when setting environment variables.
|
||||
# https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-shell-command
|
||||
echo 'export GIT_CACHE_PATH="$HOME/.gclient-cache"' >> $BASH_ENV
|
||||
|
||||
# Persist the git cache based on the hash of DEPS and .circle-sync-done
|
||||
# If the src cache was restored above then this will persist an empty cache
|
||||
step-save-git-cache: &step-save-git-cache
|
||||
save_cache:
|
||||
paths:
|
||||
- ~/.gclient-cache
|
||||
key: v2-gclient-cache-{{ arch }}-{{ checksum "src/electron/.circle-sync-done" }}-{{ checksum "src/electron/DEPS" }}
|
||||
name: Persisting git cache
|
||||
|
||||
step-run-electron-only-hooks: &step-run-electron-only-hooks
|
||||
run:
|
||||
name: Run Electron Only Hooks
|
||||
command: gclient runhooks --spec="solutions=[{'name':'src/electron','url':None,'deps_file':'DEPS','custom_vars':{'process_deps':False},'managed':False}]"
|
||||
|
||||
step-generate-deps-hash-cleanly: &step-generate-deps-hash-cleanly
|
||||
run:
|
||||
name: Generate DEPS Hash
|
||||
command: (cd src/electron && git checkout .) && node src/electron/script/generate-deps-hash.js
|
||||
|
||||
# Mark the sync as done for future cache saving
|
||||
step-mark-sync-done: &step-mark-sync-done
|
||||
run:
|
||||
name: Mark Sync Done
|
||||
command: echo DONE > src/electron/.circle-sync-done
|
||||
|
||||
# Minimize the size of the cache
|
||||
step-minimize-workspace-size-from-checkout: &step-minimize-workspace-size-from-checkout
|
||||
run:
|
||||
name: Remove some unused data to avoid storing it in the workspace/cache
|
||||
command: |
|
||||
rm -rf src/android_webview
|
||||
rm -rf src/ios
|
||||
rm -rf src/third_party/blink/web_tests
|
||||
rm -rf src/third_party/blink/perf_tests
|
||||
rm -rf src/third_party/hunspell_dictionaries
|
||||
rm -rf src/third_party/WebKit/LayoutTests
|
||||
|
||||
# Save the src cache based on the deps hash
|
||||
step-save-src-cache: &step-save-src-cache
|
||||
save_cache:
|
||||
paths:
|
||||
- ./src
|
||||
key: v5-src-cache-{{ arch }}-{{ checksum "src/electron/.depshash" }}
|
||||
name: Persisting src cache
|
||||
|
||||
# Lists of steps.
|
||||
steps-lint: &steps-lint
|
||||
steps:
|
||||
@@ -542,7 +730,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 buildtools/linux64
|
||||
gn/gn/linux-amd64 $gn_version
|
||||
@@ -562,7 +750,7 @@ steps-lint: &steps-lint
|
||||
node script/yarn install
|
||||
node script/yarn lint
|
||||
|
||||
steps-checkout: &steps-checkout
|
||||
steps-checkout-fast: &steps-checkout-fast
|
||||
steps:
|
||||
- *step-checkout-electron
|
||||
- *step-depot-tools-get
|
||||
@@ -571,40 +759,59 @@ steps-checkout: &steps-checkout
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-install-gnutar-on-mac
|
||||
|
||||
- restore_cache:
|
||||
paths:
|
||||
- ~/.gclient-cache
|
||||
keys:
|
||||
- v1-gclient-cache-{{ arch }}-{{ checksum "src/electron/DEPS" }}
|
||||
- v1-gclient-cache-{{ arch }}-
|
||||
- run:
|
||||
name: Set GIT_CACHE_PATH to make gclient to use the cache
|
||||
command: |
|
||||
# CircleCI does not support interpolation when setting environment variables.
|
||||
# https://circleci.com/docs/2.0/env-vars/#setting-an-environment-variable-in-a-shell-command
|
||||
echo 'export GIT_CACHE_PATH="$HOME/.gclient-cache"' >> $BASH_ENV
|
||||
- *step-generate-deps-hash
|
||||
- *step-touch-sync-done
|
||||
- *step-maybe-restore-src-cache
|
||||
- *step-maybe-restore-git-cache
|
||||
- *step-set-git-cache-path
|
||||
# This sync call only runs if .circle-sync-done is an EMPTY file
|
||||
- *step-gclient-sync
|
||||
- save_cache:
|
||||
paths:
|
||||
- ~/.gclient-cache
|
||||
key: v1-gclient-cache-{{ arch }}-{{ checksum "src/electron/DEPS" }}
|
||||
- save_cache:
|
||||
paths:
|
||||
- /usr/local/Homebrew
|
||||
key: v1-brew-cache-{{ arch }}
|
||||
|
||||
- store_artifacts:
|
||||
path: patches
|
||||
# These next few steps reset Electron to the correct commit regardless of which cache was restored
|
||||
- run:
|
||||
name: Remove some unused data to avoid storing it in the workspace
|
||||
command: |
|
||||
rm -rf src/android_webview
|
||||
rm -rf src/ios
|
||||
rm -rf src/third_party/WebKit/LayoutTests
|
||||
name: Wipe Electron
|
||||
command: rm -rf src/electron
|
||||
- *step-checkout-electron
|
||||
- *step-run-electron-only-hooks
|
||||
- *step-generate-deps-hash-cleanly
|
||||
- *step-mark-sync-done
|
||||
- *step-minimize-workspace-size-from-checkout
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- depot_tools
|
||||
- src
|
||||
|
||||
steps-checkout-and-save-cache: &steps-checkout-and-save-cache
|
||||
steps:
|
||||
- *step-checkout-electron
|
||||
- *step-depot-tools-get
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-restore-brew-cache
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-install-gnutar-on-mac
|
||||
|
||||
- *step-generate-deps-hash
|
||||
- *step-touch-sync-done
|
||||
- *step-maybe-restore-src-cache
|
||||
- *step-maybe-restore-git-cache
|
||||
- *step-set-git-cache-path
|
||||
# This sync call only runs if .circle-sync-done is an EMPTY file
|
||||
- *step-gclient-sync
|
||||
- *step-save-git-cache
|
||||
# These next few steps reset Electron to the correct commit regardless of which cache was restored
|
||||
- run:
|
||||
name: Wipe Electron
|
||||
command: rm -rf src/electron
|
||||
- *step-checkout-electron
|
||||
- *step-run-electron-only-hooks
|
||||
- *step-generate-deps-hash-cleanly
|
||||
- *step-mark-sync-done
|
||||
- *step-minimize-workspace-size-from-checkout
|
||||
- *step-save-src-cache
|
||||
- *step-save-brew-cache
|
||||
|
||||
steps-electron-gn-check: &steps-electron-gn-check
|
||||
steps:
|
||||
- attach_workspace:
|
||||
@@ -620,6 +827,7 @@ steps-electron-build: &steps-electron-build
|
||||
at: .
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-setup-env-for-build
|
||||
- *step-fixup-external-binaries
|
||||
- *step-gn-gen-default
|
||||
|
||||
# Electron app
|
||||
@@ -640,9 +848,12 @@ steps-electron-build-for-tests: &steps-electron-build-for-tests
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-setup-env-for-build
|
||||
- *step-restore-brew-cache
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-install-npm-deps-on-mac
|
||||
- *step-fix-sync-on-mac
|
||||
- *step-fixup-external-binaries
|
||||
- *step-gn-gen-default
|
||||
- *step-delete-git-directories
|
||||
|
||||
# Electron app
|
||||
- *step-electron-build
|
||||
@@ -686,6 +897,7 @@ steps-electron-build-for-publish: &steps-electron-build-for-publish
|
||||
- *step-restore-brew-cache
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-gclient-sync
|
||||
- *step-fixup-external-binaries
|
||||
- *step-setup-env-for-build
|
||||
- *step-gn-gen-default
|
||||
- *step-delete-git-directories
|
||||
@@ -728,6 +940,7 @@ steps-chromedriver-build: &steps-chromedriver-build
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-setup-env-for-build
|
||||
- *step-fix-sync-on-mac
|
||||
- *step-fixup-external-binaries
|
||||
- *step-gn-gen-default
|
||||
|
||||
- *step-electron-chromedriver-build
|
||||
@@ -812,7 +1025,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
|
||||
@@ -837,7 +1049,6 @@ chromium-upgrade-branches: &chromium-upgrade-branches
|
||||
/chromium\-upgrade\/[0-9]+/
|
||||
|
||||
# List of all jobs.
|
||||
version: 2
|
||||
jobs:
|
||||
# Layer 0: Lint. Standalone.
|
||||
lint:
|
||||
@@ -847,33 +1058,47 @@ jobs:
|
||||
<<: *steps-lint
|
||||
|
||||
# Layer 1: Checkout.
|
||||
linux-checkout:
|
||||
linux-checkout-fast:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-checkout
|
||||
<<: *steps-checkout-fast
|
||||
|
||||
linux-checkout-and-save-cache:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-checkout-and-save-cache
|
||||
|
||||
linux-checkout-for-native-tests:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_pyyaml=True'
|
||||
<<: *steps-checkout
|
||||
<<: *steps-checkout-fast
|
||||
|
||||
linux-checkout-for-native-tests-with-no-patches:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=apply_patches=False --custom-var=checkout_pyyaml=True'
|
||||
<<: *steps-checkout
|
||||
<<: *steps-checkout-fast
|
||||
|
||||
mac-checkout:
|
||||
mac-checkout-fast:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
<<: *steps-checkout
|
||||
<<: *steps-checkout-fast
|
||||
|
||||
mac-checkout-and-save-cache:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
<<: *steps-checkout-and-save-cache
|
||||
|
||||
# Layer 2: Builds.
|
||||
linux-x64-debug:
|
||||
@@ -930,6 +1155,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:
|
||||
@@ -977,6 +1204,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:
|
||||
@@ -1024,7 +1253,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:
|
||||
@@ -1088,7 +1319,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:
|
||||
@@ -1099,6 +1332,14 @@ jobs:
|
||||
<<: *env-enable-sccache
|
||||
<<: *steps-electron-build-for-tests
|
||||
|
||||
osx-debug:
|
||||
<<: *machine-mac-large
|
||||
environment:
|
||||
<<: *env-mac-large
|
||||
<<: *env-debug-build
|
||||
<<: *env-enable-sccache
|
||||
<<: *steps-electron-build-for-tests
|
||||
|
||||
osx-debug-gn-check:
|
||||
<<: *machine-mac
|
||||
environment:
|
||||
@@ -1135,7 +1376,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:
|
||||
@@ -1147,6 +1390,15 @@ jobs:
|
||||
<<: *env-enable-sccache
|
||||
<<: *steps-electron-build-for-tests
|
||||
|
||||
mas-debug:
|
||||
<<: *machine-mac-large
|
||||
environment:
|
||||
<<: *env-mac-large
|
||||
<<: *env-mas
|
||||
<<: *env-debug-build
|
||||
<<: *env-enable-sccache
|
||||
<<: *steps-electron-build-for-tests
|
||||
|
||||
mas-debug-gn-check:
|
||||
<<: *machine-mac
|
||||
environment:
|
||||
@@ -1187,7 +1439,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.
|
||||
@@ -1409,73 +1663,127 @@ 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
|
||||
- linux-checkout-fast
|
||||
- linux-checkout-and-save-cache
|
||||
|
||||
- linux-x64-debug:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-x64-debug-gn-check:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-x64-testing:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-x64-testing-gn-check:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-x64-testing-tests:
|
||||
requires:
|
||||
- linux-x64-testing
|
||||
|
||||
- linux-ia32-debug:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-ia32-testing:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-ia32-testing-tests:
|
||||
requires:
|
||||
- linux-ia32-testing
|
||||
|
||||
- linux-arm-debug:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm-testing:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
|
||||
- linux-arm64-debug:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm64-debug-gn-check:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm64-testing:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-arm64-testing-gn-check:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
|
||||
build-mac:
|
||||
when: << pipeline.parameters.run-build-mac >>
|
||||
jobs:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- mac-checkout-and-save-cache
|
||||
|
||||
- osx-testing:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
|
||||
- osx-debug:
|
||||
requires:
|
||||
- mac-checkout-fast
|
||||
|
||||
- osx-debug-gn-check:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
|
||||
- osx-testing-gn-check:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
|
||||
- osx-testing-tests:
|
||||
requires:
|
||||
@@ -1483,14 +1791,19 @@ workflows:
|
||||
|
||||
- mas-testing:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
|
||||
- mas-debug:
|
||||
requires:
|
||||
- mac-checkout-fast
|
||||
|
||||
- mas-debug-gn-check:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
|
||||
- mas-testing-gn-check:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
|
||||
- mas-testing-tests:
|
||||
requires:
|
||||
@@ -1506,11 +1819,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
|
||||
@@ -1522,7 +1835,7 @@ workflows:
|
||||
- linux-x64-release
|
||||
- linux-x64-chromedriver:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-x64-release-summary:
|
||||
requires:
|
||||
- linux-x64-release
|
||||
@@ -1532,7 +1845,7 @@ workflows:
|
||||
|
||||
- linux-ia32-release:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-ia32-release-tests:
|
||||
requires:
|
||||
- linux-ia32-release
|
||||
@@ -1544,7 +1857,7 @@ workflows:
|
||||
- linux-ia32-release
|
||||
- linux-ia32-chromedriver:
|
||||
requires:
|
||||
- linux-checkout
|
||||
- linux-checkout-fast
|
||||
- linux-ia32-release-summary:
|
||||
requires:
|
||||
- linux-ia32-release
|
||||
@@ -1554,10 +1867,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
|
||||
@@ -1566,10 +1879,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
|
||||
@@ -1585,11 +1898,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
|
||||
@@ -1601,7 +1914,7 @@ workflows:
|
||||
- osx-release
|
||||
- osx-chromedriver:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- osx-release-summary:
|
||||
requires:
|
||||
- osx-release
|
||||
@@ -1611,7 +1924,7 @@ workflows:
|
||||
|
||||
- mas-release:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- mas-release-tests:
|
||||
requires:
|
||||
- mas-release
|
||||
@@ -1623,7 +1936,7 @@ workflows:
|
||||
- mas-release
|
||||
- mas-chromedriver:
|
||||
requires:
|
||||
- mac-checkout
|
||||
- mac-checkout-fast
|
||||
- mas-release-summary:
|
||||
requires:
|
||||
- mas-release
|
||||
|
||||
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
|
||||
|
||||
20
BUILD.gn
20
BUILD.gn
@@ -657,9 +657,7 @@ static_library("electron_lib") {
|
||||
}
|
||||
|
||||
if (enable_desktop_capturer) {
|
||||
if (is_component_build && is_win) {
|
||||
# On windows the implementation relies on unexported
|
||||
# DxgiDuplicatorController class.
|
||||
if (is_component_build && !is_linux) {
|
||||
deps += [ "//third_party/webrtc/modules/desktop_capture" ]
|
||||
}
|
||||
sources += [
|
||||
@@ -1350,12 +1348,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",
|
||||
]
|
||||
|
||||
22
DEPS
22
DEPS
@@ -47,9 +47,11 @@ vars = {
|
||||
# Python "requests" module is used for releases only.
|
||||
'checkout_requests': False,
|
||||
|
||||
# To allow running hooks without parsing the DEPS tree
|
||||
'process_deps': True,
|
||||
# It is always needed for normal Electron builds,
|
||||
# but might be impossible for custom in-house builds.
|
||||
'download_external_binaries': True,
|
||||
'download_external_binaries': False,
|
||||
|
||||
'checkout_nacl':
|
||||
False,
|
||||
@@ -68,30 +70,30 @@ vars = {
|
||||
deps = {
|
||||
'src': {
|
||||
'url': (Var("chromium_git")) + '/chromium/src.git@' + (Var("chromium_version")),
|
||||
'condition': 'checkout_chromium',
|
||||
'condition': 'checkout_chromium and process_deps',
|
||||
},
|
||||
'src/third_party/electron_node': {
|
||||
'url': (Var("nodejs_git")) + '/node.git@' + (Var("node_version")),
|
||||
'condition': 'checkout_node',
|
||||
'condition': 'checkout_node and process_deps',
|
||||
},
|
||||
'src/electron/vendor/pyyaml': {
|
||||
'url': (Var("yaml_git")) + '/pyyaml.git@' + (Var("pyyaml_version")),
|
||||
'condition': 'checkout_pyyaml',
|
||||
'condition': 'checkout_pyyaml and process_deps',
|
||||
},
|
||||
'src/electron/vendor/boto': {
|
||||
'url': Var('boto_git') + '/boto.git' + '@' + Var('boto_version'),
|
||||
'condition': 'checkout_boto',
|
||||
'condition': 'checkout_boto and process_deps',
|
||||
},
|
||||
'src/electron/vendor/requests': {
|
||||
'url': Var('requests_git') + '/requests.git' + '@' + Var('requests_version'),
|
||||
'condition': 'checkout_requests',
|
||||
'condition': 'checkout_requests and process_deps',
|
||||
},
|
||||
}
|
||||
|
||||
hooks = [
|
||||
{
|
||||
'name': 'patch_chromium',
|
||||
'condition': 'checkout_chromium and apply_patches',
|
||||
'condition': '(checkout_chromium and apply_patches) and process_deps',
|
||||
'pattern': 'src/electron',
|
||||
'action': [
|
||||
'python',
|
||||
@@ -130,7 +132,7 @@ hooks = [
|
||||
{
|
||||
'name': 'setup_boto',
|
||||
'pattern': 'src/electron',
|
||||
'condition': 'checkout_boto',
|
||||
'condition': 'checkout_boto and process_deps',
|
||||
'action': [
|
||||
'python',
|
||||
'-c',
|
||||
@@ -140,7 +142,7 @@ hooks = [
|
||||
{
|
||||
'name': 'setup_requests',
|
||||
'pattern': 'src/electron',
|
||||
'condition': 'checkout_requests',
|
||||
'condition': 'checkout_requests and process_deps',
|
||||
'action': [
|
||||
'python',
|
||||
'-c',
|
||||
@@ -152,3 +154,5 @@ hooks = [
|
||||
recursedeps = [
|
||||
'src',
|
||||
]
|
||||
|
||||
# Touch DEPS to bust cache
|
||||
|
||||
@@ -1 +1 @@
|
||||
6.0.8
|
||||
6.1.11
|
||||
@@ -53,6 +53,8 @@ build_script:
|
||||
%GCLIENT_EXTRA_ARGS%
|
||||
"https://github.com/electron/electron"
|
||||
- gclient sync --with_branch_heads --with_tags --reset
|
||||
# Manually run update-external-binaries.py with system python
|
||||
- python src/electron/script/update-external-binaries.py
|
||||
- cd src
|
||||
- ps: $env:BUILD_CONFIG_PATH="//electron/build/args/%GN_CONFIG%.gn"
|
||||
- gn gen out/Default "--args=import(\"%BUILD_CONFIG_PATH%\") %GN_EXTRA_ARGS%"
|
||||
|
||||
@@ -15,9 +15,8 @@
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "content/public/common/content_constants.h"
|
||||
#include "content/public/common/pepper_plugin_info.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "ppapi/shared_impl/ppapi_permissions.h"
|
||||
#include "ppapi/buildflags/buildflags.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/resource/resource_bundle.h"
|
||||
#include "url/url_constants.h"
|
||||
@@ -35,6 +34,11 @@
|
||||
#include "pdf/pdf.h"
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLUGINS)
|
||||
#include "content/public/common/pepper_plugin_info.h"
|
||||
#include "ppapi/shared_impl/ppapi_permissions.h"
|
||||
#endif // BUILDFLAG(ENABLE_PLUGINS)
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
@@ -141,6 +145,7 @@ void AddPepperFlashFromCommandLine(
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_PEPPER_FLASH)
|
||||
|
||||
#if BUILDFLAG(ENABLE_PLUGINS)
|
||||
void ComputeBuiltInPlugins(std::vector<content::PepperPluginInfo>* plugins) {
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
content::PepperPluginInfo pdf_info;
|
||||
@@ -161,6 +166,7 @@ void ComputeBuiltInPlugins(std::vector<content::PepperPluginInfo>* plugins) {
|
||||
plugins->push_back(pdf_info);
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_PLUGINS)
|
||||
|
||||
void AppendDelimitedSwitchToVector(const base::StringPiece cmd_switch,
|
||||
std::vector<std::string>* append_me) {
|
||||
@@ -227,7 +233,9 @@ void AtomContentClient::AddPepperPlugins(
|
||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||
AddPepperFlashFromCommandLine(command_line, plugins);
|
||||
#endif // BUILDFLAG(ENABLE_PEPPER_FLASH)
|
||||
#if BUILDFLAG(ENABLE_PLUGINS)
|
||||
ComputeBuiltInPlugins(plugins);
|
||||
#endif // BUILDFLAG(ENABLE_PLUGINS)
|
||||
}
|
||||
|
||||
void AtomContentClient::AddContentDecryptionModules(
|
||||
|
||||
@@ -68,9 +68,14 @@ int NodeMain(int argc, char* argv[]) {
|
||||
// Initialize gin::IsolateHolder.
|
||||
JavascriptEnvironment gin_env(loop);
|
||||
|
||||
node::Environment* env = node::CreateEnvironment(
|
||||
node::CreateIsolateData(gin_env.isolate(), loop, gin_env.platform()),
|
||||
gin_env.context(), argc, argv, exec_argc, exec_argv, false);
|
||||
node::IsolateData* isolate_data =
|
||||
node::CreateIsolateData(gin_env.isolate(), loop, gin_env.platform());
|
||||
CHECK_NE(nullptr, isolate_data);
|
||||
|
||||
node::Environment* env =
|
||||
node::CreateEnvironment(isolate_data, gin_env.context(), argc, argv,
|
||||
exec_argc, exec_argv, false);
|
||||
CHECK_NE(nullptr, env);
|
||||
|
||||
// Enable support for v8 inspector.
|
||||
NodeDebugger node_debugger(env);
|
||||
@@ -118,6 +123,7 @@ int NodeMain(int argc, char* argv[]) {
|
||||
|
||||
v8::Isolate* isolate = env->isolate();
|
||||
node::FreeEnvironment(env);
|
||||
node::FreeIsolateData(isolate_data);
|
||||
|
||||
gin_env.platform()->DrainTasks(isolate);
|
||||
gin_env.platform()->CancelPendingDelayedTasks(isolate);
|
||||
|
||||
@@ -73,19 +73,13 @@ bool GlobalShortcut::RegisterAll(
|
||||
std::vector<ui::Accelerator> registered;
|
||||
|
||||
for (auto& accelerator : accelerators) {
|
||||
#if defined(OS_MACOSX)
|
||||
if (RegisteringMediaKeyForUntrustedClient(accelerator))
|
||||
return false;
|
||||
|
||||
GlobalShortcutListener* listener = GlobalShortcutListener::GetInstance();
|
||||
if (!listener->RegisterAccelerator(accelerator, this)) {
|
||||
if (!Register(accelerator, callback)) {
|
||||
// unregister all shortcuts if any failed
|
||||
UnregisterSome(registered);
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
registered.push_back(accelerator);
|
||||
accelerator_callback_map_[accelerator] = callback;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -4,16 +4,26 @@
|
||||
|
||||
#include "atom/browser/api/atom_api_menu.h"
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include "atom/browser/native_window.h"
|
||||
#include "atom/common/native_mate_converters/accelerator_converter.h"
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
#include "atom/common/native_mate_converters/image_converter.h"
|
||||
#include "atom/common/native_mate_converters/once_callback.h"
|
||||
#include "atom/common/native_mate_converters/string16_converter.h"
|
||||
#include "atom/common/node_includes.h"
|
||||
#include "native_mate/constructor.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "native_mate/object_template_builder.h"
|
||||
|
||||
namespace {
|
||||
// We need this map to keep references to currently opened menus.
|
||||
// Without this menus would be destroyed by js garbage collector
|
||||
// even when they are still displayed.
|
||||
std::map<uint32_t, v8::Global<v8::Object>> g_menus;
|
||||
} // unnamed namespace
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
@@ -192,13 +202,25 @@ bool Menu::WorksWhenHiddenAt(int index) const {
|
||||
}
|
||||
|
||||
void Menu::OnMenuWillClose() {
|
||||
g_menus.erase(weak_map_id());
|
||||
Emit("menu-will-close");
|
||||
}
|
||||
|
||||
void Menu::OnMenuWillShow() {
|
||||
g_menus[weak_map_id()] = v8::Global<v8::Object>(isolate(), GetWrapper());
|
||||
Emit("menu-will-show");
|
||||
}
|
||||
|
||||
base::OnceClosure Menu::BindSelfToClosure(base::OnceClosure callback) {
|
||||
// return ((callback, ref) => { callback() }).bind(null, callback, this)
|
||||
v8::Global<v8::Value> ref(isolate(), GetWrapper());
|
||||
return base::BindOnce(
|
||||
[](base::OnceClosure callback, v8::Global<v8::Value> ref) {
|
||||
std::move(callback).Run();
|
||||
},
|
||||
std::move(callback), std::move(ref));
|
||||
}
|
||||
|
||||
// static
|
||||
void Menu::BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "atom/browser/api/atom_api_top_level_window.h"
|
||||
#include "atom/browser/api/trackable_object.h"
|
||||
#include "atom/browser/ui/atom_menu_model.h"
|
||||
#include "atom/common/api/locker.h"
|
||||
#include "base/callback.h"
|
||||
|
||||
namespace atom {
|
||||
@@ -60,7 +61,7 @@ class Menu : public mate::TrackableObject<Menu>,
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
const base::Closure& callback) = 0;
|
||||
base::OnceClosure callback) = 0;
|
||||
virtual void ClosePopupAt(int32_t window_id) = 0;
|
||||
|
||||
std::unique_ptr<AtomMenuModel> model_;
|
||||
@@ -70,6 +71,11 @@ class Menu : public mate::TrackableObject<Menu>,
|
||||
void OnMenuWillClose() override;
|
||||
void OnMenuWillShow() override;
|
||||
|
||||
protected:
|
||||
// Returns a new callback which keeps references of the JS wrapper until the
|
||||
// passed |callback| is called.
|
||||
base::OnceClosure BindSelfToClosure(base::OnceClosure callback);
|
||||
|
||||
private:
|
||||
void InsertItemAt(int index, int command_id, const base::string16& label);
|
||||
void InsertSeparatorAt(int index);
|
||||
|
||||
@@ -27,19 +27,19 @@ class MenuMac : public Menu {
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
const base::Closure& callback) override;
|
||||
base::OnceClosure callback) override;
|
||||
void PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
|
||||
int32_t window_id,
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
base::Closure callback);
|
||||
base::OnceClosure callback);
|
||||
void ClosePopupAt(int32_t window_id) override;
|
||||
|
||||
private:
|
||||
friend class Menu;
|
||||
|
||||
void OnClosed(int32_t window_id, base::Closure callback);
|
||||
void OnClosed(int32_t window_id, base::OnceClosure callback);
|
||||
|
||||
scoped_nsobject<AtomMenuController> menu_controller_;
|
||||
|
||||
|
||||
@@ -36,15 +36,20 @@ void MenuMac::PopupAt(TopLevelWindow* window,
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
const base::Closure& callback) {
|
||||
base::OnceClosure callback) {
|
||||
NativeWindow* native_window = window->window();
|
||||
if (!native_window)
|
||||
return;
|
||||
|
||||
auto popup = base::Bind(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(),
|
||||
native_window->GetWeakPtr(), window->weak_map_id(), x,
|
||||
y, positioning_item, callback);
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, popup);
|
||||
// Make sure the Menu object would not be garbage-collected until the callback
|
||||
// has run.
|
||||
base::OnceClosure callback_with_ref = BindSelfToClosure(std::move(callback));
|
||||
|
||||
auto popup =
|
||||
base::BindOnce(&MenuMac::PopupOnUI, weak_factory_.GetWeakPtr(),
|
||||
native_window->GetWeakPtr(), window->weak_map_id(), x, y,
|
||||
positioning_item, std::move(callback_with_ref));
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, std::move(popup));
|
||||
}
|
||||
|
||||
void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
|
||||
@@ -52,16 +57,17 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
base::Closure callback) {
|
||||
base::OnceClosure callback) {
|
||||
if (!native_window)
|
||||
return;
|
||||
NSWindow* nswindow = native_window->GetNativeWindow().GetNativeNSWindow();
|
||||
|
||||
auto close_callback = base::Bind(
|
||||
&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
|
||||
popup_controllers_[window_id] = base::scoped_nsobject<AtomMenuController>([
|
||||
[AtomMenuController alloc] initWithModel:model()
|
||||
useDefaultAccelerator:NO]);
|
||||
base::OnceClosure close_callback =
|
||||
base::BindOnce(&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id,
|
||||
std::move(callback));
|
||||
popup_controllers_[window_id] = base::scoped_nsobject<AtomMenuController>(
|
||||
[[AtomMenuController alloc] initWithModel:model()
|
||||
useDefaultAccelerator:NO]);
|
||||
NSMenu* menu = [popup_controllers_[window_id] menu];
|
||||
NSView* view = [nswindow contentView];
|
||||
|
||||
@@ -96,7 +102,7 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
|
||||
if (rightmostMenuPoint > screenRight)
|
||||
position.x = position.x - [menu size].width;
|
||||
|
||||
[popup_controllers_[window_id] setCloseCallback:close_callback];
|
||||
[popup_controllers_[window_id] setCloseCallback:std::move(close_callback)];
|
||||
// Make sure events can be pumped while the menu is up.
|
||||
base::MessageLoopCurrent::ScopedNestableTaskAllower allow;
|
||||
|
||||
@@ -127,17 +133,17 @@ void MenuMac::ClosePopupAt(int32_t window_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void MenuMac::OnClosed(int32_t window_id, base::Closure callback) {
|
||||
void MenuMac::OnClosed(int32_t window_id, base::OnceClosure callback) {
|
||||
popup_controllers_.erase(window_id);
|
||||
callback.Run();
|
||||
std::move(callback).Run();
|
||||
}
|
||||
|
||||
// static
|
||||
void Menu::SetApplicationMenu(Menu* base_menu) {
|
||||
MenuMac* menu = static_cast<MenuMac*>(base_menu);
|
||||
base::scoped_nsobject<AtomMenuController> menu_controller([
|
||||
[AtomMenuController alloc] initWithModel:menu->model_.get()
|
||||
useDefaultAccelerator:YES]);
|
||||
base::scoped_nsobject<AtomMenuController> menu_controller(
|
||||
[[AtomMenuController alloc] initWithModel:menu->model_.get()
|
||||
useDefaultAccelerator:YES]);
|
||||
|
||||
NSRunLoop* currentRunLoop = [NSRunLoop currentRunLoop];
|
||||
[currentRunLoop cancelPerformSelector:@selector(setMainMenu:)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "atom/browser/api/atom_api_menu_views.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "atom/browser/native_window_views.h"
|
||||
#include "atom/browser/unresponsive_suppressor.h"
|
||||
@@ -25,7 +26,7 @@ void MenuViews::PopupAt(TopLevelWindow* window,
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
const base::Closure& callback) {
|
||||
base::OnceClosure callback) {
|
||||
auto* native_window = static_cast<NativeWindowViews*>(window->window());
|
||||
if (!native_window)
|
||||
return;
|
||||
@@ -44,12 +45,21 @@ void MenuViews::PopupAt(TopLevelWindow* window,
|
||||
// Don't emit unresponsive event when showing menu.
|
||||
atom::UnresponsiveSuppressor suppressor;
|
||||
|
||||
// Make sure the Menu object would not be garbage-collected until the callback
|
||||
// has run.
|
||||
base::OnceClosure callback_with_ref = BindSelfToClosure(std::move(callback));
|
||||
|
||||
// Show the menu.
|
||||
//
|
||||
// Note that while views::MenuRunner accepts RepeatingCallback as close
|
||||
// callback, it is fine passing OnceCallback to it because we reset the
|
||||
// menu runner immediately when the menu is closed.
|
||||
int32_t window_id = window->weak_map_id();
|
||||
auto close_callback = base::Bind(
|
||||
&MenuViews::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
|
||||
auto close_callback = base::AdaptCallbackForRepeating(
|
||||
base::BindOnce(&MenuViews::OnClosed, weak_factory_.GetWeakPtr(),
|
||||
window_id, std::move(callback_with_ref)));
|
||||
menu_runners_[window_id] =
|
||||
std::make_unique<MenuRunner>(model(), flags, close_callback);
|
||||
std::make_unique<MenuRunner>(model(), flags, std::move(close_callback));
|
||||
menu_runners_[window_id]->RunMenuAt(
|
||||
native_window->widget(), NULL, gfx::Rect(location, gfx::Size()),
|
||||
views::MenuAnchorPosition::kTopLeft, ui::MENU_SOURCE_MOUSE);
|
||||
@@ -69,9 +79,9 @@ void MenuViews::ClosePopupAt(int32_t window_id) {
|
||||
}
|
||||
}
|
||||
|
||||
void MenuViews::OnClosed(int32_t window_id, base::Closure callback) {
|
||||
void MenuViews::OnClosed(int32_t window_id, base::OnceClosure callback) {
|
||||
menu_runners_.erase(window_id);
|
||||
callback.Run();
|
||||
std::move(callback).Run();
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
@@ -27,11 +27,11 @@ class MenuViews : public Menu {
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
const base::Closure& callback) override;
|
||||
base::OnceClosure callback) override;
|
||||
void ClosePopupAt(int32_t window_id) override;
|
||||
|
||||
private:
|
||||
void OnClosed(int32_t window_id, base::Closure callback);
|
||||
void OnClosed(int32_t window_id, base::OnceClosure callback);
|
||||
|
||||
// window ID -> open context menu
|
||||
std::map<int32_t, std::unique_ptr<views::MenuRunner>> menu_runners_;
|
||||
|
||||
@@ -189,6 +189,7 @@ void Notification::NotificationClosed() {
|
||||
void Notification::Close() {
|
||||
if (notification_) {
|
||||
notification_->Dismiss();
|
||||
notification_->set_delegate(nullptr);
|
||||
notification_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,6 @@
|
||||
#include "atom/common/color_util.h"
|
||||
#include "atom/common/mouse_util.h"
|
||||
#include "atom/common/native_mate_converters/blink_converter.h"
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
#include "atom/common/native_mate_converters/content_converter.h"
|
||||
#include "atom/common/native_mate_converters/file_path_converter.h"
|
||||
#include "atom/common/native_mate_converters/gfx_converter.h"
|
||||
@@ -43,6 +42,7 @@
|
||||
#include "atom/common/native_mate_converters/image_converter.h"
|
||||
#include "atom/common/native_mate_converters/net_converter.h"
|
||||
#include "atom/common/native_mate_converters/network_converter.h"
|
||||
#include "atom/common/native_mate_converters/once_callback.h"
|
||||
#include "atom/common/native_mate_converters/string16_converter.h"
|
||||
#include "atom/common/native_mate_converters/value_converter.h"
|
||||
#include "atom/common/node_includes.h"
|
||||
@@ -85,6 +85,7 @@
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "native_mate/object_template_builder.h"
|
||||
#include "net/url_request/url_request_context.h"
|
||||
#include "ppapi/buildflags/buildflags.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||
#include "third_party/blink/public/mojom/frame/find_in_page.mojom.h"
|
||||
#include "third_party/blink/public/platform/web_cursor_info.h"
|
||||
@@ -329,6 +330,12 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options)
|
||||
// Whether to enable DevTools.
|
||||
options.Get("devTools", &enable_devtools_);
|
||||
|
||||
// BrowserViews are not attached to a window initially so they should start
|
||||
// off as hidden. This is also important for compositor recycling. See:
|
||||
// https://github.com/electron/electron/pull/21372
|
||||
bool initially_shown = type_ != Type::BROWSER_VIEW;
|
||||
options.Get(options::kShow, &initially_shown);
|
||||
|
||||
// Obtain the session.
|
||||
std::string partition;
|
||||
mate::Handle<api::Session> session;
|
||||
@@ -381,6 +388,7 @@ WebContents::WebContents(v8::Isolate* isolate, const mate::Dictionary& options)
|
||||
#endif
|
||||
} else {
|
||||
content::WebContents::CreateParams params(session->browser_context());
|
||||
params.initially_hidden = !initially_shown;
|
||||
web_contents = content::WebContents::Create(params);
|
||||
}
|
||||
|
||||
@@ -795,11 +803,20 @@ void WebContents::BeforeUnloadFired(bool proceed,
|
||||
}
|
||||
|
||||
void WebContents::RenderViewCreated(content::RenderViewHost* render_view_host) {
|
||||
auto* const impl = content::RenderWidgetHostImpl::FromID(
|
||||
render_view_host->GetProcess()->GetID(),
|
||||
render_view_host->GetRoutingID());
|
||||
if (impl)
|
||||
impl->disable_hidden_ = !background_throttling_;
|
||||
if (!background_throttling_)
|
||||
render_view_host->SetSchedulerThrottling(false);
|
||||
}
|
||||
|
||||
void WebContents::RenderFrameCreated(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
auto* rwhv = render_frame_host->GetView();
|
||||
if (!rwhv)
|
||||
return;
|
||||
|
||||
auto* rwh_impl =
|
||||
static_cast<content::RenderWidgetHostImpl*>(rwhv->GetRenderWidgetHost());
|
||||
if (rwh_impl)
|
||||
rwh_impl->disable_hidden_ = !background_throttling_;
|
||||
}
|
||||
|
||||
void WebContents::RenderViewHostChanged(content::RenderViewHost* old_host,
|
||||
@@ -833,10 +850,12 @@ void WebContents::RenderProcessGone(base::TerminationStatus status) {
|
||||
|
||||
void WebContents::PluginCrashed(const base::FilePath& plugin_path,
|
||||
base::ProcessId plugin_pid) {
|
||||
#if BUILDFLAG(ENABLE_PLUGINS)
|
||||
content::WebPluginInfo info;
|
||||
auto* plugin_service = content::PluginService::GetInstance();
|
||||
plugin_service->GetPluginInfoByPath(plugin_path, &info);
|
||||
Emit("plugin-crashed", info.name, info.version);
|
||||
#endif // BUILDFLAG(ENABLE_PLUIGNS)
|
||||
}
|
||||
|
||||
void WebContents::MediaStartedPlaying(const MediaPlayerInfo& video_type,
|
||||
@@ -1224,31 +1243,24 @@ void WebContents::NavigationEntryCommitted(
|
||||
void WebContents::SetBackgroundThrottling(bool allowed) {
|
||||
background_throttling_ = allowed;
|
||||
|
||||
auto* contents = web_contents();
|
||||
if (!contents) {
|
||||
auto* rfh = web_contents()->GetMainFrame();
|
||||
if (!rfh)
|
||||
return;
|
||||
}
|
||||
|
||||
auto* render_view_host = contents->GetRenderViewHost();
|
||||
if (!render_view_host) {
|
||||
auto* rwhv = rfh->GetView();
|
||||
if (!rwhv)
|
||||
return;
|
||||
}
|
||||
|
||||
auto* render_process_host = render_view_host->GetProcess();
|
||||
if (!render_process_host) {
|
||||
auto* rwh_impl =
|
||||
static_cast<content::RenderWidgetHostImpl*>(rwhv->GetRenderWidgetHost());
|
||||
if (!rwh_impl)
|
||||
return;
|
||||
}
|
||||
|
||||
auto* render_widget_host_impl = content::RenderWidgetHostImpl::FromID(
|
||||
render_process_host->GetID(), render_view_host->GetRoutingID());
|
||||
if (!render_widget_host_impl) {
|
||||
return;
|
||||
}
|
||||
rwh_impl->disable_hidden_ = !background_throttling_;
|
||||
web_contents()->GetRenderViewHost()->SetSchedulerThrottling(allowed);
|
||||
|
||||
render_widget_host_impl->disable_hidden_ = !background_throttling_;
|
||||
|
||||
if (render_widget_host_impl->is_hidden()) {
|
||||
render_widget_host_impl->WasShown(base::nullopt);
|
||||
if (rwh_impl->is_hidden()) {
|
||||
rwh_impl->WasShown(base::nullopt);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -413,7 +413,8 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
||||
// content::WebContentsObserver:
|
||||
void BeforeUnloadFired(bool proceed,
|
||||
const base::TimeTicks& proceed_time) override;
|
||||
void RenderViewCreated(content::RenderViewHost*) override;
|
||||
void RenderViewCreated(content::RenderViewHost* render_view_host) override;
|
||||
void RenderFrameCreated(content::RenderFrameHost* render_frame_host) override;
|
||||
void RenderViewHostChanged(content::RenderViewHost* old_host,
|
||||
content::RenderViewHost* new_host) override;
|
||||
void RenderViewDeleted(content::RenderViewHost*) override;
|
||||
|
||||
@@ -25,6 +25,21 @@
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace {
|
||||
|
||||
// Call |quit| after Chromium is fully started.
|
||||
//
|
||||
// This is important for quitting immediately in the "ready" event, when
|
||||
// certain initialization task may still be pending, and quitting at that time
|
||||
// could end up with crash on exit.
|
||||
void RunQuitClosure(base::OnceClosure quit) {
|
||||
// On Linux/Windows the "ready" event is emitted in "PreMainMessageLoopRun",
|
||||
// make sure we quit after message loop has run for once.
|
||||
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(quit));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Browser::LoginItemSettings::LoginItemSettings() = default;
|
||||
Browser::LoginItemSettings::~LoginItemSettings() = default;
|
||||
Browser::LoginItemSettings::LoginItemSettings(const LoginItemSettings& other) =
|
||||
@@ -93,7 +108,7 @@ void Browser::Shutdown() {
|
||||
observer.OnQuit();
|
||||
|
||||
if (quit_main_message_loop_) {
|
||||
std::move(quit_main_message_loop_).Run();
|
||||
RunQuitClosure(std::move(quit_main_message_loop_));
|
||||
} else {
|
||||
// There is no message loop available so we are in early stage, wait until
|
||||
// the quit_main_message_loop_ is available.
|
||||
@@ -195,7 +210,7 @@ void Browser::PreMainMessageLoopRun() {
|
||||
|
||||
void Browser::SetMainMessageLoopQuitClosure(base::OnceClosure quit_closure) {
|
||||
if (is_shutdown_)
|
||||
std::move(quit_closure).Run();
|
||||
RunQuitClosure(std::move(quit_closure));
|
||||
else
|
||||
quit_main_message_loop_ = std::move(quit_closure);
|
||||
}
|
||||
|
||||
@@ -386,11 +386,18 @@ void Browser::ShowAboutPanel() {
|
||||
NSDictionary* options = DictionaryValueToNSDictionary(about_panel_options_);
|
||||
|
||||
// Credits must be a NSAttributedString instead of NSString
|
||||
id credits = options[@"Credits"];
|
||||
NSString* credits = (NSString*)options[@"Credits"];
|
||||
if (credits != nil) {
|
||||
NSMutableDictionary* mutable_options = [options mutableCopy];
|
||||
mutable_options[@"Credits"] = [[[NSAttributedString alloc]
|
||||
initWithString:(NSString*)credits] autorelease];
|
||||
base::scoped_nsobject<NSMutableDictionary> mutable_options(
|
||||
[options mutableCopy]);
|
||||
base::scoped_nsobject<NSAttributedString> creditString(
|
||||
[[NSAttributedString alloc]
|
||||
initWithString:credits
|
||||
attributes:@{
|
||||
NSForegroundColorAttributeName : [NSColor textColor]
|
||||
}]);
|
||||
|
||||
[mutable_options setValue:creditString forKey:@"Credits"];
|
||||
options = [NSDictionary dictionaryWithDictionary:mutable_options];
|
||||
}
|
||||
|
||||
|
||||
@@ -342,6 +342,13 @@ void CommonWebContentsDelegate::ExitFullscreenModeForTab(
|
||||
return;
|
||||
SetHtmlApiFullscreen(false);
|
||||
owner_window_->NotifyWindowLeaveHtmlFullScreen();
|
||||
|
||||
if (native_fullscreen_) {
|
||||
// Explicitly trigger a view resize, as the size is not actually changing if
|
||||
// the browser is fullscreened, too. Chrome does this indirectly from
|
||||
// `chrome/browser/ui/exclusive_access/fullscreen_controller.cc`.
|
||||
source->GetRenderViewHost()->GetWidget()->SynchronizeVisualProperties();
|
||||
}
|
||||
}
|
||||
|
||||
bool CommonWebContentsDelegate::IsFullscreenForTabOrPending(
|
||||
|
||||
@@ -157,7 +157,7 @@ class NativeWindowMac : public NativeWindow {
|
||||
AtomTouchBar* touch_bar() const { return touch_bar_.get(); }
|
||||
bool zoom_to_page_width() const { return zoom_to_page_width_; }
|
||||
bool fullscreen_window_title() const { return fullscreen_window_title_; }
|
||||
bool simple_fullscreen() const { return always_simple_fullscreen_; }
|
||||
bool always_simple_fullscreen() const { return always_simple_fullscreen_; }
|
||||
|
||||
protected:
|
||||
// views::WidgetDelegate:
|
||||
|
||||
@@ -545,6 +545,8 @@ void NativeWindowMac::Close() {
|
||||
// When this is a sheet showing, performClose won't work.
|
||||
if (is_modal() && parent() && IsVisible()) {
|
||||
[parent()->GetNativeWindow().GetNativeNSWindow() endSheet:window_];
|
||||
// Manually emit close event (not triggered from close fn)
|
||||
NotifyWindowCloseButtonClicked();
|
||||
CloseImmediately();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -922,6 +922,7 @@ void NativeWindowViews::SetContentProtection(bool enable) {
|
||||
}
|
||||
|
||||
void NativeWindowViews::SetFocusable(bool focusable) {
|
||||
widget()->widget_delegate()->SetCanActivate(focusable);
|
||||
#if defined(OS_WIN)
|
||||
LONG ex_style = ::GetWindowLong(GetAcceleratedWidget(), GWL_EXSTYLE);
|
||||
if (focusable)
|
||||
|
||||
@@ -263,6 +263,9 @@ class NativeWindowViews : public NativeWindow,
|
||||
bool forwarding_mouse_messages_ = false;
|
||||
HWND legacy_window_ = NULL;
|
||||
bool layered_ = false;
|
||||
|
||||
// Whether to block Chromium from handling window messages.
|
||||
bool block_chromium_message_handler_ = false;
|
||||
#endif
|
||||
|
||||
// Handles unhandled keyboard messages coming back from the renderer process.
|
||||
|
||||
@@ -143,162 +143,28 @@ bool IsScreenReaderActive() {
|
||||
return screenReader && UiaClientsAreListening();
|
||||
}
|
||||
|
||||
// We use "enum" instead of "enum class" because we need to do bitwise compare.
|
||||
enum AppbarAutohideEdge {
|
||||
TOP = 1 << 0,
|
||||
LEFT = 1 << 1,
|
||||
BOTTOM = 1 << 2,
|
||||
RIGHT = 1 << 3,
|
||||
};
|
||||
|
||||
// The thickness of an auto-hide taskbar in pixel.
|
||||
constexpr int kAutoHideTaskbarThicknessPx = 2;
|
||||
|
||||
// Code is copied from chrome_views_delegate_win.cc.
|
||||
bool MonitorHasAutohideTaskbarForEdge(UINT edge, HMONITOR monitor) {
|
||||
APPBARDATA taskbar_data = {sizeof(APPBARDATA), NULL, 0, edge};
|
||||
taskbar_data.hWnd = ::GetForegroundWindow();
|
||||
|
||||
// MSDN documents an ABM_GETAUTOHIDEBAREX, which supposedly takes a monitor
|
||||
// rect and returns autohide bars on that monitor. This sounds like a good
|
||||
// idea for multi-monitor systems. Unfortunately, it appears to not work at
|
||||
// least some of the time (erroneously returning NULL) and there's almost no
|
||||
// online documentation or other sample code using it that suggests ways to
|
||||
// address this problem. We do the following:-
|
||||
// 1. Use the ABM_GETAUTOHIDEBAR message. If it works, i.e. returns a valid
|
||||
// window we are done.
|
||||
// 2. If the ABM_GETAUTOHIDEBAR message does not work we query the auto hide
|
||||
// state of the taskbar and then retrieve its position. That call returns
|
||||
// the edge on which the taskbar is present. If it matches the edge we
|
||||
// are looking for, we are done.
|
||||
// NOTE: This call spins a nested run loop.
|
||||
HWND taskbar = reinterpret_cast<HWND>(
|
||||
SHAppBarMessage(ABM_GETAUTOHIDEBAR, &taskbar_data));
|
||||
if (!::IsWindow(taskbar)) {
|
||||
APPBARDATA taskbar_data = {sizeof(APPBARDATA), 0, 0, 0};
|
||||
unsigned int taskbar_state = SHAppBarMessage(ABM_GETSTATE, &taskbar_data);
|
||||
if (!(taskbar_state & ABS_AUTOHIDE))
|
||||
return false;
|
||||
|
||||
taskbar_data.hWnd = ::FindWindow(L"Shell_TrayWnd", NULL);
|
||||
if (!::IsWindow(taskbar_data.hWnd))
|
||||
return false;
|
||||
|
||||
SHAppBarMessage(ABM_GETTASKBARPOS, &taskbar_data);
|
||||
if (taskbar_data.uEdge == edge)
|
||||
taskbar = taskbar_data.hWnd;
|
||||
}
|
||||
|
||||
// There is a potential race condition here:
|
||||
// 1. A maximized chrome window is fullscreened.
|
||||
// 2. It is switched back to maximized.
|
||||
// 3. In the process the window gets a WM_NCCACLSIZE message which calls us to
|
||||
// get the autohide state.
|
||||
// 4. The worker thread is invoked. It calls the API to get the autohide
|
||||
// state. On Windows versions earlier than Windows 7, taskbars could
|
||||
// easily be always on top or not.
|
||||
// This meant that we only want to look for taskbars which have the topmost
|
||||
// bit set. However this causes problems in cases where the window on the
|
||||
// main thread is still in the process of switching away from fullscreen.
|
||||
// In this case the taskbar might not yet have the topmost bit set.
|
||||
// 5. The main thread resumes and does not leave space for the taskbar and
|
||||
// hence it does not pop when hovered.
|
||||
//
|
||||
// To address point 4 above, it is best to not check for the WS_EX_TOPMOST
|
||||
// window style on the taskbar, as starting from Windows 7, the topmost
|
||||
// style is always set. We don't support XP and Vista anymore.
|
||||
if (::IsWindow(taskbar)) {
|
||||
if (MonitorFromWindow(taskbar, MONITOR_DEFAULTTONEAREST) == monitor)
|
||||
return true;
|
||||
// In some cases like when the autohide taskbar is on the left of the
|
||||
// secondary monitor, the MonitorFromWindow call above fails to return the
|
||||
// correct monitor the taskbar is on. We fallback to MonitorFromPoint for
|
||||
// the cursor position in that case, which seems to work well.
|
||||
POINT cursor_pos = {0};
|
||||
GetCursorPos(&cursor_pos);
|
||||
if (MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST) == monitor)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int GetAppbarAutohideEdges(HWND hwnd) {
|
||||
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL);
|
||||
if (!monitor)
|
||||
return 0;
|
||||
|
||||
int edges = 0;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_LEFT, monitor))
|
||||
edges |= AppbarAutohideEdge::LEFT;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_TOP, monitor))
|
||||
edges |= AppbarAutohideEdge::TOP;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_RIGHT, monitor))
|
||||
edges |= AppbarAutohideEdge::RIGHT;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_BOTTOM, monitor))
|
||||
edges |= AppbarAutohideEdge::BOTTOM;
|
||||
return edges;
|
||||
}
|
||||
|
||||
void TriggerNCCalcSize(HWND hwnd) {
|
||||
RECT rcClient;
|
||||
::GetWindowRect(hwnd, &rcClient);
|
||||
|
||||
::SetWindowPos(hwnd, NULL, rcClient.left, rcClient.top,
|
||||
rcClient.right - rcClient.left, rcClient.bottom - rcClient.top,
|
||||
SWP_FRAMECHANGED);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::set<NativeWindowViews*> NativeWindowViews::forwarding_windows_;
|
||||
HHOOK NativeWindowViews::mouse_hook_ = NULL;
|
||||
|
||||
void NativeWindowViews::Maximize() {
|
||||
int autohide_edges = 0;
|
||||
if (!has_frame())
|
||||
autohide_edges = GetAppbarAutohideEdges(GetAcceleratedWidget());
|
||||
|
||||
// Only use Maximize() when:
|
||||
// 1. window has WS_THICKFRAME style;
|
||||
// 2. and window is not frameless when there is autohide taskbar.
|
||||
if ((::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME) &&
|
||||
(has_frame() || autohide_edges == 0)) {
|
||||
if (::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME) {
|
||||
if (IsVisible())
|
||||
widget()->Maximize();
|
||||
else
|
||||
widget()->native_widget_private()->Show(ui::SHOW_STATE_MAXIMIZED,
|
||||
gfx::Rect());
|
||||
return;
|
||||
} else {
|
||||
restore_bounds_ = GetBounds();
|
||||
auto display =
|
||||
display::Screen::GetScreen()->GetDisplayNearestPoint(GetPosition());
|
||||
SetBounds(display.work_area(), false);
|
||||
}
|
||||
|
||||
gfx::Insets insets;
|
||||
if (!has_frame()) {
|
||||
// When taskbar is autohide, we need to leave some space so the window
|
||||
// isn't treated as a "fullscreen app", which would cause the taskbars
|
||||
// to disappear.
|
||||
//
|
||||
// This trick comes from hwnd_message_handler.cc. While Chromium already
|
||||
// does this for normal window, somehow it is not applying the trick when
|
||||
// using frameless window, and we have to do it ourselves.
|
||||
float scale_factor =
|
||||
display::win::ScreenWin::GetScaleFactorForHWND(GetAcceleratedWidget());
|
||||
int thickness = std::ceil(kAutoHideTaskbarThicknessPx / scale_factor);
|
||||
if (autohide_edges & AppbarAutohideEdge::LEFT)
|
||||
insets.set_left(-thickness);
|
||||
if (autohide_edges & AppbarAutohideEdge::TOP)
|
||||
insets.set_top(-thickness);
|
||||
if (autohide_edges & AppbarAutohideEdge::RIGHT)
|
||||
insets.set_right(thickness);
|
||||
if (autohide_edges & AppbarAutohideEdge::BOTTOM)
|
||||
insets.set_bottom(thickness);
|
||||
}
|
||||
|
||||
restore_bounds_ = GetBounds();
|
||||
auto display =
|
||||
display::Screen::GetScreen()->GetDisplayNearestPoint(GetPosition());
|
||||
gfx::Rect bounds = display.work_area();
|
||||
bounds.Inset(insets);
|
||||
SetBounds(bounds, false);
|
||||
}
|
||||
|
||||
bool NativeWindowViews::ExecuteWindowsCommand(int command_id) {
|
||||
@@ -314,6 +180,14 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
|
||||
LRESULT* result) {
|
||||
NotifyWindowMessage(message, w_param, l_param);
|
||||
|
||||
// See code below for why blocking Chromium from handling messages.
|
||||
if (block_chromium_message_handler_) {
|
||||
// Handle the message with default proc.
|
||||
*result = DefWindowProc(GetAcceleratedWidget(), message, w_param, l_param);
|
||||
// Tell Chromium to ignore this message.
|
||||
return true;
|
||||
}
|
||||
|
||||
switch (message) {
|
||||
// Screen readers send WM_GETOBJECT in order to get the accessibility
|
||||
// object, so take this opportunity to push Chromium into accessible
|
||||
@@ -345,74 +219,34 @@ bool NativeWindowViews::PreHandleMSG(UINT message,
|
||||
return false;
|
||||
}
|
||||
case WM_GETMINMAXINFO: {
|
||||
// We need to handle GETMINMAXINFO ourselves because chromium tries to
|
||||
// get the scale factor of the window during it's version of this handler
|
||||
// based on the window position, which is invalid at this point. The
|
||||
// previous method of calling SetWindowPlacement fixed the window
|
||||
// position for the scale factor calculation but broke other things.
|
||||
MINMAXINFO* info = reinterpret_cast<MINMAXINFO*>(l_param);
|
||||
WINDOWPLACEMENT wp;
|
||||
wp.length = sizeof(WINDOWPLACEMENT);
|
||||
|
||||
display::Display display =
|
||||
display::Screen::GetScreen()->GetDisplayNearestPoint(
|
||||
last_normal_placement_bounds_.origin());
|
||||
// We do this to work around a Windows bug, where the minimized Window
|
||||
// would report that the closest display to it is not the one that it was
|
||||
// previously on (but the leftmost one instead). We restore the position
|
||||
// of the window during the restore operation, this way chromium can
|
||||
// use the proper display to calculate the scale factor to use.
|
||||
if (!last_normal_placement_bounds_.IsEmpty() &&
|
||||
GetWindowPlacement(GetAcceleratedWidget(), &wp)) {
|
||||
wp.rcNormalPosition = last_normal_placement_bounds_.ToRECT();
|
||||
|
||||
gfx::Size min_size = gfx::ScaleToCeiledSize(
|
||||
widget()->GetMinimumSize(), display.device_scale_factor());
|
||||
gfx::Size max_size = gfx::ScaleToCeiledSize(
|
||||
widget()->GetMaximumSize(), display.device_scale_factor());
|
||||
// When calling SetWindowPlacement, Chromium would do window messages
|
||||
// handling. But since we are already in PreHandleMSG this would cause
|
||||
// crash in Chromium under some cases.
|
||||
//
|
||||
// We work around the crash by prevent Chromium from handling window
|
||||
// messages until the SetWindowPlacement call is done.
|
||||
//
|
||||
// See https://github.com/electron/electron/issues/21614 for more.
|
||||
block_chromium_message_handler_ = true;
|
||||
SetWindowPlacement(GetAcceleratedWidget(), &wp);
|
||||
block_chromium_message_handler_ = false;
|
||||
|
||||
info->ptMinTrackSize.x = min_size.width();
|
||||
info->ptMinTrackSize.y = min_size.height();
|
||||
if (max_size.width() || max_size.height()) {
|
||||
if (!max_size.width())
|
||||
max_size.set_width(GetSystemMetrics(SM_CXMAXTRACK));
|
||||
if (!max_size.height())
|
||||
max_size.set_height(GetSystemMetrics(SM_CYMAXTRACK));
|
||||
info->ptMaxTrackSize.x = max_size.width();
|
||||
info->ptMaxTrackSize.y = max_size.height();
|
||||
last_normal_placement_bounds_ = gfx::Rect();
|
||||
}
|
||||
|
||||
*result = 1;
|
||||
return true;
|
||||
}
|
||||
case WM_NCCALCSIZE: {
|
||||
if (!has_frame() && w_param == TRUE) {
|
||||
NCCALCSIZE_PARAMS* params =
|
||||
reinterpret_cast<NCCALCSIZE_PARAMS*>(l_param);
|
||||
RECT PROPOSED = params->rgrc[0];
|
||||
RECT BEFORE = params->rgrc[1];
|
||||
|
||||
// We need to call the default to have cascade and tile windows
|
||||
// working
|
||||
// (https://github.com/rossy/borderless-window/blob/master/borderless-window.c#L239),
|
||||
// but we need to provide the proposed original value as suggested in
|
||||
// https://blogs.msdn.microsoft.com/wpfsdk/2008/09/08/custom-window-chrome-in-wpf/
|
||||
DefWindowProcW(GetAcceleratedWidget(), WM_NCCALCSIZE, w_param, l_param);
|
||||
|
||||
// When fullscreen the window has no border
|
||||
int border = 0;
|
||||
if (!IsFullscreen()) {
|
||||
// When not fullscreen calculate the border size
|
||||
border = GetSystemMetrics(SM_CXFRAME) +
|
||||
GetSystemMetrics(SM_CXPADDEDBORDER);
|
||||
if (!thick_frame_) {
|
||||
border -= GetSystemMetrics(SM_CXBORDER);
|
||||
}
|
||||
}
|
||||
|
||||
if (last_window_state_ == ui::SHOW_STATE_MAXIMIZED) {
|
||||
// Position the top of the frame offset from where windows thinks by
|
||||
// exactly the border amount. When fullscreen this is 0.
|
||||
params->rgrc[0].top = PROPOSED.top + border;
|
||||
} else {
|
||||
params->rgrc[0] = PROPOSED;
|
||||
params->rgrc[1] = BEFORE;
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
case WM_COMMAND:
|
||||
// Handle thumbar button click message.
|
||||
@@ -478,11 +312,6 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) {
|
||||
switch (w_param) {
|
||||
case SIZE_MAXIMIZED: {
|
||||
last_window_state_ = ui::SHOW_STATE_MAXIMIZED;
|
||||
|
||||
if (!has_frame()) {
|
||||
TriggerNCCalcSize(GetAcceleratedWidget());
|
||||
}
|
||||
|
||||
NotifyWindowMaximize();
|
||||
break;
|
||||
}
|
||||
@@ -503,11 +332,6 @@ void NativeWindowViews::HandleSizeEvent(WPARAM w_param, LPARAM l_param) {
|
||||
case ui::SHOW_STATE_MAXIMIZED:
|
||||
last_window_state_ = ui::SHOW_STATE_NORMAL;
|
||||
NotifyWindowUnmaximize();
|
||||
|
||||
if (!has_frame()) {
|
||||
TriggerNCCalcSize(GetAcceleratedWidget());
|
||||
}
|
||||
|
||||
break;
|
||||
case ui::SHOW_STATE_MINIMIZED:
|
||||
if (IsFullscreen()) {
|
||||
|
||||
@@ -17,9 +17,9 @@
|
||||
<key>CFBundleIconFile</key>
|
||||
<string>electron.icns</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>6.0.8</string>
|
||||
<string>6.1.11</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>6.0.8</string>
|
||||
<string>6.1.11</string>
|
||||
<key>LSApplicationCategoryType</key>
|
||||
<string>public.app-category.developer-tools</string>
|
||||
<key>LSMinimumSystemVersion</key>
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 6,0,8,0
|
||||
PRODUCTVERSION 6,0,8,0
|
||||
FILEVERSION 6,1,11,0
|
||||
PRODUCTVERSION 6,1,11,0
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
@@ -68,12 +68,12 @@ BEGIN
|
||||
BEGIN
|
||||
VALUE "CompanyName", "GitHub, Inc."
|
||||
VALUE "FileDescription", "Electron"
|
||||
VALUE "FileVersion", "6.0.8"
|
||||
VALUE "FileVersion", "6.1.11"
|
||||
VALUE "InternalName", "electron.exe"
|
||||
VALUE "LegalCopyright", "Copyright (C) 2015 GitHub, Inc. All rights reserved."
|
||||
VALUE "OriginalFilename", "electron.exe"
|
||||
VALUE "ProductName", "Electron"
|
||||
VALUE "ProductVersion", "6.0.8"
|
||||
VALUE "ProductVersion", "6.1.11"
|
||||
VALUE "SquirrelAwareVersion", "1"
|
||||
END
|
||||
END
|
||||
|
||||
@@ -28,7 +28,7 @@ class AtomMenuModel;
|
||||
base::scoped_nsobject<NSMenu> menu_;
|
||||
BOOL isMenuOpen_;
|
||||
BOOL useDefaultAccelerator_;
|
||||
base::Callback<void()> closeCallback;
|
||||
base::OnceClosure closeCallback;
|
||||
}
|
||||
|
||||
@property(nonatomic, assign) atom::AtomMenuModel* model;
|
||||
@@ -37,7 +37,7 @@ class AtomMenuModel;
|
||||
// to the contents of the model after calling this will not be noticed.
|
||||
- (id)initWithModel:(atom::AtomMenuModel*)model useDefaultAccelerator:(BOOL)use;
|
||||
|
||||
- (void)setCloseCallback:(const base::Callback<void()>&)callback;
|
||||
- (void)setCloseCallback:(base::OnceClosure)callback;
|
||||
|
||||
// Populate current NSMenu with |model|.
|
||||
- (void)populateWithModel:(atom::AtomMenuModel*)model;
|
||||
|
||||
@@ -118,19 +118,21 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
[super dealloc];
|
||||
}
|
||||
|
||||
- (void)setCloseCallback:(const base::Callback<void()>&)callback {
|
||||
closeCallback = callback;
|
||||
- (void)setCloseCallback:(base::OnceClosure)callback {
|
||||
closeCallback = std::move(callback);
|
||||
}
|
||||
|
||||
- (void)populateWithModel:(atom::AtomMenuModel*)model {
|
||||
if (!menu_)
|
||||
return;
|
||||
|
||||
// Locate & retain the recent documents menu item
|
||||
if (!recentDocumentsMenuItem_) {
|
||||
// Locate & retain the recent documents menu item
|
||||
recentDocumentsMenuItem_.reset(
|
||||
[[[[[NSApp mainMenu] itemWithTitle:@"Electron"] submenu]
|
||||
itemWithTitle:@"Open Recent"] retain]);
|
||||
base::string16 title = base::ASCIIToUTF16("Open Recent");
|
||||
NSString* openTitle = l10n_util::FixUpWindowsStyleLabel(title);
|
||||
|
||||
recentDocumentsMenuItem_.reset([[[[[NSApp mainMenu]
|
||||
itemWithTitle:@"Electron"] submenu] itemWithTitle:openTitle] retain]);
|
||||
}
|
||||
|
||||
model_ = model;
|
||||
@@ -151,7 +153,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
isMenuOpen_ = NO;
|
||||
model_->MenuWillClose();
|
||||
if (!closeCallback.is_null()) {
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
|
||||
std::move(closeCallback));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,8 +196,17 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
// Replaces the item's submenu instance with the singleton recent documents
|
||||
// menu. Previously replaced menu items will be recovered.
|
||||
- (void)replaceSubmenuShowingRecentDocuments:(NSMenuItem*)item {
|
||||
NSMenu* recentDocumentsMenu =
|
||||
[[[recentDocumentsMenuItem_ submenu] retain] autorelease];
|
||||
NSMenu* recentDocumentsMenu = [recentDocumentsMenuItem_ submenu];
|
||||
if (!recentDocumentsMenu) {
|
||||
base::string16 title = base::ASCIIToUTF16("Clear Menu");
|
||||
NSString* clearTitle = l10n_util::FixUpWindowsStyleLabel(title);
|
||||
recentDocumentsMenu = [[[NSMenu alloc] initWithTitle:@""] autorelease];
|
||||
[recentDocumentsMenu
|
||||
addItem:[[[NSMenuItem alloc]
|
||||
initWithTitle:clearTitle
|
||||
action:@selector(clearRecentDocuments:)
|
||||
keyEquivalent:@""] autorelease]];
|
||||
}
|
||||
|
||||
// Remove menu items in recent documents back to swap menu
|
||||
[self moveMenuItems:recentDocumentsMenu to:recentDocumentsMenuSwap_];
|
||||
@@ -211,6 +223,9 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
// Replace submenu
|
||||
[item setSubmenu:recentDocumentsMenu];
|
||||
|
||||
DCHECK_EQ([item action], @selector(submenuAction:));
|
||||
DCHECK_EQ([item target], recentDocumentsMenu);
|
||||
|
||||
// Remember the new menu item that carries the recent documents menu
|
||||
recentDocumentsMenuItem_.reset([item retain]);
|
||||
}
|
||||
@@ -377,7 +392,8 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
// Post async task so that itemSelected runs before the close callback
|
||||
// deletes the controller from the map which deallocates it
|
||||
if (!closeCallback.is_null()) {
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
base::PostTaskWithTraits(FROM_HERE, {BrowserThread::UI},
|
||||
std::move(closeCallback));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -99,8 +99,6 @@ bool ScopedDisableResize::disable_resize_ = false;
|
||||
}
|
||||
|
||||
- (id)accessibilityAttributeValue:(NSString*)attribute {
|
||||
if ([attribute isEqual:NSAccessibilityTitleAttribute])
|
||||
return base::SysUTF8ToNSString(shell_->GetTitle());
|
||||
if ([attribute isEqual:NSAccessibilityEnabledAttribute])
|
||||
return [NSNumber numberWithBool:YES];
|
||||
if (![attribute isEqualToString:@"AXChildren"])
|
||||
@@ -121,6 +119,10 @@ bool ScopedDisableResize::disable_resize_ = false;
|
||||
return [children filteredArrayUsingPredicate:predicate];
|
||||
}
|
||||
|
||||
- (NSString*)accessibilityTitle {
|
||||
return base::SysUTF8ToNSString(shell_->GetTitle());
|
||||
}
|
||||
|
||||
- (BOOL)canBecomeMainWindow {
|
||||
return !self.disableKeyOrMainWindow;
|
||||
}
|
||||
@@ -174,8 +176,14 @@ bool ScopedDisableResize::disable_resize_ = false;
|
||||
}
|
||||
|
||||
- (void)toggleFullScreenMode:(id)sender {
|
||||
if (shell_->simple_fullscreen())
|
||||
shell_->SetSimpleFullScreen(!shell_->IsSimpleFullScreen());
|
||||
bool is_simple_fs = shell_->IsSimpleFullScreen();
|
||||
bool always_simple_fs = shell_->always_simple_fullscreen();
|
||||
|
||||
// If we're in simple fullscreen mode and trying to exit it
|
||||
// we need to ensure we exit it properly to prevent a crash
|
||||
// with NSWindowStyleMaskTitled mode
|
||||
if (is_simple_fs || always_simple_fs)
|
||||
shell_->SetSimpleFullScreen(!is_simple_fs);
|
||||
else
|
||||
[super toggleFullScreen:sender];
|
||||
}
|
||||
|
||||
@@ -61,7 +61,9 @@ NSAlert* CreateNSAlert(NativeWindow* parent_window,
|
||||
int button_count = static_cast<int>([ns_buttons count]);
|
||||
|
||||
if (default_id >= 0 && default_id < button_count) {
|
||||
// Focus the button at default_id if the user opted to do so.
|
||||
// Highlight the button at default_id
|
||||
[[ns_buttons objectAtIndex:default_id] highlight:YES];
|
||||
|
||||
// The first button added gets set as the default selected.
|
||||
// So remove that default, and make the requested button the default.
|
||||
[[ns_buttons objectAtIndex:0] setKeyEquivalent:@""];
|
||||
|
||||
@@ -53,22 +53,7 @@ void ViewsDelegate::NotifyMenuItemFocused(const base::string16& menu_name,
|
||||
int item_count,
|
||||
bool has_submenu) {}
|
||||
|
||||
#if defined(OS_WIN)
|
||||
HICON ViewsDelegate::GetDefaultWindowIcon() const {
|
||||
// Use current exe's icon as default window icon.
|
||||
return LoadIcon(GetModuleHandle(NULL),
|
||||
MAKEINTRESOURCE(1 /* IDR_MAINFRAME */));
|
||||
}
|
||||
|
||||
HICON ViewsDelegate::GetSmallWindowIcon() const {
|
||||
return GetDefaultWindowIcon();
|
||||
}
|
||||
|
||||
bool ViewsDelegate::IsWindowInMetro(gfx::NativeWindow window) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
||||
#if defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
||||
gfx::ImageSkia* ViewsDelegate::GetDefaultWindowIcon() const {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#ifndef ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_
|
||||
#define ATOM_BROWSER_UI_VIEWS_ATOM_VIEWS_DELEGATE_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
|
||||
#include "base/compiler_specific.h"
|
||||
@@ -37,6 +38,8 @@ class ViewsDelegate : public views::ViewsDelegate {
|
||||
HICON GetDefaultWindowIcon() const override;
|
||||
HICON GetSmallWindowIcon() const override;
|
||||
bool IsWindowInMetro(gfx::NativeWindow window) const override;
|
||||
int GetAppbarAutohideEdges(HMONITOR monitor,
|
||||
base::OnceClosure callback) override;
|
||||
#elif defined(OS_LINUX) && !defined(OS_CHROMEOS)
|
||||
gfx::ImageSkia* GetDefaultWindowIcon() const override;
|
||||
#endif
|
||||
@@ -50,6 +53,24 @@ class ViewsDelegate : public views::ViewsDelegate {
|
||||
bool WindowManagerProvidesTitleBar(bool maximized) override;
|
||||
|
||||
private:
|
||||
#if defined(OS_WIN)
|
||||
using AppbarAutohideEdgeMap = std::map<HMONITOR, int>;
|
||||
|
||||
// Callback on main thread with the edges. |returned_edges| is the value that
|
||||
// was returned from the call to GetAutohideEdges() that initiated the lookup.
|
||||
void OnGotAppbarAutohideEdges(base::OnceClosure callback,
|
||||
HMONITOR monitor,
|
||||
int returned_edges,
|
||||
int edges);
|
||||
|
||||
AppbarAutohideEdgeMap appbar_autohide_edge_map_;
|
||||
// If true we're in the process of notifying a callback from
|
||||
// GetAutohideEdges().start a new query.
|
||||
bool in_autohide_edges_callback_ = false;
|
||||
|
||||
base::WeakPtrFactory<ViewsDelegate> weak_factory_{this};
|
||||
#endif
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ViewsDelegate);
|
||||
};
|
||||
|
||||
|
||||
156
atom/browser/ui/views/atom_views_delegate_win.cc
Normal file
156
atom/browser/ui/views/atom_views_delegate_win.cc
Normal file
@@ -0,0 +1,156 @@
|
||||
// Copyright (c) 2017 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE-CHROMIUM file.
|
||||
|
||||
#include "atom/browser/ui/views/atom_views_delegate.h"
|
||||
|
||||
#include <dwmapi.h>
|
||||
#include <shellapi.h>
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/bind.h"
|
||||
#include "base/task/post_task.h"
|
||||
|
||||
namespace {
|
||||
|
||||
bool MonitorHasAutohideTaskbarForEdge(UINT edge, HMONITOR monitor) {
|
||||
APPBARDATA taskbar_data = {sizeof(APPBARDATA), NULL, 0, edge};
|
||||
taskbar_data.hWnd = ::GetForegroundWindow();
|
||||
|
||||
// MSDN documents an ABM_GETAUTOHIDEBAREX, which supposedly takes a monitor
|
||||
// rect and returns autohide bars on that monitor. This sounds like a good
|
||||
// idea for multi-monitor systems. Unfortunately, it appears to not work at
|
||||
// least some of the time (erroneously returning NULL) and there's almost no
|
||||
// online documentation or other sample code using it that suggests ways to
|
||||
// address this problem. We do the following:-
|
||||
// 1. Use the ABM_GETAUTOHIDEBAR message. If it works, i.e. returns a valid
|
||||
// window we are done.
|
||||
// 2. If the ABM_GETAUTOHIDEBAR message does not work we query the auto hide
|
||||
// state of the taskbar and then retrieve its position. That call returns
|
||||
// the edge on which the taskbar is present. If it matches the edge we
|
||||
// are looking for, we are done.
|
||||
// NOTE: This call spins a nested run loop.
|
||||
HWND taskbar = reinterpret_cast<HWND>(
|
||||
SHAppBarMessage(ABM_GETAUTOHIDEBAR, &taskbar_data));
|
||||
if (!::IsWindow(taskbar)) {
|
||||
APPBARDATA taskbar_data = {sizeof(APPBARDATA), 0, 0, 0};
|
||||
unsigned int taskbar_state = SHAppBarMessage(ABM_GETSTATE, &taskbar_data);
|
||||
if (!(taskbar_state & ABS_AUTOHIDE))
|
||||
return false;
|
||||
|
||||
taskbar_data.hWnd = ::FindWindow(L"Shell_TrayWnd", NULL);
|
||||
if (!::IsWindow(taskbar_data.hWnd))
|
||||
return false;
|
||||
|
||||
SHAppBarMessage(ABM_GETTASKBARPOS, &taskbar_data);
|
||||
if (taskbar_data.uEdge == edge)
|
||||
taskbar = taskbar_data.hWnd;
|
||||
}
|
||||
|
||||
// There is a potential race condition here:
|
||||
// 1. A maximized chrome window is fullscreened.
|
||||
// 2. It is switched back to maximized.
|
||||
// 3. In the process the window gets a WM_NCCACLSIZE message which calls us to
|
||||
// get the autohide state.
|
||||
// 4. The worker thread is invoked. It calls the API to get the autohide
|
||||
// state. On Windows versions earlier than Windows 7, taskbars could
|
||||
// easily be always on top or not.
|
||||
// This meant that we only want to look for taskbars which have the topmost
|
||||
// bit set. However this causes problems in cases where the window on the
|
||||
// main thread is still in the process of switching away from fullscreen.
|
||||
// In this case the taskbar might not yet have the topmost bit set.
|
||||
// 5. The main thread resumes and does not leave space for the taskbar and
|
||||
// hence it does not pop when hovered.
|
||||
//
|
||||
// To address point 4 above, it is best to not check for the WS_EX_TOPMOST
|
||||
// window style on the taskbar, as starting from Windows 7, the topmost
|
||||
// style is always set. We don't support XP and Vista anymore.
|
||||
if (::IsWindow(taskbar)) {
|
||||
if (MonitorFromWindow(taskbar, MONITOR_DEFAULTTONEAREST) == monitor)
|
||||
return true;
|
||||
// In some cases like when the autohide taskbar is on the left of the
|
||||
// secondary monitor, the MonitorFromWindow call above fails to return the
|
||||
// correct monitor the taskbar is on. We fallback to MonitorFromPoint for
|
||||
// the cursor position in that case, which seems to work well.
|
||||
POINT cursor_pos = {0};
|
||||
GetCursorPos(&cursor_pos);
|
||||
if (MonitorFromPoint(cursor_pos, MONITOR_DEFAULTTONEAREST) == monitor)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
int GetAppbarAutohideEdgesOnWorkerThread(HMONITOR monitor) {
|
||||
DCHECK(monitor);
|
||||
|
||||
int edges = 0;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_LEFT, monitor))
|
||||
edges |= views::ViewsDelegate::EDGE_LEFT;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_TOP, monitor))
|
||||
edges |= views::ViewsDelegate::EDGE_TOP;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_RIGHT, monitor))
|
||||
edges |= views::ViewsDelegate::EDGE_RIGHT;
|
||||
if (MonitorHasAutohideTaskbarForEdge(ABE_BOTTOM, monitor))
|
||||
edges |= views::ViewsDelegate::EDGE_BOTTOM;
|
||||
return edges;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
namespace atom {
|
||||
|
||||
HICON ViewsDelegate::GetDefaultWindowIcon() const {
|
||||
// Use current exe's icon as default window icon.
|
||||
return LoadIcon(GetModuleHandle(NULL),
|
||||
MAKEINTRESOURCE(1 /* IDR_MAINFRAME */));
|
||||
}
|
||||
|
||||
HICON ViewsDelegate::GetSmallWindowIcon() const {
|
||||
return GetDefaultWindowIcon();
|
||||
}
|
||||
|
||||
bool ViewsDelegate::IsWindowInMetro(gfx::NativeWindow window) const {
|
||||
return false;
|
||||
}
|
||||
|
||||
int ViewsDelegate::GetAppbarAutohideEdges(HMONITOR monitor,
|
||||
base::OnceClosure callback) {
|
||||
// Initialize the map with EDGE_BOTTOM. This is important, as if we return an
|
||||
// initial value of 0 (no auto-hide edges) then we'll go fullscreen and
|
||||
// windows will automatically remove WS_EX_TOPMOST from the appbar resulting
|
||||
// in us thinking there is no auto-hide edges. By returning at least one edge
|
||||
// we don't initially go fullscreen until we figure out the real auto-hide
|
||||
// edges.
|
||||
if (!appbar_autohide_edge_map_.count(monitor))
|
||||
appbar_autohide_edge_map_[monitor] = EDGE_BOTTOM;
|
||||
|
||||
// We use the SHAppBarMessage API to get the taskbar autohide state. This API
|
||||
// spins a modal loop which could cause callers to be reentered. To avoid
|
||||
// that we retrieve the taskbar state in a worker thread.
|
||||
if (monitor && !in_autohide_edges_callback_) {
|
||||
// TODO(robliao): Annotate this task with .WithCOM() once supported.
|
||||
// https://crbug.com/662122
|
||||
base::PostTaskWithTraitsAndReplyWithResult(
|
||||
FROM_HERE, {base::MayBlock(), base::TaskPriority::USER_BLOCKING},
|
||||
base::BindOnce(&GetAppbarAutohideEdgesOnWorkerThread, monitor),
|
||||
base::BindOnce(&ViewsDelegate::OnGotAppbarAutohideEdges,
|
||||
weak_factory_.GetWeakPtr(), std::move(callback), monitor,
|
||||
appbar_autohide_edge_map_[monitor]));
|
||||
}
|
||||
return appbar_autohide_edge_map_[monitor];
|
||||
}
|
||||
|
||||
void ViewsDelegate::OnGotAppbarAutohideEdges(base::OnceClosure callback,
|
||||
HMONITOR monitor,
|
||||
int returned_edges,
|
||||
int edges) {
|
||||
appbar_autohide_edge_map_[monitor] = edges;
|
||||
if (returned_edges == edges)
|
||||
return;
|
||||
|
||||
base::AutoReset<bool> in_callback_setter(&in_autohide_edges_callback_, true);
|
||||
std::move(callback).Run();
|
||||
}
|
||||
|
||||
} // namespace atom
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
#include "atom/browser/ui/win/atom_desktop_window_tree_host_win.h"
|
||||
|
||||
#include "ui/base/win/hwnd_metrics.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
AtomDesktopWindowTreeHostWin::AtomDesktopWindowTreeHostWin(
|
||||
@@ -29,4 +31,17 @@ bool AtomDesktopWindowTreeHostWin::HasNativeFrame() const {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AtomDesktopWindowTreeHostWin::GetClientAreaInsets(gfx::Insets* insets,
|
||||
HMONITOR monitor) const {
|
||||
if (IsMaximized() && !native_window_view_->has_frame()) {
|
||||
// Windows automatically adds a standard width border to all sides when a
|
||||
// window is maximized.
|
||||
int frame_thickness = ui::GetFrameThickness(monitor) - 1;
|
||||
*insets = gfx::Insets(frame_thickness, frame_thickness, frame_thickness,
|
||||
frame_thickness);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
} // namespace atom
|
||||
|
||||
@@ -25,6 +25,8 @@ class AtomDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin {
|
||||
LPARAM l_param,
|
||||
LRESULT* result) override;
|
||||
bool HasNativeFrame() const override;
|
||||
bool GetClientAreaInsets(gfx::Insets* insets,
|
||||
HMONITOR monitor) const override;
|
||||
|
||||
private:
|
||||
NativeWindowViews* native_window_view_; // weak ref
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/asar/archive.h"
|
||||
#include "atom/common/asar/asar_util.h"
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
#include "atom/common/native_mate_converters/file_path_converter.h"
|
||||
#include "atom/common/node_includes.h"
|
||||
@@ -127,12 +128,27 @@ void InitAsarSupport(v8::Isolate* isolate, v8::Local<v8::Value> require) {
|
||||
&asar_init_params, &asar_init_args, nullptr);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> SplitPath(v8::Isolate* isolate,
|
||||
const base::FilePath& path) {
|
||||
mate::Dictionary dict = mate::Dictionary::CreateEmpty(isolate);
|
||||
base::FilePath asar_path, file_path;
|
||||
if (asar::GetAsarArchivePath(path, &asar_path, &file_path, true)) {
|
||||
dict.Set("isAsar", true);
|
||||
dict.Set("asarPath", asar_path);
|
||||
dict.Set("filePath", file_path);
|
||||
} else {
|
||||
dict.Set("isAsar", false);
|
||||
}
|
||||
return dict.GetHandle();
|
||||
}
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
mate::Dictionary dict(context->GetIsolate(), exports);
|
||||
dict.SetMethod("createArchive", &Archive::Create);
|
||||
dict.SetMethod("splitPath", &SplitPath);
|
||||
dict.SetMethod("initAsarSupport", &InitAsarSupport);
|
||||
}
|
||||
|
||||
|
||||
@@ -16,19 +16,23 @@ namespace atom {
|
||||
// static
|
||||
void RemoteCallbackFreer::BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_contents) {
|
||||
new RemoteCallbackFreer(isolate, target, context_id, object_id, web_contents);
|
||||
new RemoteCallbackFreer(isolate, target, frame_id, context_id, object_id,
|
||||
web_contents);
|
||||
}
|
||||
|
||||
RemoteCallbackFreer::RemoteCallbackFreer(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_contents)
|
||||
: ObjectLifeMonitor(isolate, target),
|
||||
content::WebContentsObserver(web_contents),
|
||||
frame_id_(frame_id),
|
||||
context_id_(context_id),
|
||||
object_id_(object_id) {}
|
||||
|
||||
@@ -40,10 +44,15 @@ void RemoteCallbackFreer::RunDestructor() {
|
||||
int32_t sender_id = 0;
|
||||
args.AppendString(context_id_);
|
||||
args.AppendInteger(object_id_);
|
||||
auto* frame_host = web_contents()->GetMainFrame();
|
||||
if (frame_host) {
|
||||
|
||||
auto frames = web_contents()->GetAllFrames();
|
||||
auto iter = std::find_if(frames.begin(), frames.end(), [this](auto* f) {
|
||||
return f->GetRoutingID() == frame_id_;
|
||||
});
|
||||
|
||||
if (iter != frames.end() && (*iter)->IsRenderFrameLive()) {
|
||||
mojom::ElectronRendererAssociatedPtr electron_ptr;
|
||||
frame_host->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
(*iter)->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
mojo::MakeRequest(&electron_ptr));
|
||||
electron_ptr->Message(true /* internal */, false /* send_to_all */, channel,
|
||||
args.Clone(), sender_id);
|
||||
|
||||
@@ -17,6 +17,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
|
||||
public:
|
||||
static void BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_conents);
|
||||
@@ -24,6 +25,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
|
||||
protected:
|
||||
RemoteCallbackFreer(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
int frame_id,
|
||||
const std::string& context_id,
|
||||
int object_id,
|
||||
content::WebContents* web_conents);
|
||||
@@ -35,6 +37,7 @@ class RemoteCallbackFreer : public ObjectLifeMonitor,
|
||||
void RenderViewDeleted(content::RenderViewHost*) override;
|
||||
|
||||
private:
|
||||
int frame_id_;
|
||||
std::string context_id_;
|
||||
int object_id_;
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
#include "base/values.h"
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "electron/atom/common/api/api.mojom.h"
|
||||
#include "third_party/blink/public/common/associated_interfaces/associated_interface_provider.h"
|
||||
#include "services/service_manager/public/cpp/interface_provider.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
|
||||
using blink::WebLocalFrame;
|
||||
@@ -77,8 +77,8 @@ void RemoteObjectFreer::RunDestructor() {
|
||||
if (ref_mapper_[context_id_].empty())
|
||||
ref_mapper_.erase(context_id_);
|
||||
|
||||
mojom::ElectronBrowserAssociatedPtr electron_ptr;
|
||||
render_frame->GetRemoteAssociatedInterfaces()->GetInterface(
|
||||
mojom::ElectronBrowserPtr electron_ptr;
|
||||
render_frame->GetRemoteInterfaces()->GetInterface(
|
||||
mojo::MakeRequest(&electron_ptr));
|
||||
electron_ptr->Message(true, channel, args.Clone());
|
||||
}
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
#include "base/lazy_instance.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/threading/thread_local.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
|
||||
namespace asar {
|
||||
|
||||
@@ -25,6 +26,17 @@ base::LazyInstance<base::ThreadLocalPointer<ArchiveMap>>::Leaky
|
||||
|
||||
const base::FilePath::CharType kAsarExtension[] = FILE_PATH_LITERAL(".asar");
|
||||
|
||||
std::map<base::FilePath, bool> g_is_directory_cache;
|
||||
|
||||
bool IsDirectoryCached(const base::FilePath& path) {
|
||||
auto it = g_is_directory_cache.find(path);
|
||||
if (it != g_is_directory_cache.end()) {
|
||||
return it->second;
|
||||
}
|
||||
base::ThreadRestrictions::ScopedAllowIO allow_io;
|
||||
return g_is_directory_cache[path] = base::DirectoryExists(path);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
std::shared_ptr<Archive> GetOrCreateAsarArchive(const base::FilePath& path) {
|
||||
@@ -47,11 +59,12 @@ void ClearArchives() {
|
||||
|
||||
bool GetAsarArchivePath(const base::FilePath& full_path,
|
||||
base::FilePath* asar_path,
|
||||
base::FilePath* relative_path) {
|
||||
base::FilePath* relative_path,
|
||||
bool allow_root) {
|
||||
base::FilePath iter = full_path;
|
||||
while (true) {
|
||||
base::FilePath dirname = iter.DirName();
|
||||
if (iter.MatchesExtension(kAsarExtension))
|
||||
if (iter.MatchesExtension(kAsarExtension) && !IsDirectoryCached(iter))
|
||||
break;
|
||||
else if (iter == dirname)
|
||||
return false;
|
||||
@@ -59,7 +72,8 @@ bool GetAsarArchivePath(const base::FilePath& full_path,
|
||||
}
|
||||
|
||||
base::FilePath tail;
|
||||
if (!iter.AppendRelativePath(full_path, &tail))
|
||||
if (!((allow_root && iter == full_path) ||
|
||||
iter.AppendRelativePath(full_path, &tail)))
|
||||
return false;
|
||||
|
||||
*asar_path = iter;
|
||||
|
||||
@@ -25,7 +25,8 @@ void ClearArchives();
|
||||
// Separates the path to Archive out.
|
||||
bool GetAsarArchivePath(const base::FilePath& full_path,
|
||||
base::FilePath* asar_path,
|
||||
base::FilePath* relative_path);
|
||||
base::FilePath* relative_path,
|
||||
bool allow_root = false);
|
||||
|
||||
// Same with base::ReadFileToString but supports asar Archive.
|
||||
bool ReadFileToString(const base::FilePath& path, std::string* contents);
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
#define ATOM_COMMON_ATOM_VERSION_H_
|
||||
|
||||
#define ATOM_MAJOR_VERSION 6
|
||||
#define ATOM_MINOR_VERSION 0
|
||||
#define ATOM_PATCH_VERSION 8
|
||||
#define ATOM_MINOR_VERSION 1
|
||||
#define ATOM_PATCH_VERSION 11
|
||||
// clang-format off
|
||||
// #define ATOM_PRE_RELEASE_VERSION
|
||||
// clang-format on
|
||||
|
||||
@@ -28,9 +28,17 @@ namespace {
|
||||
#if defined(_WIN64)
|
||||
int CrashForException(EXCEPTION_POINTERS* info) {
|
||||
auto* reporter = crash_reporter::CrashReporterWin::GetInstance();
|
||||
if (reporter->IsInitialized())
|
||||
if (reporter->IsInitialized()) {
|
||||
reporter->GetCrashpadClient().DumpAndCrash(info);
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
return EXCEPTION_CONTINUE_SEARCH;
|
||||
}
|
||||
|
||||
// When there is exception and we do not have crashReporter set up, we just
|
||||
// let the execution continue and crash, which is the default behavior.
|
||||
//
|
||||
// We must not return EXCEPTION_CONTINUE_SEARCH here, as it would end up with
|
||||
// busy loop when there is no exception handler in the program.
|
||||
return EXCEPTION_CONTINUE_EXECUTION;
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
@@ -1,508 +0,0 @@
|
||||
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/common/crash_reporter/win/crash_service.h"
|
||||
|
||||
#include <windows.h>
|
||||
|
||||
#include <sddl.h>
|
||||
#include <fstream> // NOLINT
|
||||
#include <map>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/stl_util.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/time/time.h"
|
||||
#include "base/win/windows_version.h"
|
||||
#include "breakpad/src/client/windows/crash_generation/client_info.h"
|
||||
#include "breakpad/src/client/windows/crash_generation/crash_generation_server.h"
|
||||
#include "breakpad/src/client/windows/sender/crash_report_sender.h"
|
||||
|
||||
namespace breakpad {
|
||||
|
||||
namespace {
|
||||
|
||||
const wchar_t kWaitEventFormat[] = L"$1CrashServiceWaitEvent";
|
||||
const wchar_t kClassNameFormat[] = L"$1CrashServiceWindow";
|
||||
|
||||
const wchar_t kTestPipeName[] = L"\\\\.\\pipe\\ChromeCrashServices";
|
||||
|
||||
const wchar_t kGoogleReportURL[] = L"https://clients2.google.com/cr/report";
|
||||
const wchar_t kCheckPointFile[] = L"crash_checkpoint.txt";
|
||||
|
||||
typedef std::map<std::wstring, std::wstring> CrashMap;
|
||||
|
||||
bool CustomInfoToMap(const google_breakpad::ClientInfo* client_info,
|
||||
const std::wstring& reporter_tag,
|
||||
CrashMap* map) {
|
||||
google_breakpad::CustomClientInfo info = client_info->GetCustomInfo();
|
||||
|
||||
for (uintptr_t i = 0; i < info.count; ++i) {
|
||||
(*map)[info.entries[i].name] = info.entries[i].value;
|
||||
}
|
||||
|
||||
(*map)[L"rept"] = reporter_tag;
|
||||
|
||||
return !map->empty();
|
||||
}
|
||||
|
||||
bool WriteCustomInfoToFile(const std::wstring& dump_path, const CrashMap& map) {
|
||||
std::wstring file_path(dump_path);
|
||||
size_t last_dot = file_path.rfind(L'.');
|
||||
if (last_dot == std::wstring::npos)
|
||||
return false;
|
||||
file_path.resize(last_dot);
|
||||
file_path += L".txt";
|
||||
|
||||
std::wofstream file(file_path.c_str(), std::ios_base::out |
|
||||
std::ios_base::app |
|
||||
std::ios::binary);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
CrashMap::const_iterator pos;
|
||||
for (pos = map.begin(); pos != map.end(); ++pos) {
|
||||
std::wstring line = pos->first;
|
||||
line += L':';
|
||||
line += pos->second;
|
||||
line += L'\n';
|
||||
file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteReportIDToFile(const std::wstring& dump_path,
|
||||
const std::wstring& report_id) {
|
||||
std::wstring file_path(dump_path);
|
||||
size_t last_slash = file_path.rfind(L'\\');
|
||||
if (last_slash == std::wstring::npos)
|
||||
return false;
|
||||
file_path.resize(last_slash);
|
||||
file_path += L"\\uploads.log";
|
||||
|
||||
std::wofstream file(file_path.c_str(), std::ios_base::out |
|
||||
std::ios_base::app |
|
||||
std::ios::binary);
|
||||
if (!file.is_open())
|
||||
return false;
|
||||
|
||||
int64_t seconds_since_epoch =
|
||||
(base::Time::Now() - base::Time::UnixEpoch()).InSeconds();
|
||||
std::wstring line = base::NumberToString16(seconds_since_epoch);
|
||||
line += L',';
|
||||
line += report_id;
|
||||
line += L'\n';
|
||||
file.write(line.c_str(), static_cast<std::streamsize>(line.length()));
|
||||
return true;
|
||||
}
|
||||
|
||||
// The window procedure task is to handle when a) the user logs off.
|
||||
// b) the system shuts down or c) when the user closes the window.
|
||||
LRESULT __stdcall CrashSvcWndProc(HWND hwnd,
|
||||
UINT message,
|
||||
WPARAM wparam,
|
||||
LPARAM lparam) {
|
||||
switch (message) {
|
||||
case WM_CLOSE:
|
||||
case WM_ENDSESSION:
|
||||
case WM_DESTROY:
|
||||
PostQuitMessage(0);
|
||||
break;
|
||||
default:
|
||||
return DefWindowProc(hwnd, message, wparam, lparam);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is the main and only application window.
|
||||
HWND g_top_window = NULL;
|
||||
|
||||
bool CreateTopWindow(HINSTANCE instance,
|
||||
const base::string16& application_name,
|
||||
bool visible) {
|
||||
base::string16 class_name =
|
||||
base::ReplaceStringPlaceholders(kClassNameFormat, application_name, NULL);
|
||||
|
||||
WNDCLASSEXW wcx = {0};
|
||||
wcx.cbSize = sizeof(wcx);
|
||||
wcx.style = CS_HREDRAW | CS_VREDRAW;
|
||||
wcx.lpfnWndProc = CrashSvcWndProc;
|
||||
wcx.hInstance = instance;
|
||||
wcx.lpszClassName = class_name.c_str();
|
||||
::RegisterClassExW(&wcx);
|
||||
DWORD style = visible ? WS_POPUPWINDOW | WS_VISIBLE : WS_OVERLAPPED;
|
||||
|
||||
// The window size is zero but being a popup window still shows in the
|
||||
// task bar and can be closed using the system menu or using task manager.
|
||||
HWND window = CreateWindowExW(0, wcx.lpszClassName, L"crash service", style,
|
||||
CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, NULL, NULL,
|
||||
instance, NULL);
|
||||
if (!window)
|
||||
return false;
|
||||
|
||||
::UpdateWindow(window);
|
||||
VLOG(1) << "window handle is " << window;
|
||||
g_top_window = window;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Simple helper class to keep the process alive until the current request
|
||||
// finishes.
|
||||
class ProcessingLock {
|
||||
public:
|
||||
ProcessingLock() { ::InterlockedIncrement(&op_count_); }
|
||||
~ProcessingLock() { ::InterlockedDecrement(&op_count_); }
|
||||
static bool IsWorking() { return (op_count_ != 0); }
|
||||
|
||||
private:
|
||||
static volatile LONG op_count_;
|
||||
};
|
||||
|
||||
volatile LONG ProcessingLock::op_count_ = 0;
|
||||
|
||||
// This structure contains the information that the worker thread needs to
|
||||
// send a crash dump to the server.
|
||||
struct DumpJobInfo {
|
||||
DWORD pid;
|
||||
CrashService* self;
|
||||
CrashMap map;
|
||||
std::wstring dump_path;
|
||||
|
||||
DumpJobInfo(DWORD process_id,
|
||||
CrashService* service,
|
||||
const CrashMap& crash_map,
|
||||
const std::wstring& path)
|
||||
: pid(process_id), self(service), map(crash_map), dump_path(path) {}
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
// Command line switches:
|
||||
const char CrashService::kMaxReports[] = "max-reports";
|
||||
const char CrashService::kNoWindow[] = "no-window";
|
||||
const char CrashService::kReporterTag[] = "reporter";
|
||||
const char CrashService::kDumpsDir[] = "dumps-dir";
|
||||
const char CrashService::kPipeName[] = "pipe-name";
|
||||
const char CrashService::kReporterURL[] = "reporter-url";
|
||||
|
||||
CrashService::CrashService() {}
|
||||
|
||||
CrashService::~CrashService() {
|
||||
base::AutoLock lock(sending_);
|
||||
delete dumper_;
|
||||
delete sender_;
|
||||
}
|
||||
|
||||
bool CrashService::Initialize(const base::string16& application_name,
|
||||
const base::FilePath& operating_dir,
|
||||
const base::FilePath& dumps_path) {
|
||||
using google_breakpad::CrashGenerationServer;
|
||||
using google_breakpad::CrashReportSender;
|
||||
|
||||
std::wstring pipe_name = kTestPipeName;
|
||||
int max_reports = -1;
|
||||
|
||||
// The checkpoint file allows CrashReportSender to enforce the maximum
|
||||
// reports per day quota. Does not seem to serve any other purpose.
|
||||
base::FilePath checkpoint_path = operating_dir.Append(kCheckPointFile);
|
||||
|
||||
base::CommandLine& cmd_line = *base::CommandLine::ForCurrentProcess();
|
||||
|
||||
base::FilePath dumps_path_to_use = dumps_path;
|
||||
|
||||
if (cmd_line.HasSwitch(kDumpsDir)) {
|
||||
dumps_path_to_use =
|
||||
base::FilePath(cmd_line.GetSwitchValueNative(kDumpsDir));
|
||||
}
|
||||
|
||||
// We can override the send reports quota with a command line switch.
|
||||
if (cmd_line.HasSwitch(kMaxReports))
|
||||
max_reports = _wtoi(cmd_line.GetSwitchValueNative(kMaxReports).c_str());
|
||||
|
||||
// Allow the global pipe name to be overridden for better testability.
|
||||
if (cmd_line.HasSwitch(kPipeName))
|
||||
pipe_name = cmd_line.GetSwitchValueNative(kPipeName);
|
||||
|
||||
if (max_reports > 0) {
|
||||
// Create the http sender object.
|
||||
sender_ = new CrashReportSender(checkpoint_path.value());
|
||||
sender_->set_max_reports_per_day(max_reports);
|
||||
}
|
||||
|
||||
SECURITY_ATTRIBUTES security_attributes = {0};
|
||||
SECURITY_DESCRIPTOR* security_descriptor =
|
||||
reinterpret_cast<SECURITY_DESCRIPTOR*>(
|
||||
GetSecurityDescriptorForLowIntegrity());
|
||||
DCHECK(security_descriptor != NULL);
|
||||
|
||||
security_attributes.nLength = sizeof(security_attributes);
|
||||
security_attributes.lpSecurityDescriptor = security_descriptor;
|
||||
security_attributes.bInheritHandle = FALSE;
|
||||
|
||||
// Create the OOP crash generator object.
|
||||
dumper_ = new CrashGenerationServer(
|
||||
pipe_name, &security_attributes, &CrashService::OnClientConnected, this,
|
||||
&CrashService::OnClientDumpRequest, this, &CrashService::OnClientExited,
|
||||
this, NULL, NULL, true, &dumps_path_to_use.value());
|
||||
|
||||
if (!dumper_) {
|
||||
LOG(ERROR) << "could not create dumper";
|
||||
if (security_attributes.lpSecurityDescriptor)
|
||||
LocalFree(security_attributes.lpSecurityDescriptor);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!CreateTopWindow(::GetModuleHandleW(NULL), application_name,
|
||||
!cmd_line.HasSwitch(kNoWindow))) {
|
||||
LOG(ERROR) << "could not create window";
|
||||
if (security_attributes.lpSecurityDescriptor)
|
||||
LocalFree(security_attributes.lpSecurityDescriptor);
|
||||
return false;
|
||||
}
|
||||
|
||||
reporter_tag_ = L"crash svc";
|
||||
if (cmd_line.HasSwitch(kReporterTag))
|
||||
reporter_tag_ = cmd_line.GetSwitchValueNative(kReporterTag);
|
||||
|
||||
reporter_url_ = kGoogleReportURL;
|
||||
if (cmd_line.HasSwitch(kReporterURL))
|
||||
reporter_url_ = cmd_line.GetSwitchValueNative(kReporterURL);
|
||||
|
||||
// Log basic information.
|
||||
VLOG(1) << "pipe name is " << pipe_name << "\ndumps at "
|
||||
<< dumps_path_to_use.value();
|
||||
|
||||
if (sender_) {
|
||||
VLOG(1) << "checkpoint is " << checkpoint_path.value() << "\nserver is "
|
||||
<< reporter_url_ << "\nmaximum " << sender_->max_reports_per_day()
|
||||
<< " reports/day"
|
||||
<< "\nreporter is " << reporter_tag_;
|
||||
}
|
||||
// Start servicing clients.
|
||||
if (!dumper_->Start()) {
|
||||
LOG(ERROR) << "could not start dumper";
|
||||
if (security_attributes.lpSecurityDescriptor)
|
||||
LocalFree(security_attributes.lpSecurityDescriptor);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (security_attributes.lpSecurityDescriptor)
|
||||
LocalFree(security_attributes.lpSecurityDescriptor);
|
||||
|
||||
// Create or open an event to signal the browser process that the crash
|
||||
// service is initialized.
|
||||
base::string16 wait_name =
|
||||
base::ReplaceStringPlaceholders(kWaitEventFormat, application_name, NULL);
|
||||
HANDLE wait_event = ::CreateEventW(NULL, TRUE, TRUE, wait_name.c_str());
|
||||
::SetEvent(wait_event);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void CrashService::OnClientConnected(
|
||||
void* context,
|
||||
const google_breakpad::ClientInfo* client_info) {
|
||||
ProcessingLock lock;
|
||||
VLOG(1) << "client start. pid = " << client_info->pid();
|
||||
CrashService* self = static_cast<CrashService*>(context);
|
||||
::InterlockedIncrement(&self->clients_connected_);
|
||||
}
|
||||
|
||||
void CrashService::OnClientExited(
|
||||
void* context,
|
||||
const google_breakpad::ClientInfo* client_info) {
|
||||
ProcessingLock processing_lock;
|
||||
VLOG(1) << "client end. pid = " << client_info->pid();
|
||||
CrashService* self = static_cast<CrashService*>(context);
|
||||
::InterlockedIncrement(&self->clients_terminated_);
|
||||
|
||||
if (!self->sender_)
|
||||
return;
|
||||
|
||||
// When we are instructed to send reports we need to exit if there are
|
||||
// no more clients to service. The next client that runs will start us.
|
||||
// Only chrome.exe starts crash_service with a non-zero max_reports.
|
||||
if (self->clients_connected_ > self->clients_terminated_)
|
||||
return;
|
||||
if (self->sender_->max_reports_per_day() > 0) {
|
||||
// Wait for the other thread to send crashes, if applicable. The sender
|
||||
// thread takes the sending_ lock, so the sleep is just to give it a
|
||||
// chance to start.
|
||||
::Sleep(1000);
|
||||
base::AutoLock lock(self->sending_);
|
||||
// Some people can restart chrome very fast, check again if we have
|
||||
// a new client before exiting for real.
|
||||
if (self->clients_connected_ == self->clients_terminated_) {
|
||||
VLOG(1) << "zero clients. exiting";
|
||||
::PostMessage(g_top_window, WM_CLOSE, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CrashService::OnClientDumpRequest(
|
||||
void* context,
|
||||
const google_breakpad::ClientInfo* client_info,
|
||||
const std::wstring* file_path) {
|
||||
ProcessingLock lock;
|
||||
|
||||
if (!file_path) {
|
||||
LOG(ERROR) << "dump with no file path";
|
||||
return;
|
||||
}
|
||||
if (!client_info) {
|
||||
LOG(ERROR) << "dump with no client info";
|
||||
return;
|
||||
}
|
||||
|
||||
CrashService* self = static_cast<CrashService*>(context);
|
||||
if (!self) {
|
||||
LOG(ERROR) << "dump with no context";
|
||||
return;
|
||||
}
|
||||
|
||||
CrashMap map;
|
||||
CustomInfoToMap(client_info, self->reporter_tag_, &map);
|
||||
|
||||
// Move dump file to the directory under client breakpad dump location.
|
||||
base::FilePath dump_location = base::FilePath(*file_path);
|
||||
CrashMap::const_iterator it = map.find(L"breakpad-dump-location");
|
||||
if (it != map.end()) {
|
||||
base::FilePath alternate_dump_location = base::FilePath(it->second);
|
||||
base::CreateDirectoryW(alternate_dump_location);
|
||||
alternate_dump_location =
|
||||
alternate_dump_location.Append(dump_location.BaseName());
|
||||
base::Move(dump_location, alternate_dump_location);
|
||||
dump_location = alternate_dump_location;
|
||||
}
|
||||
|
||||
DWORD pid = client_info->pid();
|
||||
VLOG(1) << "dump for pid = " << pid << " is " << dump_location.value();
|
||||
|
||||
if (!WriteCustomInfoToFile(dump_location.value(), map)) {
|
||||
LOG(ERROR) << "could not write custom info file";
|
||||
}
|
||||
|
||||
if (!self->sender_ || map.find(L"skip_upload") != map.end())
|
||||
return;
|
||||
|
||||
// Send the crash dump using a worker thread. This operation has retry
|
||||
// logic in case there is no internet connection at the time.
|
||||
DumpJobInfo* dump_job =
|
||||
new DumpJobInfo(pid, self, map, dump_location.value());
|
||||
if (!::QueueUserWorkItem(&CrashService::AsyncSendDump, dump_job,
|
||||
WT_EXECUTELONGFUNCTION)) {
|
||||
LOG(ERROR) << "could not queue job";
|
||||
}
|
||||
}
|
||||
|
||||
// We are going to try sending the report several times. If we can't send,
|
||||
// we sleep from one minute to several hours depending on the retry round.
|
||||
DWORD CrashService::AsyncSendDump(void* context) {
|
||||
if (!context)
|
||||
return 0;
|
||||
|
||||
DumpJobInfo* info = static_cast<DumpJobInfo*>(context);
|
||||
|
||||
std::wstring report_id = L"<unsent>";
|
||||
|
||||
const DWORD kOneMinute = 60 * 1000;
|
||||
const DWORD kOneHour = 60 * kOneMinute;
|
||||
|
||||
const DWORD kSleepSchedule[] = {24 * kOneHour, 8 * kOneHour, 4 * kOneHour,
|
||||
kOneHour, 15 * kOneMinute, 0};
|
||||
|
||||
int retry_round = base::size(kSleepSchedule) - 1;
|
||||
|
||||
do {
|
||||
::Sleep(kSleepSchedule[retry_round]);
|
||||
{
|
||||
// Take the server lock while sending. This also prevent early
|
||||
// termination of the service object.
|
||||
base::AutoLock lock(info->self->sending_);
|
||||
VLOG(1) << "trying to send report for pid = " << info->pid;
|
||||
std::map<std::wstring, std::wstring> file_map;
|
||||
file_map[L"upload_file_minidump"] = info->dump_path;
|
||||
google_breakpad::ReportResult send_result =
|
||||
info->self->sender_->SendCrashReport(info->self->reporter_url_,
|
||||
info->map, file_map, &report_id);
|
||||
switch (send_result) {
|
||||
case google_breakpad::RESULT_FAILED:
|
||||
report_id = L"<network issue>";
|
||||
break;
|
||||
case google_breakpad::RESULT_REJECTED:
|
||||
report_id = L"<rejected>";
|
||||
++info->self->requests_handled_;
|
||||
retry_round = 0;
|
||||
break;
|
||||
case google_breakpad::RESULT_SUCCEEDED:
|
||||
++info->self->requests_sent_;
|
||||
++info->self->requests_handled_;
|
||||
retry_round = 0;
|
||||
WriteReportIDToFile(info->dump_path, report_id);
|
||||
break;
|
||||
case google_breakpad::RESULT_THROTTLED:
|
||||
report_id = L"<throttled>";
|
||||
break;
|
||||
default:
|
||||
report_id = L"<unknown>";
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
VLOG(1) << "dump for pid =" << info->pid << " crash2 id =" << report_id;
|
||||
--retry_round;
|
||||
} while (retry_round >= 0);
|
||||
|
||||
if (!::DeleteFileW(info->dump_path.c_str()))
|
||||
LOG(WARNING) << "could not delete " << info->dump_path;
|
||||
|
||||
delete info;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int CrashService::ProcessingLoop() {
|
||||
MSG msg;
|
||||
while (GetMessage(&msg, NULL, 0, 0)) {
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessage(&msg);
|
||||
}
|
||||
|
||||
VLOG(1) << "session ending..";
|
||||
while (ProcessingLock::IsWorking()) {
|
||||
::Sleep(50);
|
||||
}
|
||||
|
||||
VLOG(1) << "clients connected :" << clients_connected_
|
||||
<< "\nclients terminated :" << clients_terminated_
|
||||
<< "\ndumps serviced :" << requests_handled_
|
||||
<< "\ndumps reported :" << requests_sent_;
|
||||
|
||||
return static_cast<int>(msg.wParam);
|
||||
}
|
||||
|
||||
PSECURITY_DESCRIPTOR CrashService::GetSecurityDescriptorForLowIntegrity() {
|
||||
// Build the SDDL string for the label.
|
||||
std::wstring sddl = L"S:(ML;;NW;;;S-1-16-4096)";
|
||||
|
||||
PSECURITY_DESCRIPTOR sec_desc = NULL;
|
||||
|
||||
PACL sacl = NULL;
|
||||
BOOL sacl_present = FALSE;
|
||||
BOOL sacl_defaulted = FALSE;
|
||||
|
||||
if (::ConvertStringSecurityDescriptorToSecurityDescriptorW(
|
||||
sddl.c_str(), SDDL_REVISION, &sec_desc, NULL)) {
|
||||
if (::GetSecurityDescriptorSacl(sec_desc, &sacl_present, &sacl,
|
||||
&sacl_defaulted)) {
|
||||
return sec_desc;
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
} // namespace breakpad
|
||||
@@ -1,130 +0,0 @@
|
||||
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_
|
||||
#define ATOM_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/macros.h"
|
||||
#include "base/synchronization/lock.h"
|
||||
|
||||
#if defined(OS_WIN)
|
||||
#include <windows.h>
|
||||
#endif // defined(OS_WIN)
|
||||
|
||||
namespace google_breakpad {
|
||||
|
||||
class CrashReportSender;
|
||||
class CrashGenerationServer;
|
||||
class ClientInfo;
|
||||
|
||||
} // namespace google_breakpad
|
||||
|
||||
namespace breakpad {
|
||||
|
||||
// This class implements an out-of-process crash server. It uses breakpad's
|
||||
// CrashGenerationServer and CrashReportSender to generate and then send the
|
||||
// crash dumps. Internally, it uses OS specific pipe to allow applications to
|
||||
// register for crash dumps and later on when a registered application crashes
|
||||
// it will signal an event that causes this code to wake up and perform a
|
||||
// crash dump on the signaling process. The dump is then stored on disk and
|
||||
// possibly sent to the crash2 servers.
|
||||
class CrashService {
|
||||
public:
|
||||
CrashService();
|
||||
~CrashService();
|
||||
|
||||
// Starts servicing crash dumps. Returns false if it failed. Do not use
|
||||
// other members in that case. |operating_dir| is where the CrashService
|
||||
// should store breakpad's checkpoint file. |dumps_path| is the directory
|
||||
// where the crash dumps should be stored.
|
||||
bool Initialize(const base::string16& application_name,
|
||||
const base::FilePath& operating_dir,
|
||||
const base::FilePath& dumps_path);
|
||||
|
||||
// Command line switches:
|
||||
//
|
||||
// --max-reports=<number>
|
||||
// Allows to override the maximum number for reports per day. Normally
|
||||
// the crash dumps are never sent so if you want to send any you must
|
||||
// specify a positive number here.
|
||||
static const char kMaxReports[];
|
||||
// --no-window
|
||||
// Does not create a visible window on the desktop. The window does not have
|
||||
// any other functionality other than allowing the crash service to be
|
||||
// gracefully closed.
|
||||
static const char kNoWindow[];
|
||||
// --reporter=<string>
|
||||
// Allows to specify a custom string that appears on the detail crash report
|
||||
// page in the crash server. This should be a 25 chars or less string.
|
||||
// The default tag if not specified is 'crash svc'.
|
||||
static const char kReporterTag[];
|
||||
// --dumps-dir=<directory-path>
|
||||
// Override the directory to which crash dump files will be written.
|
||||
static const char kDumpsDir[];
|
||||
// --pipe-name=<string>
|
||||
// Override the name of the Windows named pipe on which we will
|
||||
// listen for crash dump request messages.
|
||||
static const char kPipeName[];
|
||||
// --reporter-url=<string>
|
||||
// Override the URL to which crash reports will be sent to.
|
||||
static const char kReporterURL[];
|
||||
|
||||
// Returns number of crash dumps handled.
|
||||
int requests_handled() const { return requests_handled_; }
|
||||
// Returns number of crash clients registered.
|
||||
int clients_connected() const { return clients_connected_; }
|
||||
// Returns number of crash clients terminated.
|
||||
int clients_terminated() const { return clients_terminated_; }
|
||||
|
||||
// Starts the processing loop. This function does not return unless the
|
||||
// user is logging off or the user closes the crash service window. The
|
||||
// return value is a good number to pass in ExitProcess().
|
||||
int ProcessingLoop();
|
||||
|
||||
private:
|
||||
static void OnClientConnected(void* context,
|
||||
const google_breakpad::ClientInfo* client_info);
|
||||
|
||||
static void OnClientDumpRequest(
|
||||
void* context,
|
||||
const google_breakpad::ClientInfo* client_info,
|
||||
const std::wstring* file_path);
|
||||
|
||||
static void OnClientExited(void* context,
|
||||
const google_breakpad::ClientInfo* client_info);
|
||||
|
||||
// This routine sends the crash dump to the server. It takes the sending_
|
||||
// lock when it is performing the send.
|
||||
static DWORD __stdcall AsyncSendDump(void* context);
|
||||
|
||||
// Returns the security descriptor which access to low integrity processes
|
||||
// The caller is supposed to free the security descriptor by calling
|
||||
// LocalFree.
|
||||
PSECURITY_DESCRIPTOR GetSecurityDescriptorForLowIntegrity();
|
||||
|
||||
google_breakpad::CrashGenerationServer* dumper_ = nullptr;
|
||||
google_breakpad::CrashReportSender* sender_ = nullptr;
|
||||
|
||||
// the extra tag sent to the server with each dump.
|
||||
std::wstring reporter_tag_;
|
||||
|
||||
// receiver URL of crash reports.
|
||||
std::wstring reporter_url_;
|
||||
|
||||
// clients serviced statistics:
|
||||
int requests_handled_ = 0;
|
||||
int requests_sent_ = 0;
|
||||
volatile LONG clients_connected_ = 0;
|
||||
volatile LONG clients_terminated_ = 0;
|
||||
base::Lock sending_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(CrashService);
|
||||
};
|
||||
|
||||
} // namespace breakpad
|
||||
|
||||
#endif // ATOM_COMMON_CRASH_REPORTER_WIN_CRASH_SERVICE_H_
|
||||
@@ -7,7 +7,6 @@
|
||||
#include <string>
|
||||
|
||||
#include "atom/common/crash_reporter/crash_reporter.h"
|
||||
#include "atom/common/crash_reporter/win/crash_service.h"
|
||||
#include "base/at_exit.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_util.h"
|
||||
|
||||
@@ -6,13 +6,17 @@
|
||||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/keyboard_util.h"
|
||||
#include "atom/common/native_mate_converters/value_converter.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "content/public/browser/native_web_keyboard_event.h"
|
||||
#include "gin/converter.h"
|
||||
#include "mojo/public/cpp/base/values_mojom_traits.h"
|
||||
#include "mojo/public/mojom/base/values.mojom.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "third_party/blink/public/platform/web_input_event.h"
|
||||
#include "third_party/blink/public/platform/web_mouse_event.h"
|
||||
@@ -527,4 +531,175 @@ bool Converter<network::mojom::ReferrerPolicy>::FromV8(
|
||||
return true;
|
||||
}
|
||||
|
||||
namespace {
|
||||
constexpr uint8_t kNewSerializationTag = 0;
|
||||
constexpr uint8_t kOldSerializationTag = 1;
|
||||
|
||||
class V8Serializer : public v8::ValueSerializer::Delegate {
|
||||
public:
|
||||
explicit V8Serializer(v8::Isolate* isolate,
|
||||
bool use_old_serialization = false)
|
||||
: isolate_(isolate),
|
||||
serializer_(isolate, this),
|
||||
use_old_serialization_(use_old_serialization) {}
|
||||
~V8Serializer() override = default;
|
||||
|
||||
bool Serialize(v8::Local<v8::Value> value, blink::CloneableMessage* out) {
|
||||
serializer_.WriteHeader();
|
||||
if (use_old_serialization_) {
|
||||
WriteTag(kOldSerializationTag);
|
||||
if (!WriteBaseValue(value)) {
|
||||
isolate_->ThrowException(
|
||||
mate::StringToV8(isolate_, "An object could not be cloned."));
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
WriteTag(kNewSerializationTag);
|
||||
bool wrote_value;
|
||||
v8::TryCatch try_catch(isolate_);
|
||||
if (!serializer_.WriteValue(isolate_->GetCurrentContext(), value)
|
||||
.To(&wrote_value)) {
|
||||
try_catch.Reset();
|
||||
if (!V8Serializer(isolate_, true).Serialize(value, out)) {
|
||||
try_catch.ReThrow();
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
DCHECK(wrote_value);
|
||||
}
|
||||
|
||||
std::pair<uint8_t*, size_t> buffer = serializer_.Release();
|
||||
DCHECK_EQ(buffer.first, data_.data());
|
||||
out->encoded_message = base::make_span(buffer.first, buffer.second);
|
||||
out->owned_encoded_message = std::move(data_);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteBaseValue(v8::Local<v8::Value> object) {
|
||||
base::Value value;
|
||||
if (!ConvertFromV8(isolate_, object, &value)) {
|
||||
return false;
|
||||
}
|
||||
mojo::Message message = mojo_base::mojom::Value::SerializeAsMessage(&value);
|
||||
|
||||
serializer_.WriteUint32(message.data_num_bytes());
|
||||
serializer_.WriteRawBytes(message.data(), message.data_num_bytes());
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteTag(uint8_t tag) { serializer_.WriteRawBytes(&tag, 1); }
|
||||
|
||||
// v8::ValueSerializer::Delegate
|
||||
void* ReallocateBufferMemory(void* old_buffer,
|
||||
size_t size,
|
||||
size_t* actual_size) override {
|
||||
DCHECK_EQ(old_buffer, data_.data());
|
||||
data_.resize(size);
|
||||
*actual_size = data_.capacity();
|
||||
return data_.data();
|
||||
}
|
||||
|
||||
void FreeBufferMemory(void* buffer) override {
|
||||
DCHECK_EQ(buffer, data_.data());
|
||||
data_ = {};
|
||||
}
|
||||
|
||||
void ThrowDataCloneError(v8::Local<v8::String> message) override {
|
||||
isolate_->ThrowException(v8::Exception::Error(message));
|
||||
}
|
||||
|
||||
private:
|
||||
v8::Isolate* isolate_;
|
||||
std::vector<uint8_t> data_;
|
||||
v8::ValueSerializer serializer_;
|
||||
bool use_old_serialization_;
|
||||
};
|
||||
|
||||
class V8Deserializer : public v8::ValueDeserializer::Delegate {
|
||||
public:
|
||||
V8Deserializer(v8::Isolate* isolate, const blink::CloneableMessage& message)
|
||||
: isolate_(isolate),
|
||||
deserializer_(isolate,
|
||||
message.encoded_message.data(),
|
||||
message.encoded_message.size(),
|
||||
this) {}
|
||||
|
||||
v8::Local<v8::Value> Deserialize() {
|
||||
v8::EscapableHandleScope scope(isolate_);
|
||||
auto context = isolate_->GetCurrentContext();
|
||||
bool read_header;
|
||||
if (!deserializer_.ReadHeader(context).To(&read_header))
|
||||
return v8::Null(isolate_);
|
||||
DCHECK(read_header);
|
||||
uint8_t tag;
|
||||
if (!ReadTag(&tag))
|
||||
return v8::Null(isolate_);
|
||||
switch (tag) {
|
||||
case kNewSerializationTag: {
|
||||
v8::Local<v8::Value> value;
|
||||
if (!deserializer_.ReadValue(context).ToLocal(&value)) {
|
||||
return v8::Null(isolate_);
|
||||
}
|
||||
return scope.Escape(value);
|
||||
}
|
||||
case kOldSerializationTag: {
|
||||
v8::Local<v8::Value> value;
|
||||
if (!ReadBaseValue(&value)) {
|
||||
return v8::Null(isolate_);
|
||||
}
|
||||
return scope.Escape(value);
|
||||
}
|
||||
default:
|
||||
NOTREACHED() << "Invalid tag: " << tag;
|
||||
return v8::Null(isolate_);
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadTag(uint8_t* tag) {
|
||||
const void* tag_bytes;
|
||||
if (!deserializer_.ReadRawBytes(1, &tag_bytes))
|
||||
return false;
|
||||
*tag = *reinterpret_cast<const uint8_t*>(tag_bytes);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadBaseValue(v8::Local<v8::Value>* value) {
|
||||
uint32_t length;
|
||||
const void* data;
|
||||
if (!deserializer_.ReadUint32(&length) ||
|
||||
!deserializer_.ReadRawBytes(length, &data)) {
|
||||
return false;
|
||||
}
|
||||
mojo::Message message(
|
||||
base::make_span(reinterpret_cast<const uint8_t*>(data), length), {});
|
||||
base::Value out;
|
||||
if (!mojo_base::mojom::Value::DeserializeFromMessage(std::move(message),
|
||||
&out)) {
|
||||
return false;
|
||||
}
|
||||
*value = ConvertToV8(isolate_, out);
|
||||
return true;
|
||||
}
|
||||
|
||||
private:
|
||||
v8::Isolate* isolate_;
|
||||
v8::ValueDeserializer deserializer_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
v8::Local<v8::Value> Converter<blink::CloneableMessage>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const blink::CloneableMessage& in) {
|
||||
return V8Deserializer(isolate, in).Deserialize();
|
||||
}
|
||||
|
||||
bool Converter<blink::CloneableMessage>::FromV8(v8::Isolate* isolate,
|
||||
v8::Handle<v8::Value> val,
|
||||
blink::CloneableMessage* out) {
|
||||
return V8Serializer(isolate).Serialize(val, out);
|
||||
}
|
||||
|
||||
} // namespace mate
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_BLINK_CONVERTER_H_
|
||||
|
||||
#include "native_mate/converter.h"
|
||||
#include "third_party/blink/public/common/messaging/cloneable_message.h"
|
||||
#include "third_party/blink/public/platform/web_cache.h"
|
||||
#include "third_party/blink/public/platform/web_input_event.h"
|
||||
#include "third_party/blink/public/web/web_context_menu_data.h"
|
||||
@@ -131,6 +132,15 @@ struct Converter<network::mojom::ReferrerPolicy> {
|
||||
network::mojom::ReferrerPolicy* out);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<blink::CloneableMessage> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const blink::CloneableMessage& in);
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
blink::CloneableMessage* out);
|
||||
};
|
||||
|
||||
v8::Local<v8::Value> EditFlagsToV8(v8::Isolate* isolate, int editFlags);
|
||||
v8::Local<v8::Value> MediaFlagsToV8(v8::Isolate* isolate, int mediaFlags);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_
|
||||
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_CALLBACK_H_
|
||||
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/api/locker.h"
|
||||
@@ -54,7 +55,8 @@ struct V8FunctionInvoker<v8::Local<v8::Value>(ArgTypes...)> {
|
||||
v8::Local<v8::Function> holder = function.NewHandle(isolate);
|
||||
v8::Local<v8::Context> context = holder->CreationContext();
|
||||
v8::Context::Scope context_scope(context);
|
||||
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
|
||||
std::vector<v8::Local<v8::Value>> args{
|
||||
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
|
||||
v8::MaybeLocal<v8::Value> ret = holder->Call(
|
||||
context, holder, args.size(), args.empty() ? nullptr : &args.front());
|
||||
if (ret.IsEmpty())
|
||||
@@ -78,7 +80,8 @@ struct V8FunctionInvoker<void(ArgTypes...)> {
|
||||
v8::Local<v8::Function> holder = function.NewHandle(isolate);
|
||||
v8::Local<v8::Context> context = holder->CreationContext();
|
||||
v8::Context::Scope context_scope(context);
|
||||
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
|
||||
std::vector<v8::Local<v8::Value>> args{
|
||||
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
|
||||
holder
|
||||
->Call(context, holder, args.size(),
|
||||
args.empty() ? nullptr : &args.front())
|
||||
@@ -101,7 +104,8 @@ struct V8FunctionInvoker<ReturnType(ArgTypes...)> {
|
||||
v8::Local<v8::Function> holder = function.NewHandle(isolate);
|
||||
v8::Local<v8::Context> context = holder->CreationContext();
|
||||
v8::Context::Scope context_scope(context);
|
||||
std::vector<v8::Local<v8::Value>> args{ConvertToV8(isolate, raw)...};
|
||||
std::vector<v8::Local<v8::Value>> args{
|
||||
ConvertToV8(isolate, std::forward<ArgTypes>(raw))...};
|
||||
v8::Local<v8::Value> result;
|
||||
auto maybe_result = holder->Call(context, holder, args.size(),
|
||||
args.empty() ? nullptr : &args.front());
|
||||
@@ -138,20 +142,6 @@ struct NativeFunctionInvoker<ReturnType(ArgTypes...)> {
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename Sig>
|
||||
struct Converter<base::OnceCallback<Sig>> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
base::OnceCallback<Sig>* out) {
|
||||
if (!val->IsFunction())
|
||||
return false;
|
||||
|
||||
*out = base::BindOnce(&internal::V8FunctionInvoker<Sig>::Go, isolate,
|
||||
internal::SafeV8Function(isolate, val));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Sig>
|
||||
struct Converter<base::RepeatingCallback<Sig>> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
|
||||
87
atom/common/native_mate_converters/once_callback.h
Normal file
87
atom/common/native_mate_converters/once_callback.h
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright (c) 2019 GitHub, Inc. All rights reserved.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_
|
||||
#define ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
|
||||
namespace mate {
|
||||
|
||||
namespace internal {
|
||||
|
||||
// Manages the OnceCallback with ref-couting.
|
||||
template <typename Sig>
|
||||
class RefCountedOnceCallback
|
||||
: public base::RefCounted<RefCountedOnceCallback<Sig>> {
|
||||
public:
|
||||
explicit RefCountedOnceCallback(base::OnceCallback<Sig> callback)
|
||||
: callback_(std::move(callback)) {}
|
||||
|
||||
base::OnceCallback<Sig> GetCallback() { return std::move(callback_); }
|
||||
|
||||
private:
|
||||
friend class base::RefCounted<RefCountedOnceCallback<Sig>>;
|
||||
~RefCountedOnceCallback() = default;
|
||||
|
||||
base::OnceCallback<Sig> callback_;
|
||||
};
|
||||
|
||||
// Invokes the OnceCallback.
|
||||
template <typename Sig>
|
||||
struct InvokeOnceCallback {};
|
||||
|
||||
template <typename... ArgTypes>
|
||||
struct InvokeOnceCallback<void(ArgTypes...)> {
|
||||
static void Go(
|
||||
scoped_refptr<RefCountedOnceCallback<void(ArgTypes...)>> holder,
|
||||
ArgTypes... args) {
|
||||
base::OnceCallback<void(ArgTypes...)> callback = holder->GetCallback();
|
||||
DCHECK(!callback.is_null());
|
||||
std::move(callback).Run(std::move(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename ReturnType, typename... ArgTypes>
|
||||
struct InvokeOnceCallback<ReturnType(ArgTypes...)> {
|
||||
static ReturnType Go(
|
||||
scoped_refptr<RefCountedOnceCallback<ReturnType(ArgTypes...)>> holder,
|
||||
ArgTypes... args) {
|
||||
base::OnceCallback<void(ArgTypes...)> callback = holder->GetCallback();
|
||||
DCHECK(!callback.is_null());
|
||||
return std::move(callback).Run(std::move(args)...);
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace internal
|
||||
|
||||
template <typename Sig>
|
||||
struct Converter<base::OnceCallback<Sig>> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
base::OnceCallback<Sig> val) {
|
||||
// Reuse the converter of base::RepeatingCallback by storing the callback
|
||||
// with a RefCounted.
|
||||
auto holder = base::MakeRefCounted<internal::RefCountedOnceCallback<Sig>>(
|
||||
std::move(val));
|
||||
return Converter<base::RepeatingCallback<Sig>>::ToV8(
|
||||
isolate,
|
||||
base::BindRepeating(&internal::InvokeOnceCallback<Sig>::Go, holder));
|
||||
}
|
||||
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
base::OnceCallback<Sig>* out) {
|
||||
if (!val->IsFunction())
|
||||
return false;
|
||||
*out = base::BindOnce(&internal::V8FunctionInvoker<Sig>::Go, isolate,
|
||||
internal::SafeV8Function(isolate, val));
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace mate
|
||||
|
||||
#endif // ATOM_COMMON_NATIVE_MATE_CONVERTERS_ONCE_CALLBACK_H_
|
||||
@@ -65,6 +65,7 @@
|
||||
V(atom_common_screen) \
|
||||
V(atom_common_shell) \
|
||||
V(atom_common_v8_util) \
|
||||
V(atom_renderer_context_bridge) \
|
||||
V(atom_renderer_ipc) \
|
||||
V(atom_renderer_web_frame)
|
||||
|
||||
|
||||
@@ -103,6 +103,16 @@ class Promise {
|
||||
return GetInner()->Reject(GetContext(), v8::Undefined(isolate()));
|
||||
}
|
||||
|
||||
v8::Maybe<bool> Reject(v8::Local<v8::Value> exception) {
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
v8::MicrotasksScope script_scope(isolate(),
|
||||
v8::MicrotasksScope::kRunMicrotasks);
|
||||
v8::Context::Scope context_scope(
|
||||
v8::Local<v8::Context>::New(isolate(), GetContext()));
|
||||
|
||||
return GetInner()->Reject(GetContext(), exception);
|
||||
}
|
||||
|
||||
// Please note that using Then is effectively the same as calling .then
|
||||
// in javascript. This means (a) it is not type safe and (b) please note
|
||||
// it is NOT type safe.
|
||||
|
||||
513
atom/renderer/api/atom_api_context_bridge.cc
Normal file
513
atom/renderer/api/atom_api_context_bridge.cc
Normal file
@@ -0,0 +1,513 @@
|
||||
// Copyright (c) 2019 Slack Technologies, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/renderer/api/atom_api_context_bridge.h"
|
||||
|
||||
#include <set>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "atom/common/api/object_life_monitor.h"
|
||||
#include "atom/common/native_mate_converters/blink_converter.h"
|
||||
#include "atom/common/native_mate_converters/callback.h"
|
||||
#include "atom/common/native_mate_converters/once_callback.h"
|
||||
#include "atom/common/promise_util.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace {
|
||||
|
||||
static int kMaxRecursion = 1000;
|
||||
|
||||
content::RenderFrame* GetRenderFrame(const v8::Local<v8::Object>& value) {
|
||||
v8::Local<v8::Context> context = value->CreationContext();
|
||||
if (context.IsEmpty())
|
||||
return nullptr;
|
||||
blink::WebLocalFrame* frame = blink::WebLocalFrame::FrameForContext(context);
|
||||
if (!frame)
|
||||
return nullptr;
|
||||
return content::RenderFrame::FromWebFrame(frame);
|
||||
}
|
||||
|
||||
std::map<content::RenderFrame*, context_bridge::RenderFramePersistenceStore*>&
|
||||
GetStoreMap() {
|
||||
static base::NoDestructor<std::map<
|
||||
content::RenderFrame*, context_bridge::RenderFramePersistenceStore*>>
|
||||
store_map;
|
||||
return *store_map;
|
||||
}
|
||||
|
||||
context_bridge::RenderFramePersistenceStore* GetOrCreateStore(
|
||||
content::RenderFrame* render_frame) {
|
||||
auto it = GetStoreMap().find(render_frame);
|
||||
if (it == GetStoreMap().end()) {
|
||||
auto* store = new context_bridge::RenderFramePersistenceStore(render_frame);
|
||||
GetStoreMap().emplace(render_frame, store);
|
||||
return store;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
// Sourced from "extensions/renderer/v8_schema_registry.cc"
|
||||
// Recursively freezes every v8 object on |object|.
|
||||
bool DeepFreeze(const v8::Local<v8::Object>& object,
|
||||
const v8::Local<v8::Context>& context,
|
||||
std::set<int> frozen = std::set<int>()) {
|
||||
int hash = object->GetIdentityHash();
|
||||
if (frozen.find(hash) != frozen.end())
|
||||
return true;
|
||||
frozen.insert(hash);
|
||||
|
||||
v8::Local<v8::Array> property_names =
|
||||
object->GetOwnPropertyNames(context).ToLocalChecked();
|
||||
for (uint32_t i = 0; i < property_names->Length(); ++i) {
|
||||
v8::Local<v8::Value> child =
|
||||
object->Get(context, property_names->Get(context, i).ToLocalChecked())
|
||||
.ToLocalChecked();
|
||||
if (child->IsObject() && !child->IsTypedArray()) {
|
||||
if (!DeepFreeze(v8::Local<v8::Object>::Cast(child), context, frozen))
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return mate::internal::IsTrue(
|
||||
object->SetIntegrityLevel(context, v8::IntegrityLevel::kFrozen));
|
||||
}
|
||||
|
||||
bool IsPlainObject(const v8::Local<v8::Value>& object) {
|
||||
if (!object->IsObject())
|
||||
return false;
|
||||
|
||||
return !(object->IsNullOrUndefined() || object->IsDate() ||
|
||||
object->IsArgumentsObject() || object->IsBigIntObject() ||
|
||||
object->IsBooleanObject() || object->IsNumberObject() ||
|
||||
object->IsStringObject() || object->IsSymbolObject() ||
|
||||
object->IsNativeError() || object->IsRegExp() ||
|
||||
object->IsPromise() || object->IsMap() || object->IsSet() ||
|
||||
object->IsMapIterator() || object->IsSetIterator() ||
|
||||
object->IsWeakMap() || object->IsWeakSet() ||
|
||||
object->IsArrayBuffer() || object->IsArrayBufferView() ||
|
||||
object->IsArray() || object->IsDataView() ||
|
||||
object->IsSharedArrayBuffer() || object->IsProxy() ||
|
||||
object->IsWebAssemblyCompiledModule() ||
|
||||
object->IsModuleNamespaceObject());
|
||||
}
|
||||
|
||||
bool IsPlainArray(const v8::Local<v8::Value>& arr) {
|
||||
if (!arr->IsArray())
|
||||
return false;
|
||||
|
||||
return !arr->IsTypedArray();
|
||||
}
|
||||
|
||||
class FunctionLifeMonitor final : public ObjectLifeMonitor {
|
||||
public:
|
||||
static void BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
size_t func_id) {
|
||||
new FunctionLifeMonitor(isolate, target, store, func_id);
|
||||
}
|
||||
|
||||
protected:
|
||||
FunctionLifeMonitor(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
size_t func_id)
|
||||
: ObjectLifeMonitor(isolate, target), store_(store), func_id_(func_id) {}
|
||||
~FunctionLifeMonitor() override = default;
|
||||
|
||||
void RunDestructor() override { store_->functions().erase(func_id_); }
|
||||
|
||||
private:
|
||||
context_bridge::RenderFramePersistenceStore* store_;
|
||||
size_t func_id_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
template <typename Sig>
|
||||
v8::Local<v8::Value> BindRepeatingFunctionToV8(
|
||||
v8::Isolate* isolate,
|
||||
const base::RepeatingCallback<Sig>& val) {
|
||||
auto translater =
|
||||
base::BindRepeating(&mate::internal::NativeFunctionInvoker<Sig>::Go, val);
|
||||
return mate::internal::CreateFunctionFromTranslater(isolate, translater,
|
||||
false);
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Value> PassValueToOtherContext(
|
||||
v8::Local<v8::Context> source_context,
|
||||
v8::Local<v8::Context> destination_context,
|
||||
v8::Local<v8::Value> value,
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
int recursion_depth) {
|
||||
if (recursion_depth >= kMaxRecursion) {
|
||||
v8::Context::Scope source_scope(source_context);
|
||||
{
|
||||
source_context->GetIsolate()->ThrowException(v8::Exception::TypeError(
|
||||
mate::StringToV8(source_context->GetIsolate(),
|
||||
"Electron contextBridge recursion depth exceeded. "
|
||||
"Nested objects "
|
||||
"deeper than 1000 are not supported.")));
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
}
|
||||
}
|
||||
// Check Cache
|
||||
auto cached_value = store->GetCachedProxiedObject(value);
|
||||
if (!cached_value.IsEmpty()) {
|
||||
return cached_value;
|
||||
}
|
||||
|
||||
// Proxy functions and monitor the lifetime in the new context to release
|
||||
// the global handle at the right time.
|
||||
if (value->IsFunction()) {
|
||||
auto func = v8::Local<v8::Function>::Cast(value);
|
||||
v8::Global<v8::Function> global_func(source_context->GetIsolate(), func);
|
||||
v8::Global<v8::Context> global_source(source_context->GetIsolate(),
|
||||
source_context);
|
||||
|
||||
size_t func_id = store->take_func_id();
|
||||
store->functions()[func_id] =
|
||||
std::make_tuple(std::move(global_func), std::move(global_source));
|
||||
v8::Context::Scope destination_scope(destination_context);
|
||||
{
|
||||
v8::Local<v8::Value> proxy_func = BindRepeatingFunctionToV8(
|
||||
destination_context->GetIsolate(),
|
||||
base::BindRepeating(&ProxyFunctionWrapper, store, func_id));
|
||||
FunctionLifeMonitor::BindTo(destination_context->GetIsolate(),
|
||||
v8::Local<v8::Object>::Cast(proxy_func),
|
||||
store, func_id);
|
||||
store->CacheProxiedObject(value, proxy_func);
|
||||
return v8::MaybeLocal<v8::Value>(proxy_func);
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy promises as they have a safe and guaranteed memory lifecycle
|
||||
if (value->IsPromise()) {
|
||||
v8::Context::Scope destination_scope(destination_context);
|
||||
{
|
||||
auto source_promise = v8::Local<v8::Promise>::Cast(value);
|
||||
auto* proxied_promise =
|
||||
new util::Promise(destination_context->GetIsolate());
|
||||
v8::Local<v8::Promise> proxied_promise_handle =
|
||||
proxied_promise->GetHandle();
|
||||
|
||||
auto then_cb = base::BindOnce(
|
||||
[](util::Promise* proxied_promise, v8::Isolate* isolate,
|
||||
v8::Global<v8::Context> global_source_context,
|
||||
v8::Global<v8::Context> global_destination_context,
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
v8::Local<v8::Value> result) {
|
||||
auto val = PassValueToOtherContext(
|
||||
global_source_context.Get(isolate),
|
||||
global_destination_context.Get(isolate), result, store, 0);
|
||||
if (!val.IsEmpty())
|
||||
proxied_promise->Resolve(val.ToLocalChecked());
|
||||
delete proxied_promise;
|
||||
},
|
||||
proxied_promise, destination_context->GetIsolate(),
|
||||
v8::Global<v8::Context>(source_context->GetIsolate(), source_context),
|
||||
v8::Global<v8::Context>(destination_context->GetIsolate(),
|
||||
destination_context),
|
||||
store);
|
||||
auto catch_cb = base::BindOnce(
|
||||
[](util::Promise* proxied_promise, v8::Isolate* isolate,
|
||||
v8::Global<v8::Context> global_source_context,
|
||||
v8::Global<v8::Context> global_destination_context,
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
v8::Local<v8::Value> result) {
|
||||
auto val = PassValueToOtherContext(
|
||||
global_source_context.Get(isolate),
|
||||
global_destination_context.Get(isolate), result, store, 0);
|
||||
if (!val.IsEmpty())
|
||||
proxied_promise->Reject(val.ToLocalChecked());
|
||||
delete proxied_promise;
|
||||
},
|
||||
proxied_promise, destination_context->GetIsolate(),
|
||||
v8::Global<v8::Context>(source_context->GetIsolate(), source_context),
|
||||
v8::Global<v8::Context>(destination_context->GetIsolate(),
|
||||
destination_context),
|
||||
store);
|
||||
|
||||
ignore_result(source_promise->Then(
|
||||
source_context,
|
||||
v8::Local<v8::Function>::Cast(
|
||||
mate::ConvertToV8(destination_context->GetIsolate(), then_cb)),
|
||||
v8::Local<v8::Function>::Cast(
|
||||
mate::ConvertToV8(destination_context->GetIsolate(), catch_cb))));
|
||||
|
||||
store->CacheProxiedObject(value, proxied_promise_handle);
|
||||
return v8::MaybeLocal<v8::Value>(proxied_promise_handle);
|
||||
}
|
||||
}
|
||||
|
||||
// Errors aren't serializable currently, we need to pull the message out and
|
||||
// re-construct in the destination context
|
||||
if (value->IsNativeError()) {
|
||||
v8::Context::Scope destination_context_scope(destination_context);
|
||||
return v8::MaybeLocal<v8::Value>(v8::Exception::Error(
|
||||
v8::Exception::CreateMessage(destination_context->GetIsolate(), value)
|
||||
->Get()));
|
||||
}
|
||||
|
||||
// Manually go through the array and pass each value individually into a new
|
||||
// array so that functions deep inside arrays get proxied or arrays of
|
||||
// promises are proxied correctly.
|
||||
if (IsPlainArray(value)) {
|
||||
v8::Context::Scope destination_context_scope(destination_context);
|
||||
{
|
||||
v8::Local<v8::Array> arr = v8::Local<v8::Array>::Cast(value);
|
||||
size_t length = arr->Length();
|
||||
v8::Local<v8::Array> cloned_arr =
|
||||
v8::Array::New(destination_context->GetIsolate(), length);
|
||||
for (size_t i = 0; i < length; i++) {
|
||||
auto value_for_array = PassValueToOtherContext(
|
||||
source_context, destination_context,
|
||||
arr->Get(source_context, i).ToLocalChecked(), store,
|
||||
recursion_depth + 1);
|
||||
if (value_for_array.IsEmpty())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
|
||||
if (!mate::internal::IsTrue(
|
||||
cloned_arr->Set(destination_context, static_cast<int>(i),
|
||||
value_for_array.ToLocalChecked()))) {
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
}
|
||||
}
|
||||
store->CacheProxiedObject(value, cloned_arr);
|
||||
return v8::MaybeLocal<v8::Value>(cloned_arr);
|
||||
}
|
||||
}
|
||||
|
||||
// Proxy all objects
|
||||
if (IsPlainObject(value)) {
|
||||
auto object_value = v8::Local<v8::Object>::Cast(value);
|
||||
auto passed_value =
|
||||
CreateProxyForAPI(object_value, source_context, destination_context,
|
||||
store, recursion_depth + 1);
|
||||
if (passed_value.IsEmpty())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
return v8::MaybeLocal<v8::Value>(passed_value.ToLocalChecked());
|
||||
}
|
||||
|
||||
// Serializable objects
|
||||
blink::CloneableMessage ret;
|
||||
{
|
||||
v8::Context::Scope source_context_scope(source_context);
|
||||
{
|
||||
// V8 serializer will throw an error if required
|
||||
if (!mate::ConvertFromV8(source_context->GetIsolate(), value, &ret))
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
}
|
||||
}
|
||||
|
||||
v8::Context::Scope destination_context_scope(destination_context);
|
||||
{
|
||||
v8::Local<v8::Value> cloned_value =
|
||||
mate::ConvertToV8(destination_context->GetIsolate(), ret);
|
||||
store->CacheProxiedObject(value, cloned_value);
|
||||
return v8::MaybeLocal<v8::Value>(cloned_value);
|
||||
}
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> ProxyFunctionWrapper(
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
size_t func_id,
|
||||
mate::Arguments* args) {
|
||||
// Context the proxy function was called from
|
||||
v8::Local<v8::Context> calling_context = args->isolate()->GetCurrentContext();
|
||||
// Context the function was created in
|
||||
v8::Local<v8::Context> func_owning_context =
|
||||
std::get<1>(store->functions()[func_id]).Get(args->isolate());
|
||||
|
||||
v8::Context::Scope func_owning_context_scope(func_owning_context);
|
||||
{
|
||||
v8::Local<v8::Function> func =
|
||||
(std::get<0>(store->functions()[func_id])).Get(args->isolate());
|
||||
|
||||
std::vector<v8::Local<v8::Value>> original_args;
|
||||
std::vector<v8::Local<v8::Value>> proxied_args;
|
||||
args->GetRemaining(&original_args);
|
||||
|
||||
for (auto value : original_args) {
|
||||
auto arg = PassValueToOtherContext(calling_context, func_owning_context,
|
||||
value, store, 0);
|
||||
if (arg.IsEmpty())
|
||||
return v8::Undefined(args->isolate());
|
||||
proxied_args.push_back(arg.ToLocalChecked());
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Value> maybe_return_value;
|
||||
bool did_error = false;
|
||||
std::string error_message;
|
||||
{
|
||||
v8::TryCatch try_catch(args->isolate());
|
||||
maybe_return_value = func->Call(func_owning_context, func,
|
||||
proxied_args.size(), proxied_args.data());
|
||||
if (try_catch.HasCaught()) {
|
||||
did_error = true;
|
||||
auto message = try_catch.Message();
|
||||
|
||||
if (message.IsEmpty() ||
|
||||
!mate::ConvertFromV8(args->isolate(), message->Get(),
|
||||
&error_message)) {
|
||||
error_message =
|
||||
"An unknown exception occurred in the isolated context, an error "
|
||||
"occurred but a valid exception was not thrown.";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (did_error) {
|
||||
v8::Context::Scope calling_context_scope(calling_context);
|
||||
{
|
||||
args->ThrowError(error_message);
|
||||
return v8::Local<v8::Object>();
|
||||
}
|
||||
}
|
||||
|
||||
if (maybe_return_value.IsEmpty())
|
||||
return v8::Undefined(args->isolate());
|
||||
|
||||
auto ret =
|
||||
PassValueToOtherContext(func_owning_context, calling_context,
|
||||
maybe_return_value.ToLocalChecked(), store, 0);
|
||||
if (ret.IsEmpty())
|
||||
return v8::Undefined(args->isolate());
|
||||
return ret.ToLocalChecked();
|
||||
}
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Object> CreateProxyForAPI(
|
||||
const v8::Local<v8::Object>& api_object,
|
||||
const v8::Local<v8::Context>& source_context,
|
||||
const v8::Local<v8::Context>& destination_context,
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
int recursion_depth) {
|
||||
mate::Dictionary api(source_context->GetIsolate(), api_object);
|
||||
mate::Dictionary proxy =
|
||||
mate::Dictionary::CreateEmpty(destination_context->GetIsolate());
|
||||
store->CacheProxiedObject(api.GetHandle(), proxy.GetHandle());
|
||||
auto maybe_keys = api.GetHandle()->GetOwnPropertyNames(
|
||||
source_context,
|
||||
static_cast<v8::PropertyFilter>(v8::ONLY_ENUMERABLE | v8::SKIP_SYMBOLS),
|
||||
v8::KeyConversionMode::kConvertToString);
|
||||
if (maybe_keys.IsEmpty())
|
||||
return v8::MaybeLocal<v8::Object>(proxy.GetHandle());
|
||||
auto keys = maybe_keys.ToLocalChecked();
|
||||
|
||||
v8::Context::Scope destination_context_scope(destination_context);
|
||||
{
|
||||
uint32_t length = keys->Length();
|
||||
std::string key_str;
|
||||
for (uint32_t i = 0; i < length; i++) {
|
||||
v8::Local<v8::Value> key =
|
||||
keys->Get(destination_context, i).ToLocalChecked();
|
||||
// Try get the key as a string
|
||||
if (!mate::ConvertFromV8(api.isolate(), key, &key_str)) {
|
||||
continue;
|
||||
}
|
||||
v8::Local<v8::Value> value;
|
||||
if (!api.Get(key_str, &value))
|
||||
continue;
|
||||
|
||||
auto passed_value =
|
||||
PassValueToOtherContext(source_context, destination_context, value,
|
||||
store, recursion_depth + 1);
|
||||
if (passed_value.IsEmpty())
|
||||
return v8::MaybeLocal<v8::Object>();
|
||||
proxy.Set(key_str, passed_value.ToLocalChecked());
|
||||
}
|
||||
|
||||
return proxy.GetHandle();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DCHECK_IS_ON
|
||||
mate::Dictionary DebugGC(mate::Dictionary empty) {
|
||||
auto* render_frame = GetRenderFrame(empty.GetHandle());
|
||||
auto* store = GetOrCreateStore(render_frame);
|
||||
mate::Dictionary ret = mate::Dictionary::CreateEmpty(empty.isolate());
|
||||
ret.Set("functionCount", store->functions().size());
|
||||
auto* proxy_map = store->proxy_map();
|
||||
ret.Set("objectCount", proxy_map->size() * 2);
|
||||
int live_from = 0;
|
||||
int live_proxy = 0;
|
||||
for (auto iter = proxy_map->begin(); iter != proxy_map->end(); iter++) {
|
||||
auto* node = iter->second;
|
||||
while (node) {
|
||||
if (!std::get<0>(node->pair).IsEmpty())
|
||||
live_from++;
|
||||
if (!std::get<1>(node->pair).IsEmpty())
|
||||
live_proxy++;
|
||||
node = node->next;
|
||||
}
|
||||
}
|
||||
ret.Set("liveFromValues", live_from);
|
||||
ret.Set("liveProxyValues", live_proxy);
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void ExposeAPIInMainWorld(const std::string& key,
|
||||
v8::Local<v8::Object> api_object,
|
||||
mate::Arguments* args) {
|
||||
auto* render_frame = GetRenderFrame(api_object);
|
||||
CHECK(render_frame);
|
||||
context_bridge::RenderFramePersistenceStore* store =
|
||||
GetOrCreateStore(render_frame);
|
||||
auto* frame = render_frame->GetWebFrame();
|
||||
CHECK(frame);
|
||||
v8::Local<v8::Context> main_context = frame->MainWorldScriptContext();
|
||||
mate::Dictionary global(main_context->GetIsolate(), main_context->Global());
|
||||
|
||||
if (global.Has(key)) {
|
||||
args->ThrowError(
|
||||
"Cannot bind an API on top of an existing property on the window "
|
||||
"object");
|
||||
return;
|
||||
}
|
||||
|
||||
v8::Local<v8::Context> isolated_context =
|
||||
frame->WorldScriptContext(args->isolate(), atom::World::ISOLATED_WORLD);
|
||||
|
||||
v8::Context::Scope main_context_scope(main_context);
|
||||
{
|
||||
v8::MaybeLocal<v8::Object> maybe_proxy =
|
||||
CreateProxyForAPI(api_object, isolated_context, main_context, store, 0);
|
||||
if (maybe_proxy.IsEmpty())
|
||||
return;
|
||||
auto proxy = maybe_proxy.ToLocalChecked();
|
||||
if (!DeepFreeze(proxy, main_context))
|
||||
return;
|
||||
|
||||
global.SetReadOnlyNonConfigurable(key, proxy);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace atom
|
||||
|
||||
namespace {
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
mate::Dictionary dict(isolate, exports);
|
||||
dict.SetMethod("exposeAPIInMainWorld", &atom::api::ExposeAPIInMainWorld);
|
||||
#ifdef DCHECK_IS_ON
|
||||
dict.SetMethod("_debugGCMaps", &atom::api::DebugGC);
|
||||
#endif
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NODE_LINKED_MODULE_CONTEXT_AWARE(atom_renderer_context_bridge, Initialize)
|
||||
41
atom/renderer/api/atom_api_context_bridge.h
Normal file
41
atom/renderer/api/atom_api_context_bridge.h
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) 2019 Slack Technologies, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_RENDERER_API_ATOM_API_CONTEXT_BRIDGE_H_
|
||||
#define ATOM_RENDERER_API_ATOM_API_CONTEXT_BRIDGE_H_
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <tuple>
|
||||
|
||||
#include "atom/common/node_includes.h"
|
||||
#include "atom/renderer/api/context_bridge/render_frame_context_bridge_store.h"
|
||||
#include "atom/renderer/atom_render_frame_observer.h"
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "content/public/renderer/render_frame_observer.h"
|
||||
#include "native_mate/converter.h"
|
||||
#include "native_mate/dictionary.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
v8::Local<v8::Value> ProxyFunctionWrapper(
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
size_t func_id,
|
||||
mate::Arguments* args);
|
||||
|
||||
v8::MaybeLocal<v8::Object> CreateProxyForAPI(
|
||||
const v8::Local<v8::Object>& api_object,
|
||||
const v8::Local<v8::Context>& source_context,
|
||||
const v8::Local<v8::Context>& target_context,
|
||||
context_bridge::RenderFramePersistenceStore* store,
|
||||
int recursion_depth);
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_RENDERER_API_ATOM_API_CONTEXT_BRIDGE_H_
|
||||
@@ -82,6 +82,7 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
const base::ListValue& arguments) {
|
||||
std::string error;
|
||||
base::Value result;
|
||||
|
||||
// A task is posted to a separate thread to execute the request so that
|
||||
@@ -98,39 +99,66 @@ class IPCRenderer : public mate::Wrappable<IPCRenderer> {
|
||||
// interface.
|
||||
auto interface_info = electron_browser_ptr_.PassInterface();
|
||||
task_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(&IPCRenderer::SendMessageSyncOnWorkerThread,
|
||||
base::Unretained(&interface_info),
|
||||
base::Unretained(&response_received_event),
|
||||
base::Unretained(&result), internal, channel,
|
||||
base::Unretained(&arguments)));
|
||||
FROM_HERE,
|
||||
base::BindOnce(&IPCRenderer::SendMessageSyncOnWorkerThread,
|
||||
base::Unretained(this),
|
||||
base::Unretained(&interface_info),
|
||||
base::Unretained(&response_received_event),
|
||||
base::Unretained(&result), internal, channel,
|
||||
base::Unretained(&arguments), base::Unretained(&error)));
|
||||
response_received_event.Wait();
|
||||
electron_browser_ptr_.Bind(std::move(interface_info));
|
||||
if (!error.empty()) {
|
||||
args->ThrowError(error);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private:
|
||||
static void SendMessageSyncOnWorkerThread(
|
||||
void SendMessageSyncOnWorkerThread(
|
||||
atom::mojom::ElectronBrowserPtrInfo* interface_info,
|
||||
base::WaitableEvent* event,
|
||||
base::Value* result,
|
||||
bool internal,
|
||||
const std::string& channel,
|
||||
const base::ListValue* arguments) {
|
||||
const base::ListValue* arguments,
|
||||
std::string* error) {
|
||||
atom::mojom::ElectronBrowserPtr browser_ptr(std::move(*interface_info));
|
||||
browser_ptr->MessageSync(
|
||||
electron_browser_ptr_ = std::move(browser_ptr);
|
||||
|
||||
electron_browser_ptr_.set_connection_error_handler(
|
||||
base::BindOnce(&IPCRenderer::HandleMojoConnectionErrorOnWorkerThread,
|
||||
base::Unretained(&electron_browser_ptr_),
|
||||
base::Unretained(interface_info),
|
||||
base::Unretained(event), base::Unretained(error)));
|
||||
electron_browser_ptr_->MessageSync(
|
||||
internal, channel, arguments->Clone(),
|
||||
base::BindOnce(&IPCRenderer::ReturnSyncResponseToMainThread,
|
||||
std::move(browser_ptr), base::Unretained(interface_info),
|
||||
base::Unretained(&electron_browser_ptr_),
|
||||
base::Unretained(interface_info),
|
||||
base::Unretained(event), base::Unretained(result)));
|
||||
}
|
||||
static void ReturnSyncResponseToMainThread(
|
||||
atom::mojom::ElectronBrowserPtr ptr,
|
||||
atom::mojom::ElectronBrowserPtr* ptr,
|
||||
atom::mojom::ElectronBrowserPtrInfo* interface_info,
|
||||
base::WaitableEvent* event,
|
||||
base::Value* result,
|
||||
base::Value response) {
|
||||
*result = std::move(response);
|
||||
*interface_info = ptr.PassInterface();
|
||||
*interface_info = ptr->PassInterface();
|
||||
event->Signal();
|
||||
}
|
||||
// If the other end of the message pipe disconnects, ensure we don't hang the
|
||||
// main thread forever.
|
||||
static void HandleMojoConnectionErrorOnWorkerThread(
|
||||
atom::mojom::ElectronBrowserPtr* ptr,
|
||||
atom::mojom::ElectronBrowserPtrInfo* interface_info,
|
||||
base::WaitableEvent* event,
|
||||
std::string* error) {
|
||||
LOG(INFO) << "Mojo connection interuppted, likely due to the Mojo receiver "
|
||||
"process crashing.";
|
||||
*error = "IPC connection fatally interrupted.";
|
||||
*interface_info = ptr->PassInterface();
|
||||
event->Signal();
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
// Copyright (c) 2019 Slack Technologies, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "atom/renderer/api/context_bridge/render_frame_context_bridge_store.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "atom/common/api/object_life_monitor.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace context_bridge {
|
||||
|
||||
namespace {
|
||||
|
||||
class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
|
||||
public:
|
||||
static void BindTo(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
RenderFramePersistenceStore* store,
|
||||
WeakGlobalPairNode* node,
|
||||
int hash) {
|
||||
new CachedProxyLifeMonitor(isolate, target, store, node, hash);
|
||||
}
|
||||
|
||||
protected:
|
||||
CachedProxyLifeMonitor(v8::Isolate* isolate,
|
||||
v8::Local<v8::Object> target,
|
||||
RenderFramePersistenceStore* store,
|
||||
WeakGlobalPairNode* node,
|
||||
int hash)
|
||||
: ObjectLifeMonitor(isolate, target),
|
||||
store_(store),
|
||||
node_(node),
|
||||
hash_(hash) {}
|
||||
|
||||
void RunDestructor() override {
|
||||
if (node_->detached) {
|
||||
delete node_;
|
||||
}
|
||||
if (node_->prev) {
|
||||
node_->prev->next = node_->next;
|
||||
}
|
||||
if (node_->next) {
|
||||
node_->next->prev = node_->prev;
|
||||
}
|
||||
if (!node_->prev && !node_->next) {
|
||||
// Must be a single length linked list
|
||||
store_->proxy_map()->erase(hash_);
|
||||
}
|
||||
node_->detached = true;
|
||||
}
|
||||
|
||||
private:
|
||||
RenderFramePersistenceStore* store_;
|
||||
WeakGlobalPairNode* node_;
|
||||
int hash_;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
WeakGlobalPairNode::WeakGlobalPairNode(WeakGlobalPair pair) {
|
||||
this->pair = std::move(pair);
|
||||
}
|
||||
|
||||
WeakGlobalPairNode::~WeakGlobalPairNode() {
|
||||
if (next) {
|
||||
delete next;
|
||||
}
|
||||
}
|
||||
|
||||
RenderFramePersistenceStore::RenderFramePersistenceStore(
|
||||
content::RenderFrame* render_frame)
|
||||
: content::RenderFrameObserver(render_frame) {}
|
||||
|
||||
RenderFramePersistenceStore::~RenderFramePersistenceStore() = default;
|
||||
|
||||
void RenderFramePersistenceStore::OnDestruct() {
|
||||
delete this;
|
||||
}
|
||||
|
||||
void RenderFramePersistenceStore::CacheProxiedObject(
|
||||
v8::Local<v8::Value> from,
|
||||
v8::Local<v8::Value> proxy_value) {
|
||||
if (from->IsObject() && !from->IsNullOrUndefined()) {
|
||||
auto obj = v8::Local<v8::Object>::Cast(from);
|
||||
int hash = obj->GetIdentityHash();
|
||||
auto global_from = v8::Global<v8::Value>(v8::Isolate::GetCurrent(), from);
|
||||
auto global_proxy =
|
||||
v8::Global<v8::Value>(v8::Isolate::GetCurrent(), proxy_value);
|
||||
// Do not retain
|
||||
global_from.SetWeak();
|
||||
global_proxy.SetWeak();
|
||||
auto iter = proxy_map_.find(hash);
|
||||
auto* node = new WeakGlobalPairNode(
|
||||
std::make_tuple(std::move(global_from), std::move(global_proxy)));
|
||||
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(), obj, this, node,
|
||||
hash);
|
||||
CachedProxyLifeMonitor::BindTo(v8::Isolate::GetCurrent(),
|
||||
v8::Local<v8::Object>::Cast(proxy_value),
|
||||
this, node, hash);
|
||||
if (iter == proxy_map_.end()) {
|
||||
proxy_map_.emplace(hash, node);
|
||||
} else {
|
||||
WeakGlobalPairNode* target = iter->second;
|
||||
while (target->next) {
|
||||
target = target->next;
|
||||
}
|
||||
target->next = node;
|
||||
node->prev = target;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
v8::MaybeLocal<v8::Value> RenderFramePersistenceStore::GetCachedProxiedObject(
|
||||
v8::Local<v8::Value> from) {
|
||||
if (!from->IsObject() || from->IsNullOrUndefined())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
|
||||
auto obj = v8::Local<v8::Object>::Cast(from);
|
||||
int hash = obj->GetIdentityHash();
|
||||
auto iter = proxy_map_.find(hash);
|
||||
if (iter == proxy_map_.end())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
WeakGlobalPairNode* target = iter->second;
|
||||
while (target) {
|
||||
auto from_cmp = std::get<0>(target->pair).Get(v8::Isolate::GetCurrent());
|
||||
if (from_cmp == from) {
|
||||
if (std::get<1>(target->pair).IsEmpty())
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
return std::get<1>(target->pair).Get(v8::Isolate::GetCurrent());
|
||||
}
|
||||
target = target->next;
|
||||
}
|
||||
return v8::MaybeLocal<v8::Value>();
|
||||
}
|
||||
|
||||
} // namespace context_bridge
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace atom
|
||||
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2019 Slack Technologies, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ATOM_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
|
||||
#define ATOM_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
|
||||
|
||||
#include <map>
|
||||
#include <tuple>
|
||||
|
||||
#include "atom/renderer/atom_render_frame_observer.h"
|
||||
#include "content/public/renderer/render_frame.h"
|
||||
#include "content/public/renderer/render_frame_observer.h"
|
||||
#include "third_party/blink/public/web/web_local_frame.h"
|
||||
|
||||
namespace atom {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace context_bridge {
|
||||
|
||||
using FunctionContextPair =
|
||||
std::tuple<v8::Global<v8::Function>, v8::Global<v8::Context>>;
|
||||
|
||||
using WeakGlobalPair = std::tuple<v8::Global<v8::Value>, v8::Global<v8::Value>>;
|
||||
|
||||
struct WeakGlobalPairNode {
|
||||
explicit WeakGlobalPairNode(WeakGlobalPair pair_);
|
||||
~WeakGlobalPairNode();
|
||||
WeakGlobalPair pair;
|
||||
bool detached = false;
|
||||
struct WeakGlobalPairNode* prev = nullptr;
|
||||
struct WeakGlobalPairNode* next = nullptr;
|
||||
};
|
||||
|
||||
class RenderFramePersistenceStore final : public content::RenderFrameObserver {
|
||||
public:
|
||||
explicit RenderFramePersistenceStore(content::RenderFrame* render_frame);
|
||||
~RenderFramePersistenceStore() override;
|
||||
|
||||
// RenderFrameObserver implementation.
|
||||
void OnDestruct() override;
|
||||
|
||||
size_t take_func_id() { return next_func_id_++; }
|
||||
|
||||
std::map<size_t, FunctionContextPair>& functions() { return functions_; }
|
||||
std::map<int, WeakGlobalPairNode*>* proxy_map() { return &proxy_map_; }
|
||||
|
||||
void CacheProxiedObject(v8::Local<v8::Value> from,
|
||||
v8::Local<v8::Value> proxy_value);
|
||||
v8::MaybeLocal<v8::Value> GetCachedProxiedObject(v8::Local<v8::Value> from);
|
||||
|
||||
private:
|
||||
// func_id ==> { function, owning_context }
|
||||
std::map<size_t, FunctionContextPair> functions_;
|
||||
size_t next_func_id_ = 1;
|
||||
|
||||
// proxy maps are weak globals, i.e. these are not retained beyond
|
||||
// there normal JS lifetime. You must check IsEmpty()
|
||||
|
||||
// object_identity ==> [from_value, proxy_value]
|
||||
std::map<int, WeakGlobalPairNode*> proxy_map_;
|
||||
};
|
||||
|
||||
} // namespace context_bridge
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace atom
|
||||
|
||||
#endif // ATOM_RENDERER_API_CONTEXT_BRIDGE_RENDER_FRAME_CONTEXT_BRIDGE_STORE_H_
|
||||
@@ -145,12 +145,6 @@ void AtomSandboxedRendererClient::InitializeBindings(
|
||||
process.SetReadOnly("sandboxed", true);
|
||||
process.SetReadOnly("type", "renderer");
|
||||
process.SetReadOnly("isMainFrame", is_main_frame);
|
||||
|
||||
// Pass in CLI flags needed to setup the renderer
|
||||
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
|
||||
if (command_line->HasSwitch(switches::kGuestInstanceID))
|
||||
b.Set(options::kGuestInstanceID,
|
||||
command_line->GetSwitchValueASCII(switches::kGuestInstanceID));
|
||||
}
|
||||
|
||||
void AtomSandboxedRendererClient::RenderFrameCreated(
|
||||
|
||||
@@ -77,8 +77,9 @@ steps:
|
||||
python electron\script\verify-ffmpeg.py --build-dir out\Default --source-root %cd% --ffmpeg-path out\ffmpeg
|
||||
displayName: 'Verify ffmpeg'
|
||||
|
||||
- script: |
|
||||
taskkill /F /IM electron.exe
|
||||
taskkill /F /IM MicrosoftEdge.exe
|
||||
- powershell: |
|
||||
Get-Process | Where Name –Like "electron*" | Stop-Process
|
||||
Get-Process | Where Name –Like "MicrosoftEdge*" | Stop-Process
|
||||
Get-Process | Where Name –Like "msedge*" | Stop-Process
|
||||
displayName: 'Kill processes left running from last test run'
|
||||
condition: always()
|
||||
@@ -17,6 +17,7 @@ buildflag_header("buildflags") {
|
||||
"ENABLE_PDF_VIEWER=$enable_pdf_viewer",
|
||||
"ENABLE_TTS=$enable_tts",
|
||||
"ENABLE_COLOR_CHOOSER=$enable_color_chooser",
|
||||
"ENABLE_MEDIA_KEY_OVERRIDES=$enable_media_key_overrides",
|
||||
"OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -18,6 +18,8 @@ declare_args() {
|
||||
|
||||
enable_color_chooser = true
|
||||
|
||||
enable_media_key_overrides = true
|
||||
|
||||
# Provide a fake location provider for mocking
|
||||
# the geolocation responses. Disable it if you
|
||||
# need to test with chromium's location provider.
|
||||
|
||||
@@ -2,10 +2,9 @@
|
||||
|
||||
<head>
|
||||
<title>Electron</title>
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self'; connect-src 'self'" />
|
||||
<meta http-equiv="Content-Security-Policy" content="default-src 'none'; script-src 'sha256-6PH54BfkNq/EMMhUY7nhHf3c+AxloOwfy7hWyT01CM8='; style-src 'self'; img-src 'self'; connect-src 'self'" />
|
||||
<link href="./styles.css" type="text/css" rel="stylesheet" />
|
||||
<link href="./octicon/build.css" type="text/css" rel="stylesheet" />
|
||||
<script defer src="./index.js"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@@ -84,6 +83,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
<script>
|
||||
window.electronDefaultApp.initialize()
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -1,30 +0,0 @@
|
||||
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
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSVG (element: HTMLSpanElement) {
|
||||
for (const cssClass of element.classList) {
|
||||
if (cssClass.startsWith('octicon-')) {
|
||||
const icon = await getOcticonSvg(cssClass.substr(8))
|
||||
if (icon) {
|
||||
for (const elemClass of element.classList) {
|
||||
icon.classList.add(elemClass)
|
||||
}
|
||||
element.before(icon)
|
||||
element.remove()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const element of document.querySelectorAll<HTMLSpanElement>('.octicon')) {
|
||||
loadSVG(element)
|
||||
}
|
||||
@@ -1,4 +1,31 @@
|
||||
import { ipcRenderer } 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
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSVG (element: HTMLSpanElement) {
|
||||
for (const cssClass of element.classList) {
|
||||
if (cssClass.startsWith('octicon-')) {
|
||||
const icon = await getOcticonSvg(cssClass.substr(8))
|
||||
if (icon) {
|
||||
for (const elemClass of element.classList) {
|
||||
icon.classList.add(elemClass)
|
||||
}
|
||||
element.before(icon)
|
||||
element.remove()
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function initialize () {
|
||||
const electronPath = ipcRenderer.sendSync('bootstrap')
|
||||
@@ -15,6 +42,12 @@ function initialize () {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', initialize)
|
||||
contextBridge.exposeInMainWorld('electronDefaultApp', {
|
||||
initialize
|
||||
})
|
||||
|
||||
@@ -1190,8 +1190,9 @@ Show the app's about panel options. These options can be overridden with `app.se
|
||||
* `website` String (optional) - The app's website. _Linux_
|
||||
* `iconPath` String (optional) - Path to the app's icon. Will be shown as 64x64 pixels while retaining aspect ratio. _Linux_
|
||||
|
||||
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`
|
||||
|
||||
@@ -1285,6 +1286,8 @@ you exactly what went wrong
|
||||
* `type` String (optional) - Can be `critical` or `informational`. The default is
|
||||
`informational`
|
||||
|
||||
Returns `Integer` an ID representing the request.
|
||||
|
||||
When `critical` is passed, the dock icon will bounce until either the
|
||||
application becomes active or the request is canceled.
|
||||
|
||||
@@ -1292,7 +1295,7 @@ When `informational` is passed, the dock icon will bounce for one second.
|
||||
However, the request remains active until either the application becomes active
|
||||
or the request is canceled.
|
||||
|
||||
Returns `Integer` an ID representing the request.
|
||||
**Nota Bene:** This method can only be used while the app is not focused; when the app is focused it will return -1.
|
||||
|
||||
### `app.dock.cancelBounce(id)` _macOS_
|
||||
|
||||
|
||||
@@ -4,18 +4,12 @@
|
||||
|
||||
Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process)
|
||||
|
||||
The following example shows how to write a string to the clipboard:
|
||||
|
||||
```javascript
|
||||
const { clipboard } = require('electron')
|
||||
clipboard.writeText('Example String')
|
||||
```
|
||||
|
||||
On Linux, there is also a `selection` clipboard. To manipulate it
|
||||
you need to pass `selection` to each method:
|
||||
|
||||
```javascript
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.writeText('Example String', 'selection')
|
||||
console.log(clipboard.readText('selection'))
|
||||
```
|
||||
@@ -28,56 +22,106 @@ The `clipboard` module has the following methods:
|
||||
|
||||
### `clipboard.readText([type])`
|
||||
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Returns `String` - The content in the clipboard as plain text.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.writeText('hello i am a bit of text!')
|
||||
|
||||
const text = clipboard.readText()
|
||||
console.log(text)
|
||||
// hello i am a bit of text!'
|
||||
```
|
||||
|
||||
### `clipboard.writeText(text[, type])`
|
||||
|
||||
* `text` String
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Writes the `text` into the clipboard as plain text.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
const text = 'hello i am a bit of text!'
|
||||
clipboard.writeText(text)
|
||||
```
|
||||
|
||||
### `clipboard.readHTML([type])`
|
||||
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Returns `String` - The content in the clipboard as markup.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.writeHTML('<b>Hi</b>')
|
||||
const html = clipboard.readHTML()
|
||||
|
||||
console.log(html)
|
||||
// <meta charset='utf-8'><b>Hi</b>
|
||||
```
|
||||
|
||||
### `clipboard.writeHTML(markup[, type])`
|
||||
|
||||
* `markup` String
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Writes `markup` to the clipboard.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.writeHTML('<b>Hi</b')
|
||||
```
|
||||
|
||||
### `clipboard.readImage([type])`
|
||||
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Returns [`NativeImage`](native-image.md) - The image content in the clipboard.
|
||||
|
||||
### `clipboard.writeImage(image[, type])`
|
||||
|
||||
* `image` [NativeImage](native-image.md)
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Writes `image` to the clipboard.
|
||||
|
||||
### `clipboard.readRTF([type])`
|
||||
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Returns `String` - The content in the clipboard as RTF.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.writeRTF('{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}')
|
||||
|
||||
const rtf = clipboard.readRTF()
|
||||
console.log(rtf)
|
||||
// {\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}
|
||||
```
|
||||
|
||||
### `clipboard.writeRTF(text[, type])`
|
||||
|
||||
* `text` String
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Writes the `text` into the clipboard in RTF.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
const rtf = '{\\rtf1\\ansi{\\fonttbl\\f0\\fswiss Helvetica;}\\f0\\pard\nThis is some {\\b bold} text.\\par\n}'
|
||||
clipboard.writeRTF(rtf)
|
||||
```
|
||||
|
||||
### `clipboard.readBookmark()` _macOS_ _Windows_
|
||||
|
||||
Returns `Object`:
|
||||
@@ -93,7 +137,7 @@ bookmark is unavailable.
|
||||
|
||||
* `title` String
|
||||
* `url` String
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Writes the `title` and `url` into the clipboard as a bookmark.
|
||||
|
||||
@@ -102,7 +146,9 @@ you can use `clipboard.write` to write both a bookmark and fallback text to the
|
||||
clipboard.
|
||||
|
||||
```js
|
||||
clipboard.write({
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.writeBookmark({
|
||||
text: 'https://electronjs.org',
|
||||
bookmark: 'Electron Homepage'
|
||||
})
|
||||
@@ -110,39 +156,50 @@ clipboard.write({
|
||||
|
||||
### `clipboard.readFindText()` _macOS_
|
||||
|
||||
Returns `String` - The text on the find pasteboard. This method uses synchronous
|
||||
IPC when called from the renderer process. The cached value is reread from the
|
||||
find pasteboard whenever the application is activated.
|
||||
Returns `String` - The text on the find pasteboard, which is the pasteboard that holds information about the current state of the active application’s find panel.
|
||||
|
||||
This method uses synchronous IPC when called from the renderer process.
|
||||
The cached value is reread from the find pasteboard whenever the application is activated.
|
||||
|
||||
### `clipboard.writeFindText(text)` _macOS_
|
||||
|
||||
* `text` String
|
||||
|
||||
Writes the `text` into the find pasteboard as plain text. This method uses
|
||||
synchronous IPC when called from the renderer process.
|
||||
Writes the `text` into the find pasteboard (the pasteboard that holds information about the current state of the active application’s find panel) as plain text. This method uses synchronous IPC when called from the renderer process.
|
||||
|
||||
### `clipboard.clear([type])`
|
||||
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Clears the clipboard content.
|
||||
|
||||
### `clipboard.availableFormats([type])`
|
||||
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Returns `String[]` - An array of supported formats for the clipboard `type`.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
const formats = clipboard.availableFormats()
|
||||
console.log(formats)
|
||||
// [ 'text/plain', 'text/html' ]
|
||||
```
|
||||
|
||||
### `clipboard.has(format[, type])` _Experimental_
|
||||
|
||||
* `format` String
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Returns `Boolean` - Whether the clipboard supports the specified `format`.
|
||||
|
||||
```javascript
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
console.log(clipboard.has('<p>selection</p>'))
|
||||
|
||||
const hasFormat = clipboard.has('<p>selection</p>')
|
||||
console.log(hasFormat)
|
||||
// 'true' or 'false
|
||||
```
|
||||
|
||||
### `clipboard.read(format)` _Experimental_
|
||||
@@ -157,14 +214,33 @@ Returns `String` - Reads `format` type from the clipboard.
|
||||
|
||||
Returns `Buffer` - Reads `format` type from the clipboard.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
const buffer = Buffer.from('this is binary', 'utf8')
|
||||
clipboard.writeBuffer('public.utf8-plain-text', buffer)
|
||||
|
||||
const ret = clipboard.readBuffer('public.utf8-plain-text')
|
||||
|
||||
console.log(buffer.equals(out))
|
||||
// true
|
||||
```
|
||||
|
||||
### `clipboard.writeBuffer(format, buffer[, type])` _Experimental_
|
||||
|
||||
* `format` String
|
||||
* `buffer` Buffer
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
Writes the `buffer` into the clipboard as `format`.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
const buffer = Buffer.from('writeBuffer', 'utf8')
|
||||
clipboard.writeBuffer('public.utf8-plain-text', buffer)
|
||||
```
|
||||
|
||||
### `clipboard.write(data[, type])`
|
||||
|
||||
* `data` Object
|
||||
@@ -172,11 +248,30 @@ Writes the `buffer` into the clipboard as `format`.
|
||||
* `html` String (optional)
|
||||
* `image` [NativeImage](native-image.md) (optional)
|
||||
* `rtf` String (optional)
|
||||
* `bookmark` String (optional) - The title of the url at `text`.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`. `selection` is only available on Linux.
|
||||
* `bookmark` String (optional) - The title of the URL at `text`.
|
||||
* `type` String (optional) - Can be `selection` or `clipboard`; default is 'clipboard'. `selection` is only available on Linux.
|
||||
|
||||
```javascript
|
||||
const { clipboard } = require('electron')
|
||||
clipboard.write({ text: 'test', html: '<b>test</b>' })
|
||||
```
|
||||
Writes `data` to the clipboard.
|
||||
|
||||
```js
|
||||
const { clipboard } = require('electron')
|
||||
|
||||
clipboard.write({
|
||||
text: 'test',
|
||||
html: '<b>Hi</b>',
|
||||
rtf: '{\\rtf1\\utf8 text}',
|
||||
bookmark: 'a title'
|
||||
})
|
||||
|
||||
console.log(clipboard.readText())
|
||||
// 'test'
|
||||
|
||||
console.log(clipboard.readHTML())
|
||||
// <meta charset='utf-8'><b>Hi</b>
|
||||
|
||||
console.log(clipboard.readRTF())
|
||||
// '{\\rtf1\\utf8 text}'
|
||||
|
||||
console.log(clipboard.readBookmark())
|
||||
// { title: 'a title', url: 'test' }
|
||||
```
|
||||
|
||||
111
docs/api/context-bridge.md
Normal file
111
docs/api/context-bridge.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# contextBridge
|
||||
|
||||
> Create a safe, bi-directional, synchronous bridge across isolated contexts
|
||||
|
||||
Process: [Renderer](../glossary.md#renderer-process)
|
||||
|
||||
An example of exposing an API to a renderer from an isolated preload script is given below:
|
||||
|
||||
```javascript
|
||||
// Preload (Isolated World)
|
||||
const { contextBridge, ipcRenderer } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'electron',
|
||||
{
|
||||
doThing: () => ipcRenderer.send('do-a-thing')
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
```javascript
|
||||
// Renderer (Main World)
|
||||
|
||||
window.electron.doThing()
|
||||
```
|
||||
|
||||
## Glossary
|
||||
|
||||
### 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.
|
||||
|
||||
### 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.
|
||||
|
||||
## Methods
|
||||
|
||||
The `contextBridge` module has the following methods:
|
||||
|
||||
### `contextBridge.exposeInMainWorld(apiKey, api)` _Experimental_
|
||||
|
||||
* `apiKey` String - The key to inject the API onto `window` with. The API will be accessible on `window[apiKey]`.
|
||||
* `api` Record<String, any> - Your API object, more information on what this API can be and how it works is available below.
|
||||
|
||||
## Usage
|
||||
|
||||
### 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.
|
||||
|
||||
`Function` values are proxied to the other context and all other values are **copied** and **frozen**. I.e. 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.
|
||||
|
||||
```javascript
|
||||
const { contextBridge } = require('electron')
|
||||
|
||||
contextBridge.exposeInMainWorld(
|
||||
'electron',
|
||||
{
|
||||
doThing: () => ipcRenderer.send('do-a-thing'),
|
||||
myPromises: [Promise.resolve(), Promise.reject(new Error('whoops'))],
|
||||
anAsyncFunction: async () => 123,
|
||||
data: {
|
||||
myFlags: ['a', 'b', 'c'],
|
||||
bootTime: 1234
|
||||
},
|
||||
nestedAPI: {
|
||||
evenDeeper: {
|
||||
youCanDoThisAsMuchAsYouWant: {
|
||||
fn: () => ({
|
||||
returnData: 123
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
### API Functions
|
||||
|
||||
`Function` values that you bind through the `contextBridge` are proxied through Electron to ensure that contexts remain isolated. This
|
||||
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.
|
||||
|
||||
| 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. |
|
||||
| `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. |
|
||||
| `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.
|
||||
@@ -117,7 +117,7 @@ dialog.showOpenDialogSync(mainWindow, {
|
||||
|
||||
Returns `Promise<Object>` - Resolve wih an object containing the following:
|
||||
|
||||
* `canceled` - Boolean - whether or not the dialog was canceled.
|
||||
* `canceled` Boolean - whether or not the dialog was canceled.
|
||||
* `filePaths` String[] (optional) - 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.
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ You can avoid much of the wait by reusing Electron CI's build output via
|
||||
optional steps (listed below) and these two environment variables:
|
||||
|
||||
```sh
|
||||
export SCCACHE_BUCKET="electronjs-sccache"
|
||||
export SCCACHE_BUCKET="electronjs-sccache-ci"
|
||||
export SCCACHE_TWO_TIER=true
|
||||
```
|
||||
|
||||
|
||||
@@ -97,6 +97,17 @@ a text file. A typical cache might look like this:
|
||||
├── SHASUMS256.txt-1.8.2-beta.3
|
||||
```
|
||||
|
||||
## Skip binary download
|
||||
When installing the `electron` NPM package, it automatically downloads the electron binary.
|
||||
|
||||
This can sometimes be unnecessary, e.g. in a CI environment, when testing another component.
|
||||
|
||||
To prevent the binary from being downloaded when you install all npm dependencies you can set the environment variable `ELECTRON_SKIP_BINARY_DOWNLOAD`.
|
||||
E.g.:
|
||||
```sh
|
||||
ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm install
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
When running `npm install electron`, some users occasionally encounter
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
<!-- Desktop Capturer API -->
|
||||
<message name="IDS_DESKTOP_MEDIA_PICKER_SINGLE_SCREEN_NAME" desc="Name for screens in the desktop media picker UI when there is only one monitor.">
|
||||
Entire screen
|
||||
Entire Screen
|
||||
</message>
|
||||
<message name="IDS_DESKTOP_MEDIA_PICKER_MULTIPLE_SCREEN_NAME" desc="Name for screens in the desktop media picker UI when there are multiple monitors.">
|
||||
{SCREEN_INDEX, plural, =1{Screen #} other{Screen #}}
|
||||
|
||||
@@ -12,6 +12,7 @@ auto_filenames = {
|
||||
"docs/api/client-request.md",
|
||||
"docs/api/clipboard.md",
|
||||
"docs/api/content-tracing.md",
|
||||
"docs/api/context-bridge.md",
|
||||
"docs/api/cookies.md",
|
||||
"docs/api/crash-reporter.md",
|
||||
"docs/api/debugger.md",
|
||||
@@ -119,7 +120,9 @@ auto_filenames = {
|
||||
"lib/common/buffer-utils.js",
|
||||
"lib/common/crash-reporter.js",
|
||||
"lib/common/error-utils.js",
|
||||
"lib/common/type-utils.js",
|
||||
"lib/common/web-view-methods.js",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
"lib/renderer/api/crash-reporter.js",
|
||||
"lib/renderer/api/desktop-capturer.js",
|
||||
"lib/renderer/api/ipc-renderer.js",
|
||||
@@ -142,6 +145,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",
|
||||
|
||||
@@ -56,12 +56,12 @@ filenames = {
|
||||
"lib/common/api/shell.js",
|
||||
"lib/common/atom-binding-setup.ts",
|
||||
"lib/common/buffer-utils.js",
|
||||
"lib/common/clipboard-utils.js",
|
||||
"lib/common/crash-reporter.js",
|
||||
"lib/common/error-utils.js",
|
||||
"lib/common/init.ts",
|
||||
"lib/common/parse-features-string.js",
|
||||
"lib/common/reset-search-paths.ts",
|
||||
"lib/common/type-utils.js",
|
||||
"lib/common/web-view-methods.js",
|
||||
"lib/renderer/callbacks-registry.js",
|
||||
"lib/renderer/chrome-api.ts",
|
||||
@@ -80,6 +80,7 @@ filenames = {
|
||||
"lib/renderer/web-view/web-view-impl.ts",
|
||||
"lib/renderer/web-view/web-view-init.ts",
|
||||
"lib/renderer/api/exports/electron.js",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
"lib/renderer/api/crash-reporter.js",
|
||||
"lib/renderer/api/ipc-renderer.js",
|
||||
"lib/renderer/api/module-list.js",
|
||||
@@ -94,7 +95,6 @@ filenames = {
|
||||
|
||||
default_app_ts_sources = [
|
||||
"default_app/default_app.ts",
|
||||
"default_app/index.ts",
|
||||
"default_app/main.ts",
|
||||
"default_app/preload.ts",
|
||||
]
|
||||
@@ -471,6 +471,7 @@ filenames = {
|
||||
"atom/browser/ui/util_gtk.cc",
|
||||
"atom/browser/ui/util_gtk.h",
|
||||
"atom/browser/ui/views/atom_views_delegate.cc",
|
||||
"atom/browser/ui/views/atom_views_delegate_win.cc",
|
||||
"atom/browser/ui/views/atom_views_delegate.h",
|
||||
"atom/browser/ui/views/autofill_popup_view.cc",
|
||||
"atom/browser/ui/views/autofill_popup_view.h",
|
||||
@@ -622,6 +623,7 @@ filenames = {
|
||||
"atom/common/native_mate_converters/net_converter.h",
|
||||
"atom/common/native_mate_converters/network_converter.cc",
|
||||
"atom/common/native_mate_converters/network_converter.h",
|
||||
"atom/common/native_mate_converters/once_callback.h",
|
||||
"atom/common/native_mate_converters/string16_converter.h",
|
||||
"atom/common/native_mate_converters/ui_base_types_converter.h",
|
||||
"atom/common/native_mate_converters/v8_value_converter.cc",
|
||||
@@ -645,6 +647,10 @@ filenames = {
|
||||
"atom/common/platform_util_win.cc",
|
||||
"atom/common/promise_util.h",
|
||||
"atom/common/promise_util.cc",
|
||||
"atom/renderer/api/context_bridge/render_frame_context_bridge_store.cc",
|
||||
"atom/renderer/api/context_bridge/render_frame_context_bridge_store.h",
|
||||
"atom/renderer/api/atom_api_context_bridge.cc",
|
||||
"atom/renderer/api/atom_api_context_bridge.h",
|
||||
"atom/renderer/api/atom_api_renderer_ipc.cc",
|
||||
"atom/renderer/api/atom_api_spell_check_client.cc",
|
||||
"atom/renderer/api/atom_api_spell_check_client.h",
|
||||
|
||||
@@ -7,6 +7,7 @@ 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')
|
||||
@@ -72,7 +73,6 @@ const defaultPrintingSetting = {
|
||||
headerFooterEnabled: false,
|
||||
marginsType: 0,
|
||||
isFirstRequest: false,
|
||||
requestID: getNextId(),
|
||||
previewUIID: 0,
|
||||
previewModifiable: true,
|
||||
printToPDF: true,
|
||||
@@ -240,7 +240,10 @@ WebContents.prototype.takeHeapSnapshot = function (filePath) {
|
||||
|
||||
// Translate the options of printToPDF.
|
||||
WebContents.prototype.printToPDF = function (options) {
|
||||
const printingSetting = Object.assign({}, defaultPrintingSetting)
|
||||
const printingSetting = {
|
||||
...defaultPrintingSetting,
|
||||
requestID: getNextId()
|
||||
}
|
||||
if (options.landscape) {
|
||||
printingSetting.landscape = options.landscape
|
||||
}
|
||||
@@ -430,9 +433,7 @@ WebContents.prototype._init = function () {
|
||||
width: 800,
|
||||
height: 600
|
||||
}
|
||||
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
|
||||
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
|
||||
@@ -454,8 +455,7 @@ WebContents.prototype._init = function () {
|
||||
webContents
|
||||
}
|
||||
const referrer = { url: '', policy: 'default' }
|
||||
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN',
|
||||
event, url, referrer, frameName, disposition, options)
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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 { serialize } = require('@electron/internal/common/type-utils')
|
||||
const {
|
||||
syncMethods,
|
||||
asyncCallbackMethods,
|
||||
@@ -386,6 +387,12 @@ handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CALL', function (event, guestInstance
|
||||
return guest[method](...args)
|
||||
})
|
||||
|
||||
handleMessage('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', async function (event, guestInstanceId, args) {
|
||||
const guest = getGuestForWebContents(guestInstanceId, event.sender)
|
||||
|
||||
return serialize(await guest.capturePage(...args))
|
||||
})
|
||||
|
||||
// Returns WebContents from its guest id hosted in given webContents.
|
||||
const getGuestForWebContents = function (guestInstanceId, contents) {
|
||||
const guest = getGuest(guestInstanceId)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
'use strict'
|
||||
|
||||
const { BrowserWindow, webContents } = require('electron')
|
||||
const electron = require('electron')
|
||||
const { BrowserWindow } = electron
|
||||
const { isSameOrigin } = process.electronBinding('v8_util')
|
||||
const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal')
|
||||
const parseFeaturesString = require('@electron/internal/common/parse-features-string')
|
||||
@@ -73,8 +74,10 @@ const mergeBrowserWindowOptions = function (embedder, options) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
@@ -188,7 +191,7 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, fra
|
||||
const options = {}
|
||||
|
||||
const ints = ['x', 'y', 'width', 'height', 'minWidth', 'maxWidth', 'minHeight', 'maxHeight', 'zoomFactor']
|
||||
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'preload', 'javascript', 'contextIsolation', 'webviewTag']
|
||||
const webPreferences = ['zoomFactor', 'nodeIntegration', 'enableRemoteModule', 'javascript', 'contextIsolation', 'webviewTag']
|
||||
const disposition = 'new-window'
|
||||
|
||||
// Used to store additional features
|
||||
@@ -240,14 +243,11 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', (event, url, fra
|
||||
}
|
||||
|
||||
const referrer = { url: '', policy: 'default' }
|
||||
ipcMainInternal.emit('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', event,
|
||||
url, referrer, frameName, disposition, options, additionalFeatures)
|
||||
internalWindowOpen(event, url, referrer, frameName, disposition, options, additionalFeatures)
|
||||
})
|
||||
|
||||
// Routed window.open messages with fully parsed options
|
||||
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', function (event, url, referrer,
|
||||
frameName, disposition, options,
|
||||
additionalFeatures, postData) {
|
||||
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
|
||||
@@ -265,10 +265,11 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_INTERNAL_WINDOW_OPEN', functio
|
||||
} else {
|
||||
event.returnValue = createGuest(event.sender, url, referrer, frameName, options, postData)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSE', function (event, guestId) {
|
||||
const guestContents = webContents.fromId(guestId)
|
||||
// Access webContents via electron to prevent circular require.
|
||||
const guestContents = electron.webContents.fromId(guestId)
|
||||
if (guestContents == null) return
|
||||
|
||||
if (!canAccessWindow(event.sender, guestContents)) {
|
||||
@@ -286,7 +287,8 @@ const windowMethods = new Set([
|
||||
])
|
||||
|
||||
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_METHOD', function (event, guestId, method, ...args) {
|
||||
const guestContents = webContents.fromId(guestId)
|
||||
// Access webContents via electron to prevent circular require.
|
||||
const guestContents = electron.webContents.fromId(guestId)
|
||||
if (guestContents == null) {
|
||||
event.returnValue = null
|
||||
return
|
||||
@@ -311,12 +313,18 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_POSTMESSAGE', function
|
||||
targetOrigin = '*'
|
||||
}
|
||||
|
||||
const guestContents = webContents.fromId(guestId)
|
||||
// Access webContents via electron to prevent circular require.
|
||||
const guestContents = electron.webContents.fromId(guestId)
|
||||
if (guestContents == null) return
|
||||
|
||||
// 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.
|
||||
if (!isRelatedWindow(event.sender, guestContents)) {
|
||||
console.error(`Blocked ${event.sender.getURL()} from calling postMessage.`)
|
||||
return
|
||||
}
|
||||
|
||||
if (targetOrigin === '*' || isSameOrigin(guestContents.getURL(), targetOrigin)) {
|
||||
const sourceId = event.sender.id
|
||||
guestContents._sendInternal('ELECTRON_GUEST_WINDOW_POSTMESSAGE', sourceId, message, sourceOrigin)
|
||||
@@ -329,7 +337,8 @@ const webContentsMethods = new Set([
|
||||
])
|
||||
|
||||
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD', function (event, guestId, method, ...args) {
|
||||
const guestContents = webContents.fromId(guestId)
|
||||
// Access webContents via electron to prevent circular require.
|
||||
const guestContents = electron.webContents.fromId(guestId)
|
||||
if (guestContents == null) return
|
||||
|
||||
if (canAccessWindow(event.sender, guestContents) && webContentsMethods.has(method)) {
|
||||
@@ -345,7 +354,8 @@ const webContentsSyncMethods = new Set([
|
||||
])
|
||||
|
||||
ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', function (event, guestId, method, ...args) {
|
||||
const guestContents = webContents.fromId(guestId)
|
||||
// Access webContents via electron to prevent circular require.
|
||||
const guestContents = electron.webContents.fromId(guestId)
|
||||
if (guestContents == null) {
|
||||
event.returnValue = null
|
||||
return
|
||||
@@ -358,3 +368,5 @@ ipcMainInternal.on('ELECTRON_GUEST_WINDOW_MANAGER_WEB_CONTENTS_METHOD_SYNC', fun
|
||||
event.returnValue = null
|
||||
}
|
||||
})
|
||||
|
||||
exports.internalWindowOpen = internalWindowOpen
|
||||
|
||||
@@ -127,6 +127,10 @@ class ObjectsRegistry {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ 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 clipboardUtils = require('@electron/internal/common/clipboard-utils')
|
||||
const typeUtils = require('@electron/internal/common/type-utils')
|
||||
|
||||
const hasProp = {}.hasOwnProperty
|
||||
|
||||
@@ -231,7 +231,7 @@ const unwrapArgs = function (sender, frameId, contextId, args) {
|
||||
v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location)
|
||||
Object.defineProperty(callIntoRenderer, 'length', { value: meta.length })
|
||||
|
||||
v8Util.setRemoteCallbackFreer(callIntoRenderer, contextId, meta.id, sender)
|
||||
v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender)
|
||||
rendererFunctions.set(objectId, callIntoRenderer)
|
||||
return callIntoRenderer
|
||||
}
|
||||
@@ -440,7 +440,6 @@ handleRemoteCommand('ELECTRON_BROWSER_DEREFERENCE', function (event, contextId,
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_CONTEXT_RELEASE', (event, contextId) => {
|
||||
objectsRegistry.clear(event.sender, contextId)
|
||||
return null
|
||||
})
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_GUEST_WEB_CONTENTS', function (event, contextId, guestInstanceId) {
|
||||
@@ -494,7 +493,7 @@ ipcMainUtils.handle('ELECTRON_BROWSER_CLIPBOARD', function (event, method, ...ar
|
||||
throw new Error(`Invalid method: ${method}`)
|
||||
}
|
||||
|
||||
return clipboardUtils.serialize(electron.clipboard[method](...clipboardUtils.deserialize(args)))
|
||||
return typeUtils.serialize(electron.clipboard[method](...typeUtils.deserialize(args)))
|
||||
})
|
||||
|
||||
const readFile = util.promisify(fs.readFile)
|
||||
@@ -518,10 +517,14 @@ ipcMainUtils.handle('ELECTRON_BROWSER_SANDBOX_LOAD', async function (event) {
|
||||
event.sender._getPreloadPath()
|
||||
]
|
||||
|
||||
const webPreferences = event.sender.getLastWebPreferences() || {}
|
||||
|
||||
return {
|
||||
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,
|
||||
|
||||
@@ -4,13 +4,13 @@ const clipboard = process.electronBinding('clipboard')
|
||||
|
||||
if (process.type === 'renderer') {
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
|
||||
const clipboardUtils = require('@electron/internal/common/clipboard-utils')
|
||||
const typeUtils = require('@electron/internal/common/type-utils')
|
||||
|
||||
const makeRemoteMethod = function (method) {
|
||||
return (...args) => {
|
||||
args = clipboardUtils.serialize(args)
|
||||
args = typeUtils.serialize(args)
|
||||
const result = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_CLIPBOARD', method, ...args)
|
||||
return clipboardUtils.deserialize(result)
|
||||
return typeUtils.deserialize(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -40,8 +40,6 @@
|
||||
return newArchive
|
||||
}
|
||||
|
||||
const ASAR_EXTENSION = '.asar'
|
||||
|
||||
// Separate asar package's path from full path.
|
||||
const splitPath = archivePathOrBuffer => {
|
||||
// Shortcut for disabled asar.
|
||||
@@ -54,22 +52,7 @@
|
||||
}
|
||||
if (typeof archivePath !== 'string') return { isAsar: false }
|
||||
|
||||
if (archivePath.endsWith(ASAR_EXTENSION)) {
|
||||
return { isAsar: true, asarPath: archivePath, filePath: '' }
|
||||
}
|
||||
|
||||
archivePath = path.normalize(archivePath)
|
||||
const index = archivePath.lastIndexOf(`${ASAR_EXTENSION}${path.sep}`)
|
||||
if (index === -1) return { isAsar: false }
|
||||
|
||||
// E.g. for "//some/path/to/archive.asar/then/internal.file"...
|
||||
return {
|
||||
isAsar: true,
|
||||
// "//some/path/to/archive.asar"
|
||||
asarPath: archivePath.substr(0, index + ASAR_EXTENSION.length),
|
||||
// "then/internal.file" (with a path separator excluded)
|
||||
filePath: archivePath.substr(index + ASAR_EXTENSION.length + 1)
|
||||
}
|
||||
return asar.splitPath(path.normalize(archivePath))
|
||||
}
|
||||
|
||||
// Convert asar archive's Stats object to fs's Stats object.
|
||||
|
||||
@@ -54,7 +54,12 @@ class CrashReporter {
|
||||
}
|
||||
|
||||
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 () {
|
||||
|
||||
@@ -64,7 +64,6 @@ exports.asyncCallbackMethods = new Set([
|
||||
|
||||
exports.asyncPromiseMethods = new Set([
|
||||
'loadURL',
|
||||
'capturePage',
|
||||
'executeJavaScript',
|
||||
'printToPDF'
|
||||
])
|
||||
|
||||
20
lib/renderer/api/context-bridge.ts
Normal file
20
lib/renderer/api/context-bridge.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
const { hasSwitch } = process.electronBinding('command_line')
|
||||
const binding = process.electronBinding('context_bridge')
|
||||
|
||||
const contextIsolationEnabled = hasSwitch('context-isolation')
|
||||
|
||||
const checkContextIsolationEnabled = () => {
|
||||
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)
|
||||
},
|
||||
debugGC: () => binding._debugGCMaps({})
|
||||
}
|
||||
|
||||
if (!binding._debugGCMaps) delete contextBridge.debugGC
|
||||
|
||||
export default contextBridge
|
||||
@@ -7,24 +7,26 @@ const v8Util = process.electronBinding('v8_util')
|
||||
const ipcRenderer = v8Util.getHiddenValue(global, 'ipc')
|
||||
const internal = false
|
||||
|
||||
ipcRenderer.send = function (channel, ...args) {
|
||||
return ipc.send(internal, channel, args)
|
||||
}
|
||||
if (!ipcRenderer.send) {
|
||||
ipcRenderer.send = function (channel, ...args) {
|
||||
return ipc.send(internal, channel, args)
|
||||
}
|
||||
|
||||
ipcRenderer.sendSync = function (channel, ...args) {
|
||||
return ipc.sendSync(internal, channel, args)[0]
|
||||
}
|
||||
ipcRenderer.sendSync = function (channel, ...args) {
|
||||
return ipc.sendSync(internal, channel, args)[0]
|
||||
}
|
||||
|
||||
ipcRenderer.sendToHost = function (channel, ...args) {
|
||||
return ipc.sendToHost(channel, args)
|
||||
}
|
||||
ipcRenderer.sendToHost = function (channel, ...args) {
|
||||
return ipc.sendToHost(channel, args)
|
||||
}
|
||||
|
||||
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, false, webContentsId, channel, args)
|
||||
}
|
||||
ipcRenderer.sendTo = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, false, webContentsId, channel, args)
|
||||
}
|
||||
|
||||
ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, true, webContentsId, channel, args)
|
||||
ipcRenderer.sendToAll = function (webContentsId, channel, ...args) {
|
||||
return ipc.sendTo(internal, true, webContentsId, channel, args)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ipcRenderer
|
||||
|
||||
@@ -8,6 +8,7 @@ const enableRemoteModule = v8Util.getHiddenValue(global, 'enableRemoteModule')
|
||||
// Renderer side modules, please sort alphabetically.
|
||||
// A module is `enabled` if there is no explicit condition defined.
|
||||
module.exports = [
|
||||
{ name: 'contextBridge', file: 'context-bridge' },
|
||||
{ name: 'crashReporter', file: 'crash-reporter', enabled: true },
|
||||
{
|
||||
name: 'desktopCapturer',
|
||||
|
||||
@@ -21,7 +21,7 @@ const contextId = v8Util.getHiddenValue(global, 'contextId')
|
||||
// to guard that situation.
|
||||
process.on('exit', () => {
|
||||
const command = 'ELECTRON_BROWSER_CONTEXT_RELEASE'
|
||||
ipcRendererInternal.sendSync(command, contextId)
|
||||
ipcRendererInternal.send(command, contextId)
|
||||
})
|
||||
|
||||
// Convert the arguments object into an array of meta data.
|
||||
|
||||
@@ -5,18 +5,20 @@ const v8Util = process.electronBinding('v8_util')
|
||||
export const ipcRendererInternal: Electron.IpcRendererInternal = v8Util.getHiddenValue(global, 'ipc-internal')
|
||||
const internal = true
|
||||
|
||||
ipcRendererInternal.send = function (channel, ...args) {
|
||||
return binding.ipc.send(internal, channel, args)
|
||||
}
|
||||
if (!ipcRendererInternal.send) {
|
||||
ipcRendererInternal.send = function (channel, ...args) {
|
||||
return binding.ipc.send(internal, channel, args)
|
||||
}
|
||||
|
||||
ipcRendererInternal.sendSync = function (channel, ...args) {
|
||||
return binding.ipc.sendSync(internal, channel, args)[0]
|
||||
}
|
||||
ipcRendererInternal.sendSync = function (channel, ...args) {
|
||||
return binding.ipc.sendSync(internal, channel, args)[0]
|
||||
}
|
||||
|
||||
ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) {
|
||||
return binding.ipc.sendTo(internal, false, webContentsId, channel, args)
|
||||
}
|
||||
ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) {
|
||||
return binding.ipc.sendTo(internal, false, webContentsId, channel, args)
|
||||
}
|
||||
|
||||
ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) {
|
||||
return binding.ipc.sendTo(internal, true, webContentsId, channel, args)
|
||||
ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) {
|
||||
return binding.ipc.sendTo(internal, true, webContentsId, channel, args)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import { deprecate, remote, webFrame } from 'electron'
|
||||
import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'
|
||||
import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal'
|
||||
import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'
|
||||
import { deserialize } from '@electron/internal/common/type-utils'
|
||||
import {
|
||||
syncMethods,
|
||||
asyncCallbackMethods,
|
||||
@@ -275,6 +276,14 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem
|
||||
for (const method of asyncPromiseMethods) {
|
||||
(WebViewElement.prototype as Record<string, any>)[method] = deprecate.promisify(createPromiseHandler(method))
|
||||
}
|
||||
|
||||
const createCapturePageHandler = function () {
|
||||
return function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
|
||||
return ipcRendererUtils.invoke('ELECTRON_GUEST_VIEW_MANAGER_CAPTURE_PAGE', this.getWebContentsId(), args).then(image => deserialize(image))
|
||||
}
|
||||
}
|
||||
|
||||
WebViewElement.prototype.capturePage = deprecate.promisify(createCapturePageHandler())
|
||||
}
|
||||
|
||||
export const webViewImplModule = {
|
||||
|
||||
@@ -168,7 +168,7 @@ class BrowserWindowProxy {
|
||||
export const windowSetup = (
|
||||
guestInstanceId: number, openerId: number, isHiddenPage: boolean, usesNativeWindowOpen: boolean
|
||||
) => {
|
||||
if (guestInstanceId == null) {
|
||||
if (!process.sandboxed && guestInstanceId == null) {
|
||||
// Override default window.close.
|
||||
window.close = function () {
|
||||
ipcRendererInternal.sendSync('ELECTRON_BROWSER_WINDOW_CLOSE')
|
||||
@@ -188,10 +188,10 @@ export const windowSetup = (
|
||||
return null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (openerId != null) {
|
||||
window.opener = getOrCreateProxy(openerId)
|
||||
}
|
||||
if (openerId != null) {
|
||||
window.opener = getOrCreateProxy(openerId)
|
||||
}
|
||||
|
||||
// But we do not support prompt().
|
||||
@@ -199,42 +199,46 @@ export const windowSetup = (
|
||||
throw new Error('prompt() is and will not be supported.')
|
||||
}
|
||||
|
||||
ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
|
||||
_event, sourceId: number, message: any, sourceOrigin: string
|
||||
) {
|
||||
// Manually dispatch event instead of using postMessage because we also need to
|
||||
// set event.source.
|
||||
//
|
||||
// Why any? We can't construct a MessageEvent and we can't
|
||||
// use `as MessageEvent` because you're not supposed to override
|
||||
// data, origin, and source
|
||||
const event: any = document.createEvent('Event')
|
||||
event.initEvent('message', false, false)
|
||||
if (!usesNativeWindowOpen || openerId != null) {
|
||||
ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function (
|
||||
_event, sourceId: number, message: any, sourceOrigin: string
|
||||
) {
|
||||
// Manually dispatch event instead of using postMessage because we also need to
|
||||
// set event.source.
|
||||
//
|
||||
// Why any? We can't construct a MessageEvent and we can't
|
||||
// use `as MessageEvent` because you're not supposed to override
|
||||
// data, origin, and source
|
||||
const event: any = document.createEvent('Event')
|
||||
event.initEvent('message', false, false)
|
||||
|
||||
event.data = message
|
||||
event.origin = sourceOrigin
|
||||
event.source = getOrCreateProxy(sourceId)
|
||||
event.data = message
|
||||
event.origin = sourceOrigin
|
||||
event.source = getOrCreateProxy(sourceId)
|
||||
|
||||
window.dispatchEvent(event as MessageEvent)
|
||||
})
|
||||
|
||||
window.history.back = function () {
|
||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK')
|
||||
window.dispatchEvent(event as MessageEvent)
|
||||
})
|
||||
}
|
||||
|
||||
window.history.forward = function () {
|
||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD')
|
||||
}
|
||||
|
||||
window.history.go = function (offset: number) {
|
||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset)
|
||||
}
|
||||
|
||||
defineProperty(window.history, 'length', {
|
||||
get: function () {
|
||||
return ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH')
|
||||
if (!process.sandboxed) {
|
||||
window.history.back = function () {
|
||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK')
|
||||
}
|
||||
})
|
||||
|
||||
window.history.forward = function () {
|
||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD')
|
||||
}
|
||||
|
||||
window.history.go = function (offset: number) {
|
||||
ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset)
|
||||
}
|
||||
|
||||
defineProperty(window.history, 'length', {
|
||||
get: function () {
|
||||
return ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (guestInstanceId != null) {
|
||||
// Webview `document.visibilityState` tracks window visibility (and ignores
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
const features = process.electronBinding('features')
|
||||
|
||||
module.exports = [
|
||||
{
|
||||
name: 'contextBridge',
|
||||
load: () => require('@electron/internal/renderer/api/context-bridge')
|
||||
},
|
||||
{
|
||||
name: 'crashReporter',
|
||||
load: () => require('@electron/internal/renderer/api/crash-reporter')
|
||||
|
||||
@@ -30,7 +30,12 @@ const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-rendere
|
||||
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils')
|
||||
|
||||
const {
|
||||
preloadScripts, isRemoteModuleEnabled, isWebViewTagEnabled, process: processProps
|
||||
preloadScripts,
|
||||
isRemoteModuleEnabled,
|
||||
isWebViewTagEnabled,
|
||||
guestInstanceId,
|
||||
openerId,
|
||||
process: processProps
|
||||
} = ipcRendererUtils.invokeSync('ELECTRON_BROWSER_SANDBOX_LOAD')
|
||||
|
||||
process.isRemoteModuleEnabled = isRemoteModuleEnabled
|
||||
@@ -107,6 +112,11 @@ function preloadRequire (module) {
|
||||
const { hasSwitch } = process.electronBinding('command_line')
|
||||
|
||||
const contextIsolation = hasSwitch('context-isolation')
|
||||
const isHiddenPage = hasSwitch('hidden-page')
|
||||
const usesNativeWindowOpen = true
|
||||
|
||||
// The arguments to be passed to isolated world.
|
||||
const isolatedWorldArgs = { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen }
|
||||
|
||||
switch (window.location.protocol) {
|
||||
case 'devtools:': {
|
||||
@@ -123,19 +133,26 @@ switch (window.location.protocol) {
|
||||
break
|
||||
}
|
||||
default: {
|
||||
// Override default web functions.
|
||||
const { windowSetup } = require('@electron/internal/renderer/window-setup')
|
||||
windowSetup(guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
|
||||
|
||||
// Inject content scripts.
|
||||
require('@electron/internal/renderer/content-scripts-injector')(binding.getRenderProcessPreferences)
|
||||
}
|
||||
}
|
||||
|
||||
const guestInstanceId = binding.guestInstanceId && parseInt(binding.guestInstanceId)
|
||||
|
||||
// Load webview tag implementation.
|
||||
if (process.isMainFrame) {
|
||||
const { webViewInit } = require('@electron/internal/renderer/web-view/web-view-init')
|
||||
webViewInit(contextIsolation, isWebViewTagEnabled, guestInstanceId)
|
||||
}
|
||||
|
||||
// Pass the arguments to isolatedWorld.
|
||||
if (contextIsolation) {
|
||||
v8Util.setHiddenValue(global, 'isolated-world-args', isolatedWorldArgs)
|
||||
}
|
||||
|
||||
const errorUtils = require('@electron/internal/common/error-utils')
|
||||
|
||||
// Wrap the script into a function executed in global scope. It won't have
|
||||
|
||||
@@ -25,7 +25,7 @@ class Arguments {
|
||||
|
||||
template<typename T>
|
||||
bool GetHolder(T* out) {
|
||||
return ConvertFromV8(isolate_, info_->Holder(), out);
|
||||
return mate::ConvertFromV8(isolate_, info_->Holder(), out);
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
@@ -40,7 +40,7 @@ class Arguments {
|
||||
return false;
|
||||
}
|
||||
v8::Local<v8::Value> val = (*info_)[next_];
|
||||
bool success = ConvertFromV8(isolate_, val, out);
|
||||
bool success = mate::ConvertFromV8(isolate_, val, out);
|
||||
if (success)
|
||||
next_++;
|
||||
return success;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <map>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
|
||||
#include "base/strings/string_piece.h"
|
||||
@@ -317,6 +318,12 @@ v8::Local<v8::Value> ConvertToV8(v8::Isolate* isolate, const T& input) {
|
||||
return Converter<T>::ToV8(isolate, input);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
v8::Local<v8::Value> ConvertToV8(v8::Isolate* isolate, T&& input) {
|
||||
return Converter<typename std::remove_reference<T>::type>::ToV8(
|
||||
isolate, std::move(input));
|
||||
}
|
||||
|
||||
inline v8::Local<v8::Value> ConvertToV8(v8::Isolate* isolate,
|
||||
const char* input) {
|
||||
return Converter<const char*>::ToV8(isolate, input);
|
||||
|
||||
@@ -40,6 +40,12 @@ class Dictionary {
|
||||
|
||||
static Dictionary CreateEmpty(v8::Isolate* isolate);
|
||||
|
||||
bool Has(base::StringPiece key) const {
|
||||
v8::Local<v8::Context> context = isolate_->GetCurrentContext();
|
||||
v8::Local<v8::String> v8_key = StringToV8(isolate_, key);
|
||||
return internal::IsTrue(GetHandle()->Has(context, v8_key));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool Get(const base::StringPiece& key, T* out) const {
|
||||
// Check for existence before getting, otherwise this method will always
|
||||
@@ -102,6 +108,17 @@ class Dictionary {
|
||||
return !result.IsNothing() && result.FromJust();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool SetReadOnlyNonConfigurable(base::StringPiece key, T val) {
|
||||
v8::Local<v8::Value> v8_value;
|
||||
if (!TryConvertToV8(isolate_, val, &v8_value))
|
||||
return false;
|
||||
v8::Maybe<bool> result = GetHandle()->DefineOwnProperty(
|
||||
isolate_->GetCurrentContext(), StringToV8(isolate_, key), v8_value,
|
||||
static_cast<v8::PropertyAttribute>(v8::ReadOnly | v8::DontDelete));
|
||||
return !result.IsNothing() && result.FromJust();
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
bool SetMethod(const base::StringPiece& key, const T& callback) {
|
||||
return GetHandle()
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef NATIVE_MATE_FUNCTION_TEMPLATE_H_
|
||||
#define NATIVE_MATE_FUNCTION_TEMPLATE_H_
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/callback.h"
|
||||
#include "base/logging.h"
|
||||
#include "native_mate/arguments.h"
|
||||
@@ -193,7 +195,7 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
|
||||
void DispatchToCallback(base::Callback<ReturnType(ArgTypes...)> callback) {
|
||||
v8::MicrotasksScope script_scope(
|
||||
args_->isolate(), v8::MicrotasksScope::kRunMicrotasks);
|
||||
args_->Return(callback.Run(ArgumentHolder<indices, ArgTypes>::value...));
|
||||
args_->Return(callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...));
|
||||
}
|
||||
|
||||
// In C++, you can declare the function foo(void), but you can't pass a void
|
||||
@@ -202,7 +204,7 @@ class Invoker<IndicesHolder<indices...>, ArgTypes...>
|
||||
void DispatchToCallback(base::Callback<void(ArgTypes...)> callback) {
|
||||
v8::MicrotasksScope script_scope(
|
||||
args_->isolate(), v8::MicrotasksScope::kRunMicrotasks);
|
||||
callback.Run(ArgumentHolder<indices, ArgTypes>::value...);
|
||||
callback.Run(std::move(ArgumentHolder<indices, ArgTypes>::value)...);
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
@@ -15,6 +15,10 @@ try {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
if (process.env.ELECTRON_SKIP_BINARY_DOWNLOAD) {
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
var platformPath = getPlatformPath()
|
||||
|
||||
var electronPath = process.env.ELECTRON_OVERRIDE_DIST_PATH || path.join(__dirname, 'dist', platformPath)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "6.0.8",
|
||||
"version": "6.1.11",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user