Compare commits

...

41 Commits

Author SHA1 Message Date
Electron Bot
469fc0ea36 Bump v9.0.0-nightly.20191201 2019-12-01 07:33:09 -08:00
Milan Burda
03824ef53f build: fix building with enable_builtin_spellchecker = false (#21334) 2019-11-30 14:17:46 -08:00
Electron Bot
74f698d7bc Bump v9.0.0-nightly.20191130 2019-11-30 07:31:23 -08:00
Electron Bot
ba77489cb6 Bump v9.0.0-nightly.20191129 2019-11-29 07:32:09 -08:00
Electron Bot
2129751966 Bump v9.0.0-nightly.20191128 2019-11-28 07:32:48 -08:00
Max
0618505722 build: disable strip_absolute_paths_from_debug_symbols on debug.gn (#20884) 2019-11-28 10:37:57 +09:00
Samuel Attard
01f5e9c5c4 build: make CI faster, magic contained within (#21086)
* build: cache the out directory for exact deps hash matches

* chore: generate a target based depshash discriminator

* fix: hash on gn args

* build: share logic on the mac builds

* build: ensure that the mksnapshot binary is built before stripping before zipping

* build: attach the workspace on macOS

* build: optimize the macOS checkout path for testing

* build: fix mksnapshot zip generation

* build: make the mac src cache restore work

* build: v2 out cache

* build: macOS cache restore is just stupidly slow

* build: strip more binaries

* build: attach the out cache to the workspace for macOS builds

* build: allow linux boxes to restore darwin out caches

* build: cat the deps hash target file

* build: ensure that the deps target hash matches on the linux box

* build: do not use host arch in target key

* build: force undefined in the target hash file

* build: only restore out cache when it isn't in the workspace

* build: fix the macOS cache workspace trick

* build: do not double restore

* build: remove the big stuff from the out dir

* build: workaround layer issue

* build: try it back on macOS again but with smaller thingy

* build: macOS needs the out cache now

* build: clean up for omptimal macOS path

* build: use old docker image

* build: idek at this point

* build: we need a deps hash

* build: yeah we need a checkout too

* chore: use testing env on save cache job

* chore: well that should fix the cache key thing

* chore: handle cross-OS path mismatch for src cache restore

* build: use a /portal directory to transfer the src cache appropriately

* build: use the correct docker image

* build: super perms for /portal

* build: increment out cache number

* build: ensure target hash is correct for args + disable pre-compiled headers on macOS

* build: wipe the cross-arch libffmpeg before building Electron
2019-11-27 16:29:53 -08:00
Jeremy Apthorp
f09cb114e4 test: retry beforeunload tests up to 3 times (#21293) 2019-11-27 09:42:54 -08:00
Electron Bot
06e349d074 Bump v9.0.0-nightly.20191127 2019-11-27 07:32:14 -08:00
Julio
96c4c48268 docs: app.dock could be undefined (#20609) 2019-11-27 22:23:13 +09:00
Shelley Vohr
5f365858c9 fix: correctly plumb checkboxChecked on win (#21248)
* fix: correctly plumb checkboxChecked on win

* address final style comment
2019-11-27 16:39:07 +09:00
Jeremy Apthorp
d25256dcf5 refactor: rewrite the net module to simplify state tracking (#21244) 2019-11-26 17:01:13 -08:00
Jeremy Apthorp
4149d76890 fix: restore --ignore-connections-limit functionality (#21286) 2019-11-26 13:27:33 -08:00
Samuel Attard
149aaeba94 feat: add session.addWordToSpellCheckerDictionary to allow custom words in the dictionary (#21266)
* feat: add session.addWordToSpellCheckerDictionary to allow custom words in the dictionary

* Update session.md
2019-11-26 13:16:43 -08:00
Electron Bot
8a9c7c484b Bump v9.0.0-nightly.20191126 2019-11-26 07:33:26 -08:00
OJ Kwon
ca61d2fae7 fix: allow reading body from non-2xx responses in net.request (#21055)
* fix(urlrequest): allow non-2xx repsponse results

- closes #21046

* test(net): add test cases to verify non-2xx body

* test(session): update spec to match clientrequest behavior

* test(net): update test cases to match clientrequest behavior

* spec: clean up async net spec
2019-11-25 14:34:25 -08:00
Jeremy Apthorp
2e25999c52 test: refactor net tests (#21265) 2019-11-25 12:56:18 -08:00
Samuel Attard
d84ba30541 fix: reloadIgnoringCache() should ignore the cache (#21263) 2019-11-25 11:12:58 -08:00
loc
f26b7931eb fix: add missing early return (#21264) 2019-11-25 11:03:54 -08:00
Electron Bot
27c764c66c Bump v9.0.0-nightly.20191125 2019-11-25 07:31:36 -08:00
Electron Bot
ab695fb2f7 Bump v9.0.0-nightly.20191124 2019-11-24 07:31:32 -08:00
Electron Bot
78ae5d410e Bump v9.0.0-nightly.20191123 2019-11-23 07:31:26 -08:00
Shelley Vohr
d20273f95b chore: improve nativeImage path converter error (#21243) 2019-11-22 19:16:43 -08:00
Samuel Attard
34452ee69e feat: expose executeJavaScriptInIsolatedWorld on webContents (#21190)
* feat: expose executeJavaScriptInIsolatedWorld on webContents

* Apply suggestions from code review

Co-Authored-By: loc <andy@slack-corp.com>
2019-11-22 15:33:55 -08:00
Jacob
a7c2f79a94 fix: prevent silent failure when DOM storage quota exceeded (#20899)
* test: update DOM storage quota limits test

* fix: update dom_storage_limits.patch (fixes #13465)

The previous version of this patch did not include
changes required to circumvent the quota enforcement
performed by StorageAreaImpl. Consequently when
the quota was exceeded, things still "appeared to
work" at first but then would later fail silently.
That is, the cache would be updated but the backing
store would not.

This could be fixed by disabling the code below
(from `content/browser/dom_storage/storage_area_impl.cc`)
```
  // Only check quota if the size is increasing, this allows
  // shrinking changes to pre-existing maps that are over budget.
  if (new_item_size > old_item_size && new_storage_used > max_size_) {
    if (map_state_ == MapState::LOADED_KEYS_ONLY) {
      receivers_.ReportBadMessage(
          "The quota in browser cannot exceed when there is only one "
          "renderer.");
    } else {
      std::move(callback).Run(false);
    }
    return;
  }
```

However, since this seems to have some unintended side-effects
(see updated notes in dom_storage_limits.patch) it seems
more prudent to simply increase the quota to a larger
yet still reasonable size rather than attempt to circumvent
the storage quota altogether.
2019-11-22 11:35:54 -08:00
Electron Bot
745363959a Bump v9.0.0-nightly.20191122 2019-11-22 07:31:58 -08:00
Jeremy Apthorp
033d309874 fix: record cpu_profiler data for main process (#21187)
* fix: record cpu_profiler data for main process

* kick ci
2019-11-21 18:05:41 -08:00
Shelley Vohr
135a64955c ci: handle network drive not being available (#21247)
Fixes 'The system cannot find the path specified' error when trying to save source to shared network drive.
2019-11-21 14:52:29 -08:00
Samuel Attard
0cadf2846b docs: remove string literal type from window events (#21235) 2019-11-21 11:12:03 -08:00
Bruce Auyeung
07b94ff578 docs: clarify uniqueness of WebContents.id, BrowserWindow.id (#21101) 2019-11-21 11:11:44 -08:00
Electron Bot
1da9959f57 Bump v9.0.0-nightly.20191121 2019-11-21 07:31:25 -08:00
Milan Burda
3f2cb91a35 refactor: declare KeyWeakMap<K, V> returned by createIDWeakMap() / createDoubleIDWeakMap() (#21171) 2019-11-21 13:32:31 +01:00
Julien Isorce
92ff39c168 ci: generate debug symbols on Linux (#18676) 2019-11-20 17:21:44 -08:00
Samuel Attard
41f1569c46 build: update release build endpoint from /jobs to /job (#21232) 2019-11-20 11:14:48 -08:00
Robo
0111f6216c fix: focus with OOPIF embedded inside <webview> (#21219)
Backports https://chromium-review.googlesource.com/c/chromium/src/+/1922650
2019-11-20 09:06:09 -08:00
John Kleinschmidt
4bc85f777f build: delete unneeded files when running a release (#21216)
* build: delete unneeded files when running a release

Needed to free up disk space on MacOS.

* Delete all the .git directories

* Update comment

* Run gn gen after deleting .git dirs
2019-11-20 10:42:38 -05:00
Electron Bot
af3bee742f Revert "Bump v9.0.0-nightly.20191120"
This reverts commit 99cafae1ec.
2019-11-20 07:37:12 -08:00
Electron Bot
99cafae1ec Bump v9.0.0-nightly.20191120 2019-11-20 07:31:46 -08:00
Cheng Zhao
50f2d2b5ab fix: menu should not be garbage-collected when popuping (#21169)
* fix: retain menu when popuping

* test: menu should not be garbage-collected when popuping
2019-11-20 20:17:39 +09:00
Samuel Attard
ea23f18e94 docs: document the channel param for event.reply (#21185) 2019-11-19 13:17:59 -08:00
Milan Burda
145dd33da1 spec: skip flaky <webview>.capturePage() test on Windows (#21175) 2019-11-19 12:57:35 -05:00
77 changed files with 2514 additions and 1788 deletions

View File

@@ -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
View File

@@ -65,3 +65,4 @@ ts-gen
# Used to accelerate CI builds
.depshash
.depshash-target

View File

@@ -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",
]

View File

@@ -1 +1 @@
9.0.0-nightly.20191119
9.0.0-nightly.20191201

View File

@@ -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"
}
}
}
}

View File

@@ -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.

View File

@@ -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",

View File

@@ -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_

View File

@@ -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`

View File

@@ -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()`

View File

@@ -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`:

View File

@@ -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[]

View File

@@ -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_

View File

@@ -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",

View File

@@ -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) {

View File

@@ -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)
}

View File

@@ -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.

View File

@@ -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
})
}

View File

@@ -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) {

View File

@@ -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": {

View File

@@ -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

View File

@@ -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

View File

@@ -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 ||

View File

@@ -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 @@

View File

@@ -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(), &params.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
View 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
View 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())

View File

@@ -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)

View File

@@ -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

View File

@@ -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')

View File

@@ -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')

View File

@@ -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`,

View File

@@ -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)

View File

@@ -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:

View File

@@ -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')

View File

@@ -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)

View File

@@ -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);
};

View File

@@ -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) {

View File

@@ -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);

View File

@@ -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_;

View File

@@ -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

View File

@@ -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

View File

@@ -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_;

View File

@@ -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

View File

@@ -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);

View File

@@ -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)

View File

@@ -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)

View 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

View 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_

View File

@@ -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

View File

@@ -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_

View File

@@ -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();

View File

@@ -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));
}
}
};

View File

@@ -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();

View File

@@ -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:

View File

@@ -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()) {

View File

@@ -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);
};

View File

@@ -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

View File

@@ -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;

View File

@@ -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));
}
}
}

View File

@@ -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;

View File

@@ -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

View File

@@ -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*>(

View File

@@ -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

View File

@@ -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_

View File

@@ -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;

View File

@@ -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()));
}

View File

@@ -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 } })

View File

@@ -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

View File

@@ -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)) })
})

View File

@@ -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()

View File

@@ -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 () => {

View File

@@ -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()
})
})
})

View File

@@ -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 })

View File

@@ -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
}

View File

@@ -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;