mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
41 Commits
v9.0.0-nig
...
v9.0.0-nig
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
469fc0ea36 | ||
|
|
03824ef53f | ||
|
|
74f698d7bc | ||
|
|
ba77489cb6 | ||
|
|
2129751966 | ||
|
|
0618505722 | ||
|
|
01f5e9c5c4 | ||
|
|
f09cb114e4 | ||
|
|
06e349d074 | ||
|
|
96c4c48268 | ||
|
|
5f365858c9 | ||
|
|
d25256dcf5 | ||
|
|
4149d76890 | ||
|
|
149aaeba94 | ||
|
|
8a9c7c484b | ||
|
|
ca61d2fae7 | ||
|
|
2e25999c52 | ||
|
|
d84ba30541 | ||
|
|
f26b7931eb | ||
|
|
27c764c66c | ||
|
|
ab695fb2f7 | ||
|
|
78ae5d410e | ||
|
|
d20273f95b | ||
|
|
34452ee69e | ||
|
|
a7c2f79a94 | ||
|
|
745363959a | ||
|
|
033d309874 | ||
|
|
135a64955c | ||
|
|
0cadf2846b | ||
|
|
07b94ff578 | ||
|
|
1da9959f57 | ||
|
|
3f2cb91a35 | ||
|
|
92ff39c168 | ||
|
|
41f1569c46 | ||
|
|
0111f6216c | ||
|
|
4bc85f777f | ||
|
|
af3bee742f | ||
|
|
99cafae1ec | ||
|
|
50f2d2b5ab | ||
|
|
ea23f18e94 | ||
|
|
145dd33da1 |
@@ -61,7 +61,7 @@ machine-linux-medium: &machine-linux-medium
|
||||
|
||||
machine-linux-2xlarge: &machine-linux-2xlarge
|
||||
<<: *docker-image
|
||||
resource_class: 2xlarge
|
||||
resource_class: 2xlarge+
|
||||
|
||||
machine-mac: &machine-mac
|
||||
macos:
|
||||
@@ -155,6 +155,10 @@ env-32bit-release: &env-32bit-release
|
||||
# Set symbol level to 1 for 32 bit releases because of https://crbug.com/648948
|
||||
GN_BUILDFLAG_ARGS: 'symbol_level = 1'
|
||||
|
||||
env-macos-build: &env-macos-build
|
||||
# Disable pre-compiled headers to reduce out size, only useful for rebuilds
|
||||
GN_BUILDFLAG_ARGS: 'enable_precompiled_headers = false'
|
||||
|
||||
# Individual (shared) steps.
|
||||
step-maybe-notify-slack-failure: &step-maybe-notify-slack-failure
|
||||
run:
|
||||
@@ -256,10 +260,11 @@ step-get-more-space-on-mac: &step-get-more-space-on-mac
|
||||
|
||||
step-delete-git-directories: &step-delete-git-directories
|
||||
run:
|
||||
name: Delete src/.git directory on MacOS to free space
|
||||
name: Delete all .git directories under src on MacOS to free space
|
||||
command: |
|
||||
if [ "`uname`" == "Darwin" ]; then
|
||||
sudo rm -rf src/.git
|
||||
cd src
|
||||
( find . -type d -name ".git" ) | xargs rm -rf
|
||||
fi
|
||||
|
||||
# On macOS the yarn install command during gclient sync was run on a linux
|
||||
@@ -333,6 +338,12 @@ step-electron-build: &step-electron-build
|
||||
name: Electron build
|
||||
no_output_timeout: 30m
|
||||
command: |
|
||||
# On arm platforms we generate a cross-arch ffmpeg that ninja does not seem
|
||||
# to realize is not correct / should be rebuilt. We delete it here so it is
|
||||
# rebuilt
|
||||
if [ "$TRIGGER_ARM_TEST" == "true" ]; then
|
||||
rm -f src/out/Default/libffmpeg.so
|
||||
fi
|
||||
cd src
|
||||
ninja -C out/Default electron -j $NUMBER_OF_NINJA_PROCESSES
|
||||
|
||||
@@ -348,9 +359,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
|
||||
@@ -524,6 +544,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
|
||||
@@ -531,6 +552,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
|
||||
if [ "$SKIP_DIST_ZIP" != "1" ]; then
|
||||
@@ -648,7 +670,7 @@ step-ninja-report: &step-ninja-report
|
||||
step-generate-deps-hash: &step-generate-deps-hash
|
||||
run:
|
||||
name: Generate DEPS Hash
|
||||
command: node src/electron/script/generate-deps-hash.js
|
||||
command: node src/electron/script/generate-deps-hash.js && cat src/electron/.depshash-target
|
||||
|
||||
step-touch-sync-done: &step-touch-sync-done
|
||||
run:
|
||||
@@ -660,10 +682,8 @@ step-touch-sync-done: &step-touch-sync-done
|
||||
# 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" }}
|
||||
- v7-src-cache-{{ checksum "src/electron/.depshash" }}
|
||||
name: Restoring src cache
|
||||
|
||||
# Restore exact or closest git cache based on the hash of DEPS and .circle-sync-done
|
||||
@@ -674,10 +694,18 @@ step-maybe-restore-git-cache: &step-maybe-restore-git-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" }}
|
||||
- v2-gclient-cache-{{ checksum "src/electron/.circle-sync-done" }}-{{ checksum "src/electron/DEPS" }}
|
||||
- v2-gclient-cache-{{ checksum "src/electron/.circle-sync-done" }}
|
||||
name: Conditionally restoring git cache
|
||||
|
||||
step-restore-out-cache: &step-restore-out-cache
|
||||
restore_cache:
|
||||
paths:
|
||||
- ./src/out/Default
|
||||
keys:
|
||||
- v7-out-cache-{{ checksum "src/electron/.depshash" }}-{{ checksum "src/electron/.depshash-target" }}
|
||||
name: Restoring out cache
|
||||
|
||||
step-set-git-cache-path: &step-set-git-cache-path
|
||||
run:
|
||||
name: Set GIT_CACHE_PATH to make gclient to use the cache
|
||||
@@ -692,9 +720,16 @@ 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" }}
|
||||
key: v2-gclient-cache-{{ checksum "src/electron/.circle-sync-done" }}-{{ checksum "src/electron/DEPS" }}
|
||||
name: Persisting git cache
|
||||
|
||||
step-save-out-cache: &step-save-out-cache
|
||||
save_cache:
|
||||
paths:
|
||||
- ./src/out/Default
|
||||
key: v7-out-cache-{{ checksum "src/electron/.depshash" }}-{{ checksum "src/electron/.depshash-target" }}
|
||||
name: Persisting out cache
|
||||
|
||||
step-run-electron-only-hooks: &step-run-electron-only-hooks
|
||||
run:
|
||||
name: Run Electron Only Hooks
|
||||
@@ -703,7 +738,7 @@ step-run-electron-only-hooks: &step-run-electron-only-hooks
|
||||
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
|
||||
command: (cd src/electron && git checkout .) && node src/electron/script/generate-deps-hash.js && cat src/electron/.depshash-target
|
||||
|
||||
# Mark the sync as done for future cache saving
|
||||
step-mark-sync-done: &step-mark-sync-done
|
||||
@@ -726,8 +761,8 @@ step-minimize-workspace-size-from-checkout: &step-minimize-workspace-size-from-c
|
||||
step-save-src-cache: &step-save-src-cache
|
||||
save_cache:
|
||||
paths:
|
||||
- ./src
|
||||
key: v5-src-cache-{{ arch }}-{{ checksum "src/electron/.depshash" }}
|
||||
- /portal
|
||||
key: v7-src-cache-{{ checksum "/portal/src/electron/.depshash" }}
|
||||
name: Persisting src cache
|
||||
|
||||
# Check for doc only change
|
||||
@@ -820,40 +855,6 @@ steps-lint: &steps-lint
|
||||
node script/yarn install --frozen-lockfile
|
||||
node script/yarn lint
|
||||
|
||||
steps-checkout-fast: &steps-checkout-fast
|
||||
steps:
|
||||
- *step-checkout-electron
|
||||
- *step-check-for-doc-only-change
|
||||
- *step-persist-doc-only-change
|
||||
- *step-maybe-early-exit-doc-only-change
|
||||
- *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
|
||||
# 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
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- depot_tools
|
||||
- src
|
||||
|
||||
steps-checkout-and-save-cache: &steps-checkout-and-save-cache
|
||||
steps:
|
||||
- *step-checkout-electron
|
||||
@@ -868,7 +869,7 @@ steps-checkout-and-save-cache: &steps-checkout-and-save-cache
|
||||
|
||||
- *step-generate-deps-hash
|
||||
- *step-touch-sync-done
|
||||
- *step-maybe-restore-src-cache
|
||||
- maybe-restore-portaled-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
|
||||
@@ -883,6 +884,13 @@ steps-checkout-and-save-cache: &steps-checkout-and-save-cache
|
||||
- *step-generate-deps-hash-cleanly
|
||||
- *step-mark-sync-done
|
||||
- *step-minimize-workspace-size-from-checkout
|
||||
- *step-delete-git-directories
|
||||
- run:
|
||||
name: Move src folder to the cross-OS portal
|
||||
command: |
|
||||
sudo mkdir -p /portal
|
||||
sudo chown -R $(id -u):$(id -g) /portal
|
||||
mv ./src /portal
|
||||
- *step-save-src-cache
|
||||
- *step-save-brew-cache
|
||||
|
||||
@@ -907,8 +915,8 @@ steps-electron-build: &steps-electron-build
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-install-npm-deps-on-mac
|
||||
- *step-fix-sync-on-mac
|
||||
- *step-gn-gen-default
|
||||
- *step-delete-git-directories
|
||||
- *step-gn-gen-default
|
||||
|
||||
# Electron app
|
||||
- *step-electron-build
|
||||
@@ -954,89 +962,6 @@ steps-electron-build: &steps-electron-build
|
||||
|
||||
- *step-maybe-notify-slack-failure
|
||||
|
||||
steps-electron-build-with-inline-checkout-for-tests: &steps-electron-build-with-inline-checkout-for-tests
|
||||
steps:
|
||||
# Checkout - Copied ffrom steps-checkout
|
||||
- *step-checkout-electron
|
||||
- *step-check-for-doc-only-change
|
||||
- *step-persist-doc-only-change
|
||||
- *step-maybe-early-exit-doc-only-change
|
||||
- *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
|
||||
# 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-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-gn-gen-default
|
||||
- *step-delete-git-directories
|
||||
|
||||
# Electron app
|
||||
- *step-electron-build
|
||||
- *step-ninja-summary
|
||||
- *step-ninja-report
|
||||
- *step-maybe-electron-dist-strip
|
||||
- *step-electron-dist-build
|
||||
- *step-electron-dist-store
|
||||
|
||||
# Native test targets
|
||||
- *step-native-unittests-build
|
||||
- *step-native-unittests-store
|
||||
|
||||
# Node.js headers
|
||||
- *step-nodejs-headers-build
|
||||
- *step-nodejs-headers-store
|
||||
|
||||
- *step-show-sccache-stats
|
||||
|
||||
# mksnapshot
|
||||
- *step-mksnapshot-build
|
||||
- *step-mksnapshot-store
|
||||
- *step-maybe-cross-arch-snapshot
|
||||
- *step-maybe-cross-arch-snapshot-store
|
||||
|
||||
# ffmpeg
|
||||
- *step-ffmpeg-gn-gen
|
||||
- *step-ffmpeg-build
|
||||
- *step-ffmpeg-store
|
||||
|
||||
# hunspell
|
||||
- *step-hunspell-build
|
||||
- *step-hunspell-store
|
||||
|
||||
# Save all data needed for a further tests run.
|
||||
- *step-persist-data-for-tests
|
||||
|
||||
- *step-maybe-generate-breakpad-symbols
|
||||
- *step-maybe-zip-symbols
|
||||
- *step-symbols-store
|
||||
|
||||
# Trigger tests on arm hardware if needed
|
||||
- *step-maybe-trigger-arm-test
|
||||
|
||||
- *step-maybe-notify-slack-failure
|
||||
|
||||
steps-electron-ts-compile-for-doc-change: &steps-electron-ts-compile-for-doc-change
|
||||
steps:
|
||||
# Checkout - Copied ffrom steps-checkout
|
||||
@@ -1050,7 +975,7 @@ steps-electron-ts-compile-for-doc-change: &steps-electron-ts-compile-for-doc-cha
|
||||
- *step-install-gnutar-on-mac
|
||||
- *step-generate-deps-hash
|
||||
- *step-touch-sync-done
|
||||
- *step-maybe-restore-src-cache
|
||||
- maybe-restore-portaled-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
|
||||
@@ -1085,8 +1010,9 @@ steps-electron-build-for-publish: &steps-electron-build-for-publish
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-gclient-sync
|
||||
- *step-setup-env-for-build
|
||||
- *step-gn-gen-default
|
||||
- *step-delete-git-directories
|
||||
- *step-minimize-workspace-size-from-checkout
|
||||
- *step-gn-gen-default
|
||||
|
||||
# Electron app
|
||||
- *step-electron-build
|
||||
@@ -1275,6 +1201,193 @@ steps-test-node: &steps-test-node
|
||||
chromium-upgrade-branches: &chromium-upgrade-branches
|
||||
/chromium\-upgrade\/[0-9]+/
|
||||
|
||||
# Command Aliases
|
||||
commands:
|
||||
maybe-restore-portaled-src-cache:
|
||||
steps:
|
||||
- run:
|
||||
name: Prepare for cross-OS sync restore
|
||||
command: |
|
||||
sudo mkdir -p /portal
|
||||
sudo chown -R $(id -u):$(id -g) /portal
|
||||
- *step-maybe-restore-src-cache
|
||||
- run:
|
||||
name: Fix the src cache restore point on macOS
|
||||
command: |
|
||||
if [ -d "/portal/src" ]; then
|
||||
echo Relocating Cache
|
||||
rm -rf src
|
||||
mv /portal/src ./
|
||||
fi
|
||||
checkout-from-cache:
|
||||
steps:
|
||||
- *step-checkout-electron
|
||||
- *step-maybe-early-exit-doc-only-change
|
||||
- *step-depot-tools-get
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-generate-deps-hash
|
||||
- maybe-restore-portaled-src-cache
|
||||
- run:
|
||||
name: Ensure src checkout worked
|
||||
command: |
|
||||
if [ ! -d "src/third_party/blink" ]; then
|
||||
echo src cache was not restored for some reason, idk what happened here...
|
||||
exit 1
|
||||
fi
|
||||
- run:
|
||||
name: Wipe Electron
|
||||
command: rm -rf src/electron
|
||||
- *step-checkout-electron
|
||||
- *step-run-electron-only-hooks
|
||||
- *step-generate-deps-hash-cleanly
|
||||
electron-build:
|
||||
parameters:
|
||||
attach:
|
||||
type: boolean
|
||||
default: false
|
||||
persist:
|
||||
type: boolean
|
||||
default: true
|
||||
persist-checkout:
|
||||
type: boolean
|
||||
default: false
|
||||
checkout:
|
||||
type: boolean
|
||||
default: true
|
||||
checkout-and-assume-cache:
|
||||
type: boolean
|
||||
default: false
|
||||
build:
|
||||
type: boolean
|
||||
default: true
|
||||
steps:
|
||||
- when:
|
||||
condition: << parameters.attach >>
|
||||
steps:
|
||||
- attach_workspace:
|
||||
at: .
|
||||
- *step-restore-brew-cache
|
||||
- *step-install-gnutar-on-mac
|
||||
- when:
|
||||
condition: << parameters.checkout-and-assume-cache >>
|
||||
steps:
|
||||
- checkout-from-cache
|
||||
- when:
|
||||
condition: << parameters.checkout >>
|
||||
steps:
|
||||
# Checkout - Copied ffrom steps-checkout
|
||||
- *step-checkout-electron
|
||||
- *step-check-for-doc-only-change
|
||||
- *step-persist-doc-only-change
|
||||
- *step-maybe-early-exit-doc-only-change
|
||||
- *step-depot-tools-get
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-generate-deps-hash
|
||||
- *step-touch-sync-done
|
||||
- maybe-restore-portaled-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
|
||||
# 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
|
||||
- when:
|
||||
condition: << parameters.persist-checkout >>
|
||||
steps:
|
||||
- persist_to_workspace:
|
||||
root: .
|
||||
paths:
|
||||
- depot_tools
|
||||
- src
|
||||
|
||||
- when:
|
||||
condition: << parameters.build >>
|
||||
steps:
|
||||
- *step-depot-tools-add-to-path
|
||||
- *step-setup-env-for-build
|
||||
- *step-get-more-space-on-mac
|
||||
- *step-fix-sync-on-mac
|
||||
- *step-delete-git-directories
|
||||
- *step-gn-gen-default
|
||||
|
||||
# Electron app
|
||||
- *step-restore-out-cache
|
||||
- *step-gn-gen-default
|
||||
- *step-electron-build
|
||||
- *step-ninja-summary
|
||||
- *step-ninja-report
|
||||
- *step-maybe-electron-dist-strip
|
||||
- *step-electron-dist-build
|
||||
- *step-electron-dist-store
|
||||
|
||||
# Native test targets
|
||||
- *step-native-unittests-build
|
||||
- *step-native-unittests-store
|
||||
|
||||
# Node.js headers
|
||||
- *step-nodejs-headers-build
|
||||
- *step-nodejs-headers-store
|
||||
|
||||
- *step-show-sccache-stats
|
||||
|
||||
# mksnapshot
|
||||
- *step-mksnapshot-build
|
||||
- *step-mksnapshot-store
|
||||
- *step-maybe-cross-arch-snapshot
|
||||
- *step-maybe-cross-arch-snapshot-store
|
||||
|
||||
# ffmpeg
|
||||
- *step-ffmpeg-gn-gen
|
||||
- *step-ffmpeg-build
|
||||
- *step-ffmpeg-store
|
||||
|
||||
# hunspell
|
||||
- *step-hunspell-build
|
||||
- *step-hunspell-store
|
||||
|
||||
# Save all data needed for a further tests run.
|
||||
- when:
|
||||
condition: << parameters.persist >>
|
||||
steps:
|
||||
- *step-persist-data-for-tests
|
||||
|
||||
- when:
|
||||
condition: << parameters.build >>
|
||||
steps:
|
||||
- *step-maybe-generate-breakpad-symbols
|
||||
- *step-maybe-zip-symbols
|
||||
- *step-symbols-store
|
||||
|
||||
- when:
|
||||
condition: << parameters.build >>
|
||||
steps:
|
||||
- run:
|
||||
name: Remove the big things on macOS, this seems to be better on average
|
||||
command: |
|
||||
if [ "`uname`" == "Darwin" ]; then
|
||||
mkdir -p src/out/Default
|
||||
cd src/out/Default
|
||||
find . -type f -size +50M -delete
|
||||
mkdir -p gen/electron
|
||||
cd gen/electron
|
||||
# These files do not seem to like being in a cache, let us remove them
|
||||
find . -type f -name '*_pkg_info' -delete
|
||||
fi
|
||||
- *step-save-out-cache
|
||||
|
||||
# Trigger tests on arm hardware if needed
|
||||
- *step-maybe-trigger-arm-test
|
||||
|
||||
- *step-maybe-notify-slack-failure
|
||||
|
||||
# List of all jobs.
|
||||
jobs:
|
||||
# Layer 0: Lint. Standalone.
|
||||
@@ -1297,7 +1410,12 @@ jobs:
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-checkout-fast
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
build: false
|
||||
checkout: true
|
||||
persist-checkout: true
|
||||
|
||||
linux-checkout-and-save-cache:
|
||||
<<: *machine-linux-2xlarge
|
||||
@@ -1311,26 +1429,45 @@ jobs:
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_pyyaml=True'
|
||||
<<: *steps-checkout-fast
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
build: false
|
||||
checkout: true
|
||||
persist-checkout: true
|
||||
|
||||
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-fast
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
build: false
|
||||
checkout: true
|
||||
persist-checkout: true
|
||||
|
||||
mac-checkout-fast:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
<<: *env-testing-build
|
||||
<<: *env-macos-build
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
<<: *steps-checkout-fast
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
build: false
|
||||
checkout: true
|
||||
persist-checkout: true
|
||||
|
||||
mac-checkout-and-save-cache:
|
||||
<<: *machine-linux-2xlarge
|
||||
environment:
|
||||
<<: *env-linux-2xlarge
|
||||
<<: *env-testing-build
|
||||
<<: *env-macos-build
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
<<: *steps-checkout-and-save-cache
|
||||
|
||||
@@ -1343,7 +1480,10 @@ jobs:
|
||||
<<: *env-enable-sccache
|
||||
<<: *env-ninja-status
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-electron-build-with-inline-checkout-for-tests
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: true
|
||||
checkout: true
|
||||
|
||||
linux-x64-testing-no-run-as-node:
|
||||
<<: *machine-linux-2xlarge
|
||||
@@ -1354,7 +1494,10 @@ jobs:
|
||||
<<: *env-ninja-status
|
||||
<<: *env-disable-run-as-node
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-electron-build-with-inline-checkout-for-tests
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
checkout: true
|
||||
|
||||
linux-x64-testing-gn-check:
|
||||
<<: *machine-linux-medium
|
||||
@@ -1401,7 +1544,10 @@ jobs:
|
||||
<<: *env-enable-sccache
|
||||
<<: *env-ninja-status
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-electron-build-with-inline-checkout-for-tests
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: true
|
||||
checkout: true
|
||||
|
||||
linux-ia32-chromedriver:
|
||||
<<: *machine-linux-medium
|
||||
@@ -1446,7 +1592,10 @@ jobs:
|
||||
<<: *env-ninja-status
|
||||
TRIGGER_ARM_TEST: true
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-electron-build-with-inline-checkout-for-tests
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
checkout: true
|
||||
|
||||
linux-arm-chromedriver:
|
||||
<<: *machine-linux-medium
|
||||
@@ -1491,7 +1640,10 @@ jobs:
|
||||
<<: *env-ninja-status
|
||||
TRIGGER_ARM_TEST: true
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
<<: *steps-electron-build-with-inline-checkout-for-tests
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: false
|
||||
checkout: true
|
||||
|
||||
linux-arm64-testing-gn-check:
|
||||
<<: *machine-linux-medium
|
||||
@@ -1540,7 +1692,14 @@ jobs:
|
||||
<<: *env-testing-build
|
||||
<<: *env-enable-sccache
|
||||
<<: *env-ninja-status
|
||||
<<: *steps-electron-build
|
||||
<<: *env-macos-build
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: true
|
||||
checkout: false
|
||||
checkout-and-assume-cache: true
|
||||
attach: false
|
||||
|
||||
osx-testing-gn-check:
|
||||
<<: *machine-mac
|
||||
@@ -1585,7 +1744,14 @@ jobs:
|
||||
<<: *env-testing-build
|
||||
<<: *env-enable-sccache
|
||||
<<: *env-ninja-status
|
||||
<<: *steps-electron-build
|
||||
<<: *env-macos-build
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
steps:
|
||||
- electron-build:
|
||||
persist: true
|
||||
checkout: false
|
||||
checkout-and-assume-cache: true
|
||||
attach: false
|
||||
|
||||
mas-testing-gn-check:
|
||||
<<: *machine-mac
|
||||
@@ -1976,7 +2142,7 @@ workflows:
|
||||
|
||||
- osx-testing:
|
||||
requires:
|
||||
- mac-checkout-fast
|
||||
- mac-checkout-and-save-cache
|
||||
|
||||
- osx-testing-gn-check:
|
||||
requires:
|
||||
@@ -1988,7 +2154,7 @@ workflows:
|
||||
|
||||
- mas-testing:
|
||||
requires:
|
||||
- mac-checkout-fast
|
||||
- mac-checkout-and-save-cache
|
||||
|
||||
- mas-testing-gn-check:
|
||||
requires:
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -65,3 +65,4 @@ ts-gen
|
||||
|
||||
# Used to accelerate CI builds
|
||||
.depshash
|
||||
.depshash-target
|
||||
21
BUILD.gn
21
BUILD.gn
@@ -354,6 +354,7 @@ source_set("electron_lib") {
|
||||
":resources",
|
||||
"buildflags",
|
||||
"chromium_src:chrome",
|
||||
"chromium_src:chrome_spellchecker",
|
||||
"native_mate",
|
||||
"shell/common/api:mojo",
|
||||
"//base:base_static",
|
||||
@@ -482,10 +483,6 @@ source_set("electron_lib") {
|
||||
]
|
||||
}
|
||||
|
||||
if (enable_builtin_spellchecker) {
|
||||
deps += [ "chromium_src:chrome_spellchecker" ]
|
||||
}
|
||||
|
||||
if (is_mac) {
|
||||
deps += [
|
||||
"//components/remote_cocoa/app_shim",
|
||||
@@ -1374,12 +1371,18 @@ dist_zip("electron_chromedriver_zip") {
|
||||
]
|
||||
}
|
||||
|
||||
mksnapshot_deps = [
|
||||
":licenses",
|
||||
"//tools/v8_context_snapshot:v8_context_snapshot_generator",
|
||||
"//v8:mksnapshot($v8_snapshot_toolchain)",
|
||||
]
|
||||
|
||||
group("electron_mksnapshot") {
|
||||
public_deps = mksnapshot_deps
|
||||
}
|
||||
|
||||
dist_zip("electron_mksnapshot_zip") {
|
||||
data_deps = [
|
||||
"//v8:mksnapshot($v8_snapshot_toolchain)",
|
||||
"//tools/v8_context_snapshot:v8_context_snapshot_generator",
|
||||
":licenses",
|
||||
]
|
||||
data_deps = mksnapshot_deps
|
||||
outputs = [
|
||||
"$root_build_dir/mksnapshot.zip",
|
||||
]
|
||||
|
||||
@@ -1 +1 @@
|
||||
9.0.0-nightly.20191119
|
||||
9.0.0-nightly.20191201
|
||||
@@ -96,7 +96,9 @@ build_script:
|
||||
if ($env:TARGET_ARCH -ne 'ia32') {
|
||||
# archive current source for future use
|
||||
# only run on x64/woa to avoid contention saving
|
||||
7z a $zipfile src -xr!android_webview -xr!electron -xr'!*\.git' -xr!third_party\WebKit\LayoutTests! -xr!third_party\blink\web_tests -xr!third_party\blink\perf_tests -slp -t7z -mmt=30
|
||||
if ($(7z a $zipfile src -xr!android_webview -xr!electron -xr'!*\.git' -xr!third_party\WebKit\LayoutTests! -xr!third_party\blink\web_tests -xr!third_party\blink\perf_tests -slp -t7z -mmt=30;$LASTEXITCODE -ne 0)) {
|
||||
Write-warning "Could not save source to shared drive; continuing anyway"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,8 @@ is_official_build = false
|
||||
dcheck_always_on = true
|
||||
symbol_level = 1
|
||||
|
||||
strip_absolute_paths_from_debug_symbols = false
|
||||
|
||||
# This may be guarded behind is_chrome_branded alongside
|
||||
# proprietary_codecs https://webrtc-review.googlesource.com/c/src/+/36321,
|
||||
# explicitly override here to build OpenH264 encoder/FFmpeg decoder.
|
||||
|
||||
@@ -308,45 +308,51 @@ source_set("plugins") {
|
||||
# You may have to add new files here during the upgrade if //chrome/browser/spellchecker
|
||||
# gets more files
|
||||
source_set("chrome_spellchecker") {
|
||||
sources = [
|
||||
"//chrome/browser/spellchecker/spell_check_host_chrome_impl.cc",
|
||||
"//chrome/browser/spellchecker/spell_check_host_chrome_impl.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_custom_dictionary.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_custom_dictionary.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_factory.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_factory.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_blacklist_policy_handler.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_blacklist_policy_handler.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_policy_handler.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_policy_handler.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_service.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_service.h",
|
||||
"//chrome/common/pref_names.h",
|
||||
]
|
||||
sources = []
|
||||
deps = []
|
||||
libs = []
|
||||
|
||||
if (has_spellcheck_panel) {
|
||||
if (enable_builtin_spellchecker) {
|
||||
sources += [
|
||||
"//chrome/browser/spellchecker/spell_check_panel_host_impl.cc",
|
||||
"//chrome/browser/spellchecker/spell_check_panel_host_impl.h",
|
||||
"//chrome/browser/spellchecker/spell_check_host_chrome_impl.cc",
|
||||
"//chrome/browser/spellchecker/spell_check_host_chrome_impl.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_custom_dictionary.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_custom_dictionary.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_factory.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_factory.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_hunspell_dictionary.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_blacklist_policy_handler.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_blacklist_policy_handler.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_policy_handler.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_language_policy_handler.h",
|
||||
"//chrome/browser/spellchecker/spellcheck_service.cc",
|
||||
"//chrome/browser/spellchecker/spellcheck_service.h",
|
||||
"//chrome/common/pref_names.h",
|
||||
]
|
||||
|
||||
if (has_spellcheck_panel) {
|
||||
sources += [
|
||||
"//chrome/browser/spellchecker/spell_check_panel_host_impl.cc",
|
||||
"//chrome/browser/spellchecker/spell_check_panel_host_impl.h",
|
||||
]
|
||||
}
|
||||
|
||||
if (use_browser_spellchecker) {
|
||||
sources += [
|
||||
"//chrome/browser/spellchecker/spelling_request.cc",
|
||||
"//chrome/browser/spellchecker/spelling_request.h",
|
||||
]
|
||||
}
|
||||
|
||||
deps += [
|
||||
"//base:base_static",
|
||||
"//components/language/core/browser",
|
||||
"//components/spellcheck:buildflags",
|
||||
"//components/sync",
|
||||
]
|
||||
}
|
||||
|
||||
if (use_browser_spellchecker) {
|
||||
sources += [
|
||||
"//chrome/browser/spellchecker/spelling_request.cc",
|
||||
"//chrome/browser/spellchecker/spelling_request.h",
|
||||
]
|
||||
}
|
||||
|
||||
deps = [
|
||||
"//base:base_static",
|
||||
"//components/language/core/browser",
|
||||
"//components/spellcheck:buildflags",
|
||||
"//components/sync",
|
||||
]
|
||||
|
||||
public_deps = [
|
||||
"//components/spellcheck/browser",
|
||||
"//components/spellcheck/common",
|
||||
|
||||
@@ -1303,7 +1303,7 @@ command line arguments that Chromium uses.
|
||||
|
||||
### `app.dock` _macOS_ _Readonly_
|
||||
|
||||
A [`Dock`](./dock.md) object that allows you to perform actions on your app icon in the user's
|
||||
A [`Dock`](./dock.md) `| undefined` object that allows you to perform actions on your app icon in the user's
|
||||
dock on macOS.
|
||||
|
||||
### `app.isPackaged` _Readonly_
|
||||
|
||||
@@ -517,7 +517,7 @@ Emitted when the window is restored from a minimized state.
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `newBounds` [`Rectangle`](structures/rectangle.md) - Size the window is being resized to.
|
||||
* `newBounds` [Rectangle](structures/rectangle.md) - Size the window is being resized to.
|
||||
|
||||
Emitted before the window is resized. Calling `event.preventDefault()` will prevent the window from being resized.
|
||||
|
||||
@@ -532,7 +532,7 @@ Emitted after the window has been resized.
|
||||
Returns:
|
||||
|
||||
* `event` Event
|
||||
* `newBounds` [`Rectangle`](structures/rectangle.md) - Location the window is being moved to.
|
||||
* `newBounds` [Rectangle](structures/rectangle.md) - Location the window is being moved to.
|
||||
|
||||
Emitted before the window is moved. On Windows, calling `event.preventDefault()` will prevent the window from being moved.
|
||||
|
||||
@@ -772,7 +772,7 @@ events.
|
||||
|
||||
#### `win.id` _Readonly_
|
||||
|
||||
A `Integer` property representing the unique ID of the window.
|
||||
A `Integer` property representing the unique ID of the window. Each ID is unique among all `BrowserWindow` instances of the entire Electron application.
|
||||
|
||||
#### `win.autoHideMenuBar`
|
||||
|
||||
|
||||
@@ -32,8 +32,8 @@ the hostname and the port number 'hostname:port'.
|
||||
* `redirect` String (optional) - The redirect mode for this request. Should be
|
||||
one of `follow`, `error` or `manual`. Defaults to `follow`. When mode is `error`,
|
||||
any redirection will be aborted. When mode is `manual` the redirection will be
|
||||
deferred until [`request.followRedirect`](#requestfollowredirect) is invoked. Listen for the [`redirect`](#event-redirect) event in
|
||||
this mode to get more details about the redirect request.
|
||||
cancelled unless [`request.followRedirect`](#requestfollowredirect) is invoked
|
||||
synchronously during the [`redirect`](#event-redirect) event.
|
||||
|
||||
`options` properties such as `protocol`, `host`, `hostname`, `port` and `path`
|
||||
strictly follow the Node.js model as described in the
|
||||
@@ -136,8 +136,11 @@ Returns:
|
||||
* `redirectUrl` String
|
||||
* `responseHeaders` Record<String, String[]>
|
||||
|
||||
Emitted when there is redirection and the mode is `manual`. Calling
|
||||
[`request.followRedirect`](#requestfollowredirect) will continue with the redirection.
|
||||
Emitted when the server returns a redirect response (e.g. 301 Moved
|
||||
Permanently). Calling [`request.followRedirect`](#requestfollowredirect) will
|
||||
continue with the redirection. If this event is handled,
|
||||
[`request.followRedirect`](#requestfollowredirect) must be called
|
||||
**synchronously**, otherwise the request will be cancelled.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
@@ -214,7 +217,8 @@ response object,it will emit the `aborted` event.
|
||||
|
||||
#### `request.followRedirect()`
|
||||
|
||||
Continues any deferred redirection request when the redirection mode is `manual`.
|
||||
Continues any pending redirection. Can only be called during a `'redirect'`
|
||||
event.
|
||||
|
||||
#### `request.getUploadProgress()`
|
||||
|
||||
|
||||
@@ -464,12 +464,16 @@ The built in spellchecker does not automatically detect what language a user is
|
||||
spell checker to correctly check their words you must call this API with an array of language codes. You can
|
||||
get the list of supported language codes with the `ses.availableSpellCheckerLanguages` property.
|
||||
|
||||
**Note:** On macOS the OS spellchecker is used and will detect your language automatically. This API is a no-op on macOS.
|
||||
|
||||
#### `ses.getSpellCheckerLanguages()`
|
||||
|
||||
Returns `String[]` - An array of language codes the spellchecker is enabled for. If this list is empty the spellchecker
|
||||
will fallback to using `en-US`. By default on launch if this setting is an empty list Electron will try to populate this
|
||||
setting with the current OS locale. This setting is persisted across restarts.
|
||||
|
||||
**Note:** On macOS the OS spellchecker is used and has it's own list of languages. This API is a no-op on macOS.
|
||||
|
||||
#### `ses.setSpellCheckerDictionaryDownloadURL(url)`
|
||||
|
||||
* `url` String - A base URL for Electron to download hunspell dictionaries from.
|
||||
@@ -479,6 +483,16 @@ behavior you can use this API to point the dictionary downloader at your own hos
|
||||
dictionaries. We publish a `hunspell_dictionaries.zip` file with each release which contains the files you need
|
||||
to host here.
|
||||
|
||||
**Note:** On macOS the OS spellchecker is used and therefore we do not download any dictionary files. This API is a no-op on macOS.
|
||||
|
||||
#### `ses.addWordToSpellCheckerDictionary(word)`
|
||||
|
||||
* `word` String - The word you want to add to the dictionary
|
||||
|
||||
Returns `Boolean` - Whether the word was successfully written to the custom dictionary.
|
||||
|
||||
**Note:** On macOS and Windows 10 this word will be written to the OS custom dictionary as well
|
||||
|
||||
### Instance Properties
|
||||
|
||||
The following properties are available on instances of `Session`:
|
||||
|
||||
@@ -4,4 +4,5 @@
|
||||
* `returnValue` any - Set this to the value to be returned in a synchronous message
|
||||
* `sender` WebContents - Returns the `webContents` that sent the message
|
||||
* `reply` Function - A function that will send an IPC message to the renderer frame that sent the original message that you are currently handling. You should use this method to "reply" to the sent message in order to guarantee the reply will go to the correct process and frame.
|
||||
* `channel` String
|
||||
* `...args` any[]
|
||||
|
||||
@@ -1031,6 +1031,17 @@ contents.executeJavaScript('fetch("https://jsonplaceholder.typicode.com/users/1"
|
||||
})
|
||||
```
|
||||
|
||||
#### `contents.executeJavaScriptInIsolatedWorld(worldId, scripts[, userGesture])`
|
||||
|
||||
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electron's `contextIsolation` feature. You can provide any integer here.
|
||||
* `scripts` [WebSource[]](structures/web-source.md)
|
||||
* `userGesture` Boolean (optional) - Default is `false`.
|
||||
|
||||
Returns `Promise<any>` - A promise that resolves with the result of the executed code
|
||||
or is rejected if the result of the code is a rejected promise.
|
||||
|
||||
Works like `executeJavaScript` but evaluates `scripts` in an isolated context.
|
||||
|
||||
#### `contents.setIgnoreMenuShortcuts(ignore)` _Experimental_
|
||||
|
||||
* `ignore` Boolean
|
||||
@@ -1771,7 +1782,7 @@ Only applicable if *offscreen rendering* is enabled.
|
||||
|
||||
#### `contents.id` _Readonly_
|
||||
|
||||
A `Integer` representing the unique ID of this WebContents.
|
||||
A `Integer` representing the unique ID of this WebContents. Each ID is unique among all `WebContents` instances of the entire Electron application.
|
||||
|
||||
#### `contents.session` _Readonly_
|
||||
|
||||
|
||||
@@ -98,8 +98,8 @@ filenames = {
|
||||
"shell/browser/api/atom_api_top_level_window.h",
|
||||
"shell/browser/api/atom_api_tray.cc",
|
||||
"shell/browser/api/atom_api_tray.h",
|
||||
"shell/browser/api/atom_api_url_request.cc",
|
||||
"shell/browser/api/atom_api_url_request.h",
|
||||
"shell/browser/api/atom_api_url_loader.cc",
|
||||
"shell/browser/api/atom_api_url_loader.h",
|
||||
"shell/browser/api/atom_api_view.cc",
|
||||
"shell/browser/api/atom_api_view.h",
|
||||
"shell/browser/api/atom_api_web_contents.cc",
|
||||
|
||||
@@ -178,6 +178,9 @@ const messageBox = (sync, window, options) => {
|
||||
if (typeof checkboxLabel !== 'string') throw new TypeError('checkboxLabel must be a string')
|
||||
|
||||
checkboxChecked = !!checkboxChecked
|
||||
if (checkboxChecked && !checkboxLabel) {
|
||||
throw new Error('checkboxChecked requires that checkboxLabel also be passed')
|
||||
}
|
||||
|
||||
// Choose a default button to get selected when dialog is cancelled.
|
||||
if (cancelId == null) {
|
||||
|
||||
@@ -2,17 +2,13 @@
|
||||
|
||||
const url = require('url')
|
||||
const { EventEmitter } = require('events')
|
||||
const { Readable } = require('stream')
|
||||
const { Readable, Writable } = require('stream')
|
||||
const { app } = require('electron')
|
||||
const { Session } = process.electronBinding('session')
|
||||
const { net, Net } = process.electronBinding('net')
|
||||
const { URLRequest } = net
|
||||
const { net, Net, _isValidHeaderName, _isValidHeaderValue } = process.electronBinding('net')
|
||||
const { URLLoader } = net
|
||||
|
||||
// Net is an EventEmitter.
|
||||
Object.setPrototypeOf(Net.prototype, EventEmitter.prototype)
|
||||
EventEmitter.call(net)
|
||||
|
||||
Object.setPrototypeOf(URLRequest.prototype, EventEmitter.prototype)
|
||||
Object.setPrototypeOf(URLLoader.prototype, EventEmitter.prototype)
|
||||
|
||||
const kSupportedProtocols = new Set(['http:', 'https:'])
|
||||
|
||||
@@ -40,32 +36,24 @@ const discardableDuplicateHeaders = new Set([
|
||||
])
|
||||
|
||||
class IncomingMessage extends Readable {
|
||||
constructor (urlRequest) {
|
||||
constructor (responseHead) {
|
||||
super()
|
||||
this.urlRequest = urlRequest
|
||||
this.shouldPush = false
|
||||
this.data = []
|
||||
this.urlRequest.on('data', (event, chunk) => {
|
||||
this._storeInternalData(chunk)
|
||||
this._pushInternalData()
|
||||
})
|
||||
this.urlRequest.on('end', () => {
|
||||
this._storeInternalData(null)
|
||||
this._pushInternalData()
|
||||
})
|
||||
this._shouldPush = false
|
||||
this._data = []
|
||||
this._responseHead = responseHead
|
||||
}
|
||||
|
||||
get statusCode () {
|
||||
return this.urlRequest.statusCode
|
||||
return this._responseHead.statusCode
|
||||
}
|
||||
|
||||
get statusMessage () {
|
||||
return this.urlRequest.statusMessage
|
||||
return this._responseHead.statusMessage
|
||||
}
|
||||
|
||||
get headers () {
|
||||
const filteredHeaders = {}
|
||||
const rawHeaders = this.urlRequest.rawResponseHeaders
|
||||
const rawHeaders = this._responseHead.headers
|
||||
Object.keys(rawHeaders).forEach(header => {
|
||||
if (header in filteredHeaders && discardableDuplicateHeaders.has(header)) {
|
||||
// do nothing with discardable duplicate headers
|
||||
@@ -88,11 +76,11 @@ class IncomingMessage extends Readable {
|
||||
}
|
||||
|
||||
get httpVersionMajor () {
|
||||
return this.urlRequest.httpVersionMajor
|
||||
return this._responseHead.httpVersion.major
|
||||
}
|
||||
|
||||
get httpVersionMinor () {
|
||||
return this.urlRequest.httpVersionMinor
|
||||
return this._responseHead.httpVersion.minor
|
||||
}
|
||||
|
||||
get rawTrailers () {
|
||||
@@ -104,170 +92,197 @@ class IncomingMessage extends Readable {
|
||||
}
|
||||
|
||||
_storeInternalData (chunk) {
|
||||
this.data.push(chunk)
|
||||
this._data.push(chunk)
|
||||
this._pushInternalData()
|
||||
}
|
||||
|
||||
_pushInternalData () {
|
||||
while (this.shouldPush && this.data.length > 0) {
|
||||
const chunk = this.data.shift()
|
||||
this.shouldPush = this.push(chunk)
|
||||
while (this._shouldPush && this._data.length > 0) {
|
||||
const chunk = this._data.shift()
|
||||
this._shouldPush = this.push(chunk)
|
||||
}
|
||||
}
|
||||
|
||||
_read () {
|
||||
this.shouldPush = true
|
||||
this._shouldPush = true
|
||||
this._pushInternalData()
|
||||
}
|
||||
}
|
||||
|
||||
URLRequest.prototype._emitRequestEvent = function (isAsync, ...rest) {
|
||||
if (isAsync) {
|
||||
process.nextTick(() => {
|
||||
this.clientRequest.emit(...rest)
|
||||
})
|
||||
} else {
|
||||
this.clientRequest.emit(...rest)
|
||||
}
|
||||
}
|
||||
|
||||
URLRequest.prototype._emitResponseEvent = function (isAsync, ...rest) {
|
||||
if (isAsync) {
|
||||
process.nextTick(() => {
|
||||
this._response.emit(...rest)
|
||||
})
|
||||
} else {
|
||||
this._response.emit(...rest)
|
||||
}
|
||||
}
|
||||
|
||||
class ClientRequest extends EventEmitter {
|
||||
constructor (options, callback) {
|
||||
/** Writable stream that buffers up everything written to it. */
|
||||
class SlurpStream extends Writable {
|
||||
constructor () {
|
||||
super()
|
||||
this._data = Buffer.alloc(0)
|
||||
}
|
||||
_write (chunk, encoding, callback) {
|
||||
this._data = Buffer.concat([this._data, chunk])
|
||||
callback()
|
||||
}
|
||||
data () { return this._data }
|
||||
}
|
||||
|
||||
class ChunkedBodyStream extends Writable {
|
||||
constructor (clientRequest) {
|
||||
super()
|
||||
this._clientRequest = clientRequest
|
||||
}
|
||||
|
||||
_write (chunk, encoding, callback) {
|
||||
if (this._downstream) {
|
||||
this._downstream.write(chunk).then(callback, callback)
|
||||
} else {
|
||||
// the contract of _write is that we won't be called again until we call
|
||||
// the callback, so we're good to just save a single chunk.
|
||||
this._pendingChunk = chunk
|
||||
this._pendingCallback = callback
|
||||
|
||||
// The first write to a chunked body stream begins the request.
|
||||
this._clientRequest._startRequest()
|
||||
}
|
||||
}
|
||||
|
||||
_final (callback) {
|
||||
this._downstream.done()
|
||||
callback()
|
||||
}
|
||||
|
||||
startReading (pipe) {
|
||||
if (this._downstream) {
|
||||
throw new Error('two startReading calls???')
|
||||
}
|
||||
this._downstream = pipe
|
||||
if (this._pendingChunk) {
|
||||
const doneWriting = (maybeError) => {
|
||||
const cb = this._pendingCallback
|
||||
delete this._pendingCallback
|
||||
delete this._pendingChunk
|
||||
cb(maybeError)
|
||||
}
|
||||
this._downstream.write(this._pendingChunk).then(doneWriting, doneWriting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function parseOptions (options) {
|
||||
if (typeof options === 'string') {
|
||||
options = url.parse(options)
|
||||
} else {
|
||||
options = { ...options }
|
||||
}
|
||||
|
||||
const method = (options.method || 'GET').toUpperCase()
|
||||
let urlStr = options.url
|
||||
|
||||
if (!urlStr) {
|
||||
const urlObj = {}
|
||||
const protocol = options.protocol || 'http:'
|
||||
if (!kSupportedProtocols.has(protocol)) {
|
||||
throw new Error('Protocol "' + protocol + '" not supported')
|
||||
}
|
||||
urlObj.protocol = protocol
|
||||
|
||||
if (options.host) {
|
||||
urlObj.host = options.host
|
||||
} else {
|
||||
if (options.hostname) {
|
||||
urlObj.hostname = options.hostname
|
||||
} else {
|
||||
urlObj.hostname = 'localhost'
|
||||
}
|
||||
|
||||
if (options.port) {
|
||||
urlObj.port = options.port
|
||||
}
|
||||
}
|
||||
|
||||
if (options.path && / /.test(options.path)) {
|
||||
// The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
|
||||
// with an additional rule for ignoring percentage-escaped characters
|
||||
// but that's a) hard to capture in a regular expression that performs
|
||||
// well, and b) possibly too restrictive for real-world usage. That's
|
||||
// why it only scans for spaces because those are guaranteed to create
|
||||
// an invalid request.
|
||||
throw new TypeError('Request path contains unescaped characters')
|
||||
}
|
||||
const pathObj = url.parse(options.path || '/')
|
||||
urlObj.pathname = pathObj.pathname
|
||||
urlObj.search = pathObj.search
|
||||
urlObj.hash = pathObj.hash
|
||||
urlStr = url.format(urlObj)
|
||||
}
|
||||
|
||||
const redirectPolicy = options.redirect || 'follow'
|
||||
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
|
||||
throw new Error('redirect mode should be one of follow, error or manual')
|
||||
}
|
||||
|
||||
if (options.headers != null && typeof options.headers !== 'object') {
|
||||
throw new TypeError('headers must be an object')
|
||||
}
|
||||
|
||||
const urlLoaderOptions = {
|
||||
method: method,
|
||||
url: urlStr,
|
||||
redirectPolicy,
|
||||
extraHeaders: options.headers || {}
|
||||
}
|
||||
for (const [name, value] of Object.entries(urlLoaderOptions.extraHeaders)) {
|
||||
if (!_isValidHeaderName(name)) {
|
||||
throw new Error(`Invalid header name: '${name}'`)
|
||||
}
|
||||
if (!_isValidHeaderValue(value.toString())) {
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`)
|
||||
}
|
||||
}
|
||||
if (options.session) {
|
||||
if (options.session instanceof Session) {
|
||||
urlLoaderOptions.session = options.session
|
||||
} else {
|
||||
throw new TypeError('`session` should be an instance of the Session class')
|
||||
}
|
||||
} else if (options.partition) {
|
||||
if (typeof options.partition === 'string') {
|
||||
urlLoaderOptions.partition = options.partition
|
||||
} else {
|
||||
throw new TypeError('`partition` should be a string')
|
||||
}
|
||||
}
|
||||
return urlLoaderOptions
|
||||
}
|
||||
|
||||
class ClientRequest extends Writable {
|
||||
constructor (options, callback) {
|
||||
super({ autoDestroy: true })
|
||||
|
||||
if (!app.isReady()) {
|
||||
throw new Error('net module can only be used after app is ready')
|
||||
}
|
||||
|
||||
if (typeof options === 'string') {
|
||||
options = url.parse(options)
|
||||
} else {
|
||||
options = Object.assign({}, options)
|
||||
}
|
||||
|
||||
const method = (options.method || 'GET').toUpperCase()
|
||||
let urlStr = options.url
|
||||
|
||||
if (!urlStr) {
|
||||
const urlObj = {}
|
||||
const protocol = options.protocol || 'http:'
|
||||
if (!kSupportedProtocols.has(protocol)) {
|
||||
throw new Error('Protocol "' + protocol + '" not supported')
|
||||
}
|
||||
urlObj.protocol = protocol
|
||||
|
||||
if (options.host) {
|
||||
urlObj.host = options.host
|
||||
} else {
|
||||
if (options.hostname) {
|
||||
urlObj.hostname = options.hostname
|
||||
} else {
|
||||
urlObj.hostname = 'localhost'
|
||||
}
|
||||
|
||||
if (options.port) {
|
||||
urlObj.port = options.port
|
||||
}
|
||||
}
|
||||
|
||||
if (options.path && / /.test(options.path)) {
|
||||
// The actual regex is more like /[^A-Za-z0-9\-._~!$&'()*+,;=/:@]/
|
||||
// with an additional rule for ignoring percentage-escaped characters
|
||||
// but that's a) hard to capture in a regular expression that performs
|
||||
// well, and b) possibly too restrictive for real-world usage. That's
|
||||
// why it only scans for spaces because those are guaranteed to create
|
||||
// an invalid request.
|
||||
throw new TypeError('Request path contains unescaped characters')
|
||||
}
|
||||
const pathObj = url.parse(options.path || '/')
|
||||
urlObj.pathname = pathObj.pathname
|
||||
urlObj.search = pathObj.search
|
||||
urlObj.hash = pathObj.hash
|
||||
urlStr = url.format(urlObj)
|
||||
}
|
||||
|
||||
const redirectPolicy = options.redirect || 'follow'
|
||||
if (!['follow', 'error', 'manual'].includes(redirectPolicy)) {
|
||||
throw new Error('redirect mode should be one of follow, error or manual')
|
||||
}
|
||||
|
||||
const urlRequestOptions = {
|
||||
method: method,
|
||||
url: urlStr,
|
||||
redirect: redirectPolicy
|
||||
}
|
||||
if (options.session) {
|
||||
if (options.session instanceof Session) {
|
||||
urlRequestOptions.session = options.session
|
||||
} else {
|
||||
throw new TypeError('`session` should be an instance of the Session class')
|
||||
}
|
||||
} else if (options.partition) {
|
||||
if (typeof options.partition === 'string') {
|
||||
urlRequestOptions.partition = options.partition
|
||||
} else {
|
||||
throw new TypeError('`partition` should be a string')
|
||||
}
|
||||
}
|
||||
|
||||
const urlRequest = new URLRequest(urlRequestOptions)
|
||||
|
||||
// Set back and forward links.
|
||||
this.urlRequest = urlRequest
|
||||
urlRequest.clientRequest = this
|
||||
|
||||
// This is a copy of the extra headers structure held by the native
|
||||
// net::URLRequest. The main reason is to keep the getHeader API synchronous
|
||||
// after the request starts.
|
||||
this.extraHeaders = {}
|
||||
|
||||
if (options.headers) {
|
||||
for (const key in options.headers) {
|
||||
this.setHeader(key, options.headers[key])
|
||||
}
|
||||
}
|
||||
|
||||
// Set when the request uses chunked encoding. Can be switched
|
||||
// to true only once and never set back to false.
|
||||
this.chunkedEncodingEnabled = false
|
||||
|
||||
urlRequest.on('response', () => {
|
||||
const response = new IncomingMessage(urlRequest)
|
||||
urlRequest._response = response
|
||||
this.emit('response', response)
|
||||
})
|
||||
|
||||
urlRequest.on('login', (event, authInfo, callback) => {
|
||||
const handled = this.emit('login', authInfo, callback)
|
||||
if (!handled) {
|
||||
// If there were no listeners, cancel the authentication request.
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
if (callback) {
|
||||
this.once('response', callback)
|
||||
}
|
||||
}
|
||||
|
||||
get chunkedEncoding () {
|
||||
return this.chunkedEncodingEnabled
|
||||
const { redirectPolicy, ...urlLoaderOptions } = parseOptions(options)
|
||||
this._urlLoaderOptions = urlLoaderOptions
|
||||
this._redirectPolicy = redirectPolicy
|
||||
this._started = false
|
||||
}
|
||||
|
||||
set chunkedEncoding (value) {
|
||||
if (!this.urlRequest.notStarted) {
|
||||
throw new Error('Can\'t set the transfer encoding, headers have been sent')
|
||||
if (this._started) {
|
||||
throw new Error('chunkedEncoding can only be set before the request is started')
|
||||
}
|
||||
if (typeof this._chunkedEncoding !== 'undefined') {
|
||||
throw new Error('chunkedEncoding can only be set once')
|
||||
}
|
||||
this._chunkedEncoding = !!value
|
||||
if (this._chunkedEncoding) {
|
||||
this._body = new ChunkedBodyStream(this)
|
||||
this._urlLoaderOptions.body = (pipe) => {
|
||||
this._body.startReading(pipe)
|
||||
}
|
||||
}
|
||||
this.chunkedEncodingEnabled = value
|
||||
}
|
||||
|
||||
setHeader (name, value) {
|
||||
@@ -277,13 +292,18 @@ class ClientRequest extends EventEmitter {
|
||||
if (value == null) {
|
||||
throw new Error('`value` required in setHeader("' + name + '", value)')
|
||||
}
|
||||
if (!this.urlRequest.notStarted) {
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error('Can\'t set headers after they are sent')
|
||||
}
|
||||
if (!_isValidHeaderName(name)) {
|
||||
throw new Error(`Invalid header name: '${name}'`)
|
||||
}
|
||||
if (!_isValidHeaderValue(value.toString())) {
|
||||
throw new Error(`Invalid value for header '${name}': '${value}'`)
|
||||
}
|
||||
|
||||
const key = name.toLowerCase()
|
||||
this.extraHeaders[key] = value
|
||||
this.urlRequest.setExtraHeader(name, value.toString())
|
||||
this._urlLoaderOptions.extraHeaders[key] = value
|
||||
}
|
||||
|
||||
getHeader (name) {
|
||||
@@ -291,12 +311,8 @@ class ClientRequest extends EventEmitter {
|
||||
throw new Error('`name` is required for getHeader(name)')
|
||||
}
|
||||
|
||||
if (!this.extraHeaders) {
|
||||
return
|
||||
}
|
||||
|
||||
const key = name.toLowerCase()
|
||||
return this.extraHeaders[key]
|
||||
return this._urlLoaderOptions.extraHeaders[key]
|
||||
}
|
||||
|
||||
removeHeader (name) {
|
||||
@@ -304,93 +320,144 @@ class ClientRequest extends EventEmitter {
|
||||
throw new Error('`name` is required for removeHeader(name)')
|
||||
}
|
||||
|
||||
if (!this.urlRequest.notStarted) {
|
||||
if (this._started || this._firstWrite) {
|
||||
throw new Error('Can\'t remove headers after they are sent')
|
||||
}
|
||||
|
||||
const key = name.toLowerCase()
|
||||
delete this.extraHeaders[key]
|
||||
this.urlRequest.removeExtraHeader(name)
|
||||
delete this._urlLoaderOptions.extraHeaders[key]
|
||||
}
|
||||
|
||||
_write (chunk, encoding, callback, isLast) {
|
||||
const chunkIsString = typeof chunk === 'string'
|
||||
const chunkIsBuffer = chunk instanceof Buffer
|
||||
if (!chunkIsString && !chunkIsBuffer) {
|
||||
throw new TypeError('First argument must be a string or Buffer')
|
||||
_write (chunk, encoding, callback) {
|
||||
this._firstWrite = true
|
||||
if (!this._body) {
|
||||
this._body = new SlurpStream()
|
||||
this._body.on('finish', () => {
|
||||
this._urlLoaderOptions.body = this._body.data()
|
||||
this._startRequest()
|
||||
})
|
||||
}
|
||||
|
||||
if (chunkIsString) {
|
||||
// We convert all strings into binary buffers.
|
||||
chunk = Buffer.from(chunk, encoding)
|
||||
}
|
||||
|
||||
// Since writing to the network is asynchronous, we conservatively
|
||||
// assume that request headers are written after delivering the first
|
||||
// buffer to the network IO thread.
|
||||
if (this.urlRequest.notStarted) {
|
||||
this.urlRequest.setChunkedUpload(this.chunkedEncoding)
|
||||
}
|
||||
|
||||
// Headers are assumed to be sent on first call to _writeBuffer,
|
||||
// i.e. after the first call to write or end.
|
||||
const result = this.urlRequest.write(chunk, isLast)
|
||||
|
||||
// The write callback is fired asynchronously to mimic Node.js.
|
||||
if (callback) {
|
||||
process.nextTick(callback)
|
||||
}
|
||||
|
||||
return result
|
||||
// TODO: is this the right way to forward to another stream?
|
||||
this._body.write(chunk, encoding, callback)
|
||||
}
|
||||
|
||||
write (data, encoding, callback) {
|
||||
if (this.urlRequest.finished) {
|
||||
const error = new Error('Write after end')
|
||||
process.nextTick(writeAfterEndNT, this, error, callback)
|
||||
return true
|
||||
_final (callback) {
|
||||
if (this._body) {
|
||||
// TODO: is this the right way to forward to another stream?
|
||||
this._body.end(callback)
|
||||
} else {
|
||||
// end() called without a body, go ahead and start the request
|
||||
this._startRequest()
|
||||
callback()
|
||||
}
|
||||
|
||||
return this._write(data, encoding, callback, false)
|
||||
}
|
||||
|
||||
end (data, encoding, callback) {
|
||||
if (this.urlRequest.finished) {
|
||||
return false
|
||||
_startRequest () {
|
||||
this._started = true
|
||||
const stringifyValues = (obj) => {
|
||||
const ret = {}
|
||||
for (const k in obj) {
|
||||
ret[k] = obj[k].toString()
|
||||
}
|
||||
return ret
|
||||
}
|
||||
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.extraHeaders) }
|
||||
this._urlLoader = new URLLoader(opts)
|
||||
this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
|
||||
const response = this._response = new IncomingMessage(responseHead)
|
||||
this.emit('response', response)
|
||||
})
|
||||
this._urlLoader.on('data', (event, data) => {
|
||||
this._response._storeInternalData(Buffer.from(data))
|
||||
})
|
||||
this._urlLoader.on('complete', () => {
|
||||
if (this._response) { this._response._storeInternalData(null) }
|
||||
})
|
||||
this._urlLoader.on('error', (event, netErrorString) => {
|
||||
const error = new Error(netErrorString)
|
||||
if (this._response) this._response.destroy(error)
|
||||
this._die(error)
|
||||
})
|
||||
|
||||
if (typeof data === 'function') {
|
||||
callback = data
|
||||
encoding = null
|
||||
data = null
|
||||
} else if (typeof encoding === 'function') {
|
||||
callback = encoding
|
||||
encoding = null
|
||||
}
|
||||
this._urlLoader.on('login', (event, authInfo, callback) => {
|
||||
const handled = this.emit('login', authInfo, callback)
|
||||
if (!handled) {
|
||||
// If there were no listeners, cancel the authentication request.
|
||||
callback()
|
||||
}
|
||||
})
|
||||
|
||||
data = data || ''
|
||||
this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
|
||||
const { statusCode, newMethod, newUrl } = redirectInfo
|
||||
if (this._redirectPolicy === 'error') {
|
||||
this._die(new Error(`Attempted to redirect, but redirect policy was 'error'`))
|
||||
} else if (this._redirectPolicy === 'manual') {
|
||||
let _followRedirect = false
|
||||
this._followRedirectCb = () => { _followRedirect = true }
|
||||
try {
|
||||
this.emit('redirect', statusCode, newMethod, newUrl, headers)
|
||||
} finally {
|
||||
this._followRedirectCb = null
|
||||
if (!_followRedirect) {
|
||||
this._die(new Error('Redirect was cancelled'))
|
||||
}
|
||||
}
|
||||
} else if (this._redirectPolicy === 'follow') {
|
||||
// Calling followRedirect() when the redirect policy is 'follow' is
|
||||
// allowed but does nothing. (Perhaps it should throw an error
|
||||
// though...? Since the redirect will happen regardless.)
|
||||
try {
|
||||
this._followRedirectCb = () => {}
|
||||
this.emit('redirect', statusCode, newMethod, newUrl, headers)
|
||||
} finally {
|
||||
this._followRedirectCb = null
|
||||
}
|
||||
} else {
|
||||
this._die(new Error(`Unexpected redirect policy '${this._redirectPolicy}'`))
|
||||
}
|
||||
})
|
||||
|
||||
return this._write(data, encoding, callback, true)
|
||||
this._urlLoader.on('upload-progress', (event, position, total) => {
|
||||
this._uploadProgress = { active: true, started: true, current: position, total }
|
||||
this.emit('upload-progress', position, total) // Undocumented, for now
|
||||
})
|
||||
|
||||
this._urlLoader.on('download-progress', (event, current) => {
|
||||
if (this._response) {
|
||||
this._response.emit('download-progress', current) // Undocumented, for now
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
followRedirect () {
|
||||
this.urlRequest.followRedirect()
|
||||
if (this._followRedirectCb) {
|
||||
this._followRedirectCb()
|
||||
} else {
|
||||
throw new Error('followRedirect() called, but was not waiting for a redirect')
|
||||
}
|
||||
}
|
||||
|
||||
abort () {
|
||||
this.urlRequest.cancel()
|
||||
if (!this._aborted) {
|
||||
process.nextTick(() => { this.emit('abort') })
|
||||
}
|
||||
this._aborted = true
|
||||
this._die()
|
||||
}
|
||||
|
||||
_die (err) {
|
||||
this.destroy(err)
|
||||
if (this._urlLoader) {
|
||||
this._urlLoader.cancel()
|
||||
if (this._response) this._response.destroy(err)
|
||||
}
|
||||
}
|
||||
|
||||
getUploadProgress () {
|
||||
return this.urlRequest.getUploadProgress()
|
||||
return { ...this._uploadProgress } || { active: false }
|
||||
}
|
||||
}
|
||||
|
||||
function writeAfterEndNT (self, error, callback) {
|
||||
self.emit('error', error)
|
||||
if (callback) callback(error)
|
||||
}
|
||||
|
||||
Net.prototype.request = function (options, callback) {
|
||||
return new ClientRequest(options, callback)
|
||||
}
|
||||
|
||||
@@ -183,22 +183,25 @@ for (const method of webFrameMethods) {
|
||||
}
|
||||
}
|
||||
|
||||
const executeJavaScript = (contents, code, hasUserGesture) => {
|
||||
return ipcMainUtils.invokeInWebContents(contents, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture)
|
||||
const waitTillCanExecuteJavaScript = async (webContents) => {
|
||||
if (webContents.getURL() && !webContents.isLoadingMainFrame()) return
|
||||
|
||||
return new Promise((resolve) => {
|
||||
webContents.once('did-stop-loading', () => {
|
||||
resolve()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Make sure WebContents::executeJavaScript would run the code only when the
|
||||
// WebContents has been loaded.
|
||||
WebContents.prototype.executeJavaScript = function (code, hasUserGesture) {
|
||||
if (this.getURL() && !this.isLoadingMainFrame()) {
|
||||
return executeJavaScript(this, code, hasUserGesture)
|
||||
} else {
|
||||
return new Promise((resolve, reject) => {
|
||||
this.once('did-stop-loading', () => {
|
||||
executeJavaScript(this, code, hasUserGesture).then(resolve, reject)
|
||||
})
|
||||
})
|
||||
}
|
||||
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
|
||||
await waitTillCanExecuteJavaScript(this)
|
||||
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScript', code, hasUserGesture)
|
||||
}
|
||||
WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (code, hasUserGesture) {
|
||||
await waitTillCanExecuteJavaScript(this)
|
||||
return ipcMainUtils.invokeInWebContents(this, false, 'ELECTRON_INTERNAL_RENDERER_WEB_FRAME_METHOD', 'executeJavaScriptInIsolatedWorld', code, hasUserGesture)
|
||||
}
|
||||
|
||||
// Translate the options of printToPDF.
|
||||
|
||||
@@ -152,7 +152,8 @@ const NavigationController = (function () {
|
||||
NavigationController.prototype.reloadIgnoringCache = function () {
|
||||
this.pendingIndex = this.currentIndex
|
||||
return this.webContents._loadURL(this.getURL(), {
|
||||
extraHeaders: 'pragma: no-cache\n'
|
||||
extraHeaders: 'pragma: no-cache\n',
|
||||
reloadIgnoringCache: true
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -23,7 +23,7 @@ const FUNCTION_PROPERTIES = [
|
||||
|
||||
// The remote functions in renderer processes.
|
||||
// id => Function
|
||||
const rendererFunctions = v8Util.createDoubleIDWeakMap()
|
||||
const rendererFunctions = v8Util.createDoubleIDWeakMap<(...args: any[]) => void>()
|
||||
|
||||
type ObjectMember = {
|
||||
name: string,
|
||||
@@ -289,7 +289,7 @@ const unwrapArgs = function (sender: electron.WebContents, frameId: number, cont
|
||||
case 'function': {
|
||||
// Merge contextId and meta.id, since meta.id can be the same in
|
||||
// different webContents.
|
||||
const objectId = [contextId, meta.id]
|
||||
const objectId: [string, number] = [contextId, meta.id]
|
||||
|
||||
// Cache the callbacks in renderer.
|
||||
if (rendererFunctions.has(objectId)) {
|
||||
@@ -373,12 +373,12 @@ const logStack = function (contents: electron.WebContents, code: string, stack:
|
||||
}
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_WRONG_CONTEXT_ERROR', function (event, contextId, passedContextId, id) {
|
||||
const objectId = [passedContextId, id]
|
||||
const objectId: [string, number] = [passedContextId, id]
|
||||
if (!rendererFunctions.has(objectId)) {
|
||||
// Do nothing if the error has already been reported before.
|
||||
return
|
||||
}
|
||||
removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId))
|
||||
removeRemoteListenersAndLogWarning(event.sender, rendererFunctions.get(objectId)!)
|
||||
})
|
||||
|
||||
handleRemoteCommand('ELECTRON_BROWSER_REQUIRE', function (event, contextId, moduleName, stack) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"version": "9.0.0-nightly.20191119",
|
||||
"version": "9.0.0-nightly.20191201",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
|
||||
@@ -87,3 +87,4 @@ backport_fix_msstl_compat_in_ui_events.patch
|
||||
build_win_fix_msstl_compatibility_for_pdf.patch
|
||||
fix_missing_algorithm_include.patch
|
||||
add_trustedauthclient_to_urlloaderfactory.patch
|
||||
fix_focusowningwebcontents_to_handle_renderwidgethosts_for_oopifs.patch
|
||||
|
||||
@@ -1,80 +1,46 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jacob Quant <jacobq@gmail.com>
|
||||
Date: Tue, 6 Nov 2018 15:26:00 -0600
|
||||
Date: Thu, 31 Oct 2019 14:00:00 -0500
|
||||
Subject: dom_storage_limits.patch
|
||||
|
||||
This patch circumvents the restriction on DOM storage objects,
|
||||
namely `localStorage` and `sessionStorage`, which chromium otherwise
|
||||
limits to approximately 10MiB.
|
||||
This patch increases the DOM storage (e.g. `localStorage`
|
||||
and `sessionStorage`) size quota from 10MiB to 100MiB.
|
||||
Previous versions of this patch attempted to circumvent
|
||||
the restriction altogether.
|
||||
However, this can lead to other problems, such as crashing
|
||||
the Dev Tools when attempting to read or write values that exceed
|
||||
`IPC::Channel::kMaximumMessageSize` (128MiB).
|
||||
|
||||
That restriction originates from a recommendation
|
||||
[in the Web Storage API specification](https://html.spec.whatwg.org/multipage/webstorage.html#disk-space-2)
|
||||
that is motivated by the concern that hostile code could abuse this
|
||||
feature to exhaust available storage capacity.
|
||||
However, in the case of Electron, where the application developers
|
||||
have control over all of the code being executed,
|
||||
this safety precaution becomes a hindrance that does not add much value.
|
||||
For example, if a malicious developer wanted to consume disk space
|
||||
on a victim's machine they could do so via Node's native file system API.
|
||||
|
||||
By disabling this restriction or increasing the quota,
|
||||
electron application developers can use `localStorage`
|
||||
as their application's "back end", without being having
|
||||
to limit the amount of data stored to 10MiB.
|
||||
|
||||
There may still be some benefit to keeping this restriction for applications that load remote content.
|
||||
Although all remote data should be from a trusted source and transferred using
|
||||
a secure channel, it is nevertheless advisable to include additional layers of protection
|
||||
to mitigate risks associated with potential compromise of those other technologies.
|
||||
With that in mind, an acceptable alternative to disabling the limit at compile-time
|
||||
(as this patch currently does) would be to instead allow it to be disabled at run-time
|
||||
for a given `BrowserWindow` via a `webPreferences` option,
|
||||
similar to [`nodeIntegration`](https://electronjs.org/docs/tutorial/security#2-disable-nodejs-integration-for-remote-content).
|
||||
Increasing the quota rather than bypassing it reduces the
|
||||
amount of chromium code that needs to be changed for Electron
|
||||
as well as keeps these storage areas limited to a bounded
|
||||
size meanwhile giving application developers more space to work with.
|
||||
|
||||
diff --git a/content/browser/dom_storage/dom_storage_types.h b/content/browser/dom_storage/dom_storage_types.h
|
||||
index 6c0b831ebaaa2c1749bbc7436ce1025656588310..b67767751cadc6072c133297c7a6cdcc6bfd0c98 100644
|
||||
index 6c0b831ebaaa2c1749bbc7436ce1025656588310..96d1c73adb09d33cf591ca569f46de9b21f9a4df 100644
|
||||
--- a/content/browser/dom_storage/dom_storage_types.h
|
||||
+++ b/content/browser/dom_storage/dom_storage_types.h
|
||||
@@ -21,6 +21,7 @@ typedef std::map<base::string16, base::NullableString16> DOMStorageValuesMap;
|
||||
@@ -21,7 +21,8 @@ typedef std::map<base::string16, base::NullableString16> DOMStorageValuesMap;
|
||||
|
||||
// The quota for each storage area.
|
||||
// This value is enforced in renderer processes and the browser process.
|
||||
+// However, Electron's dom_storage_limits.patch removes the code that checks this limit.
|
||||
const size_t kPerStorageAreaQuota = 10 * 1024 * 1024;
|
||||
-const size_t kPerStorageAreaQuota = 10 * 1024 * 1024;
|
||||
+// Electron's dom_storage_limits.patch increased this value from 10MiB to 100MiB
|
||||
+const size_t kPerStorageAreaQuota = 100 * 1024 * 1024;
|
||||
|
||||
// In the browser process we allow some overage to
|
||||
diff --git a/third_party/blink/renderer/modules/storage/cached_storage_area.cc b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
|
||||
index d91fdc2a7d52307126bc04d44167edadb8c743a8..630acfca527aaec44742d45e47ce29d7754e3385 100644
|
||||
--- a/third_party/blink/renderer/modules/storage/cached_storage_area.cc
|
||||
+++ b/third_party/blink/renderer/modules/storage/cached_storage_area.cc
|
||||
@@ -107,11 +107,13 @@ bool CachedStorageArea::SetItem(const String& key,
|
||||
Source* source) {
|
||||
DCHECK(areas_->Contains(source));
|
||||
// accomodate concurrent writes from different renderers
|
||||
diff --git a/third_party/blink/public/mojom/dom_storage/storage_area.mojom b/third_party/blink/public/mojom/dom_storage/storage_area.mojom
|
||||
index 1f1b2c6fa109aa52c4e7c7f5fcaac91beb538449..d8f15eed9be83340ffd1f2400df08558e9554710 100644
|
||||
--- a/third_party/blink/public/mojom/dom_storage/storage_area.mojom
|
||||
+++ b/third_party/blink/public/mojom/dom_storage/storage_area.mojom
|
||||
@@ -40,7 +40,8 @@ interface StorageAreaGetAllCallback {
|
||||
interface StorageArea {
|
||||
// The quota for each storage area.
|
||||
// This value is enforced in renderer processes and the browser process.
|
||||
- const uint32 kPerStorageAreaQuota = 10485760; // 10 MiB
|
||||
+ // Electron's dom_storage_limits.patch increased this value from 10MiB to 100MiB
|
||||
+ const uint32 kPerStorageAreaQuota = 104857600; // 100 MiB
|
||||
|
||||
+#if 0
|
||||
// A quick check to reject obviously overbudget items to avoid priming the
|
||||
// cache.
|
||||
if ((key.length() + value.length()) * 2 >
|
||||
mojom::blink::StorageArea::kPerStorageAreaQuota)
|
||||
return false;
|
||||
+#endif
|
||||
|
||||
EnsureLoaded();
|
||||
String old_value;
|
||||
diff --git a/third_party/blink/renderer/modules/storage/storage_area_map.cc b/third_party/blink/renderer/modules/storage/storage_area_map.cc
|
||||
index 0da8a1e891edad60355792c40b7d15e90c1086e8..df71418d598d5bdf41e9a8a4340999d9d277aeef 100644
|
||||
--- a/third_party/blink/renderer/modules/storage/storage_area_map.cc
|
||||
+++ b/third_party/blink/renderer/modules/storage/storage_area_map.cc
|
||||
@@ -113,10 +113,12 @@ bool StorageAreaMap::SetItemInternal(const String& key,
|
||||
size_t new_quota_used = quota_used_ - old_item_size + new_item_size;
|
||||
size_t new_memory_used = memory_used_ - old_item_memory + new_item_memory;
|
||||
|
||||
+#if 0
|
||||
// Only check quota if the size is increasing, this allows
|
||||
// shrinking changes to pre-existing files that are over budget.
|
||||
if (check_quota && new_item_size > old_item_size && new_quota_used > quota_)
|
||||
return false;
|
||||
+#endif
|
||||
|
||||
keys_values_.Set(key, value);
|
||||
ResetKeyIterator();
|
||||
// In the browser process we allow some overage to
|
||||
// accommodate concurrent writes from different renderers
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Moshchuk <alexmos@chromium.org>
|
||||
Date: Tue, 19 Nov 2019 22:41:28 +0000
|
||||
Subject: Fix FocusOwningWebContents to handle RenderWidgetHosts for OOPIFs.
|
||||
|
||||
Previously, FocusOwningWebContents() would not focus anything when
|
||||
called for an OOPIF's RenderWidgetHost. This is because
|
||||
GetFocusedRenderWidgetHost() would always return that RWH back,
|
||||
causing FocusOwningWebContents() to skip the call to
|
||||
SetAsFocusedWebContentsIfNecessary() because the passed-in RWH matched
|
||||
the focused RWH.
|
||||
|
||||
This is usually not a problem in Chrome, because inner WebContents
|
||||
can't have OOPIFs and so an inner WebContents would only need to be
|
||||
focused when this is called from a main frame's RenderWidgetHost, and
|
||||
the outermost WebContents would probably already be focused via other
|
||||
means. However, apparently inner WebContents could have OOPIFs in
|
||||
embedders like Electron, and then this becomes problematic. This CL
|
||||
fixes FocusOwningWebContents() to always pass in the main frame's
|
||||
RenderWidgetHost to GetFocusedRenderWidgetHost(), since the latter was
|
||||
never designed to take an OOPIF's RenderWidgetHost (it expects to take
|
||||
an event arriving at a main frame's RenderWidgetHostView and then
|
||||
target it to a subframe's RenderWidgetHost, if needed).
|
||||
|
||||
The setup in the added test is similar to ProcessSwapOnInnerContents,
|
||||
which was also apparently added for an Electron-specific use case
|
||||
(cross-process navigations inside a <webview>) which isn't currently
|
||||
possible in regular Chrome.
|
||||
|
||||
Change-Id: If9559caf53274d415a360a976ebddfcc323d37dd
|
||||
Bug: 1026056
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1922650
|
||||
Reviewed-by: James MacLean <wjmaclean@chromium.org>
|
||||
Commit-Queue: Alex Moshchuk <alexmos@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/master@{#716803}
|
||||
|
||||
diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc
|
||||
index a30192cdf4ddf857c9624c9492734e56a7c69ddb..d027c7f2a82daa092447fdb8cca9baf12a39731d 100644
|
||||
--- a/content/browser/site_per_process_browsertest.cc
|
||||
+++ b/content/browser/site_per_process_browsertest.cc
|
||||
@@ -14049,6 +14049,52 @@ IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, ProcessSwapOnInnerContents) {
|
||||
EXPECT_NE(a_view, b_view);
|
||||
}
|
||||
|
||||
+// This test ensures that WebContentsImpl::FocusOwningWebContents() focuses an
|
||||
+// inner WebContents when it is given an OOPIF's RenderWidgetHost inside that
|
||||
+// inner WebContents. This setup isn't currently supported in Chrome
|
||||
+// (requiring issue 614463), but it can happen in embedders. See
|
||||
+// https://crbug.com/1026056.
|
||||
+IN_PROC_BROWSER_TEST_F(SitePerProcessBrowserTest, FocusInnerContentsFromOOPIF) {
|
||||
+ GURL main_url(embedded_test_server()->GetURL(
|
||||
+ "a.com", "/cross_site_iframe_factory.html?a(a)"));
|
||||
+ EXPECT_TRUE(NavigateToURL(shell(), main_url));
|
||||
+
|
||||
+ // Set up and attach an artificial inner WebContents.
|
||||
+ FrameTreeNode* child_frame =
|
||||
+ web_contents()->GetFrameTree()->root()->child_at(0);
|
||||
+ WebContentsImpl* inner_contents =
|
||||
+ static_cast<WebContentsImpl*>(CreateAndAttachInnerContents(
|
||||
+ ToRenderFrameHost(child_frame).render_frame_host()));
|
||||
+ FrameTreeNode* inner_contents_root = inner_contents->GetFrameTree()->root();
|
||||
+
|
||||
+ // Navigate inner WebContents to b.com, and then navigate a subframe on that
|
||||
+ // page to c.com.
|
||||
+ GURL b_url(embedded_test_server()->GetURL(
|
||||
+ "b.com", "/cross_site_iframe_factory.html?b(b)"));
|
||||
+ NavigateFrameToURL(inner_contents_root, b_url);
|
||||
+ GURL c_url(embedded_test_server()->GetURL("c.com", "/title1.html"));
|
||||
+ FrameTreeNode* inner_child = inner_contents_root->child_at(0);
|
||||
+ NavigateFrameToURL(inner_child, c_url);
|
||||
+
|
||||
+ // Because |inner_contents| was set up without kGuestScheme, it can actually
|
||||
+ // have OOPIFs. Ensure that the subframe is in an OOPIF.
|
||||
+ EXPECT_NE(inner_contents_root->current_frame_host()->GetSiteInstance(),
|
||||
+ inner_child->current_frame_host()->GetSiteInstance());
|
||||
+ EXPECT_TRUE(inner_child->current_frame_host()->IsCrossProcessSubframe());
|
||||
+
|
||||
+ // Make sure the outer WebContents is focused to start with.
|
||||
+ web_contents()->Focus();
|
||||
+ web_contents()->SetAsFocusedWebContentsIfNecessary();
|
||||
+ EXPECT_EQ(web_contents(), web_contents()->GetFocusedWebContents());
|
||||
+
|
||||
+ // Focus the inner WebContents as if an event were received and dispatched
|
||||
+ // directly on the |inner_child|'s RenderWidgetHost, and ensure that this
|
||||
+ // took effect.
|
||||
+ inner_contents->FocusOwningWebContents(
|
||||
+ inner_child->current_frame_host()->GetRenderWidgetHost());
|
||||
+ EXPECT_EQ(inner_contents, web_contents()->GetFocusedWebContents());
|
||||
+}
|
||||
+
|
||||
// Check that a web frame can't navigate a remote subframe to a file: URL. The
|
||||
// frame should stay at the old URL, and the navigation attempt should produce
|
||||
// a console error message. See https://crbug.com/894399.
|
||||
diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc
|
||||
index a5c4162b3c69b534f843e1b6737392c867fc88ea..cd3ded209dbc54f64d398c2adf4cec34aea87244 100644
|
||||
--- a/content/browser/web_contents/web_contents_impl.cc
|
||||
+++ b/content/browser/web_contents/web_contents_impl.cc
|
||||
@@ -6420,8 +6420,10 @@ void WebContentsImpl::FocusOwningWebContents(
|
||||
if (!GuestMode::IsCrossProcessFrameGuest(this) && browser_plugin_guest_)
|
||||
return;
|
||||
|
||||
+ RenderWidgetHostImpl* main_frame_widget_host =
|
||||
+ GetMainFrame()->GetRenderWidgetHost();
|
||||
RenderWidgetHostImpl* focused_widget =
|
||||
- GetFocusedRenderWidgetHost(render_widget_host);
|
||||
+ GetFocusedRenderWidgetHost(main_frame_widget_host);
|
||||
|
||||
if (focused_widget != render_widget_host &&
|
||||
(!focused_widget ||
|
||||
@@ -9,7 +9,7 @@ failures, as it was unable to find max in the std namespace.
|
||||
Upstreamed at https://chromium-review.googlesource.com/c/chromium/src/+/1904823.
|
||||
|
||||
diff --git a/media/base/byte_queue.cc b/media/base/byte_queue.cc
|
||||
index 245fafa668568fd308e5a2806dafc1c5f0bf3dbd..f9cd0c214ac6210d4332bad47b56ef8130be67a2 100644
|
||||
index 245fafa668568fd308e5a2806dafc1c5f0bf3dbd..c54ac79bdfbba4614b9944f73269f5f30ec9a80a 100644
|
||||
--- a/media/base/byte_queue.cc
|
||||
+++ b/media/base/byte_queue.cc
|
||||
@@ -4,6 +4,8 @@
|
||||
|
||||
@@ -63,7 +63,7 @@ index ab32d5475a0e269d32f6ab71f7dc82aa9e02035a..9ada750416f02d23c2f9faae0c41e4a7
|
||||
}
|
||||
|
||||
diff --git a/chrome/browser/printing/print_view_manager_base.cc b/chrome/browser/printing/print_view_manager_base.cc
|
||||
index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4eeed184e 100644
|
||||
index 81cb954f02363c829947dde830f340a761c80a77..60ffae6ea08187e3ddf1d86ad48f2aeca3519d48 100644
|
||||
--- a/chrome/browser/printing/print_view_manager_base.cc
|
||||
+++ b/chrome/browser/printing/print_view_manager_base.cc
|
||||
@@ -27,10 +27,7 @@
|
||||
@@ -129,7 +129,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
DisconnectFromCurrentPrintJob();
|
||||
|
||||
// Don't print / print preview interstitials or crashed tabs.
|
||||
@@ -131,7 +136,14 @@ bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) {
|
||||
@@ -131,7 +137,14 @@ bool PrintViewManagerBase::PrintNow(content::RenderFrameHost* rfh) {
|
||||
return false;
|
||||
|
||||
SetPrintingRFH(rfh);
|
||||
@@ -145,7 +145,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -246,9 +258,9 @@ void PrintViewManagerBase::StartLocalPrintJob(
|
||||
@@ -246,9 +259,9 @@ void PrintViewManagerBase::StartLocalPrintJob(
|
||||
void PrintViewManagerBase::UpdatePrintingEnabled() {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
// The Unretained() is safe because ForEachFrame() is synchronous.
|
||||
@@ -158,7 +158,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
}
|
||||
|
||||
void PrintViewManagerBase::NavigationStopped() {
|
||||
@@ -351,7 +363,7 @@ void PrintViewManagerBase::OnPrintingFailed(int cookie) {
|
||||
@@ -351,7 +364,7 @@ void PrintViewManagerBase::OnPrintingFailed(int cookie) {
|
||||
PrintManager::OnPrintingFailed(cookie);
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
|
||||
@@ -167,7 +167,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
#endif
|
||||
|
||||
ReleasePrinterQuery();
|
||||
@@ -449,9 +461,13 @@ void PrintViewManagerBase::OnNotifyPrintJobEvent(
|
||||
@@ -449,9 +462,13 @@ void PrintViewManagerBase::OnNotifyPrintJobEvent(
|
||||
content::NotificationService::NoDetails());
|
||||
break;
|
||||
}
|
||||
@@ -183,7 +183,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
NOTREACHED();
|
||||
break;
|
||||
}
|
||||
@@ -546,8 +562,6 @@ bool PrintViewManagerBase::CreateNewPrintJob(
|
||||
@@ -546,8 +563,6 @@ bool PrintViewManagerBase::CreateNewPrintJob(
|
||||
DCHECK(!quit_inner_loop_);
|
||||
DCHECK(query);
|
||||
|
||||
@@ -192,7 +192,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
|
||||
// We can't print if there is no renderer.
|
||||
if (!web_contents()->GetRenderViewHost() ||
|
||||
@@ -562,8 +576,6 @@ bool PrintViewManagerBase::CreateNewPrintJob(
|
||||
@@ -562,8 +577,6 @@ bool PrintViewManagerBase::CreateNewPrintJob(
|
||||
print_job_->SetSource(PrintJob::Source::PRINT_PREVIEW, /*source_id=*/"");
|
||||
#endif // defined(OS_CHROMEOS)
|
||||
|
||||
@@ -201,7 +201,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
printing_succeeded_ = false;
|
||||
return true;
|
||||
}
|
||||
@@ -612,14 +624,24 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
@@ -612,14 +625,24 @@ void PrintViewManagerBase::ReleasePrintJob() {
|
||||
content::RenderFrameHost* rfh = printing_rfh_;
|
||||
printing_rfh_ = nullptr;
|
||||
|
||||
@@ -229,7 +229,7 @@ index 81cb954f02363c829947dde830f340a761c80a77..98043d6ec0cb2c55493ef7446a6de0c4
|
||||
print_job_ = nullptr;
|
||||
}
|
||||
diff --git a/chrome/browser/printing/print_view_manager_base.h b/chrome/browser/printing/print_view_manager_base.h
|
||||
index af49d3e2f8abaf7dc4d82dc3f9beccdf4fbd9f18..b99b5d0a4fce3b15484495948462ee2aa2e321c3 100644
|
||||
index af49d3e2f8abaf7dc4d82dc3f9beccdf4fbd9f18..c5ef1a4c1577c509e5fbe0fcf06e6dfdba30c92c 100644
|
||||
--- a/chrome/browser/printing/print_view_manager_base.h
|
||||
+++ b/chrome/browser/printing/print_view_manager_base.h
|
||||
@@ -33,6 +33,8 @@ class PrintJob;
|
||||
@@ -253,7 +253,7 @@ index af49d3e2f8abaf7dc4d82dc3f9beccdf4fbd9f18..b99b5d0a4fce3b15484495948462ee2a
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
|
||||
// Prints the document in |print_data| with settings specified in
|
||||
@@ -206,9 +210,15 @@ class PrintViewManagerBase : public content::NotificationObserver,
|
||||
@@ -206,9 +211,15 @@ class PrintViewManagerBase : public content::NotificationObserver,
|
||||
// The current RFH that is printing with a system printing dialog.
|
||||
content::RenderFrameHost* printing_rfh_;
|
||||
|
||||
@@ -270,7 +270,7 @@ index af49d3e2f8abaf7dc4d82dc3f9beccdf4fbd9f18..b99b5d0a4fce3b15484495948462ee2a
|
||||
// This means we are _blocking_ until all the necessary pages have been
|
||||
// rendered or the print settings are being loaded.
|
||||
diff --git a/chrome/browser/printing/printing_message_filter.cc b/chrome/browser/printing/printing_message_filter.cc
|
||||
index 40762a36024bc48dfe5259520161dc203197bfd0..8f2d0b808df1b68699bb946436b219315f1ea6d3 100644
|
||||
index 40762a36024bc48dfe5259520161dc203197bfd0..e38aa442df858ce362645230f7642b2eb48262ce 100644
|
||||
--- a/chrome/browser/printing/printing_message_filter.cc
|
||||
+++ b/chrome/browser/printing/printing_message_filter.cc
|
||||
@@ -22,6 +22,7 @@
|
||||
@@ -336,7 +336,7 @@ index 40762a36024bc48dfe5259520161dc203197bfd0..8f2d0b808df1b68699bb946436b21931
|
||||
std::unique_ptr<PrinterQuery> printer_query =
|
||||
queue_->PopPrinterQuery(document_cookie);
|
||||
if (!printer_query) {
|
||||
@@ -258,7 +267,9 @@ void PrintingMessageFilter::OnUpdatePrintSettingsReply(
|
||||
@@ -258,7 +266,9 @@ void PrintingMessageFilter::OnUpdatePrintSettingsReply(
|
||||
std::unique_ptr<PrinterQuery> printer_query,
|
||||
IPC::Message* reply_msg) {
|
||||
PrintMsg_PrintPages_Params params;
|
||||
@@ -347,7 +347,7 @@ index 40762a36024bc48dfe5259520161dc203197bfd0..8f2d0b808df1b68699bb946436b21931
|
||||
params.Reset();
|
||||
} else {
|
||||
RenderParamsFromPrintSettings(printer_query->settings(), ¶ms.params);
|
||||
@@ -296,7 +307,7 @@ void PrintingMessageFilter::OnUpdatePrintSettingsReply(
|
||||
@@ -296,7 +306,7 @@ void PrintingMessageFilter::OnUpdatePrintSettingsReply(
|
||||
#if BUILDFLAG(ENABLE_PRINT_PREVIEW)
|
||||
void PrintingMessageFilter::OnCheckForCancel(const PrintHostMsg_PreviewIds& ids,
|
||||
bool* cancel) {
|
||||
|
||||
66
script/add-debug-link.py
Executable file
66
script/add-debug-link.py
Executable file
@@ -0,0 +1,66 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib.config import LINUX_BINARIES, PLATFORM
|
||||
from lib.util import execute, get_objcopy_path, get_out_dir
|
||||
|
||||
def add_debug_link_into_binaries(directory, target_cpu, debug_dir):
|
||||
for binary in LINUX_BINARIES:
|
||||
binary_path = os.path.join(directory, binary)
|
||||
if os.path.isfile(binary_path):
|
||||
add_debug_link_into_binary(binary_path, target_cpu, debug_dir)
|
||||
|
||||
def add_debug_link_into_binary(binary_path, target_cpu, debug_dir):
|
||||
try:
|
||||
objcopy = get_objcopy_path(target_cpu)
|
||||
except:
|
||||
if PLATFORM == 'linux' and (target_cpu == 'x86' or target_cpu == 'arm' or
|
||||
target_cpu == 'arm64'):
|
||||
# Skip because no objcopy binary on the given target.
|
||||
return
|
||||
raise
|
||||
debug_name = get_debug_name(binary_path)
|
||||
# Make sure the path to the binary is not relative because of cwd param.
|
||||
real_binary_path = os.path.realpath(binary_path)
|
||||
cmd = [objcopy, '--add-gnu-debuglink=' + debug_name, real_binary_path]
|
||||
execute(cmd, cwd=debug_dir)
|
||||
|
||||
def get_debug_name(binary_path):
|
||||
return os.path.basename(binary_path) + '.debug'
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
if args.file:
|
||||
add_debug_link_into_binary(args.file, args.target_cpu, args.debug_dir)
|
||||
else:
|
||||
add_debug_link_into_binaries(args.directory, args.target_cpu,
|
||||
args.debug_dir)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Add debug link to binaries')
|
||||
parser.add_argument('-d', '--directory',
|
||||
help='Path to the dir that contains files to add links',
|
||||
default=get_out_dir(),
|
||||
required=False)
|
||||
parser.add_argument('-f', '--file',
|
||||
help='Path to a specific file to add debug link',
|
||||
required=False)
|
||||
parser.add_argument('-s', '--debug-dir',
|
||||
help='Path to the dir that contain the debugs',
|
||||
default=None,
|
||||
required=True)
|
||||
parser.add_argument('-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Prints the output of the subprocesses')
|
||||
parser.add_argument('--target-cpu',
|
||||
default='',
|
||||
required=False,
|
||||
help='Target cpu of binaries to add debug link')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
75
script/copy-debug-symbols.py
Executable file
75
script/copy-debug-symbols.py
Executable file
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env python
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib.config import LINUX_BINARIES, PLATFORM
|
||||
from lib.util import execute, get_objcopy_path, get_out_dir, safe_mkdir
|
||||
|
||||
# It has to be done before stripping the binaries.
|
||||
def copy_debug_from_binaries(directory, out_dir, target_cpu, compress):
|
||||
for binary in LINUX_BINARIES:
|
||||
binary_path = os.path.join(directory, binary)
|
||||
if os.path.isfile(binary_path):
|
||||
copy_debug_from_binary(binary_path, out_dir, target_cpu, compress)
|
||||
|
||||
def copy_debug_from_binary(binary_path, out_dir, target_cpu, compress):
|
||||
try:
|
||||
objcopy = get_objcopy_path(target_cpu)
|
||||
except:
|
||||
if PLATFORM == 'linux' and (target_cpu == 'x86' or target_cpu == 'arm' or
|
||||
target_cpu == 'arm64'):
|
||||
# Skip because no objcopy binary on the given target.
|
||||
return
|
||||
raise
|
||||
debug_name = get_debug_name(binary_path)
|
||||
cmd = [objcopy, '--only-keep-debug']
|
||||
if compress:
|
||||
cmd.extend(['--compress-debug-sections'])
|
||||
cmd.extend([binary_path, os.path.join(out_dir, debug_name)])
|
||||
execute(cmd)
|
||||
return debug_name
|
||||
|
||||
def get_debug_name(binary_path):
|
||||
return os.path.basename(binary_path) + '.debug'
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
safe_mkdir(args.out_dir)
|
||||
if args.file:
|
||||
copy_debug_from_binary(args.file, args.out_dir, args.target_cpu,
|
||||
args.compress)
|
||||
else:
|
||||
copy_debug_from_binaries(args.directory, args.out_dir, args.target_cpu,
|
||||
args.compress)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Copy debug from binaries')
|
||||
parser.add_argument('-d', '--directory',
|
||||
help='Path to the dir that contains files to copy',
|
||||
default=get_out_dir(),
|
||||
required=False)
|
||||
parser.add_argument('-f', '--file',
|
||||
help='Path to a specific file to copy debug symbols',
|
||||
required=False)
|
||||
parser.add_argument('-o', '--out-dir',
|
||||
help='Path to the dir that will contain the debugs',
|
||||
default=None,
|
||||
required=True)
|
||||
parser.add_argument('-v', '--verbose',
|
||||
action='store_true',
|
||||
help='Prints the output of the subprocesses')
|
||||
parser.add_argument('--target-cpu',
|
||||
default='',
|
||||
required=False,
|
||||
help='Target cpu of binaries to copy debug symbols')
|
||||
parser.add_argument('--compress',
|
||||
action='store_true',
|
||||
required=False,
|
||||
help='Compress the debug symbols')
|
||||
|
||||
return parser.parse_args()
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
@@ -35,7 +35,18 @@ for (const file of filesToHash) {
|
||||
}
|
||||
|
||||
// Add the GCLIENT_EXTRA_ARGS variable to the hash
|
||||
hasher.update(process.env.GCLIENT_EXTRA_ARGS || 'no_extra_args')
|
||||
const extraArgs = process.env.GCLIENT_EXTRA_ARGS || 'no_extra_args'
|
||||
hasher.update(extraArgs)
|
||||
|
||||
const effectivePlatform = extraArgs.includes('host_os=mac') ? 'darwin' : process.platform
|
||||
|
||||
// Write the hash to disk
|
||||
fs.writeFileSync(path.resolve(__dirname, '../.depshash'), hasher.digest('hex'))
|
||||
|
||||
let targetContent = `${effectivePlatform}\n${process.env.TARGET_ARCH}\n${process.env.GN_CONFIG}\n${undefined}\n${process.env.GN_EXTRA_ARGS}\n${process.env.GN_BUILDFLAG_ARGS}`
|
||||
const argsDir = path.resolve(__dirname, '../build/args')
|
||||
for (const argFile of fs.readdirSync(argsDir).sort()) {
|
||||
targetContent += `\n${argFile}--${crypto.createHash('SHA1').update(fs.readFileSync(path.resolve(argsDir, argFile))).digest('hex')}`
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.resolve(__dirname, '../.depshash-target'), targetContent)
|
||||
|
||||
@@ -26,6 +26,18 @@ PLATFORM = {
|
||||
'win32': 'win32',
|
||||
}[sys.platform]
|
||||
|
||||
LINUX_BINARIES = [
|
||||
'electron',
|
||||
'chrome-sandbox',
|
||||
'crashpad_handler',
|
||||
'libffmpeg.so',
|
||||
'libGLESv2.so',
|
||||
'libEGL.so',
|
||||
'swiftshader/libGLESv2.so',
|
||||
'swiftshader/libEGL.so',
|
||||
'libvk_swiftshader.so'
|
||||
]
|
||||
|
||||
verbose_mode = False
|
||||
|
||||
|
||||
|
||||
@@ -123,7 +123,8 @@ def make_zip(zip_file_path, files, dirs):
|
||||
files += dirs
|
||||
execute(['zip', '-r', '-y', zip_file_path] + files)
|
||||
else:
|
||||
zip_file = zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED)
|
||||
zip_file = zipfile.ZipFile(zip_file_path, "w", zipfile.ZIP_DEFLATED,
|
||||
allowZip64=True)
|
||||
for filename in files:
|
||||
zip_file.write(filename, filename)
|
||||
for dirname in dirs:
|
||||
@@ -266,3 +267,14 @@ def get_buildtools_executable(name):
|
||||
if sys.platform == 'win32':
|
||||
path += '.exe'
|
||||
return path
|
||||
|
||||
def get_objcopy_path(target_cpu):
|
||||
if PLATFORM != 'linux':
|
||||
raise Exception(
|
||||
"get_objcopy_path: unexpected platform '{0}'".format(PLATFORM))
|
||||
|
||||
if target_cpu != 'x64':
|
||||
raise Exception(
|
||||
"get_objcopy_path: unexpected target cpu '{0}'".format(target_cpu))
|
||||
return os.path.join(SRC_DIR, 'third_party', 'binutils', 'Linux_x64',
|
||||
'Release', 'bin', 'objcopy')
|
||||
|
||||
@@ -126,7 +126,7 @@ async function getCircleCIWorkflowId (pipelineId) {
|
||||
}
|
||||
|
||||
async function getCircleCIJobNumber (workflowId) {
|
||||
const jobInfoUrl = `https://circleci.com/api/v2/workflow/${workflowId}/jobs`
|
||||
const jobInfoUrl = `https://circleci.com/api/v2/workflow/${workflowId}/job`
|
||||
let jobNumber = 0
|
||||
while (jobNumber === 0) {
|
||||
const jobInfo = await circleCIRequest(jobInfoUrl, 'GET')
|
||||
|
||||
@@ -114,6 +114,7 @@ function assetsForVersion (version, validatingRelease) {
|
||||
`electron-${version}-linux-armv7l.zip`,
|
||||
`electron-${version}-linux-ia32-symbols.zip`,
|
||||
`electron-${version}-linux-ia32.zip`,
|
||||
`electron-${version}-linux-x64-debug.zip`,
|
||||
`electron-${version}-linux-x64-symbols.zip`,
|
||||
`electron-${version}-linux-x64.zip`,
|
||||
`electron-${version}-mas-x64-dsym.zip`,
|
||||
|
||||
@@ -35,6 +35,7 @@ DIST_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION)
|
||||
SYMBOLS_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'symbols')
|
||||
DSYM_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'dsym')
|
||||
PDB_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'pdb')
|
||||
DEBUG_NAME = get_zip_name(PROJECT_NAME, ELECTRON_VERSION, 'debug')
|
||||
|
||||
|
||||
def main():
|
||||
@@ -83,6 +84,10 @@ def main():
|
||||
pdb_zip = os.path.join(OUT_DIR, PDB_NAME)
|
||||
shutil.copy2(os.path.join(OUT_DIR, 'pdb.zip'), pdb_zip)
|
||||
upload_electron(release, pdb_zip, args)
|
||||
elif PLATFORM == 'linux':
|
||||
debug_zip = os.path.join(OUT_DIR, DEBUG_NAME)
|
||||
shutil.copy2(os.path.join(OUT_DIR, 'debug.zip'), debug_zip)
|
||||
upload_electron(release, debug_zip, args)
|
||||
|
||||
# Upload free version of ffmpeg.
|
||||
ffmpeg = get_zip_name('ffmpeg', ELECTRON_VERSION)
|
||||
|
||||
@@ -4,22 +4,11 @@ import argparse
|
||||
import os
|
||||
import sys
|
||||
|
||||
from lib.config import LINUX_BINARIES
|
||||
from lib.util import execute, get_out_dir
|
||||
|
||||
LINUX_BINARIES_TO_STRIP = [
|
||||
'electron',
|
||||
'chrome-sandbox',
|
||||
'crashpad_handler',
|
||||
'libffmpeg.so',
|
||||
'libGLESv2.so',
|
||||
'libEGL.so',
|
||||
'swiftshader/libGLESv2.so',
|
||||
'swiftshader/libEGL.so',
|
||||
'libvk_swiftshader.so'
|
||||
]
|
||||
|
||||
def strip_binaries(directory, target_cpu):
|
||||
for binary in LINUX_BINARIES_TO_STRIP:
|
||||
for binary in LINUX_BINARIES:
|
||||
binary_path = os.path.join(directory, binary)
|
||||
if os.path.isfile(binary_path):
|
||||
strip_binary(binary_path, target_cpu)
|
||||
@@ -37,7 +26,6 @@ def strip_binary(binary_path, target_cpu):
|
||||
|
||||
def main():
|
||||
args = parse_args()
|
||||
print(args)
|
||||
if args.file:
|
||||
strip_binary(args.file, args.target_cpu)
|
||||
else:
|
||||
|
||||
@@ -43,6 +43,13 @@ def main():
|
||||
pdb_zip_file = os.path.join(args.build_dir, pdb_name)
|
||||
print('Making pdb zip: ' + pdb_zip_file)
|
||||
make_zip(pdb_zip_file, pdbs + licenses, [])
|
||||
elif PLATFORM == 'linux':
|
||||
debug_name = 'debug.zip'
|
||||
with scoped_cwd(args.build_dir):
|
||||
dirs = ['debug']
|
||||
debug_zip_file = os.path.join(args.build_dir, debug_name)
|
||||
print('Making debug zip: ' + debug_zip_file)
|
||||
make_zip(debug_zip_file, licenses, dirs)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Zip symbols')
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#include "ipc/ipc_buildflags.h"
|
||||
#include "services/service_manager/embedder/switches.h"
|
||||
#include "services/service_manager/sandbox/switches.h"
|
||||
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
|
||||
#include "shell/app/atom_content_client.h"
|
||||
#include "shell/browser/atom_browser_client.h"
|
||||
#include "shell/browser/atom_gpu_client.h"
|
||||
@@ -182,6 +183,9 @@ bool AtomMainDelegate::BasicStartupComplete(int* exit_code) {
|
||||
if (env->HasVar("ELECTRON_DISABLE_SANDBOX"))
|
||||
command_line->AppendSwitch(service_manager::switches::kNoSandbox);
|
||||
|
||||
tracing_sampler_profiler_ =
|
||||
tracing::TracingSamplerProfiler::CreateOnMainThread();
|
||||
|
||||
chrome::RegisterPathProvider();
|
||||
|
||||
#if defined(OS_MACOSX)
|
||||
|
||||
@@ -11,6 +11,10 @@
|
||||
#include "content/public/app/content_main_delegate.h"
|
||||
#include "content/public/common/content_client.h"
|
||||
|
||||
namespace tracing {
|
||||
class TracingSamplerProfiler;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
void LoadResourceBundle(const std::string& locale);
|
||||
@@ -51,6 +55,7 @@ class AtomMainDelegate : public content::ContentMainDelegate {
|
||||
std::unique_ptr<content::ContentGpuClient> gpu_client_;
|
||||
std::unique_ptr<content::ContentRendererClient> renderer_client_;
|
||||
std::unique_ptr<content::ContentUtilityClient> utility_client_;
|
||||
std::unique_ptr<tracing::TracingSamplerProfiler> tracing_sampler_profiler_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(AtomMainDelegate);
|
||||
};
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "shell/browser/api/atom_api_menu.h"
|
||||
|
||||
#include <map>
|
||||
#include <utility>
|
||||
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/common/gin_converters/accelerator_converter.h"
|
||||
@@ -217,6 +218,16 @@ void Menu::OnMenuWillShow() {
|
||||
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) {
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
#include "gin/arguments.h"
|
||||
#include "shell/browser/api/atom_api_top_level_window.h"
|
||||
#include "shell/browser/ui/atom_menu_model.h"
|
||||
#include "shell/common/gin_helper/locker.h"
|
||||
#include "shell/common/gin_helper/trackable_object.h"
|
||||
|
||||
namespace electron {
|
||||
@@ -62,7 +61,7 @@ class Menu : public gin_helper::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_;
|
||||
@@ -72,6 +71,11 @@ class Menu : public gin_helper::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,20 +27,20 @@ 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;
|
||||
void ClosePopupOnUI(int32_t window_id);
|
||||
|
||||
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_;
|
||||
|
||||
|
||||
@@ -38,15 +38,19 @@ 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;
|
||||
|
||||
// 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, callback);
|
||||
positioning_item, std::move(callback_with_ref));
|
||||
base::SequencedTaskRunnerHandle::Get()->PostTask(FROM_HERE, std::move(popup));
|
||||
}
|
||||
|
||||
@@ -55,16 +59,14 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
base::Closure callback) {
|
||||
gin_helper::Locker locker(isolate());
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
|
||||
base::OnceClosure callback) {
|
||||
if (!native_window)
|
||||
return;
|
||||
NSWindow* nswindow = native_window->GetNativeWindow().GetNativeNSWindow();
|
||||
|
||||
auto close_callback = base::BindRepeating(
|
||||
&MenuMac::OnClosed, weak_factory_.GetWeakPtr(), window_id, callback);
|
||||
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]);
|
||||
@@ -102,7 +104,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;
|
||||
|
||||
@@ -140,9 +142,9 @@ void MenuMac::ClosePopupOnUI(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
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "shell/browser/api/atom_api_menu_views.h"
|
||||
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/unresponsive_suppressor.h"
|
||||
@@ -24,10 +25,7 @@ void MenuViews::PopupAt(TopLevelWindow* window,
|
||||
int x,
|
||||
int y,
|
||||
int positioning_item,
|
||||
const base::Closure& callback) {
|
||||
gin_helper::Locker locker(isolate());
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
|
||||
base::OnceClosure callback) {
|
||||
auto* native_window = static_cast<NativeWindowViews*>(window->window());
|
||||
if (!native_window)
|
||||
return;
|
||||
@@ -46,12 +44,21 @@ void MenuViews::PopupAt(TopLevelWindow* window,
|
||||
// Don't emit unresponsive event when showing menu.
|
||||
electron::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::BindRepeating(
|
||||
&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(), nullptr, gfx::Rect(location, gfx::Size()),
|
||||
views::MenuAnchorPosition::kTopLeft, ui::MENU_SOURCE_MOUSE);
|
||||
@@ -71,9 +78,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_;
|
||||
|
||||
@@ -4,9 +4,11 @@
|
||||
|
||||
#include "shell/browser/api/atom_api_net.h"
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gin/handle.h"
|
||||
#include "services/network/public/cpp/features.h"
|
||||
#include "shell/browser/api/atom_api_url_request.h"
|
||||
#include "shell/browser/api/atom_api_url_loader.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
|
||||
@@ -32,12 +34,12 @@ void Net::BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
prototype->SetClassName(gin::StringToV8(isolate, "Net"));
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetProperty("URLRequest", &Net::URLRequest);
|
||||
.SetProperty("URLLoader", &Net::URLLoader);
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
|
||||
v8::Local<v8::Value> Net::URLLoader(v8::Isolate* isolate) {
|
||||
v8::Local<v8::FunctionTemplate> constructor;
|
||||
constructor = URLRequest::GetConstructor(isolate);
|
||||
constructor = SimpleURLLoaderWrapper::GetConstructor(isolate);
|
||||
return constructor->GetFunction(isolate->GetCurrentContext())
|
||||
.ToLocalChecked();
|
||||
}
|
||||
@@ -48,8 +50,16 @@ v8::Local<v8::Value> Net::URLRequest(v8::Isolate* isolate) {
|
||||
|
||||
namespace {
|
||||
|
||||
bool IsValidHeaderName(std::string header_name) {
|
||||
return net::HttpUtil::IsValidHeaderName(header_name);
|
||||
}
|
||||
|
||||
bool IsValidHeaderValue(std::string header_value) {
|
||||
return net::HttpUtil::IsValidHeaderValue(header_value);
|
||||
}
|
||||
|
||||
using electron::api::Net;
|
||||
using electron::api::URLRequest;
|
||||
using electron::api::SimpleURLLoaderWrapper;
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
@@ -57,12 +67,15 @@ void Initialize(v8::Local<v8::Object> exports,
|
||||
void* priv) {
|
||||
v8::Isolate* isolate = context->GetIsolate();
|
||||
|
||||
URLRequest::SetConstructor(isolate, base::BindRepeating(URLRequest::New));
|
||||
SimpleURLLoaderWrapper::SetConstructor(
|
||||
isolate, base::BindRepeating(SimpleURLLoaderWrapper::New));
|
||||
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
dict.Set("net", Net::Create(isolate));
|
||||
dict.Set("Net",
|
||||
Net::GetConstructor(isolate)->GetFunction(context).ToLocalChecked());
|
||||
dict.SetMethod("_isValidHeaderName", &IsValidHeaderName);
|
||||
dict.SetMethod("_isValidHeaderValue", &IsValidHeaderValue);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
@@ -18,7 +18,7 @@ class Net : public mate::Wrappable<Net> {
|
||||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
|
||||
v8::Local<v8::Value> URLRequest(v8::Isolate* isolate);
|
||||
v8::Local<v8::Value> URLLoader(v8::Isolate* isolate);
|
||||
|
||||
protected:
|
||||
explicit Net(v8::Isolate* isolate);
|
||||
|
||||
@@ -69,9 +69,16 @@
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
|
||||
#include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h"
|
||||
#include "chrome/browser/spellchecker/spellcheck_factory.h" // nogncheck
|
||||
#include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h" // nogncheck
|
||||
#include "chrome/browser/spellchecker/spellcheck_service.h" // nogncheck
|
||||
#include "components/spellcheck/browser/pref_names.h"
|
||||
#include "components/spellcheck/common/spellcheck_common.h"
|
||||
|
||||
#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
|
||||
#include "components/spellcheck/browser/spellcheck_platform.h"
|
||||
#include "components/spellcheck/common/spellcheck_features.h"
|
||||
#endif
|
||||
#endif
|
||||
|
||||
using content::BrowserThread;
|
||||
@@ -700,6 +707,20 @@ void SetSpellCheckerDictionaryDownloadURL(gin_helper::ErrorThrower thrower,
|
||||
}
|
||||
SpellcheckHunspellDictionary::SetDownloadURLForTesting(url);
|
||||
}
|
||||
|
||||
bool Session::AddWordToSpellCheckerDictionary(const std::string& word) {
|
||||
#if BUILDFLAG(USE_BROWSER_SPELLCHECKER)
|
||||
if (spellcheck::UseBrowserSpellChecker()) {
|
||||
spellcheck_platform::AddWord(base::UTF8ToUTF16(word));
|
||||
}
|
||||
#endif
|
||||
SpellcheckService* spellcheck =
|
||||
SpellcheckServiceFactory::GetForContext(browser_context_.get());
|
||||
if (!spellcheck)
|
||||
return false;
|
||||
|
||||
return spellcheck->GetCustomDictionary()->AddWord(word);
|
||||
}
|
||||
#endif
|
||||
|
||||
// static
|
||||
@@ -780,6 +801,8 @@ void Session::BuildPrototype(v8::Isolate* isolate,
|
||||
&spellcheck::SpellCheckLanguages)
|
||||
.SetMethod("setSpellCheckerDictionaryDownloadURL",
|
||||
&SetSpellCheckerDictionaryDownloadURL)
|
||||
.SetMethod("addWordToSpellCheckerDictionary",
|
||||
&Session::AddWordToSpellCheckerDictionary)
|
||||
#endif
|
||||
.SetMethod("preconnect", &Session::Preconnect)
|
||||
.SetProperty("cookies", &Session::Cookies)
|
||||
|
||||
@@ -92,6 +92,7 @@ class Session : public gin_helper::TrackableObject<Session>,
|
||||
base::Value GetSpellCheckerLanguages();
|
||||
void SetSpellCheckerLanguages(gin_helper::ErrorThrower thrower,
|
||||
const std::vector<std::string>& languages);
|
||||
bool AddWordToSpellCheckerDictionary(const std::string& word);
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
|
||||
448
shell/browser/api/atom_api_url_loader.cc
Normal file
448
shell/browser/api/atom_api_url_loader.cc
Normal file
@@ -0,0 +1,448 @@
|
||||
// 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 "shell/browser/api/atom_api_url_loader.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/id_map.h"
|
||||
#include "gin/handle.h"
|
||||
#include "gin/object_template_builder.h"
|
||||
#include "gin/wrappable.h"
|
||||
#include "mojo/public/cpp/system/data_pipe_producer.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "services/network/public/cpp/simple_url_loader.h"
|
||||
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
|
||||
#include "services/network/public/mojom/url_loader_factory.mojom.h"
|
||||
#include "shell/browser/api/atom_api_session.h"
|
||||
#include "shell/browser/atom_browser_context.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/net_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
class BufferDataSource : public mojo::DataPipeProducer::DataSource {
|
||||
public:
|
||||
explicit BufferDataSource(base::span<char> buffer) {
|
||||
buffer_.resize(buffer.size());
|
||||
memcpy(buffer_.data(), buffer.data(), buffer_.size());
|
||||
}
|
||||
~BufferDataSource() override = default;
|
||||
|
||||
private:
|
||||
// mojo::DataPipeProducer::DataSource:
|
||||
uint64_t GetLength() const override { return buffer_.size(); }
|
||||
ReadResult Read(uint64_t offset, base::span<char> buffer) override {
|
||||
ReadResult result;
|
||||
if (offset <= buffer_.size()) {
|
||||
size_t readable_size = buffer_.size() - offset;
|
||||
size_t writable_size = buffer.size();
|
||||
size_t copyable_size = std::min(readable_size, writable_size);
|
||||
memcpy(buffer.data(), &buffer_[offset], copyable_size);
|
||||
result.bytes_read = copyable_size;
|
||||
} else {
|
||||
NOTREACHED();
|
||||
result.result = MOJO_RESULT_OUT_OF_RANGE;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<char> buffer_;
|
||||
};
|
||||
|
||||
class JSChunkedDataPipeGetter : public gin::Wrappable<JSChunkedDataPipeGetter>,
|
||||
public network::mojom::ChunkedDataPipeGetter {
|
||||
public:
|
||||
static gin::Handle<JSChunkedDataPipeGetter> Create(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Function> body_func,
|
||||
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
|
||||
chunked_data_pipe_getter) {
|
||||
return gin::CreateHandle(
|
||||
isolate, new JSChunkedDataPipeGetter(
|
||||
isolate, body_func, std::move(chunked_data_pipe_getter)));
|
||||
}
|
||||
|
||||
// gin::Wrappable
|
||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) override {
|
||||
return gin::Wrappable<JSChunkedDataPipeGetter>::GetObjectTemplateBuilder(
|
||||
isolate)
|
||||
.SetMethod("write", &JSChunkedDataPipeGetter::WriteChunk)
|
||||
.SetMethod("done", &JSChunkedDataPipeGetter::Done);
|
||||
}
|
||||
|
||||
static gin::WrapperInfo kWrapperInfo;
|
||||
~JSChunkedDataPipeGetter() override = default;
|
||||
|
||||
private:
|
||||
JSChunkedDataPipeGetter(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::Function> body_func,
|
||||
mojo::PendingReceiver<network::mojom::ChunkedDataPipeGetter>
|
||||
chunked_data_pipe_getter)
|
||||
: isolate_(isolate), body_func_(isolate, body_func) {
|
||||
receiver_.Bind(std::move(chunked_data_pipe_getter));
|
||||
}
|
||||
|
||||
// network::mojom::ChunkedDataPipeGetter:
|
||||
void GetSize(GetSizeCallback callback) override {
|
||||
size_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
|
||||
if (body_func_.IsEmpty()) {
|
||||
LOG(ERROR) << "Tried to read twice from a JSChunkedDataPipeGetter";
|
||||
// Drop the handle on the floor.
|
||||
return;
|
||||
}
|
||||
data_producer_ = std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
|
||||
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
v8::MicrotasksScope script_scope(isolate_,
|
||||
v8::MicrotasksScope::kRunMicrotasks);
|
||||
auto maybe_wrapper = GetWrapper(isolate_);
|
||||
v8::Local<v8::Value> wrapper;
|
||||
if (!maybe_wrapper.ToLocal(&wrapper)) {
|
||||
return;
|
||||
}
|
||||
v8::Local<v8::Value> argv[] = {wrapper};
|
||||
node::Environment* env = node::Environment::GetCurrent(isolate_);
|
||||
auto global = env->context()->Global();
|
||||
node::MakeCallback(isolate_, global, body_func_.Get(isolate_),
|
||||
node::arraysize(argv), argv, {0, 0});
|
||||
}
|
||||
|
||||
v8::Local<v8::Promise> WriteChunk(v8::Local<v8::Value> buffer_val) {
|
||||
gin_helper::Promise<void> promise(isolate_);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
if (!buffer_val->IsArrayBufferView()) {
|
||||
promise.RejectWithErrorMessage("Expected an ArrayBufferView");
|
||||
return handle;
|
||||
}
|
||||
if (is_writing_) {
|
||||
promise.RejectWithErrorMessage("Only one write can be pending at a time");
|
||||
return handle;
|
||||
}
|
||||
if (!size_callback_) {
|
||||
promise.RejectWithErrorMessage("Can't write after calling done()");
|
||||
return handle;
|
||||
}
|
||||
auto buffer = buffer_val.As<v8::ArrayBufferView>();
|
||||
is_writing_ = true;
|
||||
bytes_written_ += buffer->ByteLength();
|
||||
auto backing_store = buffer->Buffer()->GetBackingStore();
|
||||
auto buffer_span = base::make_span(
|
||||
static_cast<char*>(backing_store->Data()) + buffer->ByteOffset(),
|
||||
buffer->ByteLength());
|
||||
auto buffer_source = std::make_unique<BufferDataSource>(buffer_span);
|
||||
data_producer_->Write(
|
||||
std::move(buffer_source),
|
||||
base::BindOnce(&JSChunkedDataPipeGetter::OnWriteChunkComplete,
|
||||
// We're OK to use Unretained here because we own
|
||||
// |data_producer_|.
|
||||
base::Unretained(this), std::move(promise)));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void OnWriteChunkComplete(gin_helper::Promise<void> promise,
|
||||
MojoResult result) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
is_writing_ = false;
|
||||
if (result == MOJO_RESULT_OK) {
|
||||
promise.Resolve();
|
||||
} else {
|
||||
promise.RejectWithErrorMessage("mojo result not ok");
|
||||
Finished();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO(nornagon): accept a net error here to allow the data provider to
|
||||
// cancel the request with an error.
|
||||
void Done() {
|
||||
if (size_callback_) {
|
||||
std::move(size_callback_).Run(net::OK, bytes_written_);
|
||||
Finished();
|
||||
}
|
||||
}
|
||||
|
||||
void Finished() {
|
||||
size_callback_.Reset();
|
||||
body_func_.Reset();
|
||||
receiver_.reset();
|
||||
data_producer_.reset();
|
||||
}
|
||||
|
||||
GetSizeCallback size_callback_;
|
||||
mojo::Receiver<network::mojom::ChunkedDataPipeGetter> receiver_{this};
|
||||
std::unique_ptr<mojo::DataPipeProducer> data_producer_;
|
||||
bool is_writing_ = false;
|
||||
uint64_t bytes_written_ = 0;
|
||||
|
||||
v8::Isolate* isolate_;
|
||||
v8::Global<v8::Function> body_func_;
|
||||
};
|
||||
|
||||
gin::WrapperInfo JSChunkedDataPipeGetter::kWrapperInfo = {
|
||||
gin::kEmbedderNativeGin};
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace {
|
||||
|
||||
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
|
||||
net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
|
||||
semantics {
|
||||
sender: "Electron Net module"
|
||||
description:
|
||||
"Issue HTTP/HTTPS requests using Chromium's native networking "
|
||||
"library."
|
||||
trigger: "Using the Net module"
|
||||
data: "Anything the user wants to send."
|
||||
destination: OTHER
|
||||
}
|
||||
policy {
|
||||
cookies_allowed: YES
|
||||
cookies_store: "user"
|
||||
setting: "This feature cannot be disabled."
|
||||
})");
|
||||
|
||||
base::IDMap<SimpleURLLoaderWrapper*>& GetAllRequests() {
|
||||
static base::NoDestructor<base::IDMap<SimpleURLLoaderWrapper*>>
|
||||
s_all_requests;
|
||||
return *s_all_requests;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
SimpleURLLoaderWrapper::SimpleURLLoaderWrapper(
|
||||
std::unique_ptr<network::ResourceRequest> request,
|
||||
network::mojom::URLLoaderFactory* url_loader_factory)
|
||||
: id_(GetAllRequests().Add(this)) {
|
||||
// We slightly abuse the |render_frame_id| field in ResourceRequest so that
|
||||
// we can correlate any authentication events that arrive with this request.
|
||||
request->render_frame_id = id_;
|
||||
|
||||
// SimpleURLLoader wants to control the request body itself. We have other
|
||||
// ideas.
|
||||
auto request_body = std::move(request->request_body);
|
||||
auto* request_ref = request.get();
|
||||
loader_ =
|
||||
network::SimpleURLLoader::Create(std::move(request), kTrafficAnnotation);
|
||||
if (request_body) {
|
||||
request_ref->request_body = std::move(request_body);
|
||||
}
|
||||
|
||||
loader_->SetAllowHttpErrorResults(true);
|
||||
loader_->SetOnResponseStartedCallback(base::BindOnce(
|
||||
&SimpleURLLoaderWrapper::OnResponseStarted, base::Unretained(this)));
|
||||
loader_->SetOnRedirectCallback(base::BindRepeating(
|
||||
&SimpleURLLoaderWrapper::OnRedirect, base::Unretained(this)));
|
||||
loader_->SetOnUploadProgressCallback(base::BindRepeating(
|
||||
&SimpleURLLoaderWrapper::OnUploadProgress, base::Unretained(this)));
|
||||
loader_->SetOnDownloadProgressCallback(base::BindRepeating(
|
||||
&SimpleURLLoaderWrapper::OnDownloadProgress, base::Unretained(this)));
|
||||
|
||||
loader_->DownloadAsStream(url_loader_factory, this);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::Pin() {
|
||||
// Prevent ourselves from being GC'd until the request is complete.
|
||||
// Must be called after InitWithArgs(), otherwise GetWrapper() isn't
|
||||
// initialized.
|
||||
pinned_wrapper_.Reset(isolate(), GetWrapper());
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::PinBodyGetter(v8::Local<v8::Value> body_getter) {
|
||||
pinned_chunk_pipe_getter_.Reset(isolate(), body_getter);
|
||||
}
|
||||
|
||||
SimpleURLLoaderWrapper::~SimpleURLLoaderWrapper() {
|
||||
GetAllRequests().Remove(id_);
|
||||
}
|
||||
|
||||
// static
|
||||
SimpleURLLoaderWrapper* SimpleURLLoaderWrapper::FromID(uint32_t id) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
return GetAllRequests().Lookup(id);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
|
||||
std::move(auth_challenge_responder));
|
||||
// WeakPtr because if we're Cancel()ed while waiting for auth, and the
|
||||
// network service also decides to cancel at the same time and kill this
|
||||
// pipe, we might end up trying to call Cancel again on dead memory.
|
||||
auth_responder.set_disconnect_handler(base::BindOnce(
|
||||
&SimpleURLLoaderWrapper::Cancel, weak_factory_.GetWeakPtr()));
|
||||
auto cb = base::BindOnce(
|
||||
[](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
|
||||
gin::Arguments* args) {
|
||||
base::string16 username_str, password_str;
|
||||
if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
|
||||
auth_responder->OnAuthCredentials(base::nullopt);
|
||||
return;
|
||||
}
|
||||
auth_responder->OnAuthCredentials(
|
||||
net::AuthCredentials(username_str, password_str));
|
||||
},
|
||||
std::move(auth_responder));
|
||||
Emit("login", auth_info, base::AdaptCallbackForRepeating(std::move(cb)));
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::Cancel() {
|
||||
loader_.reset();
|
||||
pinned_wrapper_.Reset();
|
||||
pinned_chunk_pipe_getter_.Reset();
|
||||
// This ensures that no further callbacks will be called, so there's no need
|
||||
// for additional guards.
|
||||
}
|
||||
|
||||
// static
|
||||
mate::WrappableBase* SimpleURLLoaderWrapper::New(gin::Arguments* args) {
|
||||
gin_helper::Dictionary opts;
|
||||
if (!args->GetNext(&opts)) {
|
||||
args->ThrowTypeError("Expected a dictionary");
|
||||
return nullptr;
|
||||
}
|
||||
auto request = std::make_unique<network::ResourceRequest>();
|
||||
opts.Get("method", &request->method);
|
||||
opts.Get("url", &request->url);
|
||||
std::map<std::string, std::string> extra_headers;
|
||||
if (opts.Get("extraHeaders", &extra_headers)) {
|
||||
for (const auto& it : extra_headers) {
|
||||
if (!net::HttpUtil::IsValidHeaderName(it.first) ||
|
||||
!net::HttpUtil::IsValidHeaderValue(it.second)) {
|
||||
args->ThrowTypeError("Invalid header name or value");
|
||||
return nullptr;
|
||||
}
|
||||
request->headers.SetHeader(it.first, it.second);
|
||||
}
|
||||
}
|
||||
|
||||
v8::Local<v8::Value> body;
|
||||
v8::Local<v8::Value> chunk_pipe_getter;
|
||||
if (opts.Get("body", &body)) {
|
||||
if (body->IsArrayBufferView()) {
|
||||
auto buffer_body = body.As<v8::ArrayBufferView>();
|
||||
auto backing_store = buffer_body->Buffer()->GetBackingStore();
|
||||
request->request_body = network::ResourceRequestBody::CreateFromBytes(
|
||||
static_cast<char*>(backing_store->Data()) + buffer_body->ByteOffset(),
|
||||
buffer_body->ByteLength());
|
||||
} else if (body->IsFunction()) {
|
||||
auto body_func = body.As<v8::Function>();
|
||||
|
||||
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
|
||||
data_pipe_getter;
|
||||
chunk_pipe_getter = JSChunkedDataPipeGetter::Create(
|
||||
args->isolate(), body_func,
|
||||
data_pipe_getter.InitWithNewPipeAndPassReceiver())
|
||||
.ToV8();
|
||||
request->request_body = new network::ResourceRequestBody();
|
||||
request->request_body->SetToChunkedDataPipe(std::move(data_pipe_getter));
|
||||
}
|
||||
}
|
||||
|
||||
std::string partition;
|
||||
gin::Handle<Session> session;
|
||||
if (!opts.Get("session", &session)) {
|
||||
if (opts.Get("partition", &partition))
|
||||
session = Session::FromPartition(args->isolate(), partition);
|
||||
else // default session
|
||||
session = Session::FromPartition(args->isolate(), "");
|
||||
}
|
||||
|
||||
auto url_loader_factory = session->browser_context()->GetURLLoaderFactory();
|
||||
|
||||
auto* ret =
|
||||
new SimpleURLLoaderWrapper(std::move(request), url_loader_factory.get());
|
||||
ret->InitWithArgs(args);
|
||||
ret->Pin();
|
||||
if (!chunk_pipe_getter.IsEmpty()) {
|
||||
ret->PinBodyGetter(chunk_pipe_getter);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnDataReceived(base::StringPiece string_piece,
|
||||
base::OnceClosure resume) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
auto array_buffer = v8::ArrayBuffer::New(isolate(), string_piece.size());
|
||||
auto backing_store = array_buffer->GetBackingStore();
|
||||
memcpy(backing_store->Data(), string_piece.data(), string_piece.size());
|
||||
Emit("data", array_buffer);
|
||||
std::move(resume).Run();
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnComplete(bool success) {
|
||||
if (success) {
|
||||
Emit("complete");
|
||||
} else {
|
||||
Emit("error", net::ErrorToString(loader_->NetError()));
|
||||
}
|
||||
loader_.reset();
|
||||
pinned_wrapper_.Reset();
|
||||
pinned_chunk_pipe_getter_.Reset();
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnRetry(base::OnceClosure start_retry) {}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnResponseStarted(
|
||||
const GURL& final_url,
|
||||
const network::mojom::URLResponseHead& response_head) {
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate());
|
||||
dict.Set("statusCode", response_head.headers->response_code());
|
||||
dict.Set("statusMessage", response_head.headers->GetStatusText());
|
||||
dict.Set("headers", response_head.headers.get());
|
||||
dict.Set("httpVersion", response_head.headers->GetHttpVersion());
|
||||
Emit("response-started", final_url, dict);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnRedirect(
|
||||
const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& response_head,
|
||||
std::vector<std::string>* removed_headers) {
|
||||
Emit("redirect", redirect_info, response_head.headers.get());
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnUploadProgress(uint64_t position,
|
||||
uint64_t total) {
|
||||
Emit("upload-progress", position, total);
|
||||
}
|
||||
|
||||
void SimpleURLLoaderWrapper::OnDownloadProgress(uint64_t current) {
|
||||
Emit("download-progress", current);
|
||||
}
|
||||
|
||||
// static
|
||||
void SimpleURLLoaderWrapper::BuildPrototype(
|
||||
v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
prototype->SetClassName(gin::StringToV8(isolate, "SimpleURLLoaderWrapper"));
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetMethod("cancel", &SimpleURLLoaderWrapper::Cancel);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
93
shell/browser/api/atom_api_url_loader.h
Normal file
93
shell/browser/api/atom_api_url_loader.h
Normal file
@@ -0,0 +1,93 @@
|
||||
// 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 SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
|
||||
#define SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "net/base/auth.h"
|
||||
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "services/network/public/mojom/url_loader_factory.mojom-forward.h"
|
||||
#include "services/network/public/mojom/url_response_head.mojom.h"
|
||||
#include "shell/common/gin_helper/event_emitter.h"
|
||||
#include "url/gurl.h"
|
||||
#include "v8/include/v8.h"
|
||||
|
||||
namespace gin {
|
||||
class Arguments;
|
||||
}
|
||||
|
||||
namespace network {
|
||||
class SimpleURLLoader;
|
||||
struct ResourceRequest;
|
||||
} // namespace network
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
/** Wraps a SimpleURLLoader to make it usable from JavaScript */
|
||||
class SimpleURLLoaderWrapper
|
||||
: public gin_helper::EventEmitter<SimpleURLLoaderWrapper>,
|
||||
public network::SimpleURLLoaderStreamConsumer {
|
||||
public:
|
||||
~SimpleURLLoaderWrapper() override;
|
||||
static mate::WrappableBase* New(gin::Arguments* args);
|
||||
|
||||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
|
||||
static SimpleURLLoaderWrapper* FromID(uint32_t id);
|
||||
|
||||
void OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder);
|
||||
|
||||
void Cancel();
|
||||
|
||||
private:
|
||||
SimpleURLLoaderWrapper(std::unique_ptr<network::ResourceRequest> loader,
|
||||
network::mojom::URLLoaderFactory* url_loader_factory);
|
||||
|
||||
// SimpleURLLoaderStreamConsumer:
|
||||
void OnDataReceived(base::StringPiece string_piece,
|
||||
base::OnceClosure resume) override;
|
||||
void OnComplete(bool success) override;
|
||||
void OnRetry(base::OnceClosure start_retry) override;
|
||||
|
||||
// SimpleURLLoader callbacks
|
||||
void OnResponseStarted(const GURL& final_url,
|
||||
const network::mojom::URLResponseHead& response_head);
|
||||
void OnRedirect(const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& response_head,
|
||||
std::vector<std::string>* removed_headers);
|
||||
void OnUploadProgress(uint64_t position, uint64_t total);
|
||||
void OnDownloadProgress(uint64_t current);
|
||||
|
||||
void Start();
|
||||
void Pin();
|
||||
void PinBodyGetter(v8::Local<v8::Value>);
|
||||
|
||||
uint32_t id_;
|
||||
std::unique_ptr<network::SimpleURLLoader> loader_;
|
||||
v8::Global<v8::Value> pinned_wrapper_;
|
||||
v8::Global<v8::Value> pinned_chunk_pipe_getter_;
|
||||
|
||||
base::WeakPtrFactory<SimpleURLLoaderWrapper> weak_factory_{this};
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_API_ATOM_API_URL_LOADER_H_
|
||||
@@ -1,595 +0,0 @@
|
||||
// Copyright (c) 2019 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/api/atom_api_url_request.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/containers/id_map.h"
|
||||
#include "gin/handle.h"
|
||||
#include "mojo/public/cpp/bindings/receiver_set.h"
|
||||
#include "mojo/public/cpp/system/string_data_source.h"
|
||||
#include "net/http/http_util.h"
|
||||
#include "services/network/public/mojom/chunked_data_pipe_getter.mojom.h"
|
||||
#include "shell/browser/api/atom_api_session.h"
|
||||
#include "shell/browser/atom_browser_context.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/net_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/event_emitter_caller.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
namespace gin {
|
||||
|
||||
template <>
|
||||
struct Converter<network::mojom::RedirectMode> {
|
||||
static bool FromV8(v8::Isolate* isolate,
|
||||
v8::Local<v8::Value> val,
|
||||
network::mojom::RedirectMode* out) {
|
||||
std::string mode;
|
||||
if (!ConvertFromV8(isolate, val, &mode))
|
||||
return false;
|
||||
if (mode == "follow")
|
||||
*out = network::mojom::RedirectMode::kFollow;
|
||||
else if (mode == "error")
|
||||
*out = network::mojom::RedirectMode::kError;
|
||||
else if (mode == "manual")
|
||||
*out = network::mojom::RedirectMode::kManual;
|
||||
else
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
namespace {
|
||||
|
||||
base::IDMap<URLRequest*>& GetAllRequests() {
|
||||
static base::NoDestructor<base::IDMap<URLRequest*>> s_all_requests;
|
||||
return *s_all_requests;
|
||||
}
|
||||
|
||||
// Network state for request and response.
|
||||
enum State {
|
||||
STATE_STARTED = 1 << 0,
|
||||
STATE_FINISHED = 1 << 1,
|
||||
STATE_CANCELED = 1 << 2,
|
||||
STATE_FAILED = 1 << 3,
|
||||
STATE_CLOSED = 1 << 4,
|
||||
STATE_ERROR = STATE_CANCELED | STATE_FAILED | STATE_CLOSED,
|
||||
};
|
||||
|
||||
// Annotation tag passed to NetworkService.
|
||||
const net::NetworkTrafficAnnotationTag kTrafficAnnotation =
|
||||
net::DefineNetworkTrafficAnnotation("electron_net_module", R"(
|
||||
semantics {
|
||||
sender: "Electron Net module"
|
||||
description:
|
||||
"Issue HTTP/HTTPS requests using Chromium's native networking "
|
||||
"library."
|
||||
trigger: "Using the Net module"
|
||||
data: "Anything the user wants to send."
|
||||
destination: OTHER
|
||||
}
|
||||
policy {
|
||||
cookies_allowed: YES
|
||||
cookies_store: "user"
|
||||
setting: "This feature cannot be disabled."
|
||||
})");
|
||||
|
||||
} // namespace
|
||||
|
||||
// Common class for streaming data.
|
||||
class UploadDataPipeGetter {
|
||||
public:
|
||||
explicit UploadDataPipeGetter(URLRequest* request) : request_(request) {}
|
||||
virtual ~UploadDataPipeGetter() = default;
|
||||
|
||||
virtual void AttachToRequestBody(network::ResourceRequestBody* body) = 0;
|
||||
|
||||
protected:
|
||||
void SetCallback(network::mojom::DataPipeGetter::ReadCallback callback) {
|
||||
request_->size_callback_ = std::move(callback);
|
||||
}
|
||||
|
||||
void SetPipe(mojo::ScopedDataPipeProducerHandle pipe) {
|
||||
request_->producer_ =
|
||||
std::make_unique<mojo::DataPipeProducer>(std::move(pipe));
|
||||
request_->StartWriting();
|
||||
}
|
||||
|
||||
private:
|
||||
URLRequest* request_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(UploadDataPipeGetter);
|
||||
};
|
||||
|
||||
// Streaming multipart data to NetworkService.
|
||||
class MultipartDataPipeGetter : public UploadDataPipeGetter,
|
||||
public network::mojom::DataPipeGetter {
|
||||
public:
|
||||
explicit MultipartDataPipeGetter(URLRequest* request)
|
||||
: UploadDataPipeGetter(request) {}
|
||||
~MultipartDataPipeGetter() override = default;
|
||||
|
||||
void AttachToRequestBody(network::ResourceRequestBody* body) override {
|
||||
mojo::PendingRemote<network::mojom::DataPipeGetter> data_pipe_getter_remote;
|
||||
receivers_.Add(this,
|
||||
data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
|
||||
body->AppendDataPipe(std::move(data_pipe_getter_remote));
|
||||
}
|
||||
|
||||
private:
|
||||
// network::mojom::DataPipeGetter:
|
||||
void Read(mojo::ScopedDataPipeProducerHandle pipe,
|
||||
ReadCallback callback) override {
|
||||
SetCallback(std::move(callback));
|
||||
SetPipe(std::move(pipe));
|
||||
}
|
||||
|
||||
void Clone(
|
||||
mojo::PendingReceiver<network::mojom::DataPipeGetter> receiver) override {
|
||||
receivers_.Add(this, std::move(receiver));
|
||||
}
|
||||
|
||||
mojo::ReceiverSet<network::mojom::DataPipeGetter> receivers_;
|
||||
};
|
||||
|
||||
// Streaming chunked data to NetworkService.
|
||||
class ChunkedDataPipeGetter : public UploadDataPipeGetter,
|
||||
public network::mojom::ChunkedDataPipeGetter {
|
||||
public:
|
||||
explicit ChunkedDataPipeGetter(URLRequest* request)
|
||||
: UploadDataPipeGetter(request) {}
|
||||
~ChunkedDataPipeGetter() override = default;
|
||||
|
||||
void AttachToRequestBody(network::ResourceRequestBody* body) override {
|
||||
mojo::PendingRemote<network::mojom::ChunkedDataPipeGetter>
|
||||
data_pipe_getter_remote;
|
||||
receiver_set_.Add(this,
|
||||
data_pipe_getter_remote.InitWithNewPipeAndPassReceiver());
|
||||
body->SetToChunkedDataPipe(std::move(data_pipe_getter_remote));
|
||||
}
|
||||
|
||||
private:
|
||||
// network::mojom::ChunkedDataPipeGetter:
|
||||
void GetSize(GetSizeCallback callback) override {
|
||||
SetCallback(std::move(callback));
|
||||
}
|
||||
|
||||
void StartReading(mojo::ScopedDataPipeProducerHandle pipe) override {
|
||||
SetPipe(std::move(pipe));
|
||||
}
|
||||
|
||||
mojo::ReceiverSet<network::mojom::ChunkedDataPipeGetter> receiver_set_;
|
||||
};
|
||||
|
||||
URLRequest::URLRequest(gin::Arguments* args)
|
||||
: id_(GetAllRequests().Add(this)), weak_factory_(this) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
request_ = std::make_unique<network::ResourceRequest>();
|
||||
gin_helper::Dictionary dict;
|
||||
if (args->GetNext(&dict)) {
|
||||
dict.Get("method", &request_->method);
|
||||
dict.Get("url", &request_->url);
|
||||
dict.Get("redirect", &redirect_mode_);
|
||||
request_->redirect_mode = redirect_mode_;
|
||||
}
|
||||
|
||||
request_->render_frame_id = id_;
|
||||
|
||||
std::string partition;
|
||||
gin::Handle<api::Session> session;
|
||||
if (!dict.Get("session", &session)) {
|
||||
if (dict.Get("partition", &partition))
|
||||
session = Session::FromPartition(args->isolate(), partition);
|
||||
else // default session
|
||||
session = Session::FromPartition(args->isolate(), "");
|
||||
}
|
||||
|
||||
url_loader_factory_ = session->browser_context()->GetURLLoaderFactory();
|
||||
|
||||
InitWithArgs(args);
|
||||
}
|
||||
|
||||
URLRequest::~URLRequest() = default;
|
||||
|
||||
URLRequest* URLRequest::FromID(uint32_t id) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
return GetAllRequests().Lookup(id);
|
||||
}
|
||||
|
||||
void URLRequest::OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder(
|
||||
std::move(auth_challenge_responder));
|
||||
auth_responder.set_disconnect_handler(
|
||||
base::BindOnce(&URLRequest::Cancel, weak_factory_.GetWeakPtr()));
|
||||
auto cb = base::BindOnce(
|
||||
[](mojo::Remote<network::mojom::AuthChallengeResponder> auth_responder,
|
||||
gin::Arguments* args) {
|
||||
base::string16 username_str, password_str;
|
||||
if (!args->GetNext(&username_str) || !args->GetNext(&password_str)) {
|
||||
auth_responder->OnAuthCredentials(base::nullopt);
|
||||
return;
|
||||
}
|
||||
auth_responder->OnAuthCredentials(
|
||||
net::AuthCredentials(username_str, password_str));
|
||||
},
|
||||
std::move(auth_responder));
|
||||
Emit("login", auth_info, base::AdaptCallbackForRepeating(std::move(cb)));
|
||||
}
|
||||
|
||||
bool URLRequest::NotStarted() const {
|
||||
return request_state_ == 0;
|
||||
}
|
||||
|
||||
bool URLRequest::Finished() const {
|
||||
return request_state_ & STATE_FINISHED;
|
||||
}
|
||||
|
||||
void URLRequest::Cancel() {
|
||||
// Cancel only once.
|
||||
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
|
||||
return;
|
||||
|
||||
// Mark as canceled.
|
||||
request_state_ |= STATE_CANCELED;
|
||||
EmitEvent(EventType::kRequest, true, "abort");
|
||||
|
||||
if ((response_state_ & STATE_STARTED) && !(response_state_ & STATE_FINISHED))
|
||||
EmitEvent(EventType::kResponse, true, "aborted");
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
void URLRequest::Close() {
|
||||
if (!(request_state_ & STATE_CLOSED)) {
|
||||
request_state_ |= STATE_CLOSED;
|
||||
if (response_state_ & STATE_STARTED) {
|
||||
// Emit a close event if we really have a response object.
|
||||
EmitEvent(EventType::kResponse, true, "close");
|
||||
}
|
||||
EmitEvent(EventType::kRequest, true, "close");
|
||||
}
|
||||
Unpin();
|
||||
loader_.reset();
|
||||
}
|
||||
|
||||
bool URLRequest::Write(v8::Local<v8::Value> data, bool is_last) {
|
||||
if (request_state_ & (STATE_FINISHED | STATE_ERROR))
|
||||
return false;
|
||||
|
||||
size_t length = node::Buffer::Length(data);
|
||||
|
||||
if (!loader_) {
|
||||
// Pin on first write.
|
||||
request_state_ = STATE_STARTED;
|
||||
Pin();
|
||||
|
||||
// Create the loader.
|
||||
network::ResourceRequest* request_ref = request_.get();
|
||||
loader_ = network::SimpleURLLoader::Create(std::move(request_),
|
||||
kTrafficAnnotation);
|
||||
loader_->SetOnResponseStartedCallback(
|
||||
base::Bind(&URLRequest::OnResponseStarted, weak_factory_.GetWeakPtr()));
|
||||
loader_->SetOnRedirectCallback(
|
||||
base::Bind(&URLRequest::OnRedirect, weak_factory_.GetWeakPtr()));
|
||||
loader_->SetOnUploadProgressCallback(
|
||||
base::Bind(&URLRequest::OnUploadProgress, weak_factory_.GetWeakPtr()));
|
||||
|
||||
// Create upload data pipe if we have data to write.
|
||||
if (length > 0) {
|
||||
request_ref->request_body = new network::ResourceRequestBody();
|
||||
if (is_chunked_upload_)
|
||||
data_pipe_getter_ = std::make_unique<ChunkedDataPipeGetter>(this);
|
||||
else
|
||||
data_pipe_getter_ = std::make_unique<MultipartDataPipeGetter>(this);
|
||||
data_pipe_getter_->AttachToRequestBody(request_ref->request_body.get());
|
||||
}
|
||||
|
||||
// Start downloading.
|
||||
loader_->DownloadAsStream(url_loader_factory_.get(), this);
|
||||
}
|
||||
|
||||
if (length > 0)
|
||||
pending_writes_.emplace_back(node::Buffer::Data(data), length);
|
||||
|
||||
if (is_last) {
|
||||
// The ElementsUploadDataStream requires the knowledge of content length
|
||||
// before doing upload, while Node's stream does not give us any size
|
||||
// information. So the only option left for us is to keep all the write
|
||||
// data in memory and flush them after the write is done.
|
||||
//
|
||||
// While this looks frustrating, it is actually the behavior of the non-
|
||||
// NetworkService implementation, and we are not breaking anything.
|
||||
if (!pending_writes_.empty()) {
|
||||
last_chunk_written_ = true;
|
||||
StartWriting();
|
||||
}
|
||||
|
||||
request_state_ |= STATE_FINISHED;
|
||||
EmitEvent(EventType::kRequest, true, "finish");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void URLRequest::FollowRedirect() {
|
||||
if (request_state_ & (STATE_CANCELED | STATE_CLOSED))
|
||||
return;
|
||||
follow_redirect_ = true;
|
||||
}
|
||||
|
||||
bool URLRequest::SetExtraHeader(const std::string& name,
|
||||
const std::string& value) {
|
||||
if (!request_)
|
||||
return false;
|
||||
if (!net::HttpUtil::IsValidHeaderName(name))
|
||||
return false;
|
||||
if (!net::HttpUtil::IsValidHeaderValue(value))
|
||||
return false;
|
||||
request_->headers.SetHeader(name, value);
|
||||
return true;
|
||||
}
|
||||
|
||||
void URLRequest::RemoveExtraHeader(const std::string& name) {
|
||||
if (request_)
|
||||
request_->headers.RemoveHeader(name);
|
||||
}
|
||||
|
||||
void URLRequest::SetChunkedUpload(bool is_chunked_upload) {
|
||||
if (request_)
|
||||
is_chunked_upload_ = is_chunked_upload;
|
||||
}
|
||||
|
||||
gin::Dictionary URLRequest::GetUploadProgress() {
|
||||
gin::Dictionary progress = gin::Dictionary::CreateEmpty(isolate());
|
||||
if (loader_) {
|
||||
if (request_)
|
||||
progress.Set("started", false);
|
||||
else
|
||||
progress.Set("started", true);
|
||||
progress.Set("current", upload_position_);
|
||||
progress.Set("total", upload_total_);
|
||||
progress.Set("active", true);
|
||||
} else {
|
||||
progress.Set("active", false);
|
||||
}
|
||||
return progress;
|
||||
}
|
||||
|
||||
int URLRequest::StatusCode() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->response_code();
|
||||
return -1;
|
||||
}
|
||||
|
||||
std::string URLRequest::StatusMessage() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->GetStatusText();
|
||||
return "";
|
||||
}
|
||||
|
||||
net::HttpResponseHeaders* URLRequest::RawResponseHeaders() const {
|
||||
return response_headers_.get();
|
||||
}
|
||||
|
||||
uint32_t URLRequest::ResponseHttpVersionMajor() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->GetHttpVersion().major_value();
|
||||
return 0;
|
||||
}
|
||||
|
||||
uint32_t URLRequest::ResponseHttpVersionMinor() const {
|
||||
if (response_headers_)
|
||||
return response_headers_->GetHttpVersion().minor_value();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void URLRequest::OnDataReceived(base::StringPiece data,
|
||||
base::OnceClosure resume) {
|
||||
// In case we received an unexpected event from Chromium net, don't emit any
|
||||
// data event after request cancel/error/close.
|
||||
if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
v8::Local<v8::Value> buffer;
|
||||
auto maybe = node::Buffer::Copy(isolate(), data.data(), data.size());
|
||||
if (maybe.ToLocal(&buffer))
|
||||
Emit("data", buffer);
|
||||
}
|
||||
std::move(resume).Run();
|
||||
}
|
||||
|
||||
void URLRequest::OnRetry(base::OnceClosure start_retry) {}
|
||||
|
||||
void URLRequest::OnComplete(bool success) {
|
||||
if (success) {
|
||||
// In case we received an unexpected event from Chromium net, don't emit any
|
||||
// data event after request cancel/error/close.
|
||||
if (!(request_state_ & STATE_ERROR) && !(response_state_ & STATE_ERROR)) {
|
||||
response_state_ |= STATE_FINISHED;
|
||||
Emit("end");
|
||||
}
|
||||
} else { // failed
|
||||
// If response is started then emit response event, else emit request error.
|
||||
//
|
||||
// Error is only emitted when there is no previous failure. This is to align
|
||||
// with the behavior of non-NetworkService implementation.
|
||||
std::string error = net::ErrorToString(loader_->NetError());
|
||||
if (response_state_ & STATE_STARTED) {
|
||||
if (!(response_state_ & STATE_FAILED))
|
||||
EmitError(EventType::kResponse, error);
|
||||
} else {
|
||||
if (!(request_state_ & STATE_FAILED))
|
||||
EmitError(EventType::kRequest, error);
|
||||
}
|
||||
}
|
||||
|
||||
Close();
|
||||
}
|
||||
|
||||
void URLRequest::OnResponseStarted(
|
||||
const GURL& final_url,
|
||||
const network::mojom::URLResponseHead& response_head) {
|
||||
// Don't emit any event after request cancel.
|
||||
if (request_state_ & STATE_ERROR)
|
||||
return;
|
||||
|
||||
response_headers_ = response_head.headers;
|
||||
response_state_ |= STATE_STARTED;
|
||||
Emit("response");
|
||||
}
|
||||
|
||||
void URLRequest::OnRedirect(
|
||||
const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& response_head,
|
||||
std::vector<std::string>* to_be_removed_headers) {
|
||||
if (!loader_)
|
||||
return;
|
||||
|
||||
if (request_state_ & (STATE_CLOSED | STATE_CANCELED)) {
|
||||
NOTREACHED();
|
||||
Cancel();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (redirect_mode_) {
|
||||
case network::mojom::RedirectMode::kError:
|
||||
Cancel();
|
||||
EmitError(
|
||||
EventType::kRequest,
|
||||
"Request cannot follow redirect with the current redirect mode");
|
||||
break;
|
||||
case network::mojom::RedirectMode::kManual:
|
||||
// When redirect mode is "manual", the user has to explicitly call the
|
||||
// FollowRedirect method to continue redirecting, otherwise the request
|
||||
// would be cancelled.
|
||||
//
|
||||
// Note that the SimpleURLLoader always calls FollowRedirect and does not
|
||||
// provide a formal way for us to cancel redirection, we have to cancel
|
||||
// the request to prevent the redirection.
|
||||
follow_redirect_ = false;
|
||||
EmitEvent(EventType::kRequest, false, "redirect",
|
||||
redirect_info.status_code, redirect_info.new_method,
|
||||
redirect_info.new_url, response_head.headers.get());
|
||||
if (!follow_redirect_)
|
||||
Cancel();
|
||||
break;
|
||||
case network::mojom::RedirectMode::kFollow:
|
||||
EmitEvent(EventType::kRequest, false, "redirect",
|
||||
redirect_info.status_code, redirect_info.new_method,
|
||||
redirect_info.new_url, response_head.headers.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void URLRequest::OnUploadProgress(uint64_t position, uint64_t total) {
|
||||
upload_position_ = position;
|
||||
upload_total_ = total;
|
||||
}
|
||||
|
||||
void URLRequest::OnWrite(MojoResult result) {
|
||||
if (result != MOJO_RESULT_OK)
|
||||
return;
|
||||
|
||||
// Continue the pending writes.
|
||||
pending_writes_.pop_front();
|
||||
if (!pending_writes_.empty())
|
||||
DoWrite();
|
||||
}
|
||||
|
||||
void URLRequest::DoWrite() {
|
||||
DCHECK(producer_);
|
||||
DCHECK(!pending_writes_.empty());
|
||||
producer_->Write(
|
||||
std::make_unique<mojo::StringDataSource>(
|
||||
pending_writes_.front(), mojo::StringDataSource::AsyncWritingMode::
|
||||
STRING_STAYS_VALID_UNTIL_COMPLETION),
|
||||
base::BindOnce(&URLRequest::OnWrite, weak_factory_.GetWeakPtr()));
|
||||
}
|
||||
|
||||
void URLRequest::StartWriting() {
|
||||
if (!last_chunk_written_ || size_callback_.is_null())
|
||||
return;
|
||||
|
||||
size_t size = 0;
|
||||
for (const auto& data : pending_writes_)
|
||||
size += data.size();
|
||||
std::move(size_callback_).Run(net::OK, size);
|
||||
DoWrite();
|
||||
}
|
||||
|
||||
void URLRequest::Pin() {
|
||||
if (wrapper_.IsEmpty()) {
|
||||
wrapper_.Reset(isolate(), GetWrapper());
|
||||
}
|
||||
}
|
||||
|
||||
void URLRequest::Unpin() {
|
||||
wrapper_.Reset();
|
||||
}
|
||||
|
||||
void URLRequest::EmitError(EventType type, base::StringPiece message) {
|
||||
if (type == EventType::kRequest)
|
||||
request_state_ |= STATE_FAILED;
|
||||
else
|
||||
response_state_ |= STATE_FAILED;
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
auto error = v8::Exception::Error(gin::StringToV8(isolate(), message));
|
||||
EmitEvent(type, false, "error", error);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void URLRequest::EmitEvent(EventType type, Args&&... args) {
|
||||
const char* method =
|
||||
type == EventType::kRequest ? "_emitRequestEvent" : "_emitResponseEvent";
|
||||
v8::HandleScope handle_scope(isolate());
|
||||
gin_helper::CustomEmit(isolate(), GetWrapper(), method,
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// static
|
||||
mate::WrappableBase* URLRequest::New(gin::Arguments* args) {
|
||||
return new URLRequest(args);
|
||||
}
|
||||
|
||||
// static
|
||||
void URLRequest::BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype) {
|
||||
prototype->SetClassName(gin::StringToV8(isolate, "URLRequest"));
|
||||
gin_helper::Destroyable::MakeDestroyable(isolate, prototype);
|
||||
gin_helper::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate())
|
||||
.SetMethod("write", &URLRequest::Write)
|
||||
.SetMethod("cancel", &URLRequest::Cancel)
|
||||
.SetMethod("setExtraHeader", &URLRequest::SetExtraHeader)
|
||||
.SetMethod("removeExtraHeader", &URLRequest::RemoveExtraHeader)
|
||||
.SetMethod("setChunkedUpload", &URLRequest::SetChunkedUpload)
|
||||
.SetMethod("followRedirect", &URLRequest::FollowRedirect)
|
||||
.SetMethod("getUploadProgress", &URLRequest::GetUploadProgress)
|
||||
.SetProperty("notStarted", &URLRequest::NotStarted)
|
||||
.SetProperty("finished", &URLRequest::Finished)
|
||||
.SetProperty("statusCode", &URLRequest::StatusCode)
|
||||
.SetProperty("statusMessage", &URLRequest::StatusMessage)
|
||||
.SetProperty("rawResponseHeaders", &URLRequest::RawResponseHeaders)
|
||||
.SetProperty("httpVersionMajor", &URLRequest::ResponseHttpVersionMajor)
|
||||
.SetProperty("httpVersionMinor", &URLRequest::ResponseHttpVersionMinor);
|
||||
}
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
@@ -1,156 +0,0 @@
|
||||
// Copyright (c) 2019 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_
|
||||
#define SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_
|
||||
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "gin/arguments.h"
|
||||
#include "gin/dictionary.h"
|
||||
#include "mojo/public/cpp/system/data_pipe_producer.h"
|
||||
#include "services/network/public/cpp/shared_url_loader_factory.h"
|
||||
#include "services/network/public/cpp/simple_url_loader.h"
|
||||
#include "services/network/public/cpp/simple_url_loader_stream_consumer.h"
|
||||
#include "services/network/public/mojom/data_pipe_getter.mojom.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "shell/common/gin_helper/event_emitter.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace api {
|
||||
|
||||
class UploadDataPipeGetter;
|
||||
|
||||
class URLRequest : public gin_helper::EventEmitter<URLRequest>,
|
||||
public network::SimpleURLLoaderStreamConsumer {
|
||||
public:
|
||||
static mate::WrappableBase* New(gin::Arguments* args);
|
||||
|
||||
static void BuildPrototype(v8::Isolate* isolate,
|
||||
v8::Local<v8::FunctionTemplate> prototype);
|
||||
static URLRequest* FromID(uint32_t id);
|
||||
|
||||
void OnAuthRequired(
|
||||
const GURL& url,
|
||||
bool first_auth_attempt,
|
||||
net::AuthChallengeInfo auth_info,
|
||||
network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder);
|
||||
|
||||
protected:
|
||||
explicit URLRequest(gin::Arguments* args);
|
||||
~URLRequest() override;
|
||||
|
||||
bool NotStarted() const;
|
||||
bool Finished() const;
|
||||
|
||||
void Cancel();
|
||||
void Close();
|
||||
|
||||
bool Write(v8::Local<v8::Value> data, bool is_last);
|
||||
void FollowRedirect();
|
||||
bool SetExtraHeader(const std::string& name, const std::string& value);
|
||||
void RemoveExtraHeader(const std::string& name);
|
||||
void SetChunkedUpload(bool is_chunked_upload);
|
||||
gin::Dictionary GetUploadProgress();
|
||||
int StatusCode() const;
|
||||
std::string StatusMessage() const;
|
||||
net::HttpResponseHeaders* RawResponseHeaders() const;
|
||||
uint32_t ResponseHttpVersionMajor() const;
|
||||
uint32_t ResponseHttpVersionMinor() const;
|
||||
|
||||
// SimpleURLLoaderStreamConsumer:
|
||||
void OnDataReceived(base::StringPiece string_piece,
|
||||
base::OnceClosure resume) override;
|
||||
void OnComplete(bool success) override;
|
||||
void OnRetry(base::OnceClosure start_retry) override;
|
||||
|
||||
private:
|
||||
friend class UploadDataPipeGetter;
|
||||
|
||||
void OnResponseStarted(const GURL& final_url,
|
||||
const network::mojom::URLResponseHead& response_head);
|
||||
void OnRedirect(const net::RedirectInfo& redirect_info,
|
||||
const network::mojom::URLResponseHead& response_head,
|
||||
std::vector<std::string>* to_be_removed_headers);
|
||||
void OnUploadProgress(uint64_t position, uint64_t total);
|
||||
void OnWrite(MojoResult result);
|
||||
|
||||
// Write the first data of |pending_writes_|.
|
||||
void DoWrite();
|
||||
|
||||
// Start streaming.
|
||||
void StartWriting();
|
||||
|
||||
// Manage lifetime of wrapper.
|
||||
void Pin();
|
||||
void Unpin();
|
||||
|
||||
// Emit events.
|
||||
enum class EventType {
|
||||
kRequest,
|
||||
kResponse,
|
||||
};
|
||||
void EmitError(EventType type, base::StringPiece error);
|
||||
template <typename... Args>
|
||||
void EmitEvent(EventType type, Args&&... args);
|
||||
|
||||
std::unique_ptr<network::ResourceRequest> request_;
|
||||
std::unique_ptr<network::SimpleURLLoader> loader_;
|
||||
scoped_refptr<network::SharedURLLoaderFactory> url_loader_factory_;
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers_;
|
||||
|
||||
// Redirect mode.
|
||||
//
|
||||
// Note that we store it ourselves instead of reading from the one stored in
|
||||
// |request_|, this is because with multiple redirections, the original one
|
||||
// might be modified.
|
||||
network::mojom::RedirectMode redirect_mode_ =
|
||||
network::mojom::RedirectMode::kFollow;
|
||||
|
||||
// The DataPipeGetter passed to reader.
|
||||
bool is_chunked_upload_ = false;
|
||||
std::unique_ptr<UploadDataPipeGetter> data_pipe_getter_;
|
||||
|
||||
// Passed from DataPipeGetter for streaming data.
|
||||
network::mojom::DataPipeGetter::ReadCallback size_callback_;
|
||||
std::unique_ptr<mojo::DataPipeProducer> producer_;
|
||||
|
||||
// Whether request.end() has been called.
|
||||
bool last_chunk_written_ = false;
|
||||
|
||||
// Whether the redirect should be followed.
|
||||
bool follow_redirect_ = true;
|
||||
|
||||
// Upload progress.
|
||||
uint64_t upload_position_ = 0;
|
||||
uint64_t upload_total_ = 0;
|
||||
|
||||
// Current status.
|
||||
int request_state_ = 0;
|
||||
int response_state_ = 0;
|
||||
|
||||
// Pending writes that not yet sent to NetworkService.
|
||||
std::list<std::string> pending_writes_;
|
||||
|
||||
// Used by pin/unpin to manage lifetime.
|
||||
v8::Global<v8::Object> wrapper_;
|
||||
|
||||
uint32_t id_;
|
||||
|
||||
base::WeakPtrFactory<URLRequest> weak_factory_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(URLRequest);
|
||||
};
|
||||
|
||||
} // namespace api
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // SHELL_BROWSER_API_ATOM_API_URL_REQUEST_H_
|
||||
@@ -1383,6 +1383,12 @@ void WebContents::LoadURL(const GURL& url,
|
||||
params.load_type = content::NavigationController::LOAD_TYPE_DATA;
|
||||
}
|
||||
|
||||
bool reload_ignoring_cache = false;
|
||||
if (options.Get("reloadIgnoringCache", &reload_ignoring_cache) &&
|
||||
reload_ignoring_cache) {
|
||||
params.reload_type = content::ReloadType::BYPASSING_CACHE;
|
||||
}
|
||||
|
||||
// Calling LoadURLWithParams() can trigger JS which destroys |this|.
|
||||
auto weak_this = GetWeakPtr();
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
#include "services/network/public/cpp/features.h"
|
||||
#include "services/network/public/cpp/wrapper_shared_url_loader_factory.h"
|
||||
#include "services/network/public/mojom/network_context.mojom.h"
|
||||
#include "shell/browser/api/atom_api_url_request.h"
|
||||
#include "shell/browser/api/atom_api_url_loader.h"
|
||||
#include "shell/browser/atom_browser_client.h"
|
||||
#include "shell/browser/atom_browser_main_parts.h"
|
||||
#include "shell/browser/atom_download_manager_delegate.h"
|
||||
@@ -362,11 +362,12 @@ class AuthResponder : public network::mojom::TrustedAuthClient {
|
||||
::network::mojom::URLResponseHeadPtr head,
|
||||
mojo::PendingRemote<network::mojom::AuthChallengeResponder>
|
||||
auth_challenge_responder) override {
|
||||
api::URLRequest* url_request = api::URLRequest::FromID(routing_id);
|
||||
if (url_request) {
|
||||
url_request->OnAuthRequired(url, first_auth_attempt, auth_info,
|
||||
std::move(head),
|
||||
std::move(auth_challenge_responder));
|
||||
api::SimpleURLLoaderWrapper* url_loader =
|
||||
api::SimpleURLLoaderWrapper::FromID(routing_id);
|
||||
if (url_loader) {
|
||||
url_loader->OnAuthRequired(url, first_auth_attempt, auth_info,
|
||||
std::move(head),
|
||||
std::move(auth_challenge_responder));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
#include "services/device/public/mojom/constants.mojom.h"
|
||||
#include "services/network/public/cpp/features.h"
|
||||
#include "services/service_manager/public/cpp/connector.h"
|
||||
#include "services/tracing/public/cpp/stack_sampling/tracing_sampler_profiler.h"
|
||||
#include "shell/app/atom_main_delegate.h"
|
||||
#include "shell/browser/api/atom_api_app.h"
|
||||
#include "shell/browser/atom_browser_client.h"
|
||||
@@ -389,6 +390,12 @@ int AtomBrowserMainParts::PreCreateThreads() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
void AtomBrowserMainParts::PostCreateThreads() {
|
||||
base::PostTask(
|
||||
FROM_HERE, {content::BrowserThread::IO},
|
||||
base::BindOnce(&tracing::TracingSamplerProfiler::CreateOnChildThread));
|
||||
}
|
||||
|
||||
void AtomBrowserMainParts::PostDestroyThreads() {
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
extensions_browser_client_.reset();
|
||||
|
||||
@@ -92,6 +92,7 @@ class AtomBrowserMainParts : public content::BrowserMainParts {
|
||||
void PostMainMessageLoopStart() override;
|
||||
void PostMainMessageLoopRun() override;
|
||||
void PreMainMessageLoopStart() override;
|
||||
void PostCreateThreads() override;
|
||||
void PostDestroyThreads() override;
|
||||
|
||||
private:
|
||||
|
||||
@@ -6,13 +6,17 @@
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include "base/command_line.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "content/public/browser/browser_context.h"
|
||||
#include "extensions/browser/extension_navigation_ui_data.h"
|
||||
#include "mojo/public/cpp/bindings/binding.h"
|
||||
#include "net/base/completion_repeating_callback.h"
|
||||
#include "net/base/load_flags.h"
|
||||
#include "net/http/http_util.h"
|
||||
#include "services/network/public/cpp/features.h"
|
||||
#include "shell/browser/net/asar/asar_url_loader.h"
|
||||
#include "shell/common/options_switches.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -689,18 +693,40 @@ ProxyingURLLoaderFactory::ProxyingURLLoaderFactory(
|
||||
|
||||
if (header_client_receiver)
|
||||
url_loader_header_client_receiver_.Bind(std::move(header_client_receiver));
|
||||
|
||||
ignore_connections_limit_domains_ = base::SplitString(
|
||||
base::CommandLine::ForCurrentProcess()->GetSwitchValueASCII(
|
||||
switches::kIgnoreConnectionsLimit),
|
||||
",", base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY);
|
||||
}
|
||||
|
||||
ProxyingURLLoaderFactory::~ProxyingURLLoaderFactory() = default;
|
||||
|
||||
bool ProxyingURLLoaderFactory::ShouldIgnoreConnectionsLimit(
|
||||
const network::ResourceRequest& request) {
|
||||
for (const auto& domain : ignore_connections_limit_domains_) {
|
||||
if (request.url.DomainIs(domain)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ProxyingURLLoaderFactory::CreateLoaderAndStart(
|
||||
mojo::PendingReceiver<network::mojom::URLLoader> loader,
|
||||
int32_t routing_id,
|
||||
int32_t request_id,
|
||||
uint32_t options,
|
||||
const network::ResourceRequest& request,
|
||||
const network::ResourceRequest& original_request,
|
||||
network::mojom::URLLoaderClientPtr client,
|
||||
const net::MutableNetworkTrafficAnnotationTag& traffic_annotation) {
|
||||
// Take a copy so we can mutate the request.
|
||||
network::ResourceRequest request = original_request;
|
||||
|
||||
if (ShouldIgnoreConnectionsLimit(request)) {
|
||||
request.load_flags |= net::LOAD_IGNORE_LIMITS;
|
||||
}
|
||||
|
||||
// Check if user has intercepted this scheme.
|
||||
auto it = intercepted_handlers_.find(request.url.scheme());
|
||||
if (it != intercepted_handlers_.end()) {
|
||||
|
||||
@@ -250,6 +250,8 @@ class ProxyingURLLoaderFactory
|
||||
void RemoveRequest(int32_t network_service_request_id, uint64_t request_id);
|
||||
void MaybeDeleteThis();
|
||||
|
||||
bool ShouldIgnoreConnectionsLimit(const network::ResourceRequest& request);
|
||||
|
||||
// Passed from api::WebRequest.
|
||||
WebRequestAPI* web_request_api_;
|
||||
|
||||
@@ -279,6 +281,8 @@ class ProxyingURLLoaderFactory
|
||||
// internally generated request ID for the same request.
|
||||
std::map<int32_t, uint64_t> network_request_id_to_web_request_id_;
|
||||
|
||||
std::vector<std::string> ignore_connections_limit_domains_;
|
||||
|
||||
DISALLOW_COPY_AND_ASSIGN(ProxyingURLLoaderFactory);
|
||||
};
|
||||
|
||||
|
||||
@@ -50,8 +50,8 @@ END
|
||||
//
|
||||
|
||||
VS_VERSION_INFO VERSIONINFO
|
||||
FILEVERSION 9,0,0,20191119
|
||||
PRODUCTVERSION 9,0,0,20191119
|
||||
FILEVERSION 9,0,0,20191201
|
||||
PRODUCTVERSION 9,0,0,20191201
|
||||
FILEFLAGSMASK 0x3fL
|
||||
#ifdef _DEBUG
|
||||
FILEFLAGS 0x1L
|
||||
|
||||
@@ -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) electron::AtomMenuModel* model;
|
||||
@@ -38,7 +38,7 @@ class AtomMenuModel;
|
||||
- (id)initWithModel:(electron::AtomMenuModel*)model
|
||||
useDefaultAccelerator:(BOOL)use;
|
||||
|
||||
- (void)setCloseCallback:(const base::Callback<void()>&)callback;
|
||||
- (void)setCloseCallback:(base::OnceClosure)callback;
|
||||
|
||||
// Populate current NSMenu with |model|.
|
||||
- (void)populateWithModel:(electron::AtomMenuModel*)model;
|
||||
|
||||
@@ -118,8 +118,8 @@ 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:(electron::AtomMenuModel*)model {
|
||||
@@ -153,7 +153,7 @@ static base::scoped_nsobject<NSMenu> recentDocumentsMenuSwap_;
|
||||
isMenuOpen_ = NO;
|
||||
model_->MenuWillClose();
|
||||
if (!closeCallback.is_null()) {
|
||||
base::PostTask(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(closeCallback));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -385,7 +385,7 @@ 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::PostTask(FROM_HERE, {BrowserThread::UI}, closeCallback);
|
||||
base::PostTask(FROM_HERE, {BrowserThread::UI}, std::move(closeCallback));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
#define SHELL_BROWSER_UI_MESSAGE_BOX_H_
|
||||
|
||||
#include <string>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/callback_forward.h"
|
||||
@@ -29,6 +30,8 @@ enum MessageBoxOptions {
|
||||
MESSAGE_BOX_NO_LINK = 1 << 0,
|
||||
};
|
||||
|
||||
using DialogResult = std::pair<int, bool>;
|
||||
|
||||
struct MessageBoxSettings {
|
||||
electron::NativeWindow* parent_window = nullptr;
|
||||
MessageBoxType type = electron::MessageBoxType::kNone;
|
||||
|
||||
@@ -78,18 +78,18 @@ void MapToCommonID(const std::vector<base::string16>& buttons,
|
||||
}
|
||||
}
|
||||
|
||||
int ShowTaskDialogUTF16(NativeWindow* parent,
|
||||
MessageBoxType type,
|
||||
const std::vector<base::string16>& buttons,
|
||||
int default_id,
|
||||
int cancel_id,
|
||||
int options,
|
||||
const base::string16& title,
|
||||
const base::string16& message,
|
||||
const base::string16& detail,
|
||||
const base::string16& checkbox_label,
|
||||
bool* checkbox_checked,
|
||||
const gfx::ImageSkia& icon) {
|
||||
DialogResult ShowTaskDialogUTF16(NativeWindow* parent,
|
||||
MessageBoxType type,
|
||||
const std::vector<base::string16>& buttons,
|
||||
int default_id,
|
||||
int cancel_id,
|
||||
int options,
|
||||
const base::string16& title,
|
||||
const base::string16& message,
|
||||
const base::string16& detail,
|
||||
const base::string16& checkbox_label,
|
||||
bool checkbox_checked,
|
||||
const gfx::ImageSkia& icon) {
|
||||
TASKDIALOG_FLAGS flags =
|
||||
TDF_SIZE_TO_CONTENT | // Show all content.
|
||||
TDF_ALLOW_DIALOG_CANCELLATION; // Allow canceling the dialog.
|
||||
@@ -148,10 +148,8 @@ int ShowTaskDialogUTF16(NativeWindow* parent,
|
||||
|
||||
if (!checkbox_label.empty()) {
|
||||
config.pszVerificationText = checkbox_label.c_str();
|
||||
|
||||
if (checkbox_checked && *checkbox_checked) {
|
||||
if (checkbox_checked)
|
||||
config.dwFlags |= TDF_VERIFICATION_FLAG_CHECKED;
|
||||
}
|
||||
}
|
||||
|
||||
// Iterate through the buttons, put common buttons in dwCommonButtons
|
||||
@@ -172,22 +170,24 @@ int ShowTaskDialogUTF16(NativeWindow* parent,
|
||||
config.dwFlags |= TDF_USE_COMMAND_LINKS; // custom buttons as links.
|
||||
}
|
||||
|
||||
int button_id;
|
||||
|
||||
int id = 0;
|
||||
BOOL verificationFlagChecked = FALSE;
|
||||
TaskDialogIndirect(&config, &id, nullptr, &verificationFlagChecked);
|
||||
if (checkbox_checked) {
|
||||
*checkbox_checked = verificationFlagChecked;
|
||||
}
|
||||
|
||||
if (id_map.find(id) != id_map.end()) // common button.
|
||||
return id_map[id];
|
||||
button_id = id_map[id];
|
||||
else if (id >= kIDStart) // custom button.
|
||||
return id - kIDStart;
|
||||
button_id = id - kIDStart;
|
||||
else
|
||||
return cancel_id;
|
||||
button_id = cancel_id;
|
||||
|
||||
return std::make_pair(button_id,
|
||||
checkbox_checked ? verificationFlagChecked : false);
|
||||
}
|
||||
|
||||
int ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
|
||||
DialogResult ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
|
||||
std::vector<base::string16> utf16_buttons;
|
||||
for (const auto& button : settings.buttons)
|
||||
utf16_buttons.push_back(base::UTF8ToUTF16(button));
|
||||
@@ -197,21 +197,20 @@ int ShowTaskDialogUTF8(const MessageBoxSettings& settings) {
|
||||
const base::string16 detail_16 = base::UTF8ToUTF16(settings.detail);
|
||||
const base::string16 checkbox_label_16 =
|
||||
base::UTF8ToUTF16(settings.checkbox_label);
|
||||
bool cb_checked = settings.checkbox_checked;
|
||||
|
||||
return ShowTaskDialogUTF16(
|
||||
settings.parent_window, settings.type, utf16_buttons, settings.default_id,
|
||||
settings.cancel_id, settings.options, title_16, message_16, detail_16,
|
||||
checkbox_label_16, &cb_checked, settings.icon);
|
||||
checkbox_label_16, settings.checkbox_checked, settings.icon);
|
||||
}
|
||||
|
||||
void RunMessageBoxInNewThread(base::Thread* thread,
|
||||
const MessageBoxSettings& settings,
|
||||
MessageBoxCallback callback) {
|
||||
int result = ShowTaskDialogUTF8(settings);
|
||||
DialogResult result = ShowTaskDialogUTF8(settings);
|
||||
base::PostTask(
|
||||
FROM_HERE, {content::BrowserThread::UI},
|
||||
base::BindOnce(std::move(callback), result, settings.checkbox_checked));
|
||||
base::BindOnce(std::move(callback), result.first, result.second));
|
||||
content::BrowserThread::DeleteSoon(content::BrowserThread::UI, FROM_HERE,
|
||||
thread);
|
||||
}
|
||||
@@ -220,7 +219,8 @@ void RunMessageBoxInNewThread(base::Thread* thread,
|
||||
|
||||
int ShowMessageBoxSync(const MessageBoxSettings& settings) {
|
||||
electron::UnresponsiveSuppressor suppressor;
|
||||
return ShowTaskDialogUTF8(settings);
|
||||
DialogResult result = ShowTaskDialogUTF8(settings);
|
||||
return result.first;
|
||||
}
|
||||
|
||||
void ShowMessageBox(const MessageBoxSettings& settings,
|
||||
@@ -243,7 +243,7 @@ void ShowMessageBox(const MessageBoxSettings& settings,
|
||||
void ShowErrorBox(const base::string16& title, const base::string16& content) {
|
||||
electron::UnresponsiveSuppressor suppressor;
|
||||
ShowTaskDialogUTF16(nullptr, MessageBoxType::kError, {}, -1, 0, 0, L"Error",
|
||||
title, content, L"", nullptr, gfx::ImageSkia());
|
||||
title, content, L"", false, gfx::ImageSkia());
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/strings/pattern.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "net/base/data_url.h"
|
||||
#include "shell/common/asar/asar_util.h"
|
||||
@@ -542,8 +543,18 @@ bool Converter<electron::api::NativeImage*>::FromV8(
|
||||
base::FilePath path;
|
||||
if (ConvertFromV8(isolate, val, &path)) {
|
||||
*out = electron::api::NativeImage::CreateFromPath(isolate, path).get();
|
||||
// Should throw when failed to initialize from path.
|
||||
return !(*out)->image().IsEmpty();
|
||||
if ((*out)->image().IsEmpty()) {
|
||||
#if defined(OS_WIN)
|
||||
const auto img_path = base::UTF16ToUTF8(path.value());
|
||||
#else
|
||||
const auto img_path = path.value();
|
||||
#endif
|
||||
isolate->ThrowException(v8::Exception::Error(
|
||||
StringToV8(isolate, "Image could not be created from " + img_path)));
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
*out = static_cast<electron::api::NativeImage*>(
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "net/cert/x509_certificate.h"
|
||||
#include "net/cert/x509_util.h"
|
||||
#include "net/http/http_response_headers.h"
|
||||
#include "net/http/http_version.h"
|
||||
#include "net/url_request/redirect_info.h"
|
||||
#include "services/network/public/cpp/resource_request.h"
|
||||
#include "shell/browser/api/atom_api_data_pipe_holder.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
@@ -337,7 +339,7 @@ bool Converter<scoped_refptr<network::ResourceRequestBody>>::FromV8(
|
||||
v8::Local<v8::Value> Converter<network::ResourceRequest>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const network::ResourceRequest& val) {
|
||||
gin::Dictionary dict(isolate, v8::Object::New(isolate));
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("method", val.method);
|
||||
dict.Set("url", val.url.spec());
|
||||
dict.Set("referrer", val.referrer.spec());
|
||||
@@ -359,4 +361,32 @@ v8::Local<v8::Value> Converter<electron::VerifyRequestParams>::ToV8(
|
||||
return ConvertToV8(isolate, dict);
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> Converter<net::HttpVersion>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const net::HttpVersion& val) {
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("major", static_cast<uint32_t>(val.major_value()));
|
||||
dict.Set("minor", static_cast<uint32_t>(val.minor_value()));
|
||||
return ConvertToV8(isolate, dict);
|
||||
}
|
||||
|
||||
// static
|
||||
v8::Local<v8::Value> Converter<net::RedirectInfo>::ToV8(
|
||||
v8::Isolate* isolate,
|
||||
const net::RedirectInfo& val) {
|
||||
gin::Dictionary dict = gin::Dictionary::CreateEmpty(isolate);
|
||||
|
||||
dict.Set("statusCode", val.status_code);
|
||||
dict.Set("newMethod", val.new_method);
|
||||
dict.Set("newUrl", val.new_url);
|
||||
dict.Set("newSiteForCookies", val.new_site_for_cookies);
|
||||
dict.Set("newReferrer", val.new_referrer);
|
||||
dict.Set("insecureSchemeWasUpgraded", val.insecure_scheme_was_upgraded);
|
||||
dict.Set("isSignedExchangeFallbackRedirect",
|
||||
val.is_signed_exchange_fallback_redirect);
|
||||
|
||||
return ConvertToV8(isolate, dict);
|
||||
}
|
||||
|
||||
} // namespace gin
|
||||
|
||||
@@ -5,7 +5,10 @@
|
||||
#ifndef SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
|
||||
#define SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
|
||||
|
||||
#include <string>
|
||||
|
||||
#include "gin/converter.h"
|
||||
#include "services/network/public/mojom/fetch_api.mojom.h"
|
||||
#include "shell/browser/net/cert_verifier_client.h"
|
||||
|
||||
namespace base {
|
||||
@@ -19,6 +22,7 @@ class URLRequest;
|
||||
class X509Certificate;
|
||||
class HttpResponseHeaders;
|
||||
struct CertPrincipal;
|
||||
class HttpVersion;
|
||||
} // namespace net
|
||||
|
||||
namespace network {
|
||||
@@ -96,6 +100,18 @@ struct Converter<electron::VerifyRequestParams> {
|
||||
electron::VerifyRequestParams val);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<net::HttpVersion> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const net::HttpVersion& val);
|
||||
};
|
||||
|
||||
template <>
|
||||
struct Converter<net::RedirectInfo> {
|
||||
static v8::Local<v8::Value> ToV8(v8::Isolate* isolate,
|
||||
const net::RedirectInfo& val);
|
||||
};
|
||||
|
||||
} // namespace gin
|
||||
|
||||
#endif // SHELL_COMMON_GIN_CONVERTERS_NET_CONVERTER_H_
|
||||
|
||||
@@ -40,6 +40,7 @@ class CachedProxyLifeMonitor final : public ObjectLifeMonitor {
|
||||
void RunDestructor() override {
|
||||
if (node_->detached) {
|
||||
delete node_;
|
||||
return;
|
||||
}
|
||||
if (node_->prev) {
|
||||
node_->prev->next = node_->next;
|
||||
|
||||
@@ -67,7 +67,7 @@ void InvokeIpcCallback(v8::Local<v8::Context> context,
|
||||
.ToLocalChecked();
|
||||
auto callback_value = ipcNative->Get(context, callback_key).ToLocalChecked();
|
||||
DCHECK(callback_value->IsFunction()); // set by init.ts
|
||||
auto callback = v8::Local<v8::Function>::Cast(callback_value);
|
||||
auto callback = callback_value.As<v8::Function>();
|
||||
ignore_result(callback->Call(context, ipcNative, args.size(), args.data()));
|
||||
}
|
||||
|
||||
|
||||
@@ -2368,7 +2368,12 @@ describe('BrowserWindow module', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('beforeunload handler', () => {
|
||||
describe('beforeunload handler', function () {
|
||||
// TODO(nornagon): I feel like these tests _oughtn't_ be flakey, but
|
||||
// beforeunload is in general not reliable on the web, so i'm not going to
|
||||
// worry about it too much for now.
|
||||
this.retries(3)
|
||||
|
||||
let w: BrowserWindow = null as unknown as BrowserWindow
|
||||
beforeEach(() => {
|
||||
w = new BrowserWindow({ show: false, webPreferences: { nodeIntegration: true } })
|
||||
|
||||
@@ -822,6 +822,31 @@ describe('Menu module', function () {
|
||||
menu.closePopup()
|
||||
})
|
||||
})
|
||||
|
||||
it('prevents menu from getting garbage-collected when popuping', (done) => {
|
||||
const menu = Menu.buildFromTemplate([{ role: 'paste' }])
|
||||
menu.popup({ window: w })
|
||||
|
||||
// Keep a weak reference to the menu.
|
||||
const v8Util = process.electronBinding('v8_util')
|
||||
const map = v8Util.createIDWeakMap<Electron.Menu>()
|
||||
map.set(0, menu)
|
||||
|
||||
setTimeout(() => {
|
||||
// Do garbage collection, since |menu| is not referenced in this closure
|
||||
// it would be gone after next call.
|
||||
v8Util.requestGarbageCollectionForTesting()
|
||||
setTimeout(() => {
|
||||
// Try to receive menu from weak reference.
|
||||
if (map.has(0)) {
|
||||
map.get(0)!.closePopup()
|
||||
done()
|
||||
} else {
|
||||
done('Menu is garbage-collected while popuping')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('Menu.setApplicationMenu', () => {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -511,12 +511,19 @@ describe('session module', () => {
|
||||
const fetch = (url: string) => new Promise((resolve, reject) => {
|
||||
const request = net.request({ url, session: ses })
|
||||
request.on('response', (response) => {
|
||||
let data = ''
|
||||
let data: string | null = null
|
||||
response.on('data', (chunk) => {
|
||||
if (!data) {
|
||||
data = ''
|
||||
}
|
||||
data += chunk
|
||||
})
|
||||
response.on('end', () => {
|
||||
resolve(data)
|
||||
if (!data) {
|
||||
reject(new Error('Empty response'))
|
||||
} else {
|
||||
resolve(data)
|
||||
}
|
||||
})
|
||||
response.on('error', (error: any) => { reject(new Error(error)) })
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { expect } from 'chai'
|
||||
import { Menu, Tray, nativeImage } from 'electron'
|
||||
import { ifdescribe, ifit } from './spec-helpers'
|
||||
import * as path from 'path'
|
||||
|
||||
describe('tray module', () => {
|
||||
let tray: Tray
|
||||
@@ -12,6 +13,15 @@ describe('tray module', () => {
|
||||
tray = null as any
|
||||
})
|
||||
|
||||
describe('new Tray', () => {
|
||||
it('throws a descriptive error for a missing file', () => {
|
||||
const badPath = path.resolve('I', 'Do', 'Not', 'Exist')
|
||||
expect(() => {
|
||||
tray = new Tray(badPath)
|
||||
}).to.throw(/Image could not be created from .*/)
|
||||
})
|
||||
})
|
||||
|
||||
ifdescribe(process.platform === 'darwin')('tray get/set ignoreDoubleClickEvents', () => {
|
||||
it('returns false by default', () => {
|
||||
const ignored = tray.getIgnoreDoubleClickEvents()
|
||||
|
||||
@@ -214,6 +214,23 @@ describe('webContents module', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('webContents.executeJavaScriptInIsolatedWorld', () => {
|
||||
let w: BrowserWindow
|
||||
|
||||
before(async () => {
|
||||
w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: true } })
|
||||
await w.loadURL('about:blank')
|
||||
})
|
||||
|
||||
it('resolves the returned promise with the result', async () => {
|
||||
await w.webContents.executeJavaScriptInIsolatedWorld(999, [{ code: 'window.X = 123' }])
|
||||
const isolatedResult = await w.webContents.executeJavaScriptInIsolatedWorld(999, [{ code: 'window.X' }])
|
||||
const mainWorldResult = await w.webContents.executeJavaScript('window.X')
|
||||
expect(isolatedResult).to.equal(123)
|
||||
expect(mainWorldResult).to.equal(undefined)
|
||||
})
|
||||
})
|
||||
|
||||
describe('loadURL() promise API', () => {
|
||||
let w: BrowserWindow
|
||||
beforeEach(async () => {
|
||||
|
||||
@@ -371,16 +371,42 @@ describe('chromium feature', () => {
|
||||
})
|
||||
|
||||
describe('storage', () => {
|
||||
describe('DOM storage quota override', () => {
|
||||
describe('DOM storage quota increase', () => {
|
||||
['localStorage', 'sessionStorage'].forEach((storageName) => {
|
||||
it(`allows saving at least 50MiB in ${storageName}`, () => {
|
||||
const storage = window[storageName]
|
||||
const testKeyName = '_electronDOMStorageQuotaOverrideTest'
|
||||
// 25 * 2^20 UTF-16 characters will require 50MiB
|
||||
const arraySize = 25 * Math.pow(2, 20)
|
||||
storage[testKeyName] = new Array(arraySize).fill('X').join('')
|
||||
expect(storage[testKeyName]).to.have.lengthOf(arraySize)
|
||||
delete storage[testKeyName]
|
||||
const storage = window[storageName]
|
||||
it(`allows saving at least 40MiB in ${storageName}`, (done) => {
|
||||
// Although JavaScript strings use UTF-16, the underlying
|
||||
// storage provider may encode strings differently, muddling the
|
||||
// translation between character and byte counts. However,
|
||||
// a string of 40 * 2^20 characters will require at least 40MiB
|
||||
// and presumably no more than 80MiB, a size guaranteed to
|
||||
// to exceed the original 10MiB quota yet stay within the
|
||||
// new 100MiB quota.
|
||||
// Note that both the key name and value affect the total size.
|
||||
const testKeyName = '_electronDOMStorageQuotaIncreasedTest'
|
||||
const length = 40 * Math.pow(2, 20) - testKeyName.length
|
||||
storage.setItem(testKeyName, 'X'.repeat(length))
|
||||
// Wait at least one turn of the event loop to help avoid false positives
|
||||
// Although not entirely necessary, the previous version of this test case
|
||||
// failed to detect a real problem (perhaps related to DOM storage data caching)
|
||||
// wherein calling `getItem` immediately after `setItem` would appear to work
|
||||
// but then later (e.g. next tick) it would not.
|
||||
setTimeout(() => {
|
||||
expect(storage.getItem(testKeyName)).to.have.lengthOf(length)
|
||||
storage.removeItem(testKeyName)
|
||||
done()
|
||||
}, 1)
|
||||
})
|
||||
it(`throws when attempting to use more than 128MiB in ${storageName}`, () => {
|
||||
expect(() => {
|
||||
const testKeyName = '_electronDOMStorageQuotaStillEnforcedTest'
|
||||
const length = 128 * Math.pow(2, 20) - testKeyName.length
|
||||
try {
|
||||
storage.setItem(testKeyName, 'X'.repeat(length))
|
||||
} finally {
|
||||
storage.removeItem(testKeyName)
|
||||
}
|
||||
}).to.throw()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1023,6 +1023,13 @@ describe('<webview> tag', function () {
|
||||
})
|
||||
|
||||
describe('<webview>.capturePage()', () => {
|
||||
before(function () {
|
||||
// TODO(miniak): figure out why this is failing on windows
|
||||
if (process.platform === 'win32') {
|
||||
this.skip()
|
||||
}
|
||||
})
|
||||
|
||||
it('returns a Promise with a NativeImage', async () => {
|
||||
const src = 'data:text/html,%3Ch1%3EHello%2C%20World!%3C%2Fh1%3E'
|
||||
await loadWebView(webview, { src })
|
||||
|
||||
3
typings/internal-ambient.d.ts
vendored
3
typings/internal-ambient.d.ts
vendored
@@ -29,7 +29,8 @@ declare namespace NodeJS {
|
||||
setHiddenValue<T>(obj: any, key: string, value: T): void;
|
||||
deleteHiddenValue(obj: any, key: string): void;
|
||||
requestGarbageCollectionForTesting(): void;
|
||||
createDoubleIDWeakMap(): any;
|
||||
createIDWeakMap<V>(): ElectronInternal.KeyWeakMap<number, V>;
|
||||
createDoubleIDWeakMap<V>(): ElectronInternal.KeyWeakMap<[string, number], V>;
|
||||
setRemoteCallbackFreer(fn: Function, frameId: number, contextId: String, id: number, sender: any): void
|
||||
}
|
||||
|
||||
|
||||
7
typings/internal-electron.d.ts
vendored
7
typings/internal-electron.d.ts
vendored
@@ -112,6 +112,13 @@ declare namespace ElectronInternal {
|
||||
appIcon: string | null;
|
||||
}
|
||||
|
||||
interface KeyWeakMap<K, V> {
|
||||
set(key: K, value: V): void;
|
||||
get(key: K): V | undefined;
|
||||
has(key: K): boolean;
|
||||
remove(key: K): void;
|
||||
}
|
||||
|
||||
// Internal IPC has _replyInternal and NO reply method
|
||||
interface IpcMainInternalEvent extends Omit<Electron.IpcMainEvent, 'reply'> {
|
||||
_replyInternal(...args: any[]): void;
|
||||
|
||||
Reference in New Issue
Block a user