diff --git a/.circleci/config.yml b/.circleci/config.yml index 22fc1da9b8330..723b74b6b974a 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -61,7 +61,7 @@ parameters: # Build machines configs. docker-image: &docker-image docker: - - image: electronjs/build:d09fd95029bd8c1c73069888231b29688ef385ed + - image: electron.azurecr.io/build:4cec2c5ab66765caa724e37bae2bffb9b29722a5 machine-linux-medium: &machine-linux-medium <<: *docker-image @@ -94,6 +94,7 @@ env-release-build: &env-release-build STRIP_BINARIES: true GENERATE_SYMBOLS: true CHECK_DIST_MANIFEST: '1' + IS_RELEASE: true env-headless-testing: &env-headless-testing DISPLAY: ':99.0' @@ -134,9 +135,6 @@ env-mas: &env-mas MAS_BUILD: 'true' # Misc build configuration options. -env-enable-sccache: &env-enable-sccache - USE_SCCACHE: true - env-send-slack-notifications: &env-send-slack-notifications NOTIFY_SLACK: true @@ -232,23 +230,34 @@ step-gclient-sync: &step-gclient-sync "$CIRCLE_REPOSITORY_URL" ELECTRON_USE_THREE_WAY_MERGE_FOR_PATCHES=1 gclient sync --with_branch_heads --with_tags - # Re-export all the patches to check if there were changes. - python src/electron/script/export_all_patches.py src/electron/patches/config.json - cd src/electron - git update-index --refresh || true - if ! git diff-index --quiet HEAD --; then - # There are changes to the patches. Make a git commit with the updated patches - git add patches - GIT_COMMITTER_NAME="Electron Bot" GIT_COMMITTER_EMAIL="anonymous@electronjs.org" git commit -m "update patches" --author="Electron Bot " - # Export it - mkdir -p ../../patches - git format-patch -1 --stdout --keep-subject --no-stat --full-index > ../../patches/update-patches.patch - echo - echo "======================================================================" - echo "There were changes to the patches when applying." - echo "Check the CI artifacts for a patch you can apply to fix it." - echo "======================================================================" - exit 1 + if [ "$IS_RELEASE" != "true" ]; then + # Re-export all the patches to check if there were changes. + python src/electron/script/export_all_patches.py src/electron/patches/config.json + cd src/electron + git update-index --refresh || true + if ! git diff-index --quiet HEAD --; then + # There are changes to the patches. Make a git commit with the updated patches + git add patches + GIT_COMMITTER_NAME="PatchUp" GIT_COMMITTER_EMAIL="73610968+patchup[bot]@users.noreply.github.com" git commit -m "chore: update patches" --author="PatchUp <73610968+patchup[bot]@users.noreply.github.com>" + # Export it + mkdir -p ../../patches + git format-patch -1 --stdout --keep-subject --no-stat --full-index > ../../patches/update-patches.patch + if (node ./script/push-patch.js 2> /dev/null > /dev/null); then + echo + echo "======================================================================" + echo "Changes to the patches when applying, we have auto-pushed the diff to the current branch" + echo "A new CI job will kick off shortly" + echo "======================================================================" + exit 1 + else + echo + echo "======================================================================" + echo "There were changes to the patches when applying." + echo "Check the CI artifacts for a patch you can apply to fix it." + echo "======================================================================" + exit 1 + fi + fi fi fi @@ -259,26 +268,14 @@ step-setup-env-for-build: &step-setup-env-for-build # To find `gn` executable. echo 'export CHROMIUM_BUILDTOOLS_PATH="'"$PWD"'/src/buildtools"' >> $BASH_ENV - if [ "$USE_SCCACHE" == "true" ]; then - # https://github.com/mozilla/sccache - SCCACHE_PATH="$PWD/src/electron/external_binaries/sccache" - echo 'export SCCACHE_PATH="'"$SCCACHE_PATH"'"' >> $BASH_ENV - if [ "$CIRCLE_PR_NUMBER" != "" ]; then - #if building a fork set readonly access to sccache - echo 'export SCCACHE_BUCKET="electronjs-sccache-ci"' >> $BASH_ENV - echo 'export SCCACHE_TWO_TIER=true' >> $BASH_ENV - fi - fi - step-setup-goma-for-build: &step-setup-goma-for-build run: name: Setup Goma command: | - echo 'export USE_GOMA=true' >> $BASH_ENV - if [ "`uname`" == "Linux" ]; then - echo 'export NUMBER_OF_NINJA_PROCESSES=300' >> $BASH_ENV - else - echo 'export NUMBER_OF_NINJA_PROCESSES=25' >> $BASH_ENV + echo 'export NUMBER_OF_NINJA_PROCESSES=300' >> $BASH_ENV + if [ "`uname`" == "Darwin" ]; then + echo 'ulimit -n 10000' >> $BASH_ENV + echo 'sudo launchctl limit maxfiles 65536 200000' >> $BASH_ENV fi if [ ! -z "$RAW_GOMA_AUTH" ]; then echo $RAW_GOMA_AUTH > ~/.goma_oauth2_config @@ -287,24 +284,27 @@ step-setup-goma-for-build: &step-setup-goma-for-build cd build-tools npm install mkdir third_party - node -e "require('./src/utils/goma.js').downloadAndPrepare()" - node -e "require('./src/utils/goma.js').ensure()" + node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })" + third_party/goma/goma_ctl.py ensure_start echo 'export GN_GOMA_FILE='`node -e "console.log(require('./src/utils/goma.js').gnFilePath)"` >> $BASH_ENV echo 'export LOCAL_GOMA_DIR='`node -e "console.log(require('./src/utils/goma.js').dir)"` >> $BASH_ENV + echo 'export GOMA_FALLBACK_ON_AUTH_FAILURE=true' >> $BASH_ENV cd .. step-restore-brew-cache: &step-restore-brew-cache restore_cache: paths: - - /usr/local/Homebrew + - /usr/local/Cellar/gnu-tar + - /usr/local/bin/gtar keys: - - v1-brew-cache-{{ arch }} + - v4-brew-cache-{{ arch }} step-save-brew-cache: &step-save-brew-cache save_cache: paths: - - /usr/local/Homebrew - key: v1-brew-cache-{{ arch }} + - /usr/local/Cellar/gnu-tar + - /usr/local/bin/gtar + key: v4-brew-cache-{{ arch }} name: Persisting brew cache step-get-more-space-on-mac: &step-get-more-space-on-mac @@ -380,8 +380,10 @@ step-install-gnutar-on-mac: &step-install-gnutar-on-mac name: Install gnu-tar on macos command: | if [ "`uname`" == "Darwin" ]; then - brew update - brew install gnu-tar + if [ ! -d /usr/local/Cellar/gnu-tar/ ]; then + brew update + brew install gnu-tar + fi ln -fs /usr/local/bin/gtar /usr/local/bin/tar fi @@ -390,11 +392,7 @@ step-gn-gen-default: &step-gn-gen-default name: Default GN gen command: | cd src - if [ "$USE_GOMA" == "true" ]; then - gn gen out/Default --args="import(\"$GN_CONFIG\") import(\"$GN_GOMA_FILE\") $GN_EXTRA_ARGS $GN_BUILDFLAG_ARGS" - else - gn gen out/Default --args="import(\"$GN_CONFIG\") cc_wrapper=\"$SCCACHE_PATH\" $GN_EXTRA_ARGS $GN_BUILDFLAG_ARGS" - fi + gn gen out/Default --args="import(\"$GN_CONFIG\") import(\"$GN_GOMA_FILE\") $GN_EXTRA_ARGS $GN_BUILDFLAG_ARGS" step-gn-check: &step-gn-check run: @@ -491,11 +489,7 @@ step-electron-maybe-chromedriver-gn-gen: &step-electron-maybe-chromedriver-gn-ge command: | cd src if [ "$TARGET_ARCH" == "arm" ] || [ "$TARGET_ARCH" == "arm64" ]; then - if [ "$USE_GOMA" == "true" ]; then - gn gen out/chromedriver --args="import(\"$GN_CONFIG\") import(\"$GN_GOMA_FILE\") is_component_ffmpeg=false proprietary_codecs=false $GN_EXTRA_ARGS $GN_BUILDFLAG_ARGS" - else - gn gen out/chromedriver --args="import(\"$GN_CONFIG\") cc_wrapper=\"$SCCACHE_PATH\" is_component_ffmpeg=false proprietary_codecs=false $GN_EXTRA_ARGS $GN_BUILDFLAG_ARGS" - fi + gn gen out/chromedriver --args="import(\"$GN_CONFIG\") import(\"$GN_GOMA_FILE\") is_component_ffmpeg=false proprietary_codecs=false $GN_EXTRA_ARGS $GN_BUILDFLAG_ARGS" fi step-electron-chromedriver-build: &step-electron-chromedriver-build @@ -550,10 +544,10 @@ step-electron-publish: &step-electron-publish cd src/electron if [ "$UPLOAD_TO_S3" == "1" ]; then echo 'Uploading Electron release distribution to S3' - script/release/uploaders/upload.py --upload_to_s3 + script/release/uploaders/upload.py --verbose --upload_to_s3 else echo 'Uploading Electron release distribution to Github releases' - script/release/uploaders/upload.py + script/release/uploaders/upload.py --verbose fi step-persist-data-for-tests: &step-persist-data-for-tests @@ -606,11 +600,7 @@ step-ffmpeg-gn-gen: &step-ffmpeg-gn-gen name: ffmpeg GN gen command: | cd src - if [ "$USE_GOMA" == "true" ]; then - gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") import(\"$GN_GOMA_FILE\") $GN_EXTRA_ARGS" - else - gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") cc_wrapper=\"$SCCACHE_PATH\" $GN_EXTRA_ARGS" - fi + gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") import(\"$GN_GOMA_FILE\") $GN_EXTRA_ARGS" step-ffmpeg-build: &step-ffmpeg-build run: @@ -653,16 +643,17 @@ step-setup-linux-for-headless-testing: &step-setup-linux-for-headless-testing sh -e /etc/init.d/xvfb start fi -step-show-sccache-stats: &step-show-sccache-stats +step-show-goma-stats: &step-show-goma-stats run: - name: Check sccache/goma stats after build + shell: /bin/bash + name: Check goma stats after build command: | - if [ "$SCCACHE_PATH" != "" ]; then - $SCCACHE_PATH -s - fi - if [ "$USE_GOMA" == "true" ]; then - $LOCAL_GOMA_DIR/goma_ctl.py stat - fi + set +e + set +o pipefail + $LOCAL_GOMA_DIR/goma_ctl.py stat + $LOCAL_GOMA_DIR/diagnose_goma_log.py + true + when: always step-mksnapshot-build: &step-mksnapshot-build run: @@ -789,6 +780,8 @@ step-ninja-summary: &step-ninja-summary run: name: Print ninja summary command: | + set +e + set +o pipefail python depot_tools/post_build_ninja_summary.py -C src/out/Default step-ninja-report: &step-ninja-report @@ -993,7 +986,6 @@ steps-checkout-and-save-cache: &steps-checkout-and-save-cache - *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 @@ -1032,6 +1024,7 @@ steps-electron-gn-check: &steps-electron-gn-check - *step-maybe-early-exit-doc-only-change - *step-depot-tools-add-to-path - *step-setup-env-for-build + - *step-setup-goma-for-build - *step-gn-gen-default - *step-gn-check @@ -1065,6 +1058,7 @@ steps-electron-ts-compile-for-doc-change: &steps-electron-ts-compile-for-doc-cha - *step-depot-tools-add-to-path - *step-setup-env-for-build + - *step-setup-goma-for-build - *step-restore-brew-cache - *step-get-more-space-on-mac - *step-install-npm-deps-on-mac @@ -1074,26 +1068,13 @@ steps-electron-ts-compile-for-doc-change: &steps-electron-ts-compile-for-doc-cha #Compile ts/js to verify doc change didn't break anything - *step-ts-compile -steps-chromedriver-build: &steps-chromedriver-build - steps: - - attach_workspace: - at: . - - *step-depot-tools-add-to-path - - *step-setup-env-for-build - - *step-fix-sync-on-mac - - - *step-electron-maybe-chromedriver-gn-gen - - *step-electron-chromedriver-build - - *step-electron-chromedriver-store - - - *step-maybe-notify-slack-failure - steps-native-tests: &steps-native-tests steps: - attach_workspace: at: . - *step-depot-tools-add-to-path - *step-setup-env-for-build + - *step-setup-goma-for-build - *step-gn-gen-default - run: @@ -1101,7 +1082,7 @@ steps-native-tests: &steps-native-tests command: | cd src ninja -C out/Default $BUILD_TARGET - - *step-show-sccache-stats + - *step-show-goma-stats - *step-setup-linux-for-headless-testing - run: @@ -1395,7 +1376,7 @@ commands: - *step-nodejs-headers-build - *step-nodejs-headers-store - - *step-show-sccache-stats + - *step-show-goma-stats # mksnapshot - *step-mksnapshot-build @@ -1483,13 +1464,14 @@ commands: - *step-gclient-sync - *step-delete-git-directories - *step-minimize-workspace-size-from-checkout - - *step-fix-sync-on-mac + - *step-fix-sync-on-mac - *step-setup-env-for-build + - *step-setup-goma-for-build - *step-gn-gen-default # Electron app - *step-electron-build - - *step-show-sccache-stats + - *step-show-goma-stats - *step-maybe-generate-breakpad-symbols - *step-maybe-electron-dist-strip - *step-electron-dist-build @@ -1673,14 +1655,6 @@ jobs: <<: *env-testing-build <<: *steps-electron-gn-check - linux-x64-chromedriver: - <<: *machine-linux-medium - environment: - <<: *env-linux-medium - <<: *env-release-build - <<: *env-send-slack-notifications - <<: *steps-chromedriver-build - linux-x64-release: <<: *machine-linux-2xlarge environment: @@ -1700,8 +1674,8 @@ jobs: <<: *env-linux-2xlarge-release GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True' <<: *env-release-build - <<: *env-enable-sccache UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: false @@ -1712,8 +1686,8 @@ jobs: environment: <<: *env-linux-2xlarge-release <<: *env-release-build - <<: *env-enable-sccache UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: true @@ -1733,15 +1707,6 @@ jobs: checkout: true use-out-cache: false - linux-ia32-chromedriver: - <<: *machine-linux-medium - environment: - <<: *env-linux-medium - <<: *env-ia32 - <<: *env-release-build - <<: *env-send-slack-notifications - <<: *steps-chromedriver-build - linux-ia32-release: <<: *machine-linux-2xlarge environment: @@ -1763,9 +1728,9 @@ jobs: GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True' <<: *env-ia32 <<: *env-release-build - <<: *env-enable-sccache <<: *env-32bit-release UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: false @@ -1777,9 +1742,9 @@ jobs: <<: *env-linux-2xlarge-release <<: *env-ia32 <<: *env-release-build - <<: *env-enable-sccache <<: *env-32bit-release UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: true @@ -1800,15 +1765,6 @@ jobs: checkout: true use-out-cache: false - linux-arm-chromedriver: - <<: *machine-linux-medium - environment: - <<: *env-linux-medium - <<: *env-arm - <<: *env-release-build - <<: *env-send-slack-notifications - <<: *steps-chromedriver-build - linux-arm-release: <<: *machine-linux-2xlarge environment: @@ -1829,10 +1785,10 @@ jobs: <<: *env-linux-2xlarge-release <<: *env-arm <<: *env-release-build - <<: *env-enable-sccache <<: *env-32bit-release GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_boto=True --custom-var=checkout_requests=True' UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: false @@ -1844,9 +1800,9 @@ jobs: <<: *env-linux-2xlarge-release <<: *env-arm <<: *env-release-build - <<: *env-enable-sccache <<: *env-32bit-release UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: true @@ -1875,15 +1831,6 @@ jobs: <<: *env-testing-build <<: *steps-electron-gn-check - linux-arm64-chromedriver: - <<: *machine-linux-medium - environment: - <<: *env-linux-medium - <<: *env-arm64 - <<: *env-release-build - <<: *env-send-slack-notifications - <<: *steps-chromedriver-build - linux-arm64-release: <<: *machine-linux-2xlarge environment: @@ -1904,9 +1851,9 @@ jobs: <<: *env-linux-2xlarge-release <<: *env-arm64 <<: *env-release-build - <<: *env-enable-sccache GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm64=True --custom-var=checkout_boto=True --custom-var=checkout_requests=True' UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: false @@ -1918,8 +1865,8 @@ jobs: <<: *env-linux-2xlarge-release <<: *env-arm64 <<: *env-release-build - <<: *env-enable-sccache UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: true @@ -1947,14 +1894,6 @@ jobs: <<: *env-testing-build <<: *steps-electron-gn-check - osx-chromedriver: - <<: *machine-mac - environment: - <<: *env-machine-mac - <<: *env-release-build - <<: *env-send-slack-notifications - <<: *steps-chromedriver-build - osx-release: <<: *machine-mac-large environment: @@ -1974,9 +1913,9 @@ jobs: environment: <<: *env-mac-large-release <<: *env-release-build - <<: *env-enable-sccache GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True' UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: false @@ -1987,8 +1926,8 @@ jobs: environment: <<: *env-mac-large-release <<: *env-release-build - <<: *env-enable-sccache UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: true @@ -2039,9 +1978,9 @@ jobs: <<: *env-mac-large-release <<: *env-mas <<: *env-release-build - <<: *env-enable-sccache GCLIENT_EXTRA_ARGS: '--custom-var=checkout_boto=True --custom-var=checkout_requests=True' UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> + <<: *env-ninja-status steps: - electron-publish: attach: false @@ -2053,7 +1992,6 @@ jobs: <<: *env-mac-large-release <<: *env-mas <<: *env-release-build - <<: *env-enable-sccache UPLOAD_TO_S3: << pipeline.parameters.upload-to-s3 >> steps: - electron-publish: @@ -2351,9 +2289,11 @@ workflows: - osx-publish-skip-checkout: requires: - mac-checkout + context: release-env - mas-publish-skip-checkout: requires: - mac-checkout + context: release-env lint: when: << pipeline.parameters.run-lint >> diff --git a/.env.example b/.env.example index eb3df4b6bdf9c..4d218327bd60e 100644 --- a/.env.example +++ b/.env.example @@ -4,4 +4,3 @@ APPVEYOR_CLOUD_TOKEN= CIRCLE_TOKEN= ELECTRON_GITHUB_TOKEN= -VSTS_TOKEN= \ No newline at end of file diff --git a/.gitattributes b/.gitattributes index 9d6933d53b98a..88189455c32c1 100644 --- a/.gitattributes +++ b/.gitattributes @@ -2,3 +2,13 @@ # files to be checked out with LF endings even if core.autocrlf is true. *.patch text eol=lf patches/**/.patches merge=union + +# Source code and markdown files should always use LF as line ending. +*.cc text eol=lf +*.mm text eol=lf +*.h text eol=lf +*.js text eol=lf +*.ts text eol=lf +*.py text eol=lf +*.ps1 text eol=lf +*.md text eol=lf diff --git a/BUILD.gn b/BUILD.gn index c093dc2846aeb..2d722308ec902 100644 --- a/BUILD.gn +++ b/BUILD.gn @@ -370,6 +370,7 @@ source_set("electron_lib") { "//third_party/libyuv", "//third_party/webrtc_overrides:webrtc_component", "//third_party/widevine/cdm:headers", + "//third_party/zlib/google:zip", "//ui/base/idle", "//ui/events:dom_keycode_converter", "//ui/gl", @@ -675,6 +676,14 @@ source_set("electron_lib") { "shell/browser/electron_pdf_web_contents_helper_client.h", ] } + + if (is_win && enable_win_dark_mode_window_ui) { + sources += [ + "shell/browser/win/dark_mode.cc", + "shell/browser/win/dark_mode.h", + ] + libs += [ "uxtheme.lib" ] + } } electron_paks("packed_resources") { @@ -1094,22 +1103,32 @@ if (is_mac) { "//components/crash/core/app:run_as_crashpad_handler", ] + ldflags = [] + libs = [ "comctl32.lib", "uiautomationcore.lib", "wtsapi32.lib", ] - configs += [ "//build/config/win:windowed" ] - - ldflags = [ - # Windows 7 doesn't have these DLLs. - # TODO: are there other DLLs we need to list here to be win7 - # compatible? - "/DELAYLOAD:api-ms-win-core-winrt-l1-1-0.dll", - "/DELAYLOAD:api-ms-win-core-winrt-string-l1-1-0.dll", + configs += [ + "//build/config/win:windowed", + "//build/config/win:delayloads", ] + if (current_cpu == "x86") { + # Set the initial stack size to 0.5MiB, instead of the 1.5MiB needed by + # Chrome's main thread. This saves significant memory on threads (like + # those in the Windows thread pool, and others) whose stack size we can + # only control through this setting. Because Chrome's main thread needs + # a minimum 1.5 MiB stack, the main thread (in 32-bit builds only) uses + # fibers to switch to a 1.5 MiB stack before running any other code. + ldflags += [ "/STACK:0x80000" ] + } else { + # Increase the initial stack size. The default is 1MB, this is 8MB. + ldflags += [ "/STACK:0x800000" ] + } + # This is to support renaming of electron.exe. node-gyp has hard-coded # executable names which it will recognise as node. This module definition # file claims that the electron executable is in fact named "node.exe", diff --git a/DEPS b/DEPS index 37a727621fc55..1a21349c85daa 100644 --- a/DEPS +++ b/DEPS @@ -14,7 +14,7 @@ gclient_gn_args = [ vars = { 'chromium_version': - '85.0.4183.87', + '85.0.4183.121', 'node_version': 'v12.16.3', 'nan_version': diff --git a/ELECTRON_VERSION b/ELECTRON_VERSION index 3b9bddfcc41d5..4ab9d20ad3e80 100644 --- a/ELECTRON_VERSION +++ b/ELECTRON_VERSION @@ -1 +1 @@ -10.1.0 \ No newline at end of file +10.4.7 \ No newline at end of file diff --git a/README.md b/README.md index 453587ae40241..50e3bc1cbe4ef 100644 --- a/README.md +++ b/README.md @@ -28,15 +28,12 @@ The preferred method is to install Electron as a development dependency in your app: ```sh -npm install electron --save-dev [--save-exact] +npm install electron --save-dev ``` -The `--save-exact` flag is recommended for Electron prior to version 2, as it does not follow semantic -versioning. As of version 2.0.0, Electron follows semver, so you don't need `--save-exact` flag. For info on how to manage Electron versions in your apps, see -[Electron versioning](docs/tutorial/electron-versioning.md). - For more installation options and troubleshooting tips, see -[installation](docs/tutorial/installation.md). +[installation](docs/tutorial/installation.md). For info on how to manage Electron versions in your apps, see +[Electron versioning](docs/tutorial/electron-versioning.md). ## Quick start & Electron Fiddle diff --git a/appveyor.yml b/appveyor.yml index 622450e836ff0..20f07607e825d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -36,6 +36,7 @@ environment: ELECTRON_ENABLE_STACK_DUMPING: 1 MOCHA_REPORTER: mocha-multi-reporters MOCHA_MULTI_REPORTERS: mocha-appveyor-reporter, tap + GOMA_FALLBACK_ON_AUTH_FAILURE: true notifications: - provider: Webhook url: https://electron-mission-control.herokuapp.com/rest/appveyor-hook @@ -53,7 +54,9 @@ build_script: } else { node script/yarn.js install --frozen-lockfile - if ($(node script/doc-only-change.js --prNumber=$env:APPVEYOR_PULL_REQUEST_NUMBER --prBranch=$env:APPVEYOR_REPO_BRANCH;$LASTEXITCODE -eq 0)) { + $result = node script/doc-only-change.js --prNumber=$env:APPVEYOR_PULL_REQUEST_NUMBER --prBranch=$env:APPVEYOR_REPO_BRANCH + Write-Output $result + if ($result.ExitCode -eq 0) { Write-warning "Skipping build for doc only change"; Exit-AppveyorBuild } } @@ -133,7 +136,7 @@ build_script: cd build-tools npm install mkdir third_party - node -e "require('./src/utils/goma.js').downloadAndPrepare()" + node -e "require('./src/utils/goma.js').downloadAndPrepare({ gomaOneForAll: true })" $env:GN_GOMA_FILE = node -e "console.log(require('./src/utils/goma.js').gnFilePath)" $env:LOCAL_GOMA_DIR = node -e "console.log(require('./src/utils/goma.js').dir)" cd .. @@ -218,10 +221,10 @@ deploy_script: if (Test-Path Env:\ELECTRON_RELEASE) { if (Test-Path Env:\UPLOAD_TO_S3) { Write-Output "Uploading Electron release distribution to s3" - & python script\release\uploaders\upload.py --upload_to_s3 + & python script\release\uploaders\upload.py --verbose --upload_to_s3 } else { Write-Output "Uploading Electron release distribution to github releases" - & python script\release\uploaders\upload.py + & python script\release\uploaders\upload.py --verbose } } elseif (Test-Path Env:\TEST_WOA) { node script/release/ci-release-build.js --job=electron-woa-testing --ci=VSTS --armTest --appveyorJobId=$env:APPVEYOR_JOB_ID $env:APPVEYOR_REPO_BRANCH diff --git a/azure-pipelines-woa.yml b/azure-pipelines-woa.yml index 4807e5ec03e98..4bd34bbe59bca 100644 --- a/azure-pipelines-woa.yml +++ b/azure-pipelines-woa.yml @@ -92,6 +92,6 @@ steps: condition: always() - powershell: | - Remove-Item -path $env:APPDATA/Electron* -Recurse + Remove-Item -path $env:APPDATA/Electron* -Recurse -Force -ErrorAction Ignore displayName: 'Delete user app data directories' condition: always() diff --git a/build/webpack/run-compiler.js b/build/webpack/run-compiler.js index 9aa75e7bf5498..5acd9884770f4 100644 --- a/build/webpack/run-compiler.js +++ b/build/webpack/run-compiler.js @@ -10,10 +10,9 @@ config.output = { filename: path.basename(outPath) } -const { wrapInitWithProfilingTimeout } = config; -delete config.wrapInitWithProfilingTimeout; +const { wrapInitWithProfilingTimeout, wrapInitWithTryCatch, ...webpackConfig } = config; -webpack(config, (err, stats) => { +webpack(webpackConfig, (err, stats) => { if (err) { console.error(err) process.exit(1) @@ -21,9 +20,17 @@ webpack(config, (err, stats) => { console.error(stats.toString('normal')) process.exit(1) } else { + let contents = fs.readFileSync(outPath, 'utf8'); + if (wrapInitWithTryCatch) { + contents = `try { +${contents} +} catch (err) { + console.error('Electron ${webpackConfig.output.filename} script failed to run'); + console.error(err); +}`; + } if (wrapInitWithProfilingTimeout) { - const contents = fs.readFileSync(outPath, 'utf8'); - const newContents = `function ___electron_webpack_init__() { + contents = `function ___electron_webpack_init__() { ${contents} }; if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) { @@ -31,8 +38,8 @@ if ((globalThis.process || binding.process).argv.includes("--profile-electron-in } else { ___electron_webpack_init__(); }`; - fs.writeFileSync(outPath, newContents); } + fs.writeFileSync(outPath, contents) process.exit(0) } }) diff --git a/build/webpack/webpack.config.base.js b/build/webpack/webpack.config.base.js index f507ea11d20af..43f83ffed8891 100644 --- a/build/webpack/webpack.config.base.js +++ b/build/webpack/webpack.config.base.js @@ -69,7 +69,8 @@ module.exports = ({ loadElectronFromAlternateTarget, targetDeletesNodeGlobals, target, - wrapInitWithProfilingTimeout + wrapInitWithProfilingTimeout, + wrapInitWithTryCatch }) => { let entry = path.resolve(electronRoot, 'lib', target, 'init.ts') if (!fs.existsSync(entry)) { @@ -85,6 +86,7 @@ module.exports = ({ filename: `${target}.bundle.js` }, wrapInitWithProfilingTimeout, + wrapInitWithTryCatch, resolve: { alias: { '@electron/internal': path.resolve(electronRoot, 'lib'), diff --git a/build/webpack/webpack.config.isolated_renderer.js b/build/webpack/webpack.config.isolated_renderer.js index 28b9e940fa971..95a553d899547 100644 --- a/build/webpack/webpack.config.isolated_renderer.js +++ b/build/webpack/webpack.config.isolated_renderer.js @@ -1,4 +1,5 @@ module.exports = require('./webpack.config.base')({ target: 'isolated_renderer', - alwaysHasNode: false + alwaysHasNode: false, + wrapInitWithTryCatch: true }) diff --git a/build/webpack/webpack.config.renderer.js b/build/webpack/webpack.config.renderer.js index 8f890ee6e0d76..594aaa7725d88 100644 --- a/build/webpack/webpack.config.renderer.js +++ b/build/webpack/webpack.config.renderer.js @@ -2,5 +2,6 @@ module.exports = require('./webpack.config.base')({ target: 'renderer', alwaysHasNode: true, targetDeletesNodeGlobals: true, - wrapInitWithProfilingTimeout: true + wrapInitWithProfilingTimeout: true, + wrapInitWithTryCatch: true }) diff --git a/build/webpack/webpack.config.sandboxed_renderer.js b/build/webpack/webpack.config.sandboxed_renderer.js index 9c38d6acb99bf..7a96344978ab3 100644 --- a/build/webpack/webpack.config.sandboxed_renderer.js +++ b/build/webpack/webpack.config.sandboxed_renderer.js @@ -2,4 +2,5 @@ module.exports = require('./webpack.config.base')({ target: 'sandboxed_renderer', alwaysHasNode: false, wrapInitWithProfilingTimeout: true, + wrapInitWithTryCatch: true }) diff --git a/build/webpack/webpack.config.worker.js b/build/webpack/webpack.config.worker.js index 7fc167b54f281..007c82dee27b5 100644 --- a/build/webpack/webpack.config.worker.js +++ b/build/webpack/webpack.config.worker.js @@ -2,5 +2,6 @@ module.exports = require('./webpack.config.base')({ target: 'worker', loadElectronFromAlternateTarget: 'renderer', alwaysHasNode: true, - targetDeletesNodeGlobals: true + targetDeletesNodeGlobals: true, + wrapInitWithTryCatch: true }) diff --git a/buildflags/BUILD.gn b/buildflags/BUILD.gn index f49fc1613d0dd..9b76b62cc19db 100644 --- a/buildflags/BUILD.gn +++ b/buildflags/BUILD.gn @@ -21,6 +21,7 @@ buildflag_header("buildflags") { "ENABLE_ELECTRON_EXTENSIONS=$enable_electron_extensions", "ENABLE_BUILTIN_SPELLCHECKER=$enable_builtin_spellchecker", "ENABLE_PICTURE_IN_PICTURE=$enable_picture_in_picture", + "ENABLE_WIN_DARK_MODE_WINDOW_UI=$enable_win_dark_mode_window_ui", "OVERRIDE_LOCATION_PROVIDER=$enable_fake_location_provider", ] } diff --git a/buildflags/buildflags.gni b/buildflags/buildflags.gni index ab79d37d091d3..7a9927f06fea9 100644 --- a/buildflags/buildflags.gni +++ b/buildflags/buildflags.gni @@ -36,4 +36,7 @@ declare_args() { # Enable Spellchecker support enable_builtin_spellchecker = true + + # Undocumented Windows dark mode API + enable_win_dark_mode_window_ui = false } diff --git a/chromium_src/chrome/browser/certificate_manager_model.cc b/chromium_src/chrome/browser/certificate_manager_model.cc index c8f580fa4baac..8a38b5370853a 100644 --- a/chromium_src/chrome/browser/certificate_manager_model.cc +++ b/chromium_src/chrome/browser/certificate_manager_model.cc @@ -36,6 +36,7 @@ net::NSSCertDatabase* GetNSSCertDatabaseForResourceContext( // Linux has only a single persistent slot compared to ChromeOS's separate // public and private slot. // Redirect any slot usage to this persistent slot on Linux. + crypto::EnsureNSSInit(); g_nss_cert_database = new net::NSSCertDatabase( crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()) /* public slot */, crypto::ScopedPK11Slot(PK11_GetInternalKeySlot()) /* private slot */); diff --git a/chromium_src/chrome/browser/process_singleton_posix.cc b/chromium_src/chrome/browser/process_singleton_posix.cc index 69a0a53b05342..56909b706c9b5 100644 --- a/chromium_src/chrome/browser/process_singleton_posix.cc +++ b/chromium_src/chrome/browser/process_singleton_posix.cc @@ -826,10 +826,9 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout( to_send.append(current_dir.value()); const std::vector& argv = electron::ElectronCommandLine::argv(); - for (std::vector::const_iterator it = argv.begin(); - it != argv.end(); ++it) { + for (const auto& arg : argv) { to_send.push_back(kTokenDelimiter); - to_send.append(*it); + to_send.append(arg); } // Send the message diff --git a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc index 32aa74606b614..0c419e06f6ded 100644 --- a/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc +++ b/chromium_src/chrome/browser/ui/views/frame/global_menu_bar_registrar_x11.cc @@ -122,8 +122,7 @@ void GlobalMenuBarRegistrarX11::OnNameOwnerChanged(GObject* /* ignored */, GParamSpec* /* ignored */) { // If the name owner changed, we need to reregister all the live x11::Window // with the system. - for (std::set::const_iterator it = live_windows_.begin(); - it != live_windows_.end(); ++it) { - RegisterXWindow(*it); + for (const auto& window : live_windows_) { + RegisterXWindow(window); } } diff --git a/default_app/default_app.ts b/default_app/default_app.ts index 7c2d8a3b9eecb..08d1c7ce39558 100644 --- a/default_app/default_app.ts +++ b/default_app/default_app.ts @@ -41,14 +41,14 @@ ipcMain.handle('bootstrap', (event) => { return isTrustedSender(event.sender) ? electronPath : null; }); -async function createWindow () { +async function createWindow (backgroundColor?: string) { await app.whenReady(); const options: Electron.BrowserWindowConstructorOptions = { width: 960, height: 620, autoHideMenuBar: true, - backgroundColor: '#2f3241', + backgroundColor, webPreferences: { preload: path.resolve(__dirname, 'preload.js'), contextIsolation: true, @@ -96,7 +96,7 @@ export const loadURL = async (appUrl: string) => { }; export const loadFile = async (appPath: string) => { - mainWindow = await createWindow(); + mainWindow = await createWindow(appPath === 'index.html' ? '#2f3241' : undefined); mainWindow.loadFile(appPath); mainWindow.focus(); }; diff --git a/docs/api/app.md b/docs/api/app.md index 7e43ccb46564a..eda52dbb73489 100644 --- a/docs/api/app.md +++ b/docs/api/app.md @@ -32,7 +32,8 @@ In most cases, you should do everything in the `ready` event handler. Returns: -* `launchInfo` unknown _macOS_ +* `event` Event +* `launchInfo` Record _macOS_ Emitted once, when Electron has finished initializing. On macOS, `launchInfo` holds the `userInfo` of the `NSUserNotification` that was used to open the @@ -387,7 +388,7 @@ Returns: * `killed` - Process was sent a SIGTERM or otherwise killed externally * `crashed` - Process crashed * `oom` - Process ran out of memory - * `launch-failure` - Process never successfully launched + * `launch-failed` - Process never successfully launched * `integrity-failure` - Windows code integrity checks failed Emitted when the renderer process unexpectedly dissapears. This is normally @@ -1267,7 +1268,7 @@ systems Application folder. Use in combination with `app.moveToApplicationsFolde ### `app.moveToApplicationsFolder([options])` _macOS_ * `options` Object (optional) - * `conflictHandler` Function (optional) - A handler for potential conflict in move failure. + * `conflictHandler` Function\ (optional) - A handler for potential conflict in move failure. * `conflictType` String - The type of move conflict encountered by the handler; can be `exists` or `existsAndRunning`, where `exists` means that an app of the same name is present in the Applications directory and `existsAndRunning` means both that it exists and that it's presently running. Returns `Boolean` - Whether the move was successful. Please note that if diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 6f48540be58ea..8ef399fd0d41d 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -1829,6 +1829,13 @@ Replacement API for setBrowserView supporting work with multi browser views. * `browserView` [BrowserView](browser-view.md) +#### `win.setTopBrowserView(browserView)` _Experimental_ + +* `browserView` [BrowserView](browser-view.md) + +Raises `browserView` above other `BrowserView`s attached to `win`. +Throws an error if `browserView` is not attached to `win`. + #### `win.getBrowserViews()` _Experimental_ Returns `BrowserView[]` - an array of all BrowserViews that have been attached diff --git a/docs/api/command-line-switches.md b/docs/api/command-line-switches.md index e75d722ea5446..b5667d95b8ca4 100644 --- a/docs/api/command-line-switches.md +++ b/docs/api/command-line-switches.md @@ -198,6 +198,14 @@ logging level for all code in the source files under a `foo/bar` directory. This switch only works when `--enable-logging` is also passed. +### --force_high_performance_gpu + +Force using discrete GPU when there are multiple GPUs available. + +### --force_low_power_gpu + +Force using integrated GPU when there are multiple GPUs available. + ## Node.js Flags Electron supports some of the [CLI flags][node-cli] supported by Node.js. diff --git a/docs/api/extensions.md b/docs/api/extensions.md index 4a93951024921..3faad34aa8706 100644 --- a/docs/api/extensions.md +++ b/docs/api/extensions.md @@ -102,3 +102,21 @@ The following methods of `chrome.tabs` are supported: > **Note:** In Chrome, passing `-1` as a tab ID signifies the "currently active > tab". Since Electron has no such concept, passing `-1` as a tab ID is not > supported and will raise an error. + +### `chrome.management` + +The following methods of `chrome.management` are supported: + +- `chrome.management.getAll` +- `chrome.management.get` +- `chrome.management.getSelf` +- `chrome.management.getPermissionWarningsById` +- `chrome.management.getPermissionWarningsByManifest` +- `chrome.management.onEnabled` +- `chrome.management.onDisabled` + +### `chrome.webRequest` + +All features of this API are supported. + +> **NOTE:** Electron's [`webRequest`](web-request.md) module takes precedence over `chrome.webRequest` if there are conflicting handlers. diff --git a/docs/api/global-shortcut.md b/docs/api/global-shortcut.md index 78263901761d3..bb9dd27fbd37b 100644 --- a/docs/api/global-shortcut.md +++ b/docs/api/global-shortcut.md @@ -9,7 +9,7 @@ with the operating system so that you can customize the operations for various shortcuts. **Note:** The shortcut is global; it will work even if the app does -not have the keyboard focus. You should not use this module until the `ready` +not have the keyboard focus. This module cannot be used before the `ready` event of the app module is emitted. ```javascript diff --git a/docs/api/ipc-main.md b/docs/api/ipc-main.md index ea020c8866588..00123b7a48cfc 100644 --- a/docs/api/ipc-main.md +++ b/docs/api/ipc-main.md @@ -91,7 +91,7 @@ Removes listeners of the specified `channel`. ### `ipcMain.handle(channel, listener)` * `channel` String -* `listener` Function | any> +* `listener` Function | any> * `event` IpcMainInvokeEvent * `...args` any[] @@ -123,7 +123,7 @@ WebContents is the source of the invoke request. ### `ipcMain.handleOnce(channel, listener)` * `channel` String -* `listener` Function | any> +* `listener` Function | any> * `event` IpcMainInvokeEvent * `...args` any[] diff --git a/docs/api/ipc-renderer.md b/docs/api/ipc-renderer.md index a47d408429507..8f8b78f58570c 100644 --- a/docs/api/ipc-renderer.md +++ b/docs/api/ipc-renderer.md @@ -61,9 +61,13 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception. -> **NOTE**: Sending non-standard JavaScript types such as DOM objects or -> special Electron objects is deprecated, and will begin throwing an exception -> starting with Electron 9. +> **NOTE:** Sending non-standard JavaScript types such as DOM objects or +> special Electron objects will throw an exception. +> +> Since the main process does not have support for DOM objects such as +> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over +> Electron's IPC to the main process, as the main process would have no way to decode +> them. Attempting to send such objects over IPC will result in an error. The main process handles it by listening for `channel` with the [`ipcMain`](ipc-main.md) module. @@ -85,9 +89,13 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception. -> **NOTE**: Sending non-standard JavaScript types such as DOM objects or -> special Electron objects is deprecated, and will begin throwing an exception -> starting with Electron 9. +> **NOTE:** Sending non-standard JavaScript types such as DOM objects or +> special Electron objects will throw an exception. +> +> Since the main process does not have support for DOM objects such as +> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over +> Electron's IPC to the main process, as the main process would have no way to decode +> them. Attempting to send such objects over IPC will result in an error. The main process should listen for `channel` with [`ipcMain.handle()`](ipc-main.md#ipcmainhandlechannel-listener). @@ -123,9 +131,13 @@ Algorithm][SCA], just like [`window.postMessage`][], so prototype chains will no included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception. -> **NOTE**: Sending non-standard JavaScript types such as DOM objects or -> special Electron objects is deprecated, and will begin throwing an exception -> starting with Electron 9. +> **NOTE:** Sending non-standard JavaScript types such as DOM objects or +> special Electron objects will throw an exception. +> +> Since the main process does not have support for DOM objects such as +> `ImageBitmap`, `File`, `DOMMatrix` and so on, such objects cannot be sent over +> Electron's IPC to the main process, as the main process would have no way to decode +> them. Attempting to send such objects over IPC will result in an error. The main process handles it by listening for `channel` with [`ipcMain`](ipc-main.md) module, and replies by setting `event.returnValue`. diff --git a/docs/api/menu.md b/docs/api/menu.md index cd9e177ae08da..5938d386b0d69 100644 --- a/docs/api/menu.md +++ b/docs/api/menu.md @@ -22,8 +22,10 @@ Sets `menu` as the application menu on macOS. On Windows and Linux, the Also on Windows and Linux, you can use a `&` in the top-level item name to indicate which letter should get a generated accelerator. For example, using `&File` for the file menu would result in a generated `Alt-F` accelerator that -opens the associated menu. The indicated character in the button label gets an -underline. The `&` character is not displayed on the button label. +opens the associated menu. The indicated character in the button label then gets an +underline, and the `&` character is not displayed on the button label. + +In order to escape the `&` character in an item name, add a proceeding `&`. For example, `&&File` would result in `&File` displayed on the button label. Passing `null` will suppress the default menu. On Windows and Linux, this has the additional effect of removing the menu bar from the window. diff --git a/docs/api/power-monitor.md b/docs/api/power-monitor.md index 1e94f1d25dfb4..234b41a67071a 100644 --- a/docs/api/power-monitor.md +++ b/docs/api/power-monitor.md @@ -8,19 +8,19 @@ Process: [Main](../glossary.md#main-process) The `powerMonitor` module emits the following events: -### Event: 'suspend' _Linux_ _Windows_ +### Event: 'suspend' _macOS_ _Windows_ Emitted when the system is suspending. -### Event: 'resume' _Linux_ _Windows_ +### Event: 'resume' _macOS_ _Windows_ Emitted when system is resuming. -### Event: 'on-ac' _Windows_ +### Event: 'on-ac' _macOS_ _Windows_ Emitted when the system changes to AC power. -### Event: 'on-battery' _Windows_ +### Event: 'on-battery' _macOS_ _Windows_ Emitted when system changes to battery power. diff --git a/docs/api/session.md b/docs/api/session.md index 68d337bea5162..254aa5e5a886f 100644 --- a/docs/api/session.md +++ b/docs/api/session.md @@ -376,7 +376,7 @@ session.fromPartition('some-partition').setPermissionRequestHandler((webContents #### `ses.setPermissionCheckHandler(handler)` -* `handler` Function | null +* `handler` Function\ | null * `webContents` [WebContents](web-contents.md) - WebContents checking the permission. Please note that if the request comes from a subframe you should use `requestingUrl` to check the request origin. * `permission` String - Enum of 'media'. * `requestingOrigin` String - The origin URL of the permission check diff --git a/docs/api/structures/ipc-main-event.md b/docs/api/structures/ipc-main-event.md index f222de35b86dc..ae32dc46a6888 100644 --- a/docs/api/structures/ipc-main-event.md +++ b/docs/api/structures/ipc-main-event.md @@ -1,5 +1,6 @@ # IpcMainEvent Object extends `Event` +* `processId` Integer - The internal ID of the renderer process that sent this message * `frameId` Integer - The ID of the renderer frame that sent this message * `returnValue` any - Set this to the value to be returned in a synchronous message * `sender` WebContents - Returns the `webContents` that sent the message diff --git a/docs/api/structures/ipc-main-invoke-event.md b/docs/api/structures/ipc-main-invoke-event.md index 235b219c2d6ea..b765791e8258c 100644 --- a/docs/api/structures/ipc-main-invoke-event.md +++ b/docs/api/structures/ipc-main-invoke-event.md @@ -1,4 +1,5 @@ # IpcMainInvokeEvent Object extends `Event` +* `processId` Integer - The internal ID of the renderer process that sent this message * `frameId` Integer - The ID of the renderer frame that sent this message * `sender` WebContents - Returns the `webContents` that sent the message diff --git a/docs/api/structures/stream-protocol-response.md b/docs/api/structures/stream-protocol-response.md deleted file mode 100644 index ac5718d07fdf4..0000000000000 --- a/docs/api/structures/stream-protocol-response.md +++ /dev/null @@ -1,5 +0,0 @@ -# StreamProtocolResponse Object - -* `statusCode` Number (optional) - The HTTP response code. -* `headers` Record (optional) - An object containing the response headers. -* `data` ReadableStream | null - A Node.js readable stream representing the response body. diff --git a/docs/api/structures/string-protocol-response.md b/docs/api/structures/string-protocol-response.md deleted file mode 100644 index 19414e3f2aa7c..0000000000000 --- a/docs/api/structures/string-protocol-response.md +++ /dev/null @@ -1,5 +0,0 @@ -# StringProtocolResponse Object - -* `mimeType` String (optional) - MIME type of the response. -* `charset` String (optional) - Charset of the response. -* `data` String | null - A string representing the response body. diff --git a/docs/api/system-preferences.md b/docs/api/system-preferences.md index a9c5ed5c5b4cd..704c72ea4c594 100644 --- a/docs/api/system-preferences.md +++ b/docs/api/system-preferences.md @@ -132,6 +132,8 @@ This is necessary for events such as `NSUserDefaultsDidChangeNotification`. * `userInfo` Record * `object` String +Returns `Number` - The ID of this subscription + Same as `subscribeNotification`, but uses `NSWorkspace.sharedWorkspace.notificationCenter`. This is necessary for events such as `NSWorkspaceDidActivateApplicationNotification`. diff --git a/docs/api/web-contents.md b/docs/api/web-contents.md index 20a5e9e569404..f202dea689809 100644 --- a/docs/api/web-contents.md +++ b/docs/api/web-contents.md @@ -364,7 +364,7 @@ Returns: * `killed` - Process was sent a SIGTERM or otherwise killed externally * `crashed` - Process crashed * `oom` - Process ran out of memory - * `launch-failure` - Process never successfully launched + * `launch-failed` - Process never successfully launched * `integrity-failure` - Windows code integrity checks failed Emitted when the renderer process unexpectedly dissapears. This is normally @@ -1296,9 +1296,9 @@ Returns [`PrinterInfo[]`](structures/printer-info.md) * `pagesPerSheet` Number (optional) - The number of pages to print per page sheet. * `collate` Boolean (optional) - Whether the web page should be collated. * `copies` Number (optional) - The number of copies of the web page to print. - * `pageRanges` Record (optional) - The page range to print. - * `from` Number - the start page. - * `to` Number - the end page. + * `pageRanges` Object[] (optional) - The page range to print. On macOS, only one range is honored. + * `from` Number - Index of the first page to print (0-based). + * `to` Number - Index of the last page to print (inclusive) (0-based). * `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`. * `dpi` Record (optional) * `horizontal` Number (optional) - The horizontal dpi. @@ -1338,8 +1338,8 @@ win.webContents.print(options, (success, errorType) => { default margin, 1 for no margin, and 2 for minimum margin. * `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100. * `pageRanges` Record (optional) - The page range to print. - * `from` Number - the first page to print. - * `to` Number - the last page to print (inclusive). + * `from` Number - Index of the first page to print (0-based). + * `to` Number - Index of the last page to print (inclusive) (0-based). * `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`, `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` and `width` in microns. * `printBackground` Boolean (optional) - Whether to print CSS backgrounds. @@ -1549,8 +1549,7 @@ included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception. > **NOTE**: Sending non-standard JavaScript types such as DOM objects or -> special Electron objects is deprecated, and will begin throwing an exception -> starting with Electron 9. +> special Electron objects will throw an exception. The renderer process can handle the message by listening to `channel` with the [`ipcRenderer`](ipc-renderer.md) module. @@ -1586,7 +1585,9 @@ app.whenReady().then(() => { #### `contents.sendToFrame(frameId, channel, ...args)` -* `frameId` Integer +* `frameId` Integer | [number, number] - the ID of the frame to send to, or a + pair of `[processId, frameId]` if the frame is in a different process to the + main frame. * `channel` String * `...args` any[] @@ -1596,9 +1597,8 @@ Send an asynchronous message to a specific frame in a renderer process via chains will not be included. Sending Functions, Promises, Symbols, WeakMaps, or WeakSets will throw an exception. -> **NOTE**: Sending non-standard JavaScript types such as DOM objects or -> special Electron objects is deprecated, and will begin throwing an exception -> starting with Electron 9. +> **NOTE:** Sending non-standard JavaScript types such as DOM objects or +> special Electron objects will throw an exception. The renderer process can handle the message by listening to `channel` with the [`ipcRenderer`](ipc-renderer.md) module. @@ -1760,7 +1760,7 @@ Returns `Boolean` - If *offscreen rendering* is enabled returns whether it is cu * `fps` Integer If *offscreen rendering* is enabled sets the frame rate to the specified number. -Only values between 1 and 60 are accepted. +Only values between 1 and 240 are accepted. #### `contents.getFrameRate()` @@ -1858,7 +1858,7 @@ The zoom factor is the zoom percent divided by 100, so 300% = 3.0. #### `contents.frameRate` An `Integer` property that sets the frame rate of the web contents to the specified number. -Only values between 1 and 60 are accepted. +Only values between 1 and 240 are accepted. Only applicable if *offscreen rendering* is enabled. diff --git a/docs/api/webview-tag.md b/docs/api/webview-tag.md index f60a8276d2781..a4d670e792dd2 100644 --- a/docs/api/webview-tag.md +++ b/docs/api/webview-tag.md @@ -560,9 +560,9 @@ Stops any `findInPage` request for the `webview` with the provided `action`. * `pagesPerSheet` Number (optional) - The number of pages to print per page sheet. * `collate` Boolean (optional) - Whether the web page should be collated. * `copies` Number (optional) - The number of copies of the web page to print. - * `pageRanges` Record (optional) - The page range to print. - * `from` Number - the start page. - * `to` Number - the end page. + * `pageRanges` Object[] (optional) - The page range to print. On macOS, only the first range is honored. + * `from` Number - Index of the first page to print (0-based). + * `to` Number - Index of the last page to print (inclusive) (0-based). * `duplexMode` String (optional) - Set the duplex mode of the printed web page. Can be `simplex`, `shortEdge`, or `longEdge`. * `dpi` Record (optional) * `horizontal` Number (optional) - The horizontal dpi. @@ -588,8 +588,8 @@ Prints `webview`'s web page. Same as `webContents.print([options])`. and `width` in microns. * `scaleFactor` Number (optional) - The scale factor of the web page. Can range from 0 to 100. * `pageRanges` Record (optional) - The page range to print. - * `from` Number - the first page to print. - * `to` Number - the last page to print (inclusive). + * `from` Number - Index of the first page to print (0-based). + * `to` Number - Index of the last page to print (inclusive) (0-based). * `pageSize` String | Size (optional) - Specify page size of the generated PDF. Can be `A3`, `A4`, `A5`, `Legal`, `Letter`, `Tabloid` or an Object containing `height` * `printBackground` Boolean (optional) - Whether to print CSS backgrounds. diff --git a/docs/breaking-changes-ns.md b/docs/breaking-changes-ns.md deleted file mode 100644 index 1914c1f43f449..0000000000000 --- a/docs/breaking-changes-ns.md +++ /dev/null @@ -1,61 +0,0 @@ -# Breaking changes (NetworkService) (Draft) - -This document describes changes to Electron APIs after migrating network code -to NetworkService API. - -We don't currently have an estimate of when we will enable `NetworkService` by -default in Electron, but as Chromium is already removing non-`NetworkService` -code, we might switch before Electron 10. - -The content of this document should be moved to `breaking-changes.md` once we have -determined when to enable `NetworkService` in Electron. - -## Planned Breaking API Changes - -### `protocol.unregisterProtocol` -### `protocol.uninterceptProtocol` - -The APIs are now synchronous and the optional callback is no longer needed. - -```javascript -// Deprecated -protocol.unregisterProtocol(scheme, () => { /* ... */ }) -// Replace with -protocol.unregisterProtocol(scheme) -``` - -### `protocol.registerFileProtocol` -### `protocol.registerBufferProtocol` -### `protocol.registerStringProtocol` -### `protocol.registerHttpProtocol` -### `protocol.registerStreamProtocol` -### `protocol.interceptFileProtocol` -### `protocol.interceptStringProtocol` -### `protocol.interceptBufferProtocol` -### `protocol.interceptHttpProtocol` -### `protocol.interceptStreamProtocol` - -The APIs are now synchronous and the optional callback is no longer needed. - -```javascript -// Deprecated -protocol.registerFileProtocol(scheme, handler, () => { /* ... */ }) -// Replace with -protocol.registerFileProtocol(scheme, handler) -``` - -The registered or intercepted protocol does not have effect on current page -until navigation happens. - -### `protocol.isProtocolHandled` - -This API is deprecated and users should use `protocol.isProtocolRegistered` -and `protocol.isProtocolIntercepted` instead. - -```javascript -// Deprecated -protocol.isProtocolHandled(scheme).then(() => { /* ... */ }) -// Replace with -const isRegistered = protocol.isProtocolRegistered(scheme) -const isIntercepted = protocol.isProtocolIntercepted(scheme) -``` diff --git a/docs/breaking-changes.md b/docs/breaking-changes.md index d5bb629de0675..4a9b0de228ce2 100644 --- a/docs/breaking-changes.md +++ b/docs/breaking-changes.md @@ -124,6 +124,54 @@ const w = new BrowserWindow({ We [recommend moving away from the remote module](https://medium.com/@nornagon/electrons-remote-module-considered-harmful-70d69500f31). +### `protocol.unregisterProtocol` +### `protocol.uninterceptProtocol` + +The APIs are now synchronous and the optional callback is no longer needed. + +```javascript +// Deprecated +protocol.unregisterProtocol(scheme, () => { /* ... */ }) +// Replace with +protocol.unregisterProtocol(scheme) +``` + +### `protocol.registerFileProtocol` +### `protocol.registerBufferProtocol` +### `protocol.registerStringProtocol` +### `protocol.registerHttpProtocol` +### `protocol.registerStreamProtocol` +### `protocol.interceptFileProtocol` +### `protocol.interceptStringProtocol` +### `protocol.interceptBufferProtocol` +### `protocol.interceptHttpProtocol` +### `protocol.interceptStreamProtocol` + +The APIs are now synchronous and the optional callback is no longer needed. + +```javascript +// Deprecated +protocol.registerFileProtocol(scheme, handler, () => { /* ... */ }) +// Replace with +protocol.registerFileProtocol(scheme, handler) +``` + +The registered or intercepted protocol does not have effect on current page +until navigation happens. + +### `protocol.isProtocolHandled` + +This API is deprecated and users should use `protocol.isProtocolRegistered` +and `protocol.isProtocolIntercepted` instead. + +```javascript +// Deprecated +protocol.isProtocolHandled(scheme).then(() => { /* ... */ }) +// Replace with +const isRegistered = protocol.isProtocolRegistered(scheme) +const isIntercepted = protocol.isProtocolIntercepted(scheme) +``` + ## Planned Breaking API Changes (9.0) ### Default Changed: Loading non-context-aware native modules in the renderer process is disabled by default @@ -138,6 +186,45 @@ you should plan to update your native modules to be context aware. For more detailed information see [#18397](https://github.com/electron/electron/issues/18397). +### Deprecated: `BrowserWindow` extension APIs + +The following extension APIs have been deprecated: +* `BrowserWindow.addExtension(path)` +* `BrowserWindow.addDevToolsExtension(path)` +* `BrowserWindow.removeExtension(name)` +* `BrowserWindow.removeDevToolsExtension(name)` +* `BrowserWindow.getExtensions()` +* `BrowserWindow.getDevToolsExtensions()` + +Use the session APIs instead: +* `ses.loadExtension(path)` +* `ses.removeExtension(extension_id)` +* `ses.getAllExtensions()` + +```js +// Deprecated in Electron 9 +BrowserWindow.addExtension(path) +BrowserWindow.addDevToolsExtension(path) +// Replace with +session.defaultSession.loadExtension(path) +``` + +```js +// Deprecated in Electron 9 +BrowserWindow.removeExtension(name) +BrowserWindow.removeDevToolsExtension(name) +// Replace with +session.defaultSession.removeExtension(extension_id) +``` + +```js +// Deprecated in Electron 9 +BrowserWindow.getExtensions() +BrowserWindow.getDevToolsExtensions() +// Replace with +session.defaultSession.getAllExtensions() +``` + ### Removed: `.getWebContents()` This API, which was deprecated in Electron 8.0, is now removed. @@ -281,6 +368,52 @@ in Electron 8.x, and cease to exist in Electron 9.x. The layout zoom level limits are now fixed at a minimum of 0.25 and a maximum of 5.0, as defined [here](https://chromium.googlesource.com/chromium/src/+/938b37a6d2886bf8335fc7db792f1eb46c65b2ae/third_party/blink/common/page/page_zoom.cc#11). +### Deprecated events in `systemPreferences` + +The following `systemPreferences` events have been deprecated: +* `inverted-color-scheme-changed` +* `high-contrast-color-scheme-changed` + +Use the new `updated` event on the `nativeTheme` module instead. + +```js +// Deprecated +systemPreferences.on('inverted-color-scheme-changed', () => { /* ... */ }) +systemPreferences.on('high-contrast-color-scheme-changed', () => { /* ... */ }) + +// Replace with +nativeTheme.on('updated', () => { /* ... */ }) +``` + +### Deprecated: methods in `systemPreferences` + +The following `systemPreferences` methods have been deprecated: +* `systemPreferences.isDarkMode()` +* `systemPreferences.isInvertedColorScheme()` +* `systemPreferences.isHighContrastColorScheme()` + +Use the following `nativeTheme` properties instead: +* `nativeTheme.shouldUseDarkColors` +* `nativeTheme.shouldUseInvertedColorScheme` +* `nativeTheme.shouldUseHighContrastColors` + +```js +// Deprecated +systemPreferences.isDarkMode() +// Replace with +nativeTheme.shouldUseDarkColors + +// Deprecated +systemPreferences.isInvertedColorScheme() +// Replace with +nativeTheme.shouldUseInvertedColorScheme + +// Deprecated +systemPreferences.isHighContrastColorScheme() +// Replace with +nativeTheme.shouldUseHighContrastColors +``` + ## Planned Breaking API Changes (7.0) ### Deprecated: Atom.io Node Headers URL diff --git a/docs/development/build-instructions-gn.md b/docs/development/build-instructions-gn.md index 09f6ee2bcd49a..dceaf92ac0589 100644 --- a/docs/development/build-instructions-gn.md +++ b/docs/development/build-instructions-gn.md @@ -173,12 +173,12 @@ $ gn gen out/Testing-x86 --args='... target_cpu = "x86"' Not all combinations of source and target CPU/OS are supported by Chromium. - - - - - -
HostTargetStatus
Windows x64Windows arm64Experimental
Windows x64Windows x86Automatically tested
Linux x64Linux x86Automatically tested
+| Host | Target | Status | +|-------------|---------------|----------------------| +| Windows x64 | Windows arm64 | Experimental | +| Windows x64 | Windows x86 | Automatically tested | +| Linux x64 | Linux x86 | Automatically tested | + If you test other combinations and find them to work, please update this document :) diff --git a/docs/development/build-instructions-macos.md b/docs/development/build-instructions-macos.md index 62720006c1dc4..d88b277f5e885 100644 --- a/docs/development/build-instructions-macos.md +++ b/docs/development/build-instructions-macos.md @@ -42,7 +42,7 @@ $ pip install pyobjc If you're developing Electron and don't plan to redistribute your custom Electron build, you may skip this section. -Official Electron builds are built with [Xcode 9.4.1](http://adcdownload.apple.com/Developer_Tools/Xcode_9.4.1/Xcode_9.4.1.xip), and the macOS 10.13 SDK. Building with a newer SDK works too, but the releases currently use the 10.13 SDK. +Official Electron builds are built with [Xcode 11.1](https://download.developer.apple.com/Developer_Tools/Xcode_11.1/Xcode_11.1.xip), and the macOS 10.15 SDK. Building with a newer SDK works too, but the releases currently use the 10.15 SDK. ## Building Electron diff --git a/docs/development/setting-up-symbol-server.md b/docs/development/setting-up-symbol-server.md index 0f5030cbf5179..ad6f1f5252acd 100644 --- a/docs/development/setting-up-symbol-server.md +++ b/docs/development/setting-up-symbol-server.md @@ -43,8 +43,8 @@ SRV*c:\code\symbols\*https://msdl.microsoft.com/download/symbols;SRV*c:\code\sym ## Using the symbol server in Visual Studio - - +![Tools -> Options](https://mdn.mozillademos.org/files/733/symbol-server-vc8express-menu.jpg) +![Symbols Settings](https://mdn.mozillademos.org/files/2497/2005_options.gif) ## Troubleshooting: Symbols will not load diff --git a/docs/fiddles/native-ui/dialogs/error-dialog/main.js b/docs/fiddles/native-ui/dialogs/error-dialog/main.js index 07612a621a548..7567aa411bda2 100644 --- a/docs/fiddles/native-ui/dialogs/error-dialog/main.js +++ b/docs/fiddles/native-ui/dialogs/error-dialog/main.js @@ -1,95 +1,95 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow, ipcMain, dialog } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -ipcMain.on('open-error-dialog', event => { - dialog.showErrorBox('An Error Message', 'Demonstrating an error message.') -}) - -ipcMain.on('open-information-dialog', event => { - const options = { - type: 'info', - title: 'Information', - message: "This is an information dialog. Isn't it nice?", - buttons: ['Yes', 'No'] - } - dialog.showMessageBox(options, index => { - event.sender.send('information-dialog-selection', index) - }) -}) - -ipcMain.on('open-file-dialog', event => { - dialog.showOpenDialog( - { - properties: ['openFile', 'openDirectory'] - }, - files => { - if (files) { - event.sender.send('selected-directory', files) - } - } - ) -}) - -ipcMain.on('save-dialog', event => { - const options = { - title: 'Save an Image', - filters: [{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }] - } - dialog.showSaveDialog(options, filename => { - event.sender.send('saved-file', filename) - }) -}) - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. +// Modules to control application life and create native browser window +const { app, BrowserWindow, ipcMain, dialog } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +ipcMain.on('open-error-dialog', event => { + dialog.showErrorBox('An Error Message', 'Demonstrating an error message.') +}) + +ipcMain.on('open-information-dialog', event => { + const options = { + type: 'info', + title: 'Information', + message: "This is an information dialog. Isn't it nice?", + buttons: ['Yes', 'No'] + } + dialog.showMessageBox(options, index => { + event.sender.send('information-dialog-selection', index) + }) +}) + +ipcMain.on('open-file-dialog', event => { + dialog.showOpenDialog( + { + properties: ['openFile', 'openDirectory'] + }, + files => { + if (files) { + event.sender.send('selected-directory', files) + } + } + ) +}) + +ipcMain.on('save-dialog', event => { + const options = { + title: 'Save an Image', + filters: [{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }] + } + dialog.showSaveDialog(options, filename => { + event.sender.send('saved-file', filename) + }) +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/native-ui/dialogs/error-dialog/renderer.js b/docs/fiddles/native-ui/dialogs/error-dialog/renderer.js index c61527b5043f5..4011066587dcb 100644 --- a/docs/fiddles/native-ui/dialogs/error-dialog/renderer.js +++ b/docs/fiddles/native-ui/dialogs/error-dialog/renderer.js @@ -1,18 +1,18 @@ -const { ipcRenderer, shell } = require('electron') - -const links = document.querySelectorAll('a[href]') -const errorBtn = document.getElementById('error-dialog') - -errorBtn.addEventListener('click', event => { - ipcRenderer.send('open-error-dialog') -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } +const { ipcRenderer, shell } = require('electron') + +const links = document.querySelectorAll('a[href]') +const errorBtn = document.getElementById('error-dialog') + +errorBtn.addEventListener('click', event => { + ipcRenderer.send('open-error-dialog') +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } }) \ No newline at end of file diff --git a/docs/fiddles/native-ui/dialogs/information-dialog/main.js b/docs/fiddles/native-ui/dialogs/information-dialog/main.js index 71c9bafbe0d1a..3e81a5782076f 100644 --- a/docs/fiddles/native-ui/dialogs/information-dialog/main.js +++ b/docs/fiddles/native-ui/dialogs/information-dialog/main.js @@ -1,70 +1,70 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow, ipcMain, dialog } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - - -ipcMain.on('open-information-dialog', event => { - const options = { - type: 'info', - title: 'Information', - message: "This is an information dialog. Isn't it nice?", - buttons: ['Yes', 'No'] - } - dialog.showMessageBox(options, index => { - event.sender.send('information-dialog-selection', index) - }) -}) - - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. +// Modules to control application life and create native browser window +const { app, BrowserWindow, ipcMain, dialog } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + + +ipcMain.on('open-information-dialog', event => { + const options = { + type: 'info', + title: 'Information', + message: "This is an information dialog. Isn't it nice?", + buttons: ['Yes', 'No'] + } + dialog.showMessageBox(options, index => { + event.sender.send('information-dialog-selection', index) + }) +}) + + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/native-ui/dialogs/information-dialog/renderer.js b/docs/fiddles/native-ui/dialogs/information-dialog/renderer.js index 32a5ff872363a..69ea9cdb1a2f2 100644 --- a/docs/fiddles/native-ui/dialogs/information-dialog/renderer.js +++ b/docs/fiddles/native-ui/dialogs/information-dialog/renderer.js @@ -1,25 +1,25 @@ -const { ipcRenderer, shell } = require('electron') - -const informationBtn = document.getElementById('information-dialog') -const links = document.querySelectorAll('a[href]') - -informationBtn.addEventListener('click', event => { - ipcRenderer.send('open-information-dialog') -}) - -ipcRenderer.on('information-dialog-selection', (event, index) => { - let message = 'You selected ' - if (index === 0) message += 'yes.' - else message += 'no.' - document.getElementById('info-selection').innerHTML = message -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } +const { ipcRenderer, shell } = require('electron') + +const informationBtn = document.getElementById('information-dialog') +const links = document.querySelectorAll('a[href]') + +informationBtn.addEventListener('click', event => { + ipcRenderer.send('open-information-dialog') +}) + +ipcRenderer.on('information-dialog-selection', (event, index) => { + let message = 'You selected ' + if (index === 0) message += 'yes.' + else message += 'no.' + document.getElementById('info-selection').innerHTML = message +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } }) \ No newline at end of file diff --git a/docs/fiddles/native-ui/dialogs/open-file-or-directory/main.js b/docs/fiddles/native-ui/dialogs/open-file-or-directory/main.js index c47083dfe46b2..24b9164dab3a9 100644 --- a/docs/fiddles/native-ui/dialogs/open-file-or-directory/main.js +++ b/docs/fiddles/native-ui/dialogs/open-file-or-directory/main.js @@ -1,70 +1,70 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow, ipcMain, dialog } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - - -ipcMain.on('open-file-dialog', event => { - dialog.showOpenDialog( - { - properties: ['openFile', 'openDirectory'] - }, - files => { - if (files) { - event.sender.send('selected-directory', files) - } - } - ) -}) - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. +// Modules to control application life and create native browser window +const { app, BrowserWindow, ipcMain, dialog } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + + +ipcMain.on('open-file-dialog', event => { + dialog.showOpenDialog( + { + properties: ['openFile', 'openDirectory'] + }, + files => { + if (files) { + event.sender.send('selected-directory', files) + } + } + ) +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/native-ui/dialogs/open-file-or-directory/renderer.js b/docs/fiddles/native-ui/dialogs/open-file-or-directory/renderer.js index 25953b2267f0f..5389ea50709a0 100644 --- a/docs/fiddles/native-ui/dialogs/open-file-or-directory/renderer.js +++ b/docs/fiddles/native-ui/dialogs/open-file-or-directory/renderer.js @@ -1,22 +1,22 @@ -const { ipcRenderer, shell } = require('electron') - -const selectDirBtn = document.getElementById('select-directory') -const links = document.querySelectorAll('a[href]') - -selectDirBtn.addEventListener('click', event => { - ipcRenderer.send('open-file-dialog') -}) - -ipcRenderer.on('selected-directory', (event, path) => { - document.getElementById('selected-file').innerHTML = `You selected: ${path}` -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } -}) +const { ipcRenderer, shell } = require('electron') + +const selectDirBtn = document.getElementById('select-directory') +const links = document.querySelectorAll('a[href]') + +selectDirBtn.addEventListener('click', event => { + ipcRenderer.send('open-file-dialog') +}) + +ipcRenderer.on('selected-directory', (event, path) => { + document.getElementById('selected-file').innerHTML = `You selected: ${path}` +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } +}) diff --git a/docs/fiddles/native-ui/dialogs/save-dialog/main.js b/docs/fiddles/native-ui/dialogs/save-dialog/main.js index 16d222825e831..b6e6ec1331be9 100644 --- a/docs/fiddles/native-ui/dialogs/save-dialog/main.js +++ b/docs/fiddles/native-ui/dialogs/save-dialog/main.js @@ -1,66 +1,66 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow, ipcMain, dialog } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -ipcMain.on('save-dialog', event => { - const options = { - title: 'Save an Image', - filters: [{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }] - } - dialog.showSaveDialog(options, filename => { - event.sender.send('saved-file', filename) - }) -}) - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. +// Modules to control application life and create native browser window +const { app, BrowserWindow, ipcMain, dialog } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +ipcMain.on('save-dialog', event => { + const options = { + title: 'Save an Image', + filters: [{ name: 'Images', extensions: ['jpg', 'png', 'gif'] }] + } + dialog.showSaveDialog(options, filename => { + event.sender.send('saved-file', filename) + }) +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/native-ui/dialogs/save-dialog/renderer.js b/docs/fiddles/native-ui/dialogs/save-dialog/renderer.js index aad625d8445b0..9f6da5546946d 100644 --- a/docs/fiddles/native-ui/dialogs/save-dialog/renderer.js +++ b/docs/fiddles/native-ui/dialogs/save-dialog/renderer.js @@ -1,23 +1,23 @@ -const { ipcRenderer, shell } = require('electron') - -const saveBtn = document.getElementById('save-dialog') -const links = document.querySelectorAll('a[href]') - -saveBtn.addEventListener('click', event => { - ipcRenderer.send('save-dialog') -}) - -ipcRenderer.on('saved-file', (event, path) => { - if (!path) path = 'No path' - document.getElementById('file-saved').innerHTML = `Path selected: ${path}` -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } +const { ipcRenderer, shell } = require('electron') + +const saveBtn = document.getElementById('save-dialog') +const links = document.querySelectorAll('a[href]') + +saveBtn.addEventListener('click', event => { + ipcRenderer.send('save-dialog') +}) + +ipcRenderer.on('saved-file', (event, path) => { + if (!path) path = 'No path' + document.getElementById('file-saved').innerHTML = `Path selected: ${path}` +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } }) \ No newline at end of file diff --git a/docs/fiddles/native-ui/external-links-file-manager/main.js b/docs/fiddles/native-ui/external-links-file-manager/main.js index 66313f6abc236..6291dcad9c993 100644 --- a/docs/fiddles/native-ui/external-links-file-manager/main.js +++ b/docs/fiddles/native-ui/external-links-file-manager/main.js @@ -1,56 +1,56 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -// In this file you can include the rest of your app's specific main process +// Modules to control application life and create native browser window +const { app, BrowserWindow } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/native-ui/external-links-file-manager/renderer.js b/docs/fiddles/native-ui/external-links-file-manager/renderer.js index 29c2529fa339a..5ce5bae2da3aa 100644 --- a/docs/fiddles/native-ui/external-links-file-manager/renderer.js +++ b/docs/fiddles/native-ui/external-links-file-manager/renderer.js @@ -1,13 +1,13 @@ -const { shell } = require('electron') -const os = require('os') - -const exLinksBtn = document.getElementById('open-ex-links') -const fileManagerBtn = document.getElementById('open-file-manager') - -fileManagerBtn.addEventListener('click', (event) => { - shell.showItemInFolder(os.homedir()) -}) - -exLinksBtn.addEventListener('click', (event) => { - shell.openExternal('https://electronjs.org') +const { shell } = require('electron') +const os = require('os') + +const exLinksBtn = document.getElementById('open-ex-links') +const fileManagerBtn = document.getElementById('open-file-manager') + +fileManagerBtn.addEventListener('click', (event) => { + shell.showItemInFolder(os.homedir()) +}) + +exLinksBtn.addEventListener('click', (event) => { + shell.openExternal('https://electronjs.org') }) diff --git a/docs/fiddles/native-ui/notifications/main.js b/docs/fiddles/native-ui/notifications/main.js index 66313f6abc236..6291dcad9c993 100644 --- a/docs/fiddles/native-ui/notifications/main.js +++ b/docs/fiddles/native-ui/notifications/main.js @@ -1,56 +1,56 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -// In this file you can include the rest of your app's specific main process +// Modules to control application life and create native browser window +const { app, BrowserWindow } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/native-ui/notifications/renderer.js b/docs/fiddles/native-ui/notifications/renderer.js index b4dfdf0364604..9a97f7a869e03 100644 --- a/docs/fiddles/native-ui/notifications/renderer.js +++ b/docs/fiddles/native-ui/notifications/renderer.js @@ -1,29 +1,29 @@ -const basicNotification = { - title: 'Basic Notification', - body: 'Short message part' -} - -const notification = { - title: 'Notification with image', - body: 'Short message plus a custom image', - icon: 'https://via.placeholder.com/150' -} - -const basicNotificationButton = document.getElementById('basic-noti') -const notificationButton = document.getElementById('advanced-noti') - -notificationButton.addEventListener('click', () => { - const myNotification = new window.Notification(notification.title, notification) - - myNotification.onclick = () => { - console.log('Notification clicked') - } -}) - -basicNotificationButton.addEventListener('click', () => { - const myNotification = new window.Notification(basicNotification.title, basicNotification) - - myNotification.onclick = () => { - console.log('Notification clicked') - } -}) +const basicNotification = { + title: 'Basic Notification', + body: 'Short message part' +} + +const notification = { + title: 'Notification with image', + body: 'Short message plus a custom image', + icon: 'https://via.placeholder.com/150' +} + +const basicNotificationButton = document.getElementById('basic-noti') +const notificationButton = document.getElementById('advanced-noti') + +notificationButton.addEventListener('click', () => { + const myNotification = new window.Notification(notification.title, notification) + + myNotification.onclick = () => { + console.log('Notification clicked') + } +}) + +basicNotificationButton.addEventListener('click', () => { + const myNotification = new window.Notification(basicNotification.title, basicNotification) + + myNotification.onclick = () => { + console.log('Notification clicked') + } +}) diff --git a/docs/fiddles/native-ui/tray/main.js b/docs/fiddles/native-ui/tray/main.js index b110266929890..9a94efe327315 100644 --- a/docs/fiddles/native-ui/tray/main.js +++ b/docs/fiddles/native-ui/tray/main.js @@ -1,77 +1,77 @@ -// Modules to control application life and create native browser window -const { ipcMain, app, nativeImage, Menu, Tray, BrowserWindow } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow -let appIcon = null - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -ipcMain.on('put-in-tray', (event) => { - const icon = nativeImage.createFromDataURL('data:image/png;base64,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') - - appIcon = new Tray(icon) - - const contextMenu = Menu.buildFromTemplate([{ - label: 'Remove', - click: () => { - event.sender.send('tray-removed') - } - }]) - - appIcon.setToolTip('Electron Demo in the tray.') - appIcon.setContextMenu(contextMenu) -}) - -ipcMain.on('remove-tray', () => { - appIcon.destroy() -}) - -// In this file you can include the rest of your app's specific main process -// code. You can also put them in separate files and require them here. +// Modules to control application life and create native browser window +const { ipcMain, app, nativeImage, Menu, Tray, BrowserWindow } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow +let appIcon = null + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +ipcMain.on('put-in-tray', (event) => { + const icon = nativeImage.createFromDataURL('data:image/png;base64,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') + + appIcon = new Tray(icon) + + const contextMenu = Menu.buildFromTemplate([{ + label: 'Remove', + click: () => { + event.sender.send('tray-removed') + } + }]) + + appIcon.setToolTip('Electron Demo in the tray.') + appIcon.setContextMenu(contextMenu) +}) + +ipcMain.on('remove-tray', () => { + appIcon.destroy() +}) + +// In this file you can include the rest of your app's specific main process +// code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/native-ui/tray/renderer.js b/docs/fiddles/native-ui/tray/renderer.js index 8fe79ffd94daf..40eee8c859bdf 100644 --- a/docs/fiddles/native-ui/tray/renderer.js +++ b/docs/fiddles/native-ui/tray/renderer.js @@ -1,35 +1,35 @@ -const { ipcRenderer, shell } = require('electron') - -const trayBtn = document.getElementById('put-in-tray') -const links = document.querySelectorAll('a[href]') - -let trayOn = false - -trayBtn.addEventListener('click', function (event) { - if (trayOn) { - trayOn = false - document.getElementById('tray-countdown').innerHTML = '' - ipcRenderer.send('remove-tray') - } else { - trayOn = true - const message = 'Click demo again to remove.' - document.getElementById('tray-countdown').innerHTML = message - ipcRenderer.send('put-in-tray') - } -}) -// Tray removed from context menu on icon -ipcRenderer.on('tray-removed', function () { - ipcRenderer.send('remove-tray') - trayOn = false - document.getElementById('tray-countdown').innerHTML = '' -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } +const { ipcRenderer, shell } = require('electron') + +const trayBtn = document.getElementById('put-in-tray') +const links = document.querySelectorAll('a[href]') + +let trayOn = false + +trayBtn.addEventListener('click', function (event) { + if (trayOn) { + trayOn = false + document.getElementById('tray-countdown').innerHTML = '' + ipcRenderer.send('remove-tray') + } else { + trayOn = true + const message = 'Click demo again to remove.' + document.getElementById('tray-countdown').innerHTML = message + ipcRenderer.send('put-in-tray') + } +}) +// Tray removed from context menu on icon +ipcRenderer.on('tray-removed', function () { + ipcRenderer.send('remove-tray') + trayOn = false + document.getElementById('tray-countdown').innerHTML = '' +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } }) \ No newline at end of file diff --git a/docs/fiddles/windows/manage-windows/frameless-window/main.js b/docs/fiddles/windows/manage-windows/frameless-window/main.js index 66313f6abc236..6291dcad9c993 100644 --- a/docs/fiddles/windows/manage-windows/frameless-window/main.js +++ b/docs/fiddles/windows/manage-windows/frameless-window/main.js @@ -1,56 +1,56 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -// In this file you can include the rest of your app's specific main process +// Modules to control application life and create native browser window +const { app, BrowserWindow } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/windows/manage-windows/frameless-window/renderer.js b/docs/fiddles/windows/manage-windows/frameless-window/renderer.js index 42dcf80227c11..89550c0e08d2f 100644 --- a/docs/fiddles/windows/manage-windows/frameless-window/renderer.js +++ b/docs/fiddles/windows/manage-windows/frameless-window/renderer.js @@ -1,25 +1,25 @@ -const { BrowserWindow } = require('electron').remote -const shell = require('electron').shell - -const framelessWindowBtn = document.getElementById('frameless-window') - -const links = document.querySelectorAll('a[href]') - -framelessWindowBtn.addEventListener('click', (event) => { - const modalPath = 'https://electronjs.org' - let win = new BrowserWindow({ frame: false }) - - win.on('close', () => { win = null }) - win.loadURL(modalPath) - win.show() -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } -}) +const { BrowserWindow } = require('electron').remote +const shell = require('electron').shell + +const framelessWindowBtn = document.getElementById('frameless-window') + +const links = document.querySelectorAll('a[href]') + +framelessWindowBtn.addEventListener('click', (event) => { + const modalPath = 'https://electronjs.org' + let win = new BrowserWindow({ frame: false }) + + win.on('close', () => { win = null }) + win.loadURL(modalPath) + win.show() +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } +}) diff --git a/docs/fiddles/windows/manage-windows/manage-window-state/main.js b/docs/fiddles/windows/manage-windows/manage-window-state/main.js index 66313f6abc236..6291dcad9c993 100644 --- a/docs/fiddles/windows/manage-windows/manage-window-state/main.js +++ b/docs/fiddles/windows/manage-windows/manage-window-state/main.js @@ -1,56 +1,56 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -// In this file you can include the rest of your app's specific main process +// Modules to control application life and create native browser window +const { app, BrowserWindow } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/windows/manage-windows/manage-window-state/renderer.js b/docs/fiddles/windows/manage-windows/manage-window-state/renderer.js index 8d054f8358bc4..5f69fc0282e99 100644 --- a/docs/fiddles/windows/manage-windows/manage-window-state/renderer.js +++ b/docs/fiddles/windows/manage-windows/manage-window-state/renderer.js @@ -1,35 +1,35 @@ -const { BrowserWindow } = require('electron').remote -const shell = require('electron').shell - -const manageWindowBtn = document.getElementById('manage-window') - -const links = document.querySelectorAll('a[href]') - -let win - -manageWindowBtn.addEventListener('click', (event) => { - const modalPath = 'https://electronjs.org' - win = new BrowserWindow({ width: 400, height: 275 }) - - win.on('resize', updateReply) - win.on('move', updateReply) - win.on('close', () => { win = null }) - win.loadURL(modalPath) - win.show() - - function updateReply () { - const manageWindowReply = document.getElementById('manage-window-reply') - const message = `Size: ${win.getSize()} Position: ${win.getPosition()}` - manageWindowReply.innerText = message - } -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } -}) +const { BrowserWindow } = require('electron').remote +const shell = require('electron').shell + +const manageWindowBtn = document.getElementById('manage-window') + +const links = document.querySelectorAll('a[href]') + +let win + +manageWindowBtn.addEventListener('click', (event) => { + const modalPath = 'https://electronjs.org' + win = new BrowserWindow({ width: 400, height: 275 }) + + win.on('resize', updateReply) + win.on('move', updateReply) + win.on('close', () => { win = null }) + win.loadURL(modalPath) + win.show() + + function updateReply () { + const manageWindowReply = document.getElementById('manage-window-reply') + const message = `Size: ${win.getSize()} Position: ${win.getPosition()}` + manageWindowReply.innerText = message + } +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } +}) diff --git a/docs/fiddles/windows/manage-windows/window-events/main.js b/docs/fiddles/windows/manage-windows/window-events/main.js index 66313f6abc236..6291dcad9c993 100644 --- a/docs/fiddles/windows/manage-windows/window-events/main.js +++ b/docs/fiddles/windows/manage-windows/window-events/main.js @@ -1,56 +1,56 @@ -// Modules to control application life and create native browser window -const { app, BrowserWindow } = require('electron') - -// Keep a global reference of the window object, if you don't, the window will -// be closed automatically when the JavaScript object is garbage collected. -let mainWindow - -function createWindow () { - // Create the browser window. - mainWindow = new BrowserWindow({ - width: 800, - height: 600, - webPreferences: { - nodeIntegration: true - } - }) - - // and load the index.html of the app. - mainWindow.loadFile('index.html') - - // Open the DevTools. - // mainWindow.webContents.openDevTools() - - // Emitted when the window is closed. - mainWindow.on('closed', function () { - // Dereference the window object, usually you would store windows - // in an array if your app supports multi windows, this is the time - // when you should delete the corresponding element. - mainWindow = null - }) -} - -// This method will be called when Electron has finished -// initialization and is ready to create browser windows. -// Some APIs can only be used after this event occurs. -app.whenReady().then(createWindow) - -// Quit when all windows are closed. -app.on('window-all-closed', function () { - // On macOS it is common for applications and their menu bar - // to stay active until the user quits explicitly with Cmd + Q - if (process.platform !== 'darwin') { - app.quit() - } -}) - -app.on('activate', function () { - // On macOS it is common to re-create a window in the app when the - // dock icon is clicked and there are no other windows open. - if (mainWindow === null) { - createWindow() - } -}) - -// In this file you can include the rest of your app's specific main process +// Modules to control application life and create native browser window +const { app, BrowserWindow } = require('electron') + +// Keep a global reference of the window object, if you don't, the window will +// be closed automatically when the JavaScript object is garbage collected. +let mainWindow + +function createWindow () { + // Create the browser window. + mainWindow = new BrowserWindow({ + width: 800, + height: 600, + webPreferences: { + nodeIntegration: true + } + }) + + // and load the index.html of the app. + mainWindow.loadFile('index.html') + + // Open the DevTools. + // mainWindow.webContents.openDevTools() + + // Emitted when the window is closed. + mainWindow.on('closed', function () { + // Dereference the window object, usually you would store windows + // in an array if your app supports multi windows, this is the time + // when you should delete the corresponding element. + mainWindow = null + }) +} + +// This method will be called when Electron has finished +// initialization and is ready to create browser windows. +// Some APIs can only be used after this event occurs. +app.whenReady().then(createWindow) + +// Quit when all windows are closed. +app.on('window-all-closed', function () { + // On macOS it is common for applications and their menu bar + // to stay active until the user quits explicitly with Cmd + Q + if (process.platform !== 'darwin') { + app.quit() + } +}) + +app.on('activate', function () { + // On macOS it is common to re-create a window in the app when the + // dock icon is clicked and there are no other windows open. + if (mainWindow === null) { + createWindow() + } +}) + +// In this file you can include the rest of your app's specific main process // code. You can also put them in separate files and require them here. diff --git a/docs/fiddles/windows/manage-windows/window-events/renderer.js b/docs/fiddles/windows/manage-windows/window-events/renderer.js index 63326c33f41e0..1fac7212672dc 100644 --- a/docs/fiddles/windows/manage-windows/window-events/renderer.js +++ b/docs/fiddles/windows/manage-windows/window-events/renderer.js @@ -1,48 +1,48 @@ -const { BrowserWindow } = require('electron').remote -const shell = require('electron').shell - -const listenToWindowBtn = document.getElementById('listen-to-window') -const focusModalBtn = document.getElementById('focus-on-modal-window') - -const links = document.querySelectorAll('a[href]') - -let win - -listenToWindowBtn.addEventListener('click', () => { - const modalPath = 'https://electronjs.org' - win = new BrowserWindow({ width: 600, height: 400 }) - - const hideFocusBtn = () => { - focusModalBtn.classList.add('disappear') - focusModalBtn.classList.remove('smooth-appear') - focusModalBtn.removeEventListener('click', clickHandler) - } - - const showFocusBtn = (btn) => { - if (!win) return - focusModalBtn.classList.add('smooth-appear') - focusModalBtn.classList.remove('disappear') - focusModalBtn.addEventListener('click', clickHandler) - } - - win.on('focus', hideFocusBtn) - win.on('blur', showFocusBtn) - win.on('close', () => { - hideFocusBtn() - win = null - }) - win.loadURL(modalPath) - win.show() - - const clickHandler = () => { win.focus() } -}) - -Array.prototype.forEach.call(links, (link) => { - const url = link.getAttribute('href') - if (url.indexOf('http') === 0) { - link.addEventListener('click', (e) => { - e.preventDefault() - shell.openExternal(url) - }) - } -}) +const { BrowserWindow } = require('electron').remote +const shell = require('electron').shell + +const listenToWindowBtn = document.getElementById('listen-to-window') +const focusModalBtn = document.getElementById('focus-on-modal-window') + +const links = document.querySelectorAll('a[href]') + +let win + +listenToWindowBtn.addEventListener('click', () => { + const modalPath = 'https://electronjs.org' + win = new BrowserWindow({ width: 600, height: 400 }) + + const hideFocusBtn = () => { + focusModalBtn.classList.add('disappear') + focusModalBtn.classList.remove('smooth-appear') + focusModalBtn.removeEventListener('click', clickHandler) + } + + const showFocusBtn = (btn) => { + if (!win) return + focusModalBtn.classList.add('smooth-appear') + focusModalBtn.classList.remove('disappear') + focusModalBtn.addEventListener('click', clickHandler) + } + + win.on('focus', hideFocusBtn) + win.on('blur', showFocusBtn) + win.on('close', () => { + hideFocusBtn() + win = null + }) + win.loadURL(modalPath) + win.show() + + const clickHandler = () => { win.focus() } +}) + +Array.prototype.forEach.call(links, (link) => { + const url = link.getAttribute('href') + if (url.indexOf('http') === 0) { + link.addEventListener('click', (e) => { + e.preventDefault() + shell.openExternal(url) + }) + } +}) diff --git a/docs/tutorial/devtools-extension.md b/docs/tutorial/devtools-extension.md index 135f8e0f82dcd..377fc5c50f79e 100644 --- a/docs/tutorial/devtools-extension.md +++ b/docs/tutorial/devtools-extension.md @@ -1,25 +1,28 @@ # DevTools Extension -Electron supports the [Chrome DevTools Extension][devtools-extension], which can -be used to extend the ability of devtools for debugging popular web frameworks. +Electron supports [Chrome DevTools extensions][devtools-extension], which can +be used to extend the ability of Chrome's developer tools for debugging +popular web frameworks. -## How to load a DevTools Extension +## Loading a DevTools extension with tooling -This document outlines the process for manually loading an extension. -You may also try -[electron-devtools-installer](https://github.com/GPMDP/electron-devtools-installer), -a third-party tool that downloads extensions directly from the Chrome WebStore. +The easiest way to load a DevTools extension is to use third-party tooling to automate the +process for you. [electron-devtools-installer][electron-devtools-installer] is a popular +NPM package that does just that. -To load an extension in Electron, you need to download it in Chrome browser, -locate its filesystem path, and then load it by calling the -`BrowserWindow.addDevToolsExtension(extension)` API. +## Manually loading a DevTools extension -Using the [React Developer Tools][react-devtools] as example: +If you don't want to use the tooling approach, you can also do all of the necessary +operations by hand. To load an extension in Electron, you need to download it via Chrome, +locate its filesystem path, and then load it into your [Session][session] by calling the +[`ses.loadExtension`] API. -1. Install it in Chrome browser. +Using the [React Developer Tools][react-devtools] as an example: + +1. Install the extension in Google Chrome. 1. Navigate to `chrome://extensions`, and find its extension ID, which is a hash string like `fmkadmapgofadopljbjfkapdkoienihi`. -1. Find out filesystem location used by Chrome for storing extensions: +1. Find out the filesystem location used by Chrome for storing extensions: * on Windows it is `%LOCALAPPDATA%\Google\Chrome\User Data\Default\Extensions`; * on Linux it could be: * `~/.config/google-chrome/Default/Extensions/` @@ -27,36 +30,48 @@ Using the [React Developer Tools][react-devtools] as example: * `~/.config/google-chrome-canary/Default/Extensions/` * `~/.config/chromium/Default/Extensions/` * on macOS it is `~/Library/Application Support/Google/Chrome/Default/Extensions`. -1. Pass the location of the extension to `BrowserWindow.addDevToolsExtension` - API, for the React Developer Tools, it is something like: +1. Pass the location of the extension to the [`ses.loadExtension`][load-extension] + API. For React Developer Tools `v4.9.0`, it looks something like: ```javascript - const path = require('path') - const os = require('os') - - BrowserWindow.addDevToolsExtension( - path.join(os.homedir(), '/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.3.0_0') - ) + const { app, session } = require('electron') + const path = require('path') + const os = require('os') + + // on macOS + const reactDevToolsPath = path.join( + os.homedir(), + '/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.9.0_0' + ) + + app.whenReady().then(async () => { + await session.defaultSession.loadExtension(reactDevToolsPath) + }) ``` -**Note:** The `BrowserWindow.addDevToolsExtension` API cannot be called before the -ready event of the app module is emitted. +**Notes:** + +* `loadExtension` returns a Promise with an [Extension object][extension-structure], +which contains metadata about the extension that was loaded. This promise needs to +resolve (e.g. with an `await` expression) before loading a page. Otherwise, the +extension won't be guaranteed to load. +* `loadExtension` cannot be called before the `ready` event of the `app` module +is emitted, nor can it be called on in-memory (non-persistent) sessions. +* `loadExtension` must be called on every boot of your app if you want the +extension to be loaded. -The extension will be remembered so you only need to call this API once per -extension. If you try to add an extension that has already been loaded, this method -will not return and instead log a warning to the console. +### Removing a DevTools extension -### How to remove a DevTools Extension +You can pass the extension's ID to the [`ses.removeExtension`][remove-extension] API to +remove it from your Session. Loaded extensions are not persisted between +app launches. -You can pass the name of the extension to the `BrowserWindow.removeDevToolsExtension` -API to remove it. The name of the extension is returned by -`BrowserWindow.addDevToolsExtension` and you can get the names of all installed -DevTools Extensions using the `BrowserWindow.getDevToolsExtensions` API. +## DevTools extension support -## Supported DevTools Extensions +Electron only supports +[a limited set of `chrome.*` APIs][supported-extension-apis], +so extensions using unsupported `chrome.*` APIs under the hood may not work. -Electron only supports a limited set of `chrome.*` APIs, so some extensions -using unsupported `chrome.*` APIs for chrome extension features may not work. -Following Devtools Extensions are tested and guaranteed to work in Electron: +The following Devtools extensions have been tested to work in Electron: * [Ember Inspector](https://chrome.google.com/webstore/detail/ember-inspector/bmdblncegkenkacieihfhpjfppoconhi) * [React Developer Tools](https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi) @@ -68,14 +83,22 @@ Following Devtools Extensions are tested and guaranteed to work in Electron: * [Redux DevTools Extension](https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd) * [MobX Developer Tools](https://chrome.google.com/webstore/detail/mobx-developer-tools/pfgnfdagidkfgccljigdamigbcnndkod) -### What should I do if a DevTools Extension is not working? +### What should I do if a DevTools extension is not working? -First please make sure the extension is still being maintained, some extensions -can not even work for recent versions of Chrome browser, and we are not able to -do anything for them. +First, please make sure the extension is still being maintained and is compatible +with the latest version of Google Chrome. We cannot provide additional support for +unsupported extensions. -Then file a bug at Electron's issues list, and describe which part of the -extension is not working as expected. +If the extension works on Chrome but not on Electron, file a bug in Electron's +[issue tracker][issue-tracker] and describe which part +of the extension is not working as expected. [devtools-extension]: https://developer.chrome.com/extensions/devtools +[session]: ../api/session.md [react-devtools]: https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi +[load-extension]: ../api/session.md#sesloadextensionpath +[extension-structure]: ../api/structures/extension.md +[remove-extension]: ../api/session.md#sesremoveextensionextensionid +[electron-devtools-installer]: https://github.com/MarshallOfSound/electron-devtools-installer +[supported-extension-apis]: ../api/extensions.md +[issue-tracker]: https://github.com/electron/electron/issues diff --git a/docs/tutorial/offscreen-rendering.md b/docs/tutorial/offscreen-rendering.md index 74bfff02be469..93e07ee42c01e 100644 --- a/docs/tutorial/offscreen-rendering.md +++ b/docs/tutorial/offscreen-rendering.md @@ -9,7 +9,7 @@ Two modes of rendering can be used and only the dirty area is passed in the `'paint'` event to be more efficient. The rendering can be stopped, continued and the frame rate can be set. The specified frame rate is a top limit value, when there is nothing happening on a webpage, no frames are generated. The -maximum frame rate is 60, because above that there is no benefit, only +maximum frame rate is 240, because above that there is no benefit, only performance loss. **Note:** An offscreen window is always created as a [Frameless Window](../api/frameless-window.md). diff --git a/docs/tutorial/support.md b/docs/tutorial/support.md index e7e5798e5a50b..ce2cb6ddad485 100644 --- a/docs/tutorial/support.md +++ b/docs/tutorial/support.md @@ -9,8 +9,12 @@ If you're looking for programming help, for answers to questions, or to join in discussion with other developers who use Electron, you can interact with the community in these locations: -- [`electron`](https://discuss.atom.io/c/electron) category on the Atom -forums +- [`Electron's Discord`](https://discord.com/invite/electron) has channels for: + - Getting help + - Ecosystem apps like [Electron Forge](https://github.com/electron-userland/electron-forge) and [Electron Fiddle](https://github.com/electron/fiddle) + - Sharing ideas with other Electron app developers + - And more! +- [`electron`](https://discuss.atom.io/c/electron) category on the Atom forums - `#atom-shell` channel on Freenode - `#electron` channel on [Atom's Slack](https://discuss.atom.io/t/join-us-on-slack/16638?source_topic_id=25406) - [`electron-ru`](https://telegram.me/electron_ru) *(Russian)* diff --git a/electron_paks.gni b/electron_paks.gni index 3ba06a4982136..cdb96f2561a8d 100644 --- a/electron_paks.gni +++ b/electron_paks.gni @@ -54,7 +54,6 @@ template("electron_extra_paks") { ]) output = "${invoker.output_dir}/resources.pak" sources = [ - "$root_gen_dir/chrome/print_preview_pdf_resources.pak", "$root_gen_dir/components/components_resources.pak", "$root_gen_dir/content/browser/resources/media/media_internals_resources.pak", "$root_gen_dir/content/browser/tracing/tracing_resources.pak", @@ -69,7 +68,6 @@ template("electron_extra_paks") { "$target_gen_dir/electron_resources.pak", ] deps = [ - "//chrome/browser/resources:print_preview_pdf_resources", "//components/resources", "//content:content_resources", "//content:dev_ui_content_resources", @@ -94,6 +92,10 @@ template("electron_extra_paks") { sources += [ "$root_gen_dir/content/browser/devtools/devtools_resources.pak" ] deps += [ "//content/browser/devtools:devtools_resources" ] + if (enable_pdf_viewer) { + sources += [ "$root_gen_dir/chrome/print_preview_pdf_resources.pak" ] + deps += [ "//chrome/browser/resources:print_preview_pdf_resources" ] + } if (enable_print_preview) { sources += [ "$root_gen_dir/chrome/print_preview_resources.pak" ] deps += [ "//chrome/browser/resources:print_preview_resources" ] diff --git a/filenames.auto.gni b/filenames.auto.gni index cdac19998e960..876e12f29123c 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -119,8 +119,6 @@ auto_filenames = { "docs/api/structures/shared-worker-info.md", "docs/api/structures/shortcut-details.md", "docs/api/structures/size.md", - "docs/api/structures/stream-protocol-response.md", - "docs/api/structures/string-protocol-response.md", "docs/api/structures/task.md", "docs/api/structures/thumbar-button.md", "docs/api/structures/trace-categories-and-options.md", @@ -206,7 +204,7 @@ auto_filenames = { "lib/browser/api/module-list.ts", "lib/browser/api/native-image.ts", "lib/browser/api/native-theme.ts", - "lib/browser/api/net-log.js", + "lib/browser/api/net-log.ts", "lib/browser/api/net.ts", "lib/browser/api/notification.js", "lib/browser/api/power-monitor.ts", diff --git a/filenames.gni b/filenames.gni index bd2284e045233..8019dfcee4aac 100644 --- a/filenames.gni +++ b/filenames.gni @@ -184,8 +184,12 @@ filenames = { "shell/browser/electron_web_ui_controller_factory.h", "shell/browser/event_emitter_mixin.cc", "shell/browser/event_emitter_mixin.h", + "shell/browser/extended_web_contents_observer.h", "shell/browser/feature_list.cc", "shell/browser/feature_list.h", + "shell/browser/file_select_helper.cc", + "shell/browser/file_select_helper.h", + "shell/browser/file_select_helper_mac.mm", "shell/browser/font_defaults.cc", "shell/browser/font_defaults.h", "shell/browser/javascript_environment.cc", @@ -345,6 +349,7 @@ filenames = { "shell/browser/ui/devtools_manager_delegate.h", "shell/browser/ui/devtools_ui.cc", "shell/browser/ui/devtools_ui.h", + "shell/browser/ui/drag_util.cc", "shell/browser/ui/drag_util.h", "shell/browser/ui/drag_util_mac.mm", "shell/browser/ui/drag_util_views.cc", @@ -626,6 +631,8 @@ filenames = { "shell/browser/extensions/api/resources_private/resources_private_api.h", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc", "shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h", + "shell/browser/extensions/api/management/electron_management_api_delegate.cc", + "shell/browser/extensions/api/management/electron_management_api_delegate.h", "shell/browser/extensions/api/tabs/tabs_api.cc", "shell/browser/extensions/api/tabs/tabs_api.h", "shell/browser/extensions/api/streams_private/streams_private_api.cc", diff --git a/lib/browser/api/app.ts b/lib/browser/api/app.ts index 771b85542c09a..a8b6fc20e82af 100644 --- a/lib/browser/api/app.ts +++ b/lib/browser/api/app.ts @@ -135,6 +135,10 @@ for (const name of events) { }); } +app.on('-session-created' as any, (event, session) => { + app.emit('session-created', session); +}); + // Deprecate allowRendererProcessReuse but only if they set it to false, no need to log if // they are setting it to true deprecate.removeProperty(app, 'allowRendererProcessReuse', [false]); diff --git a/lib/browser/api/browser-window.js b/lib/browser/api/browser-window.js index 7c3e8579831f2..bf0cfe2a9face 100644 --- a/lib/browser/api/browser-window.js +++ b/lib/browser/api/browser-window.js @@ -30,9 +30,11 @@ BrowserWindow.prototype._init = function () { // Though this hack is only needed on macOS when the app is launched from // Finder, we still do it on all platforms in case of other bugs we don't // know. - this.webContents.once('load-url', function () { - this.focus(); - }); + if (this.webContents._initiallyShown) { + this.webContents.once('load-url', function () { + this.focus(); + }); + } // Redirect focus/blur event to app instance too. this.on('blur', (event) => { diff --git a/lib/browser/api/menu-utils.js b/lib/browser/api/menu-utils.js index 493f04c19c7b0..c7325198f1bc8 100644 --- a/lib/browser/api/menu-utils.js +++ b/lib/browser/api/menu-utils.js @@ -160,8 +160,14 @@ function sortGroups (groups) { } function sortMenuItems (menuItems) { - const isSeparator = (item) => item.type === 'separator'; - const separators = menuItems.filter(i => i.type === 'separator'); + const isSeparator = (opts) => { + return opts.type === 'separator' && + !opts.before && + !opts.after && + !opts.beforeGroupContaining && + !opts.afterGroupContaining; + }; + const separators = menuItems.filter(isSeparator); // Split the items into their implicit groups based upon separators. const groups = splitArray(menuItems, isSeparator); diff --git a/lib/browser/api/menu.js b/lib/browser/api/menu.js index c0db47e77b6b1..095d2d675d605 100644 --- a/lib/browser/api/menu.js +++ b/lib/browser/api/menu.js @@ -182,11 +182,11 @@ Menu.buildFromTemplate = function (template) { throw new TypeError('Invalid template for MenuItem: must have at least one of label, role or type'); } - const filtered = removeExtraSeparators(template); - const sorted = sortTemplate(filtered); + const sorted = sortTemplate(template); + const filtered = removeExtraSeparators(sorted); const menu = new Menu(); - sorted.forEach(item => { + filtered.forEach(item => { if (item instanceof MenuItem) { menu.append(item); } else { diff --git a/lib/browser/api/net-log.js b/lib/browser/api/net-log.js deleted file mode 100644 index 11b8071880ab7..0000000000000 --- a/lib/browser/api/net-log.js +++ /dev/null @@ -1,32 +0,0 @@ -'use strict'; - -// TODO(deepak1556): Deprecate and remove standalone netLog module, -// it is now a property of session module. -const { app, session } = require('electron'); - -// Fallback to default session. -Object.setPrototypeOf(module.exports, new Proxy({}, { - get (target, property) { - if (!app.isReady()) return; - - const netLog = session.defaultSession.netLog; - - if (!Object.prototype.hasOwnProperty.call(Object.getPrototypeOf(netLog), property)) return; - - // check for properties on the prototype chain that aren't functions - if (typeof netLog[property] !== 'function') return netLog[property]; - - // Returning a native function directly would throw error. - return (...args) => netLog[property](...args); - }, - - ownKeys () { - if (!app.isReady()) return []; - - return Object.getOwnPropertyNames(Object.getPrototypeOf(session.defaultSession.netLog)); - }, - - getOwnPropertyDescriptor (target) { - return { configurable: true, enumerable: true }; - } -})); diff --git a/lib/browser/api/net-log.ts b/lib/browser/api/net-log.ts new file mode 100644 index 0000000000000..81a9e5152435f --- /dev/null +++ b/lib/browser/api/net-log.ts @@ -0,0 +1,22 @@ +// TODO(deepak1556): Deprecate and remove standalone netLog module, +// it is now a property of session module. +import { app, session } from 'electron'; + +const startLogging: typeof session.defaultSession.netLog.startLogging = async (path, options) => { + if (!app.isReady()) return; + return session.defaultSession.netLog.startLogging(path, options); +}; + +const stopLogging: typeof session.defaultSession.netLog.stopLogging = async () => { + if (!app.isReady()) return; + return session.defaultSession.netLog.stopLogging(); +}; + +export default { + startLogging, + stopLogging, + get currentlyLogging (): boolean { + if (!app.isReady()) return false; + return session.defaultSession.netLog.currentlyLogging; + } +}; diff --git a/lib/browser/api/system-preferences.ts b/lib/browser/api/system-preferences.ts index 9ad72dec0ea24..bdc77c4dcd9c9 100644 --- a/lib/browser/api/system-preferences.ts +++ b/lib/browser/api/system-preferences.ts @@ -16,7 +16,7 @@ if ('getAppLevelAppearance' in systemPreferences) { } if ('getEffectiveAppearance' in systemPreferences) { - const nativeEAGetter = systemPreferences.getAppLevelAppearance; + const nativeEAGetter = systemPreferences.getEffectiveAppearance; Object.defineProperty(SystemPreferences.prototype, 'effectiveAppearance', { get: () => nativeEAGetter.call(systemPreferences) }); diff --git a/lib/browser/api/top-level-window.js b/lib/browser/api/top-level-window.js index 9a90887fcf250..933a168a0fe56 100644 --- a/lib/browser/api/top-level-window.js +++ b/lib/browser/api/top-level-window.js @@ -45,7 +45,7 @@ Object.defineProperty(TopLevelWindow.prototype, 'kiosk', { }); Object.defineProperty(TopLevelWindow.prototype, 'documentEdited', { - get: function () { return this.isFullscreen(); }, + get: function () { return this.isDocumentEdited(); }, set: function (edited) { this.setDocumentEdited(edited); } }); diff --git a/lib/browser/api/web-contents.js b/lib/browser/api/web-contents.js index ef7638495051f..3def8d218907e 100644 --- a/lib/browser/api/web-contents.js +++ b/lib/browser/api/web-contents.js @@ -167,29 +167,29 @@ WebContents.prototype._sendInternalToAll = function (channel, ...args) { return this._send(internal, sendToAll, channel, args); }; -WebContents.prototype.sendToFrame = function (frameId, channel, ...args) { +WebContents.prototype.sendToFrame = function (frame, channel, ...args) { if (typeof channel !== 'string') { throw new Error('Missing required channel argument'); - } else if (typeof frameId !== 'number') { - throw new Error('Missing required frameId argument'); + } else if (!(typeof frame === 'number' || Array.isArray(frame))) { + throw new Error('Missing required frame argument (must be number or array)'); } const internal = false; const sendToAll = false; - return this._sendToFrame(internal, sendToAll, frameId, channel, args); + return this._sendToFrame(internal, sendToAll, frame, channel, args); }; -WebContents.prototype._sendToFrameInternal = function (frameId, channel, ...args) { +WebContents.prototype._sendToFrameInternal = function (frame, channel, ...args) { if (typeof channel !== 'string') { throw new Error('Missing required channel argument'); - } else if (typeof frameId !== 'number') { - throw new Error('Missing required frameId argument'); + } else if (!(typeof frame === 'number' || Array.isArray(frame))) { + throw new Error('Missing required frame argument (must be number or array)'); } const internal = true; const sendToAll = false; - return this._sendToFrame(internal, sendToAll, frameId, channel, args); + return this._sendToFrame(internal, sendToAll, frame, channel, args); }; // Following methods are mapped to webFrame. @@ -445,8 +445,9 @@ WebContents.prototype.loadFile = function (filePath, options = {}) { }; const addReplyToEvent = (event) => { + const { processId, frameId } = event; event.reply = (...args) => { - event.sender.sendToFrame(event.frameId, ...args); + event.sender.sendToFrame([processId, frameId], ...args); }; }; @@ -467,6 +468,10 @@ const addReturnValueToEvent = (event) => { }); }; +const loggingEnabled = () => { + return process.env.ELECTRON_ENABLE_LOGGING || app.commandLine.hasSwitch('enable-logging'); +}; + // Add JavaScript wrappers for WebContents class. WebContents.prototype._init = function () { // The navigation controller. @@ -535,8 +540,13 @@ WebContents.prototype._init = function () { app.emit('renderer-process-crashed', event, this, ...args); }); - this.on('render-process-gone', (event, ...args) => { - app.emit('render-process-gone', event, this, ...args); + this.on('render-process-gone', (event, details) => { + app.emit('render-process-gone', event, this, details); + + // Log out a hint to help users better debug renderer crashes. + if (loggingEnabled()) { + console.info(`Renderer process ${details.reason} - see https://www.electronjs.org/docs/tutorial/application-debugging for potential debugging information.`); + } }); // The devtools requests the webContents to reload. @@ -578,7 +588,6 @@ WebContents.prototype._init = function () { width: 800, height: 600, webContents, - title: frameName, webPreferences, ...options }; @@ -596,6 +605,15 @@ WebContents.prototype._init = function () { app.emit('login', event, this, ...args); }); + this.on('ready-to-show', () => { + const owner = this.getOwnerBrowserWindow(); + if (owner && !owner.isDestroyed()) { + process.nextTick(() => { + owner.emit('ready-to-show'); + }); + } + }); + const event = process.electronBinding('event').createEmpty(); app.emit('web-contents-created', event, this); diff --git a/lib/browser/desktop-capturer.ts b/lib/browser/desktop-capturer.ts index 9d9569ea27f06..e0fbbc94fa360 100644 --- a/lib/browser/desktop-capturer.ts +++ b/lib/browser/desktop-capturer.ts @@ -48,6 +48,9 @@ export const getSourcesImpl = (event: Electron.IpcMainEvent | null, args: Electr } // Remove from currentlyRunning once we resolve or reject currentlyRunning = currentlyRunning.filter(running => running.options !== options); + if (event) { + event.sender.removeListener('destroyed', stopRunning); + } }; capturer._onerror = (error: string) => { @@ -66,7 +69,7 @@ export const getSourcesImpl = (event: Electron.IpcMainEvent | null, args: Electr // reference to emit and the capturer itself so that it never dispatches // back to the renderer if (event) { - event.sender.once('destroyed', () => stopRunning()); + event.sender.once('destroyed', stopRunning); } }); diff --git a/lib/browser/remote/server.ts b/lib/browser/remote/server.ts index 2ed49817a8c0b..038e83a550af3 100644 --- a/lib/browser/remote/server.ts +++ b/lib/browser/remote/server.ts @@ -276,7 +276,7 @@ const fakeConstructor = (constructor: Function, name: string) => }); // Convert array of meta data from renderer into array of real values. -const unwrapArgs = function (sender: electron.WebContents, frameId: number, contextId: string, args: any[]) { +const unwrapArgs = function (sender: electron.WebContents, frameId: [number, number], contextId: string, args: any[]) { const metaToValue = function (meta: MetaTypeFromRenderer): any { switch (meta.type) { case 'nativeimage': @@ -331,7 +331,7 @@ const unwrapArgs = function (sender: electron.WebContents, frameId: number, cont v8Util.setHiddenValue(callIntoRenderer, 'location', meta.location); Object.defineProperty(callIntoRenderer, 'length', { value: meta.length }); - v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId, contextId, meta.id, sender); + v8Util.setRemoteCallbackFreer(callIntoRenderer, frameId[0], frameId[1], contextId, meta.id, sender); rendererFunctions.set(objectId, callIntoRenderer); return callIntoRenderer; } @@ -480,7 +480,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CURRENT_WEB_CONTENTS', function (event, co }); handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, id, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const constructor = objectsRegistry.get(id); if (constructor == null) { @@ -491,7 +491,7 @@ handleRemoteCommand('ELECTRON_BROWSER_CONSTRUCTOR', function (event, contextId, }); handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId, id, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const func = objectsRegistry.get(id); if (func == null) { @@ -508,7 +508,7 @@ handleRemoteCommand('ELECTRON_BROWSER_FUNCTION_CALL', function (event, contextId }); handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const object = objectsRegistry.get(id); if (object == null) { @@ -519,7 +519,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CONSTRUCTOR', function (event, cont }); handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, id, method, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const object = objectsRegistry.get(id); if (object == null) { @@ -536,7 +536,7 @@ handleRemoteCommand('ELECTRON_BROWSER_MEMBER_CALL', function (event, contextId, }); handleRemoteCommand('ELECTRON_BROWSER_MEMBER_SET', function (event, contextId, id, name, args) { - args = unwrapArgs(event.sender, event.frameId, contextId, args); + args = unwrapArgs(event.sender, [event.processId, event.frameId], contextId, args); const obj = objectsRegistry.get(id); if (obj == null) { diff --git a/lib/browser/rpc-server.js b/lib/browser/rpc-server.js index d9e84754ee316..bdea74b2e7c8b 100644 --- a/lib/browser/rpc-server.js +++ b/lib/browser/rpc-server.js @@ -83,7 +83,7 @@ const getPreloadScript = async function (preloadPath) { let preloadSrc = null; let preloadError = null; try { - preloadSrc = (await fs.promises.readFile(preloadPath)).toString(); + preloadSrc = await fs.promises.readFile(preloadPath, 'utf8'); } catch (error) { preloadError = error; } diff --git a/lib/common/asar.js b/lib/common/asar.js index e7dd3d26dcedc..5c1779afd72e9 100644 --- a/lib/common/asar.js +++ b/lib/common/asar.js @@ -597,7 +597,13 @@ if (options.withFileTypes) { const dirents = []; for (const file of files) { - const stats = archive.stat(file); + const childPath = path.join(filePath, file); + const stats = archive.stat(childPath); + if (!stats) { + const error = createError(AsarError.NOT_FOUND, { asarPath, filePath: childPath }); + nextTick(callback, [error]); + return; + } if (stats.isFile) { dirents.push(new fs.Dirent(file, fs.constants.UV_DIRENT_FILE)); } else if (stats.isDirectory) { @@ -633,7 +639,11 @@ if (options.withFileTypes) { const dirents = []; for (const file of files) { - const stats = archive.stat(file); + const childPath = path.join(filePath, file); + const stats = archive.stat(childPath); + if (!stats) { + throw createError(AsarError.NOT_FOUND, { asarPath, filePath: childPath }); + } if (stats.isFile) { dirents.push(new fs.Dirent(file, fs.constants.UV_DIRENT_FILE)); } else if (stats.isDirectory) { diff --git a/lib/common/parse-features-string.ts b/lib/common/parse-features-string.ts index cfc817351274a..698b178dc6798 100644 --- a/lib/common/parse-features-string.ts +++ b/lib/common/parse-features-string.ts @@ -38,7 +38,7 @@ const keysOfTypeNumber = ['top', 'left', ...Object.keys(keysOfTypeNumberCompileT type CoercedValue = string | number | boolean; function coerce (key: string, value: string): CoercedValue { if (keysOfTypeNumber.includes(key)) { - return Number(value); + return parseInt(value, 10); } switch (value) { diff --git a/lib/common/reset-search-paths.ts b/lib/common/reset-search-paths.ts index cad6d37d1ef70..de2cac114b3bb 100644 --- a/lib/common/reset-search-paths.ts +++ b/lib/common/reset-search-paths.ts @@ -43,10 +43,10 @@ if (process.type === 'renderer') { } const originalResolveFilename = Module._resolveFilename; -Module._resolveFilename = function (request: string, parent: NodeModule, isMain: boolean) { +Module._resolveFilename = function (request: string, parent: NodeModule, isMain: boolean, options?: { paths: Array}) { if (request === 'electron' || request.startsWith('electron/')) { return 'electron'; } else { - return originalResolveFilename(request, parent, isMain); + return originalResolveFilename(request, parent, isMain, options); } }; diff --git a/lib/renderer/api/remote.js b/lib/renderer/api/remote.js index 9773a266ef14e..3501d7ec2dcb6 100644 --- a/lib/renderer/api/remote.js +++ b/lib/renderer/api/remote.js @@ -276,7 +276,7 @@ function metaToError (meta) { } function handleMessage (channel, handler) { - ipcRendererInternal.on(channel, (event, passedContextId, id, ...args) => { + ipcRendererInternal.onMessageFromMain(channel, (event, passedContextId, id, ...args) => { if (passedContextId === contextId) { handler(id, ...args); } else { diff --git a/lib/renderer/api/web-frame.ts b/lib/renderer/api/web-frame.ts index cb13c0fe54899..8e22181b7ee30 100644 --- a/lib/renderer/api/web-frame.ts +++ b/lib/renderer/api/web-frame.ts @@ -49,7 +49,7 @@ class WebFrame extends EventEmitter { } const { hasSwitch } = process.electronBinding('command_line'); -const worldSafeJS = hasSwitch('world-safe-execute-javascript') && hasSwitch('context-isolation'); +const worldSafeJS = hasSwitch('world-safe-execute-javascript') || !hasSwitch('context-isolation'); // Populate the methods. for (const name in binding) { diff --git a/lib/renderer/extensions/web-navigation.ts b/lib/renderer/extensions/web-navigation.ts index 768eb342804b1..a8000bfd7afa6 100644 --- a/lib/renderer/extensions/web-navigation.ts +++ b/lib/renderer/extensions/web-navigation.ts @@ -6,11 +6,11 @@ class WebNavigation { private onCompleted = new Event() constructor () { - ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event: Electron.IpcRendererEvent, details: any) => { + ipcRendererInternal.onMessageFromMain('CHROME_WEBNAVIGATION_ONBEFORENAVIGATE', (event: Electron.IpcRendererEvent, details: any) => { this.onBeforeNavigate.emit(details); }); - ipcRendererInternal.on('CHROME_WEBNAVIGATION_ONCOMPLETED', (event: Electron.IpcRendererEvent, details: any) => { + ipcRendererInternal.onMessageFromMain('CHROME_WEBNAVIGATION_ONCOMPLETED', (event: Electron.IpcRendererEvent, details: any) => { this.onCompleted.emit(details); }); } diff --git a/lib/renderer/ipc-renderer-internal-utils.ts b/lib/renderer/ipc-renderer-internal-utils.ts index 709880ffe5cb1..62d12da389af9 100644 --- a/lib/renderer/ipc-renderer-internal-utils.ts +++ b/lib/renderer/ipc-renderer-internal-utils.ts @@ -3,7 +3,7 @@ import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-in type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any export const handle = function (channel: string, handler: T) { - ipcRendererInternal.on(channel, async (event, requestId, ...args) => { + ipcRendererInternal.onMessageFromMain(channel, async (event, requestId, ...args) => { const replyChannel = `${channel}_RESPONSE_${requestId}`; try { event.sender.send(replyChannel, null, await handler(event, ...args)); diff --git a/lib/renderer/ipc-renderer-internal.ts b/lib/renderer/ipc-renderer-internal.ts index 6c4098f0f908d..89ce236708f4c 100644 --- a/lib/renderer/ipc-renderer-internal.ts +++ b/lib/renderer/ipc-renderer-internal.ts @@ -30,4 +30,27 @@ if (!ipcRendererInternal.send) { } return result; }; + + ipcRendererInternal.onMessageFromMain = function (channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + return ipcRendererInternal.on(channel, (event, ...args) => { + if (event.senderId !== 0) { + console.error(`Message ${channel} sent by unexpected WebContents (${event.senderId})`); + return; + } + + listener(event, ...args); + }); + }; + + ipcRendererInternal.onceMessageFromMain = function (channel: string, listener: (event: Electron.IpcRendererEvent, ...args: any[]) => void) { + return ipcRendererInternal.on(channel, function wrapper (event, ...args) { + if (event.senderId !== 0) { + console.error(`Message ${channel} sent by unexpected WebContents (${event.senderId})`); + return; + } + + ipcRendererInternal.removeListener(channel, wrapper); + listener(event, ...args); + }); + }; } diff --git a/lib/renderer/security-warnings.ts b/lib/renderer/security-warnings.ts index accbbc927979b..4f17200b5ffbb 100644 --- a/lib/renderer/security-warnings.ts +++ b/lib/renderer/security-warnings.ts @@ -76,10 +76,11 @@ const isLocalhost = function () { * * @returns {boolean} Is a CSP with `unsafe-eval` set? */ -const isUnsafeEvalEnabled = function () { - return webFrame.executeJavaScript(`(${(() => { +const isUnsafeEvalEnabled: () => Promise = function () { + // Call _executeJavaScript to bypass the world-safe deprecation warning + return (webFrame as any)._executeJavaScript(`(${(() => { try { - new Function(''); // eslint-disable-line no-new,no-new-func + eval(window.trustedTypes.emptyScript); // eslint-disable-line no-eval } catch { return false; } @@ -178,7 +179,7 @@ const warnAboutInsecureCSP = function () { console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)', 'font-weight: bold;', warning); - }); + }).catch(() => {}); }; /** @@ -223,7 +224,7 @@ const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPre const warnAboutEnableBlinkFeatures = function (webPreferences?: Electron.WebPreferences) { if (!webPreferences || !Object.prototype.hasOwnProperty.call(webPreferences, 'enableBlinkFeatures') || - (webPreferences.enableBlinkFeatures && webPreferences.enableBlinkFeatures.length === 0)) { + (webPreferences.enableBlinkFeatures != null && webPreferences.enableBlinkFeatures.length === 0)) { return; } diff --git a/lib/renderer/web-view/guest-view-internal.ts b/lib/renderer/web-view/guest-view-internal.ts index 0c0d9804ae7f5..ccdc53f9c1060 100644 --- a/lib/renderer/web-view/guest-view-internal.ts +++ b/lib/renderer/web-view/guest-view-internal.ts @@ -1,4 +1,4 @@ -import { webFrame, IpcMessageEvent } from 'electron'; +import { webFrame } from 'electron'; import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'; import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-internal-utils'; @@ -27,6 +27,7 @@ const WEB_VIEW_EVENTS: Record> = { 'focus-change': ['focus', 'guestInstanceId'], close: [], crashed: [], + 'render-process-gone': ['details'], 'plugin-crashed': ['name', 'version'], destroyed: [], 'page-title-updated': ['title', 'explicitSet'], @@ -51,38 +52,33 @@ const dispatchEvent = function ( dispatchEvent(webView, DEPRECATED_EVENTS[eventName], eventKey, ...args); } - const domEvent = new Event(eventName) as ElectronInternal.WebViewEvent; + const props: Record = {}; WEB_VIEW_EVENTS[eventKey].forEach((prop, index) => { - (domEvent as any)[prop] = args[index]; + props[prop] = args[index]; }); - webView.dispatchEvent(domEvent); + webView.dispatchEvent(eventName, props); if (eventName === 'load-commit') { - webView.onLoadCommit(domEvent); + webView.onLoadCommit(props); } else if (eventName === 'focus-change') { webView.onFocusChange(); } }; export function registerEvents (webView: WebViewImpl, viewInstanceId: number) { - ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () { + ipcRendererInternal.onMessageFromMain(`ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-${viewInstanceId}`, function () { webView.guestInstanceId = undefined; webView.reset(); - const domEvent = new Event('destroyed'); - webView.dispatchEvent(domEvent); + webView.dispatchEvent('destroyed'); }); - ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) { + ipcRendererInternal.onMessageFromMain(`ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-${viewInstanceId}`, function (event, eventName, ...args) { dispatchEvent(webView, eventName, eventName, ...args); }); - ipcRendererInternal.on(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) { - const domEvent = new Event('ipc-message') as IpcMessageEvent; - domEvent.channel = channel; - domEvent.args = args; - - webView.dispatchEvent(domEvent); + ipcRendererInternal.onMessageFromMain(`ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-${viewInstanceId}`, function (event, channel, ...args) { + webView.dispatchEvent('ipc-message', { channel, args }); }); } diff --git a/lib/renderer/web-view/web-view-attributes.ts b/lib/renderer/web-view/web-view-attributes.ts index 4ce18e9a9d296..5ce4a7f9d8199 100644 --- a/lib/renderer/web-view/web-view-attributes.ts +++ b/lib/renderer/web-view/web-view-attributes.ts @@ -17,7 +17,7 @@ interface MutationHandler { // Attribute objects. // Default implementation of a WebView attribute. -class WebViewAttribute implements MutationHandler { +export class WebViewAttribute implements MutationHandler { public value: any; public ignoreMutation = false; @@ -78,7 +78,7 @@ class BooleanAttribute extends WebViewAttribute { } // Attribute representing the state of the storage partition. -class PartitionAttribute extends WebViewAttribute { +export class PartitionAttribute extends WebViewAttribute { public validPartitionId = true constructor (public webViewImpl: WebViewImpl) { @@ -102,7 +102,7 @@ class PartitionAttribute extends WebViewAttribute { } // Attribute that handles the location and navigation of the webview. -class SrcAttribute extends WebViewAttribute { +export class SrcAttribute extends WebViewAttribute { public observer!: MutationObserver; constructor (public webViewImpl: WebViewImpl) { @@ -168,7 +168,7 @@ class SrcAttribute extends WebViewAttribute { } public parse () { - if (!this.webViewImpl.elementAttached || !this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].validPartitionId || !this.getValue()) { + if (!this.webViewImpl.elementAttached || !(this.webViewImpl.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION) as PartitionAttribute).validPartitionId || !this.getValue()) { return; } if (this.webViewImpl.guestInstanceId == null) { @@ -182,12 +182,12 @@ class SrcAttribute extends WebViewAttribute { // Navigate to |this.src|. const opts: Record = {}; - const httpreferrer = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER].getValue(); + const httpreferrer = this.webViewImpl.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER)!.getValue(); if (httpreferrer) { opts.httpReferrer = httpreferrer; } - const useragent = this.webViewImpl.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT].getValue(); + const useragent = this.webViewImpl.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT)!.getValue(); if (useragent) { opts.userAgent = useragent; } @@ -274,19 +274,18 @@ class EnableRemoteModuleAttribute extends WebViewAttribute { // Sets up all of the webview attributes. WebViewImpl.prototype.setupWebViewAttributes = function () { - this.attributes = {}; - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION] = new PartitionAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC] = new SrcAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER] = new HttpReferrerAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT] = new UserAgentAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS] = new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE] = new EnableRemoteModuleAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this); - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES] = new WebPreferencesAttribute(this); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION, new PartitionAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC, new SrcAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_HTTPREFERRER, new HttpReferrerAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_USERAGENT, new UserAgentAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATION, this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_NODEINTEGRATIONINSUBFRAMES, this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_PLUGINS, this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEWEBSECURITY, this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, new BooleanAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_ALLOWPOPUPS, this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_ENABLEREMOTEMODULE, new EnableRemoteModuleAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_PRELOAD, new PreloadAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_BLINKFEATURES, new BlinkFeaturesAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_DISABLEBLINKFEATURES, new DisableBlinkFeaturesAttribute(this)); + this.attributes.set(WEB_VIEW_CONSTANTS.ATTRIBUTE_WEBPREFERENCES, new WebPreferencesAttribute(this)); }; diff --git a/lib/renderer/web-view/web-view-constants.ts b/lib/renderer/web-view/web-view-constants.ts index 433d12ae3b8ce..02bf33400f488 100644 --- a/lib/renderer/web-view/web-view-constants.ts +++ b/lib/renderer/web-view/web-view-constants.ts @@ -16,9 +16,6 @@ export const enum WEB_VIEW_CONSTANTS { ATTRIBUTE_DISABLEBLINKFEATURES = 'disableblinkfeatures', ATTRIBUTE_WEBPREFERENCES = 'webpreferences', - // Internal attribute. - ATTRIBUTE_INTERNALINSTANCEID = 'internalinstanceid', - // Error messages. ERROR_MSG_ALREADY_NAVIGATED = 'The object has already navigated, so its partition cannot be changed.', ERROR_MSG_CANNOT_INJECT_SCRIPT = ' = ' + 'Script cannot be injected into content until the page has loaded.', diff --git a/lib/renderer/web-view/web-view-element.ts b/lib/renderer/web-view/web-view-element.ts index dcf3dbc753400..c63748456abad 100644 --- a/lib/renderer/web-view/web-view-element.ts +++ b/lib/renderer/web-view/web-view-element.ts @@ -10,6 +10,7 @@ import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'; import { WebViewImpl as IWebViewImpl, webViewImplModule } from '@electron/internal/renderer/web-view/web-view-impl'; +import type { SrcAttribute } from '@electron/internal/renderer/web-view/web-view-attributes'; // Return a WebViewElement class that is defined in this context. const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof webViewImplModule) => { @@ -38,7 +39,13 @@ const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof constructor () { super(); - v8Util.setHiddenValue(this, 'internal', new WebViewImpl(this)); + const internal = new WebViewImpl(this); + internal.dispatchEventInMainWorld = (eventName, props) => { + const event = new Event(eventName); + Object.assign(event, props); + return internal.webviewNode.dispatchEvent(event); + }; + v8Util.setHiddenValue(this, 'internal', internal); } connectedCallback () { @@ -49,7 +56,7 @@ const defineWebViewElement = (v8Util: NodeJS.V8UtilBinding, webViewImpl: typeof if (!internal.elementAttached) { guestViewInternal.registerEvents(internal, internal.viewInstanceId); internal.elementAttached = true; - internal.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].parse(); + (internal.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC) as SrcAttribute).parse(); } } diff --git a/lib/renderer/web-view/web-view-impl.ts b/lib/renderer/web-view/web-view-impl.ts index ea26bcbc36677..0cac4986f79b3 100644 --- a/lib/renderer/web-view/web-view-impl.ts +++ b/lib/renderer/web-view/web-view-impl.ts @@ -5,6 +5,7 @@ import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-inte import * as guestViewInternal from '@electron/internal/renderer/web-view/guest-view-internal'; import { WEB_VIEW_CONSTANTS } from '@electron/internal/renderer/web-view/web-view-constants'; import { syncMethods, asyncMethods, properties } from '@electron/internal/common/web-view-methods'; +import type { WebViewAttribute, PartitionAttribute } from '@electron/internal/renderer/web-view/web-view-attributes'; import { deserialize } from '@electron/internal/common/type-utils'; const { webFrame } = electron; @@ -33,14 +34,18 @@ export class WebViewImpl { public internalElement: HTMLIFrameElement // Replaced in web-view-attributes - public attributes: Record = {} + public attributes = new Map(); public setupWebViewAttributes (): void {} + public dispatchEventInMainWorld?: (eventName: string, props: any) => boolean; + constructor (public webviewNode: HTMLElement) { // Create internal iframe element. this.internalElement = this.createInternalElement(); const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' }); - shadowRoot.innerHTML = ''; + const style = shadowRoot.ownerDocument!.createElement('style'); + style.textContent = ':host { display: flex; }'; + shadowRoot.appendChild(style); this.setupWebViewAttributes(); this.viewInstanceId = getNextId(); shadowRoot.appendChild(this.internalElement); @@ -76,7 +81,7 @@ export class WebViewImpl { } this.beforeFirstNavigation = true; - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].validPartitionId = true; + (this.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION) as PartitionAttribute).validPartitionId = true; // Since attachment swaps a local frame for a remote frame, we need our // internal iframe element to be local again before we can reattach. @@ -95,19 +100,20 @@ export class WebViewImpl { // attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more // details. handleWebviewAttributeMutation (attributeName: string, oldValue: any, newValue: any) { - if (!this.attributes[attributeName] || this.attributes[attributeName].ignoreMutation) { + if (!this.attributes.has(attributeName) || this.attributes.get(attributeName)!.ignoreMutation) { return; } // Let the changed attribute handle its own mutation - this.attributes[attributeName].handleMutation(oldValue, newValue); + this.attributes.get(attributeName)!.handleMutation(oldValue, newValue); } onElementResize () { - const resizeEvent = new Event('resize') as ElectronInternal.WebFrameResizeEvent; - resizeEvent.newWidth = this.webviewNode.clientWidth; - resizeEvent.newHeight = this.webviewNode.clientHeight; - this.dispatchEvent(resizeEvent); + const props = { + newWidth: this.webviewNode.clientWidth, + newHeight: this.webviewNode.clientHeight + }; + this.dispatchEvent('resize', props); } createGuest () { @@ -116,8 +122,8 @@ export class WebViewImpl { }); } - dispatchEvent (webViewEvent: Electron.Event) { - this.webviewNode.dispatchEvent(webViewEvent); + dispatchEvent (eventName: string, props: Record = {}) { + this.dispatchEventInMainWorld!(eventName, props); } // Adds an 'on' property on the webview, which can be used to set/unset @@ -142,28 +148,28 @@ export class WebViewImpl { } // Updates state upon loadcommit. - onLoadCommit (webViewEvent: ElectronInternal.WebViewEvent) { + onLoadCommit (props: Record) { const oldValue = this.webviewNode.getAttribute(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC); - const newValue = webViewEvent.url; - if (webViewEvent.isMainFrame && (oldValue !== newValue)) { + const newValue = props.url; + if (props.isMainFrame && (oldValue !== newValue)) { // Touching the src attribute triggers a navigation. To avoid // triggering a page reload on every guest-initiated navigation, // we do not handle this mutation. - this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC].setValueIgnoreMutation(newValue); + this.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_SRC)!.setValueIgnoreMutation(newValue); } } // Emits focus/blur events. onFocusChange () { - const hasFocus = document.activeElement === this.webviewNode; + const hasFocus = this.webviewNode.ownerDocument!.activeElement === this.webviewNode; if (hasFocus !== this.hasFocus) { this.hasFocus = hasFocus; - this.dispatchEvent(new Event(hasFocus ? 'focus' : 'blur')); + this.dispatchEvent(hasFocus ? 'focus' : 'blur'); } } onAttach (storagePartitionId: number) { - return this.attributes[WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION].setValue(storagePartitionId); + return this.attributes.get(WEB_VIEW_CONSTANTS.ATTRIBUTE_PARTITION)!.setValue(storagePartitionId); } buildParams () { @@ -172,10 +178,8 @@ export class WebViewImpl { userAgentOverride: this.userAgentOverride }; - for (const attributeName in this.attributes) { - if (Object.prototype.hasOwnProperty.call(this.attributes, attributeName)) { - params[attributeName] = this.attributes[attributeName].getValue(); - } + for (const [attributeName, attribute] of this.attributes) { + params[attributeName] = attribute.getValue(); } return params; diff --git a/lib/renderer/window-setup.ts b/lib/renderer/window-setup.ts index 87b58409a2e36..1379324e303b6 100644 --- a/lib/renderer/window-setup.ts +++ b/lib/renderer/window-setup.ts @@ -181,7 +181,7 @@ class BrowserWindowProxy { this.guestId = guestId; this._location = new LocationProxy(guestId); - ipcRendererInternal.once(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { + ipcRendererInternal.onceMessageFromMain(`ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_CLOSED_${guestId}`, () => { removeProxy(guestId); this.closed = true; }); @@ -281,7 +281,7 @@ export const windowSetup = ( if (contextIsolationEnabled) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['prompt'], window.prompt); if (!usesNativeWindowOpen || openerId != null) { - ipcRendererInternal.on('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function ( + ipcRendererInternal.onMessageFromMain('ELECTRON_GUEST_WINDOW_POSTMESSAGE', function ( _event, sourceId: number, message: any, sourceOrigin: string ) { // Manually dispatch event instead of using postMessage because we also need to @@ -336,7 +336,7 @@ export const windowSetup = ( let cachedVisibilityState = isHiddenPage ? 'hidden' : 'visible'; // Subscribe to visibilityState changes. - ipcRendererInternal.on('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', function (_event, visibilityState: VisibilityState) { + ipcRendererInternal.onMessageFromMain('ELECTRON_GUEST_INSTANCE_VISIBILITY_CHANGE', function (_event, visibilityState: VisibilityState) { if (cachedVisibilityState !== visibilityState) { cachedVisibilityState = visibilityState; document.dispatchEvent(new Event('visibilitychange')); diff --git a/package.json b/package.json index 56b7b629771a0..8752e7dc3bb41 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,12 @@ { "name": "electron", - "version": "10.1.0", + "version": "10.4.7", "repository": "https://github.com/electron/electron", "description": "Build cross platform desktop apps with JavaScript, HTML, and CSS", "devDependencies": { - "@electron/docs-parser": "^0.7.2", + "@electron/docs-parser": "^0.10.0", "@electron/typescript-definitions": "^8.7.2", + "@octokit/auth-app": "^2.10.0", "@octokit/rest": "^16.3.2", "@primer/octicons": "^9.1.1", "@types/basic-auth": "^1.1.3", @@ -99,7 +100,6 @@ ], "husky": { "hooks": { - "pre-commit": "lint-staged", "pre-push": "check-for-leaks" } }, diff --git a/patches/angle/.patches b/patches/angle/.patches new file mode 100644 index 0000000000000..d02488bd9b539 --- /dev/null +++ b/patches/angle/.patches @@ -0,0 +1,2 @@ +cherry-pick-2882e1afd982.patch +d3d11_skip_blits_if_there_is_no_intersection_of_dest_areas.patch diff --git a/patches/angle/cherry-pick-2882e1afd982.patch b/patches/angle/cherry-pick-2882e1afd982.patch new file mode 100644 index 0000000000000..dbe426a98e324 --- /dev/null +++ b/patches/angle/cherry-pick-2882e1afd982.patch @@ -0,0 +1,65 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jamie Madill +Date: Tue, 20 Oct 2020 09:45:23 -0400 +Subject: Fix missing validation cache update on VAO binding. + +Bug: chromium:1139398 +Change-Id: I85a0d7a72bc2c97b07ebc5f86effd8e36aefd544 +Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2485581 +Reviewed-by: Geoff Lang +Commit-Queue: Jamie Madill + +diff --git a/src/libANGLE/Context.cpp b/src/libANGLE/Context.cpp +index c2d3868ff04af0813d496432c0f34b4f4a134fda..a6fa42c33a36a3e83f1bf959cbb566c6160e551e 100644 +--- a/src/libANGLE/Context.cpp ++++ b/src/libANGLE/Context.cpp +@@ -8666,6 +8666,7 @@ void StateCache::onVertexArrayBindingChange(Context *context) + updateActiveAttribsMask(context); + updateVertexElementLimits(context); + updateBasicDrawStatesError(); ++ updateBasicDrawElementsError(); + } + + void StateCache::onProgramExecutableChange(Context *context) +diff --git a/src/libANGLE/Context.h b/src/libANGLE/Context.h +index 10fa566d0040b2abe8d7193d668198bc2e461e38..8f33d1db627ccadb8f4bb8755bb43713365a6c6d 100644 +--- a/src/libANGLE/Context.h ++++ b/src/libANGLE/Context.h +@@ -203,6 +203,7 @@ class StateCache final : angle::NonCopyable + // 2. onVertexArrayBufferStateChange. + // 3. onBufferBindingChange. + // 4. onVertexArrayStateChange. ++ // 5. onVertexArrayBindingStateChange. + intptr_t getBasicDrawElementsError(const Context *context) const + { + if (mCachedBasicDrawElementsError != kInvalidPointer) +diff --git a/src/tests/gl_tests/StateChangeTest.cpp b/src/tests/gl_tests/StateChangeTest.cpp +index 7356edc230e0f067e47023d952cb18f41b154ada..1c886e9c66caaf7508867bf06c94b8910d5fd736 100644 +--- a/src/tests/gl_tests/StateChangeTest.cpp ++++ b/src/tests/gl_tests/StateChangeTest.cpp +@@ -5206,6 +5206,25 @@ TEST_P(RobustBufferAccessWebGL2ValidationStateChangeTest, BindZeroSizeBufferThen + ASSERT_GL_NO_ERROR(); + } + ++// Tests DrawElements with an empty buffer using a VAO. ++TEST_P(WebGL2ValidationStateChangeTest, DrawElementsEmptyVertexArray) ++{ ++ ANGLE_GL_PROGRAM(program, essl1_shaders::vs::Simple(), essl1_shaders::fs::Red()); ++ ++ glUseProgram(program); ++ ++ // Draw with empty buffer. Out of range but valid. ++ GLBuffer buffer; ++ glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffer); ++ glDrawElements(GL_TRIANGLES, 0, GL_UNSIGNED_SHORT, reinterpret_cast(0x1000)); ++ ++ // Switch VAO. No buffer bound, should be an error. ++ GLVertexArray vao; ++ glBindVertexArray(vao); ++ glDrawElements(GL_LINE_STRIP, 0x1000, GL_UNSIGNED_SHORT, ++ reinterpret_cast(0x1000)); ++ EXPECT_GL_ERROR(GL_INVALID_OPERATION); ++} + } // anonymous namespace + + ANGLE_INSTANTIATE_TEST_ES2(StateChangeTest); diff --git a/patches/angle/d3d11_skip_blits_if_there_is_no_intersection_of_dest_areas.patch b/patches/angle/d3d11_skip_blits_if_there_is_no_intersection_of_dest_areas.patch new file mode 100644 index 0000000000000..7712badda757d --- /dev/null +++ b/patches/angle/d3d11_skip_blits_if_there_is_no_intersection_of_dest_areas.patch @@ -0,0 +1,137 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Geoff Lang +Date: Mon, 19 Apr 2021 12:47:05 -0400 +Subject: D3D11: Skip blits if there is no intersection of dest areas + +Blit11 would clip the destination rectangle with the destination size +but ignore the result. gl::ClipRectangle returns false when the +rectangles do not intersect at all, indicating the blit can be skipped. + +This could lead to an out-of-bounds write to the GPU memory for the +destination texture. + +Mark ClipRectangle as nodiscard to prevent future issues. + +Bug: chromium:1199402 +Change-Id: I260e82d0917b8aa7e7887f2c9f7ed4b1a03ba785 +Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2836786 +Reviewed-by: Jamie Madill +Commit-Queue: Geoff Lang +(cherry picked from commit b574643ef28c92fcea5122dd7a72acb42a514eed) +Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2846982 +Reviewed-by: Geoff Lang + +diff --git a/src/libANGLE/angletypes.h b/src/libANGLE/angletypes.h +index e9e955b58f053a4a895c3c3fa105aac9db3c8baf..37740dda4be1aa59b57529841e0f13ee8a349af6 100644 +--- a/src/libANGLE/angletypes.h ++++ b/src/libANGLE/angletypes.h +@@ -62,7 +62,7 @@ struct Rectangle + bool operator==(const Rectangle &a, const Rectangle &b); + bool operator!=(const Rectangle &a, const Rectangle &b); + +-bool ClipRectangle(const Rectangle &source, const Rectangle &clip, Rectangle *intersection); ++ANGLE_NO_DISCARD bool ClipRectangle(const Rectangle &source, const Rectangle &clip, Rectangle *intersection); + + struct Offset + { +diff --git a/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp b/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp +index 55f8f8f4d38f30990d7061de6ebaa0595f0447da..6d9365af8db5fa9835127650162d5aeb6ce46b77 100644 +--- a/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp ++++ b/src/libANGLE/renderer/d3d/d3d11/Blit11.cpp +@@ -141,7 +141,10 @@ void StretchedBlitNearest(const gl::Box &sourceArea, + uint8_t *destData) + { + gl::Rectangle clippedDestArea(destArea.x, destArea.y, destArea.width, destArea.height); +- gl::ClipRectangle(clippedDestArea, clipRect, &clippedDestArea); ++ if (!gl::ClipRectangle(clippedDestArea, clipRect, &clippedDestArea)) ++ { ++ return; ++ } + + // Determine if entire rows can be copied at once instead of each individual pixel. There + // must be no out of bounds lookups, whole rows copies, and no scale. +diff --git a/src/libANGLE/renderer/gl/FramebufferGL.cpp b/src/libANGLE/renderer/gl/FramebufferGL.cpp +index 2dd83c1f2d7c4be71bcc16a3c5d6f7f209a5b6b1..d709449547af9021ccf4ef2476e217af2f1517e5 100644 +--- a/src/libANGLE/renderer/gl/FramebufferGL.cpp ++++ b/src/libANGLE/renderer/gl/FramebufferGL.cpp +@@ -1115,7 +1115,10 @@ angle::Result FramebufferGL::clipSrcRegion(const gl::Context *context, + // If pixels lying outside the read framebuffer, adjust src region + // and dst region to appropriate in-bounds regions respectively. + gl::Rectangle realSourceRegion; +- ClipRectangle(bounds.sourceRegion, bounds.sourceBounds, &realSourceRegion); ++ if (!ClipRectangle(bounds.sourceRegion, bounds.sourceBounds, &realSourceRegion)) ++ { ++ return angle::Result::Stop; ++ } + GLuint xOffset = realSourceRegion.x - bounds.sourceRegion.x; + GLuint yOffset = realSourceRegion.y - bounds.sourceRegion.y; + +diff --git a/src/libANGLE/renderer/metal/ContextMtl.mm b/src/libANGLE/renderer/metal/ContextMtl.mm +index d8d421f925675113a7d95250572e692fecb986ef..4bda9cf8518f24b8628ec4576e19ee7499131f10 100644 +--- a/src/libANGLE/renderer/metal/ContextMtl.mm ++++ b/src/libANGLE/renderer/metal/ContextMtl.mm +@@ -1282,7 +1282,10 @@ void ContextMtl::updateScissor(const gl::State &glState) + + // Clip the render area to the viewport. + gl::Rectangle viewportClippedRenderArea; +- gl::ClipRectangle(renderArea, glState.getViewport(), &viewportClippedRenderArea); ++ if (!gl::ClipRectangle(renderArea, glState.getViewport(), &viewportClippedRenderArea)) ++ { ++ viewportClippedRenderArea = gl::Rectangle(); ++ } + + gl::Rectangle scissoredArea = ClipRectToScissor(getState(), viewportClippedRenderArea, false); + if (framebufferMtl->flipY()) +diff --git a/src/libANGLE/renderer/vulkan/ContextVk.cpp b/src/libANGLE/renderer/vulkan/ContextVk.cpp +index 0276a4a8b579d36f239cd86527a6f8e08291d058..6092575364db4896caaf755eeaf93fb1d2fad9c4 100644 +--- a/src/libANGLE/renderer/vulkan/ContextVk.cpp ++++ b/src/libANGLE/renderer/vulkan/ContextVk.cpp +@@ -2533,8 +2533,11 @@ angle::Result ContextVk::updateScissor(const gl::State &glState) + + // Clip the render area to the viewport. + gl::Rectangle viewportClippedRenderArea; +- gl::ClipRectangle(renderArea, getCorrectedViewport(glState.getViewport()), +- &viewportClippedRenderArea); ++ if (!gl::ClipRectangle(renderArea, getCorrectedViewport(glState.getViewport()), ++ &viewportClippedRenderArea)) ++ { ++ viewportClippedRenderArea = gl::Rectangle(); ++ } + + gl::Rectangle scissoredArea = ClipRectToScissor(getState(), viewportClippedRenderArea, false); + gl::Rectangle rotatedScissoredArea; +diff --git a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp +index 79ba46aa10ae8c689b1546b3e1e99ce815f414bd..b38bebb31f17d9f1d127e170495fd810aba21390 100644 +--- a/src/tests/gl_tests/BlitFramebufferANGLETest.cpp ++++ b/src/tests/gl_tests/BlitFramebufferANGLETest.cpp +@@ -1997,6 +1997,30 @@ TEST_P(BlitFramebufferTest, BlitFramebufferSizeOverflow2) + EXPECT_GL_ERROR(GL_INVALID_VALUE); + } + ++// Test an edge case in D3D11 stencil blitting on the CPU that does not properly clip the ++// destination regions ++TEST_P(BlitFramebufferTest, BlitFramebufferStencilClipNoIntersection) ++{ ++ GLFramebuffer framebuffers[2]; ++ glBindFramebuffer(GL_READ_FRAMEBUFFER, framebuffers[0]); ++ glBindFramebuffer(GL_DRAW_FRAMEBUFFER, framebuffers[1]); ++ ++ GLRenderbuffer renderbuffers[2]; ++ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffers[0]); ++ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 4, 4); ++ glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, ++ renderbuffers[0]); ++ ++ glBindRenderbuffer(GL_RENDERBUFFER, renderbuffers[1]); ++ glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 4, 4); ++ glFramebufferRenderbuffer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, GL_RENDERBUFFER, ++ renderbuffers[1]); ++ ++ glBlitFramebuffer(0, 0, 4, 4, 1 << 24, 1 << 24, 1 << 25, 1 << 25, GL_STENCIL_BUFFER_BIT, ++ GL_NEAREST); ++ EXPECT_GL_NO_ERROR(); ++} ++ + // Use this to select which configurations (e.g. which renderer, which GLES major version) these + // tests should be run against. + ANGLE_INSTANTIATE_TEST(BlitFramebufferANGLETest, diff --git a/patches/chromium/.patches b/patches/chromium/.patches index 9b6293a9342ee..6da7ce572c8d8 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -74,7 +74,6 @@ add_trustedauthclient_to_urlloaderfactory.patch feat_allow_disabling_blink_scheduler_throttling_per_renderview.patch accessible_pane_view.patch fix_use_the_new_mediaplaypause_key_listener_for_internal_chrome.patch -use_electron_resources_in_pdf_util.patch hack_plugin_response_interceptor_to_point_to_electron.patch fix_route_mouse_event_navigations_through_the_web_contents_delegate.patch feat_add_support_for_overriding_the_base_spellchecker_download_url.patch @@ -94,10 +93,96 @@ fix_swap_global_proxies_before_initializing_the_windows_proxies.patch allow_setting_secondary_label_via_simplemenumodel.patch refactor_expose_cursor_changes_to_the_webcontentsobserver.patch disable_unnecessary_ischromefirstrun_check.patch -use_electron_resources_in_icon_reader_service.patch fix_include_missing_header_file.patch cherrypick_future_chrome_commit_to_remove_superfluous_dcheck.patch worker_feat_add_hook_to_notify_script_ready.patch provide_axtextchangevaluestartmarker_for_macos_a11y_value_change.patch allow_focus_to_move_into_an_editable_combobox_s_listbox.patch reconnect_p2p_socket_dispatcher_if_network_service_dies.patch +fix_properly_honor_printing_page_ranges.patch +cherry-pick-8629cd7f8af3.patch +avoid_use-after-free.patch +don_t_create_providers_if_context_is_lost.patch +fix_use_electron_generated_resources.patch +chore_expose_v8_initialization_isolate_callbacks.patch +cherry-pick-f440137cd96a.patch +cherry-pick-30261f9de11e.patch +cherry-pick-88f263f401b4.patch +cherry-pick-229fdaf8fc05.patch +cherry-pick-1ed869ad4bb3.patch +cherry-pick-8f24f935c903.patch +crashpad-initialize-logging.patch +cherry-pick-bbb64b5c6916.patch +ignore_renderframehostimpl_detach_for_speculative_rfhs.patch +cherry-pick-eec5025668f8.patch +cherry-pick-bbc6ab5bb49c.patch +cherry-pick-3abc372c9c00.patch +cherry-pick-d8d64b7cd244.patch +cherry-pick-5ffbb7ed173a.patch +ui_check_that_unpremultiply_is_passed_a_32bpp_image.patch +cherry-pick-ecdec1fb0f42.patch +merge_m86_ensure_that_buffers_used_by_imagedecoder_haven_t_been.patch +cherry-pick-2d18de63acf1.patch +only_zero_out_cross-origin_audio_that_doesn_t_get_played_out.patch +fix_setparentacessibile_crash_win.patch +backport_1142331.patch +backport_1151865.patch +cherry-pick-19aeffd4d93f.patch +cherry-pick-4794770cf175.patch +cherry-pick-79440c3a0675.patch +cherry-pick-d866af575997.patch +cherry-pick-da9b5ec032ad.patch +cherry-pick-861253f1de98.patch +cherry-pick-3ca3d70c7af5.patch +mediacapabilities_use_threadsafe_static_wtf_string.patch +cherry-pick-d74ba931c4b7.patch +cherry-pick-9ec949913373.patch +ots_backport_maxp_sanitization.patch +ots_backport_of_glyf_guard_access_to_maxp_version_1_field.patch +layoutng_fix_an_incorrect_cache-hit_for_line_boxes.patch +cherry-pick-0d2bf89e15cc.patch +cherry-pick-df438f22f7d2.patch +cherry-pick-9afec1792cfc.patch +cherry-pick-76cb1cc32baa.patch +stop_using_raw_webcontents_ptr_in_dragdownloadfile.patch +fix_heap_overflow_in_videoframeyuvconverter.patch +disable_gpu_acceleration_on_all_mesa_software_rasterizers.patch +websocket_don_t_clear_event_queue_on_destruction.patch +cherry-pick-7e0e52df283c.patch +cherry-pick-dea071d8b30f.patch +cherry-pick-a4faa754a9ef.patch +mediarecorder_tolerate_non-gmb_nv12_frames_for_h264.patch +cherry-pick-6e8856624cbb.patch +cherry-pick-b772b48067c4.patch +cherry-pick-3910c9f5cde6.patch +cherry-pick-5651fb858b75.patch +cherry-pick-b3dc4c4b349d.patch +cherry-pick-c6d6f7aee733.patch +cherry-pick-37210e5ab006.patch +reland_reland_fsa_add_issafepathcomponent_checks_to.patch +css_make_fetches_from_inline_css_use_the_document_s_url_as_referrer.patch +cherry-pick-3c80bb2a594f.patch +cherry-pick-6a6361c9f31c.patch +cherry-pick-012e9baf46c9.patch +cherry-pick-8c3eb9d1c409.patch +use_idtype_for_permission_change_subscriptions.patch +cherry-pick-fe85e04a1797.patch +m86-lts_add_null_pointer_check_in_renderwidgethostinputeventrouter.patch +m86-lts_add_weak_pointer_to_rwhier_framesinkidownermap_and.patch +cherry-pick-406ae3e8a9a8.patch +cherry-pick-fe20b05a0e5e.patch +cherry-pick-6b84dc72351b.patch +cherry-pick-7dd3b1c86795.patch +cherry-pick-1028ffc9bd83.patch +cherry-pick-5745eaf16077.patch +cherry-pick-02f5ef8c88d7.patch +cherry-pick-668cf831e912.patch +cherry-pick-f37149c4434f.patch +replace_std_vector_with_base_observerlist_to_support_container.patch +guard_webcontents_downloadimage_against_malformed_renderer.patch +fileapi_terminate_filereaderloader_before_dispatching_onabort_event.patch +notifications_crash_if_improper_action_icons_sent_from_renderer.patch +reland_views_handle_deletion_when_toggling_fullscreen.patch +media_feeds_disable_media_feeds_and_related_features.patch +remove_tabs_and_line_breaks_from_the_middle_of_app_names_when.patch +autofill_fixed_refill_of_changed_form.patch diff --git a/patches/chromium/autofill_fixed_refill_of_changed_form.patch b/patches/chromium/autofill_fixed_refill_of_changed_form.patch new file mode 100644 index 0000000000000..521b4d1ee0c8f --- /dev/null +++ b/patches/chromium/autofill_fixed_refill_of_changed_form.patch @@ -0,0 +1,41 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Belenko +Date: Wed, 19 May 2021 17:24:48 +0200 +Subject: Fixed refill of changed form. + +(cherry picked from commit 533bb3adcfe3499f90e2646fc60312f303b963ac) + +Bug: 1203667 +Change-Id: I2693a024531775e0e60cc330107d77d10558f466 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2867655 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2874611 + +diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc +index ded9c514894eee34a34eb562b08e7484673dfc4a..ca3b53835c2cacfeab893128a75e4d427d7993e8 100644 +--- a/components/autofill/core/browser/autofill_manager.cc ++++ b/components/autofill/core/browser/autofill_manager.cc +@@ -1740,7 +1740,10 @@ void AutofillManager::FillOrPreviewDataModelForm( + form_structure->RationalizePhoneNumbersInSection(autofill_field->section); + + FormData result = form; +- DCHECK_EQ(form_structure->field_count(), form.fields.size()); ++ // TODO(crbug/1203667#c9): Skip if the form has changed in the meantime, which ++ // may happen with refills. ++ if (form_structure->field_count() != form.fields.size()) ++ return; + + if (action == AutofillDriver::FORM_DATA_ACTION_FILL && !is_refill) { + filling_contexts_map_[form_structure->GetIdentifierForRefill()] = +@@ -1784,8 +1787,10 @@ void AutofillManager::FillOrPreviewDataModelForm( + continue; + } + +- // The field order should be the same in |form_structure| and |result|. +- DCHECK(form_structure->field(i)->SameFieldAs(result.fields[i])); ++ // TODO(crbug/1203667#c9): Skip if the form has changed in the meantime, ++ // which may happen with refills. ++ if (!form_structure->field(i)->SameFieldAs(result.fields[i])) ++ continue; + + AutofillField* cached_field = form_structure->field(i); + FieldTypeGroup field_group_type = cached_field->Type().group(); diff --git a/patches/chromium/avoid_use-after-free.patch b/patches/chromium/avoid_use-after-free.patch new file mode 100644 index 0000000000000..ce9961095ab11 --- /dev/null +++ b/patches/chromium/avoid_use-after-free.patch @@ -0,0 +1,50 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bruce Dawson +Date: Thu, 17 Sep 2020 22:34:58 +0000 +Subject: Avoid use-after-free + +SetNotWaitingForResponse can trigger a message pump which can then free +the object which |this| points to. This use-after-free can be avoided by +not dereferencing |this| after the call, by ensuring that calling +SetNotWaitingForResponse is the last thing done. + +(cherry picked from commit e1c5c8442210bccfbc2475c9bc75a9cf99bb259e) + +Bug: 1125199 +Change-Id: Ie1289c93112151978e6daaa1d24326770028c529 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2407065 +Reviewed-by: Alex Moshchuk +Commit-Queue: Bruce Dawson +Cr-Original-Commit-Position: refs/heads/master@{#806839} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2416264 +Reviewed-by: Bruce Dawson +Cr-Commit-Position: refs/branch-heads/4240@{#816} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc +index 279875a1c109568ec8e658d51a55efdea998dd7d..a7c76413b86fac18f6f1f54c87e67218f094e6b2 100644 +--- a/content/browser/web_contents/web_contents_impl.cc ++++ b/content/browser/web_contents/web_contents_impl.cc +@@ -3561,10 +3561,11 @@ void WebContentsImpl::SetNotWaitingForResponse() { + return; + + waiting_for_response_ = false; +- if (delegate_) +- delegate_->LoadingStateChanged(this, is_load_to_different_document_); + for (auto& observer : observers_) + observer.DidReceiveResponse(); ++ ++ if (delegate_) ++ delegate_->LoadingStateChanged(this, is_load_to_different_document_); + } + + void WebContentsImpl::SendScreenRects() { +@@ -4533,6 +4534,8 @@ void WebContentsImpl::ReadyToCommitNavigation( + : false); + } + ++ // LoadingStateChanged must be called last in case it triggers deletion of ++ // |this| due to recursive message pumps. + SetNotWaitingForResponse(); + } + diff --git a/patches/chromium/backport_1142331.patch b/patches/chromium/backport_1142331.patch new file mode 100644 index 0000000000000..1d9bdc3fa52c9 --- /dev/null +++ b/patches/chromium/backport_1142331.patch @@ -0,0 +1,141 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Belenko +Date: Thu, 10 Dec 2020 18:04:03 +0100 +Subject: Chromium backport: crbug.com/1142331 + +M87-1 +Clipboard: Fix UaP in ClipboardWriter/FileReaderLoader +https://chromium-review.googlesource.com/c/chromium/src/+/2536946 +CVE-2020-16037 + +diff --git a/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc b/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc +index fc5f32d86fd2cc4aeeaadddc94da6ce5e8e7990a..9c72fb55426f685045418947427406016d947589 100644 +--- a/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc ++++ b/third_party/blink/renderer/modules/clipboard/clipboard_promise.cc +@@ -104,7 +104,7 @@ ScriptPromise ClipboardPromise::CreateForWriteText(ExecutionContext* context, + + ClipboardPromise::ClipboardPromise(ExecutionContext* context, + ScriptState* script_state) +- : ExecutionContextClient(context), ++ : ExecutionContextLifecycleObserver(context), + script_state_(script_state), + script_promise_resolver_( + MakeGarbageCollected(script_state)), +@@ -483,13 +483,20 @@ scoped_refptr ClipboardPromise::GetTaskRunner() { + return GetExecutionContext()->GetTaskRunner(TaskType::kUserInteraction); + } + ++// ExecutionContextLifecycleObserver implementation. ++void ClipboardPromise::ContextDestroyed() { ++ script_promise_resolver_->Reject(MakeGarbageCollected( ++ DOMExceptionCode::kNotAllowedError, "Document detached.")); ++ clipboard_writer_.Clear(); ++} ++ + void ClipboardPromise::Trace(Visitor* visitor) const { + visitor->Trace(script_state_); + visitor->Trace(script_promise_resolver_); + visitor->Trace(clipboard_writer_); + visitor->Trace(permission_service_); + visitor->Trace(clipboard_item_data_); +- ExecutionContextClient::Trace(visitor); ++ ExecutionContextLifecycleObserver::Trace(visitor); + } + + } // namespace blink +diff --git a/third_party/blink/renderer/modules/clipboard/clipboard_promise.h b/third_party/blink/renderer/modules/clipboard/clipboard_promise.h +index 18efbc8c632dd7061fb31437529f1b14a25beb3a..307ce3b51a7c75b60301885685f5c0d780997250 100644 +--- a/third_party/blink/renderer/modules/clipboard/clipboard_promise.h ++++ b/third_party/blink/renderer/modules/clipboard/clipboard_promise.h +@@ -26,7 +26,7 @@ class ExecutionContext; + class ClipboardItemOptions; + + class ClipboardPromise final : public GarbageCollected, +- public ExecutionContextClient { ++ public ExecutionContextLifecycleObserver { + USING_GARBAGE_COLLECTED_MIXIN(ClipboardPromise); + + public: +@@ -83,6 +83,9 @@ class ClipboardPromise final : public GarbageCollected, + LocalFrame* GetLocalFrame() const; + scoped_refptr GetTaskRunner(); + ++ // ExecutionContextLifecycleObserver ++ void ContextDestroyed() override; ++ + Member script_state_; + Member script_promise_resolver_; + +diff --git a/third_party/blink/renderer/modules/clipboard/clipboard_writer.cc b/third_party/blink/renderer/modules/clipboard/clipboard_writer.cc +index 2891db58d47b30575efd782ae1c7cf8ee7558cc4..4b224c9679ca51c01328479685970235f35a32fd 100644 +--- a/third_party/blink/renderer/modules/clipboard/clipboard_writer.cc ++++ b/third_party/blink/renderer/modules/clipboard/clipboard_writer.cc +@@ -188,9 +188,12 @@ ClipboardWriter::ClipboardWriter(SystemClipboard* system_clipboard, + file_reading_task_runner_(promise->GetExecutionContext()->GetTaskRunner( + TaskType::kFileReading)), + system_clipboard_(system_clipboard), +- raw_system_clipboard_(raw_system_clipboard) {} ++ raw_system_clipboard_(raw_system_clipboard), ++ self_keep_alive_(PERSISTENT_FROM_HERE, this) {} + +-ClipboardWriter::~ClipboardWriter() = default; ++ClipboardWriter::~ClipboardWriter() { ++ DCHECK(!file_reader_); ++} + + // static + bool ClipboardWriter::IsValidType(const String& type, bool is_raw) { +@@ -220,7 +223,9 @@ void ClipboardWriter::DidFinishLoading() { + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DOMArrayBuffer* array_buffer = file_reader_->ArrayBufferResult(); + DCHECK(array_buffer); ++ + file_reader_.reset(); ++ self_keep_alive_.Clear(); + + worker_pool::PostTask( + FROM_HERE, CrossThreadBindOnce(&ClipboardWriter::DecodeOnBackgroundThread, +@@ -230,6 +235,8 @@ void ClipboardWriter::DidFinishLoading() { + } + + void ClipboardWriter::DidFail(FileErrorCode error_code) { ++ file_reader_.reset(); ++ self_keep_alive_.Clear(); + promise_->RejectFromReadOrDecodeFailure(); + } + +diff --git a/third_party/blink/renderer/modules/clipboard/clipboard_writer.h b/third_party/blink/renderer/modules/clipboard/clipboard_writer.h +index 527b063cd20900653dc37027bef8d24af31fb6de..3de3f5ad34b8ebf378421c64c917e3091e5343c6 100644 +--- a/third_party/blink/renderer/modules/clipboard/clipboard_writer.h ++++ b/third_party/blink/renderer/modules/clipboard/clipboard_writer.h +@@ -9,6 +9,7 @@ + #include "third_party/blink/renderer/core/fileapi/blob.h" + #include "third_party/blink/renderer/core/fileapi/file_reader_loader_client.h" + #include "third_party/blink/renderer/platform/heap/heap.h" ++#include "third_party/blink/renderer/platform/heap/self_keep_alive.h" + #include "third_party/skia/include/core/SkImage.h" + + namespace blink { +@@ -27,6 +28,11 @@ class RawSystemClipboard; + // take advantage of vulnerabilities in their decoders. In + // ClipboardRawDataWriter, this decoding is skipped. + // (3) Writing the blob's decoded contents to the system clipboard. ++// ++// ClipboardWriter is owned only by itself and ClipboardPromise. It keeps ++// itself alive for the duration of FileReaderLoader's async operations using ++// SelfKeepAlive, and keeps itself alive afterwards during cross-thread ++// operations by using WrapCrossThreadPersistent. + class ClipboardWriter : public GarbageCollected, + public FileReaderLoaderClient { + public: +@@ -80,6 +86,10 @@ class ClipboardWriter : public GarbageCollected, + Member system_clipboard_; + // Access to the global unsanitized system clipboard. + Member raw_system_clipboard_; ++ ++ // Oilpan: ClipboardWriter must remain alive until Member::Clear() is ++ // called, to keep the FileReaderLoader alive and avoid unexpected UaPs. ++ SelfKeepAlive self_keep_alive_; + }; + + } // namespace blink diff --git a/patches/chromium/backport_1151865.patch b/patches/chromium/backport_1151865.patch new file mode 100644 index 0000000000000..fac6e8a6765a7 --- /dev/null +++ b/patches/chromium/backport_1151865.patch @@ -0,0 +1,23 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Belenko +Date: Thu, 10 Dec 2020 22:16:48 +0100 +Subject: Chromium backport: crbug.com/1151865 + +M87-1 +Reject mojom::DataElement serialization if array size read failed +https://chromium-review.googlesource.com/c/chromium/src/+/2567130 +CVE-2020-16041 + +diff --git a/services/network/public/cpp/url_request_mojom_traits.cc b/services/network/public/cpp/url_request_mojom_traits.cc +index ce1478f6df691d5b1f7862a45ac3989a43e2d814..881bcb23ab3291e61088458f46c446fe9e7fb7cf 100644 +--- a/services/network/public/cpp/url_request_mojom_traits.cc ++++ b/services/network/public/cpp/url_request_mojom_traits.cc +@@ -286,6 +286,8 @@ bool StructTraits:: + if (data.type() == network::mojom::DataElementType::kBytes) { + if (!data.ReadBuf(&out->buf_)) + return false; ++ if (data.length() != out->buf_.size()) ++ return false; + } + out->type_ = data.type(); + out->data_pipe_getter_ = data.TakeDataPipeGetter< diff --git a/patches/chromium/can_create_window.patch b/patches/chromium/can_create_window.patch index 8f8b084f53331..607cc4da7e43b 100644 --- a/patches/chromium/can_create_window.patch +++ b/patches/chromium/can_create_window.patch @@ -9,10 +9,10 @@ potentially prevent a window from being created. TODO(loc): this patch is currently broken. diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc -index 0e54861c260ecfa1635ee56c1c9e931914afe901..948e3e5cf64f74ae560c64d0490c22a810d0d96f 100644 +index 54cc513534148a250993969a73b503bc3dd7a524..c1d7a3052516c6c8ac5a8e65c9688d04b8edec70 100644 --- a/content/browser/frame_host/render_frame_host_impl.cc +++ b/content/browser/frame_host/render_frame_host_impl.cc -@@ -4823,6 +4823,7 @@ void RenderFrameHostImpl::CreateNewWindow( +@@ -4816,6 +4816,7 @@ void RenderFrameHostImpl::CreateNewWindow( last_committed_origin_, params->window_container_type, params->target_url, params->referrer.To(), params->frame_name, params->disposition, *params->features, diff --git a/patches/chromium/cherry-pick-012e9baf46c9.patch b/patches/chromium/cherry-pick-012e9baf46c9.patch new file mode 100644 index 0000000000000..f2021d4766405 --- /dev/null +++ b/patches/chromium/cherry-pick-012e9baf46c9.patch @@ -0,0 +1,86 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jana Grill +Date: Thu, 15 Apr 2021 20:49:42 +0000 +Subject: Mojo: Remove some inappropriate DCHECKs + +There are a few places where we DCHECK conditions that cannot be +reliably asserted since they depend on untrusted inputs. These are +replaced with logic to conditionally terminate the connection to the +offending peer process. + +(cherry picked from commit a32b061fc92cc3864d036ffb8c22c12b05202589) + +Fixed: 1195333 +Change-Id: I0c6873bf55d6b0b1d0cbb3c2e5b256e1a57ff696 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2808893 +Reviewed-by: Robert Sesek +Commit-Queue: Ken Rockot +Cr-Original-Commit-Position: refs/heads/master@{#870007} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2821958 +Reviewed-by: Achuith Bhandarkar +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Achuith Bhandarkar +Owners-Override: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1608} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc +index c7646fa4dc5c5062e8a8a620e55839301af51bed..c333ed64f71f0dfe5d0012b07bcedccfd94cd5e9 100644 +--- a/mojo/core/node_controller.cc ++++ b/mojo/core/node_controller.cc +@@ -942,7 +942,11 @@ void NodeController::OnBrokerClientAdded(const ports::NodeName& from_node, + void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, + const ports::NodeName& broker_name, + PlatformHandle broker_channel) { +- DCHECK(!GetConfiguration().is_broker_process); ++ if (GetConfiguration().is_broker_process) { ++ // The broker should never receive this message from anyone. ++ DropPeer(from_node, nullptr); ++ return; ++ } + + // This node should already have an inviter in bootstrap mode. + ports::NodeName inviter_name; +@@ -953,8 +957,13 @@ void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, + inviter = bootstrap_inviter_channel_; + bootstrap_inviter_channel_ = nullptr; + } +- DCHECK(inviter_name == from_node); +- DCHECK(inviter); ++ ++ if (inviter_name != from_node || !inviter || ++ broker_name == ports::kInvalidNodeName) { ++ // We are not expecting this message. Assume the source is hostile. ++ DropPeer(from_node, nullptr); ++ return; ++ } + + base::queue pending_broker_clients; + std::unordered_map +@@ -965,22 +974,22 @@ void NodeController::OnAcceptBrokerClient(const ports::NodeName& from_node, + std::swap(pending_broker_clients, pending_broker_clients_); + std::swap(pending_relay_messages, pending_relay_messages_); + } +- DCHECK(broker_name != ports::kInvalidNodeName); + + // It's now possible to add both the broker and the inviter as peers. + // Note that the broker and inviter may be the same node. + scoped_refptr broker; + if (broker_name == inviter_name) { +- DCHECK(!broker_channel.is_valid()); + broker = inviter; +- } else { +- DCHECK(broker_channel.is_valid()); ++ } else if (broker_channel.is_valid()) { + broker = NodeChannel::Create( + this, + ConnectionParams(PlatformChannelEndpoint(std::move(broker_channel))), + Channel::HandlePolicy::kAcceptHandles, io_task_runner_, + ProcessErrorCallback()); + AddPeer(broker_name, broker, true /* start_channel */); ++ } else { ++ DropPeer(from_node, nullptr); ++ return; + } + + AddPeer(inviter_name, inviter, false /* start_channel */); diff --git a/patches/chromium/cherry-pick-02f5ef8c88d7.patch b/patches/chromium/cherry-pick-02f5ef8c88d7.patch new file mode 100644 index 0000000000000..535b1c6519186 --- /dev/null +++ b/patches/chromium/cherry-pick-02f5ef8c88d7.patch @@ -0,0 +1,106 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Josh Karlin +Date: Wed, 14 Apr 2021 09:21:00 +0000 +Subject: Fix removal of observers in NetworkStateNotifier + +The NetworkStateNotifier has a per-thread list of observer pointers. If +one is deleted mid-iteration, what we do is replace the pointer in the +list with a 0, and add the index to the zeroed list of observers to +remove after iteration completes. Well, the removal step was broken +for cases where there were multiple elements to remove. It didn't adjust +for the fact that the indexes shifted after each removal. + +(cherry picked from commit 5d34987de6cffb8d747c5ed16e82614e9146cc0a) + +Bug: 1170148 +Change-Id: I446acaae5f8a805a58142848634a0ee8c5f90882 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2727306 +Reviewed-by: Kentaro Hara +Commit-Queue: Josh Karlin +Cr-Original-Commit-Position: refs/heads/master@{#858853} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2821797 +Reviewed-by: Achuith Bhandarkar +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Jana Grill +Cr-Commit-Position: refs/branch-heads/4240@{#1602} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/platform/network/network_state_notifier.cc b/third_party/blink/renderer/platform/network/network_state_notifier.cc +index dbc9d03a813c5ed50b033ce135851311a731c02a..f9bfbe1c520fe8f293f5ffbd948052c6a8c5d6b7 100644 +--- a/third_party/blink/renderer/platform/network/network_state_notifier.cc ++++ b/third_party/blink/renderer/platform/network/network_state_notifier.cc +@@ -394,8 +394,14 @@ void NetworkStateNotifier::CollectZeroedObservers( + + // If any observers were removed during the iteration they will have + // 0 values, clean them up. +- for (wtf_size_t i = 0; i < list->zeroed_observers.size(); ++i) +- list->observers.EraseAt(list->zeroed_observers[i]); ++ std::sort(list->zeroed_observers.begin(), list->zeroed_observers.end()); ++ int removed = 0; ++ for (wtf_size_t i = 0; i < list->zeroed_observers.size(); ++i) { ++ int index_to_remove = list->zeroed_observers[i] - removed; ++ DCHECK_EQ(nullptr, list->observers[index_to_remove]); ++ list->observers.EraseAt(index_to_remove); ++ removed += 1; ++ } + + list->zeroed_observers.clear(); + +diff --git a/third_party/blink/renderer/platform/network/network_state_notifier_test.cc b/third_party/blink/renderer/platform/network/network_state_notifier_test.cc +index eb2bd791529df4339ebd8159700769fcd06d795f..f7c359235a87adc231c00eb252dc24a7d95065f8 100644 +--- a/third_party/blink/renderer/platform/network/network_state_notifier_test.cc ++++ b/third_party/blink/renderer/platform/network/network_state_notifier_test.cc +@@ -528,6 +528,53 @@ TEST_F(NetworkStateNotifierTest, RemoveFutureObserverWhileNotifying) { + kUnknownThroughputMbps, SaveData::kOff)); + } + ++// It should be safe to remove multiple observers in one iteration. ++TEST_F(NetworkStateNotifierTest, RemoveMultipleObserversWhileNotifying) { ++ StateObserver observer1, observer2, observer3; ++ std::unique_ptr handle1 = ++ notifier_.AddConnectionObserver(&observer1, GetTaskRunner()); ++ std::unique_ptr handle2 = ++ notifier_.AddConnectionObserver(&observer2, GetTaskRunner()); ++ std::unique_ptr handle3 = ++ notifier_.AddConnectionObserver(&observer3, GetTaskRunner()); ++ observer1.RemoveObserverOnNotification(std::move(handle1)); ++ observer3.RemoveObserverOnNotification(std::move(handle3)); ++ ++ // Running the first time should delete observers 1 and 3. ++ SetConnection(kWebConnectionTypeBluetooth, kBluetoothMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, ++ kUnknownRtt, kUnknownThroughputMbps, SaveData::kOff); ++ EXPECT_TRUE(VerifyObservations( ++ observer1, kWebConnectionTypeBluetooth, kBluetoothMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, kUnknownRtt, ++ kUnknownThroughputMbps, SaveData::kOff)); ++ EXPECT_TRUE(VerifyObservations( ++ observer2, kWebConnectionTypeBluetooth, kBluetoothMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, kUnknownRtt, ++ kUnknownThroughputMbps, SaveData::kOff)); ++ EXPECT_TRUE(VerifyObservations( ++ observer3, kWebConnectionTypeBluetooth, kBluetoothMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, kUnknownRtt, ++ kUnknownThroughputMbps, SaveData::kOff)); ++ ++ // Run again and only observer 2 should have been updated. ++ SetConnection(kWebConnectionTypeEthernet, kEthernetMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, ++ kUnknownRtt, kUnknownThroughputMbps, SaveData::kOff); ++ EXPECT_TRUE(VerifyObservations( ++ observer1, kWebConnectionTypeBluetooth, kBluetoothMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, kUnknownRtt, ++ kUnknownThroughputMbps, SaveData::kOff)); ++ EXPECT_TRUE(VerifyObservations( ++ observer2, kWebConnectionTypeEthernet, kEthernetMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, kUnknownRtt, ++ kUnknownThroughputMbps, SaveData::kOff)); ++ EXPECT_TRUE(VerifyObservations( ++ observer3, kWebConnectionTypeBluetooth, kBluetoothMaxBandwidthMbps, ++ WebEffectiveConnectionType::kTypeUnknown, kUnknownRtt, kUnknownRtt, ++ kUnknownThroughputMbps, SaveData::kOff)); ++} ++ + TEST_F(NetworkStateNotifierTest, MultipleContextsAddObserver) { + StateObserver observer1, observer2; + std::unique_ptr handle1 = diff --git a/patches/chromium/cherry-pick-0d2bf89e15cc.patch b/patches/chromium/cherry-pick-0d2bf89e15cc.patch new file mode 100644 index 0000000000000..51faff386338d --- /dev/null +++ b/patches/chromium/cherry-pick-0d2bf89e15cc.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sam McNally +Date: Thu, 17 Dec 2020 02:58:09 +0000 +Subject: Observe ProcessManager shutdowns from KeepAliveImpl. + +ExtensionRegistry uses the underlying BrowserContext for incognito +contexts. Thus, for incognito uses, the ProcessManager can be destroyed +without the KeepAliveImpl being notified. Observe ProcessManager +shutdowns directly to ensure KeepAliveImpls are cleaned up when a +ProcessManager is shut down. + +[Substituted ScopedObserver for base::ScopedObservation since the latter + was introduced in 88] + +(cherry picked from commit 5a55fe16e633dd02e3c40e513acabf4324bb6318) + +(cherry picked from commit c2bf2463fbeff3959ea1998a4f3ae82dd648b56c) + +Bug: 1149177 +Change-Id: I39a0cf54bcf8cf0d58e36560935b8d2f79399cd2 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2548585 +Auto-Submit: Sam McNally +Commit-Queue: Ben Wells +Reviewed-by: Ben Wells +Cr-Original-Original-Commit-Position: refs/heads/master@{#830107} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2563379 +Reviewed-by: Sam McNally +Commit-Queue: Sam McNally +Cr-Original-Commit-Position: refs/branch-heads/4280@{#1653} +Cr-Original-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2587154 +Commit-Queue: Achuith Bhandarkar +Reviewed-by: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1496} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/extensions/browser/mojo/keep_alive_impl.cc b/extensions/browser/mojo/keep_alive_impl.cc +index 0cc74657805296772e00d98225f8113b69904ac5..a7344015d62c4d84eeeddcf9da6f5d38db7e802c 100644 +--- a/extensions/browser/mojo/keep_alive_impl.cc ++++ b/extensions/browser/mojo/keep_alive_impl.cc +@@ -8,7 +8,6 @@ + + #include "base/bind.h" + #include "content/public/browser/browser_context.h" +-#include "extensions/browser/process_manager.h" + + namespace extensions { + +@@ -32,6 +31,7 @@ KeepAliveImpl::KeepAliveImpl(content::BrowserContext* context, + receiver_.set_disconnect_handler( + base::BindOnce(&KeepAliveImpl::OnDisconnected, base::Unretained(this))); + extension_registry_observer_.Add(ExtensionRegistry::Get(context_)); ++ process_manager_observation_.Add(ProcessManager::Get(context_)); + } + + KeepAliveImpl::~KeepAliveImpl() = default; +@@ -54,4 +54,8 @@ void KeepAliveImpl::OnDisconnected() { + delete this; + } + ++void KeepAliveImpl::OnProcessManagerShutdown(ProcessManager* manager) { ++ delete this; ++} ++ + } // namespace extensions +diff --git a/extensions/browser/mojo/keep_alive_impl.h b/extensions/browser/mojo/keep_alive_impl.h +index a3e4233a3e6691999decb281dbaf80f74bf922cc..331fc2fdf670e44d51adc610ee39144dac15ae87 100644 +--- a/extensions/browser/mojo/keep_alive_impl.h ++++ b/extensions/browser/mojo/keep_alive_impl.h +@@ -10,6 +10,8 @@ + #include "base/scoped_observer.h" + #include "extensions/browser/extension_registry.h" + #include "extensions/browser/extension_registry_observer.h" ++#include "extensions/browser/process_manager.h" ++#include "extensions/browser/process_manager_observer.h" + #include "extensions/common/mojom/keep_alive.mojom.h" + #include "mojo/public/cpp/bindings/pending_receiver.h" + #include "mojo/public/cpp/bindings/receiver.h" +@@ -21,10 +23,13 @@ class RenderFrameHost; + + namespace extensions { + class Extension; ++class ProcessManager; + + // An RAII mojo service implementation for extension keep alives. This adds a + // keep alive on construction and removes it on destruction. +-class KeepAliveImpl : public KeepAlive, public ExtensionRegistryObserver { ++class KeepAliveImpl : public KeepAlive, ++ public ExtensionRegistryObserver, ++ public ProcessManagerObserver { + public: + // Create a keep alive for |extension| running in |context| and connect it to + // |receiver|. When the receiver closes its pipe, the keep alive ends. +@@ -45,6 +50,9 @@ class KeepAliveImpl : public KeepAlive, public ExtensionRegistryObserver { + UnloadedExtensionReason reason) override; + void OnShutdown(ExtensionRegistry* registry) override; + ++ // ProcessManagerObserver overrides. ++ void OnProcessManagerShutdown(ProcessManager* manager) override; ++ + // Invoked when the mojo connection is disconnected. + void OnDisconnected(); + +@@ -52,6 +60,8 @@ class KeepAliveImpl : public KeepAlive, public ExtensionRegistryObserver { + const Extension* extension_; + ScopedObserver + extension_registry_observer_{this}; ++ ScopedObserver ++ process_manager_observation_{this}; + mojo::Receiver receiver_; + + DISALLOW_COPY_AND_ASSIGN(KeepAliveImpl); +diff --git a/extensions/browser/mojo/keep_alive_impl_unittest.cc b/extensions/browser/mojo/keep_alive_impl_unittest.cc +index 90599cc46d24dc7ec0eabb1da17545b489d93445..e6f60c39cf29d9554cb0f1096ed87345d3e38923 100644 +--- a/extensions/browser/mojo/keep_alive_impl_unittest.cc ++++ b/extensions/browser/mojo/keep_alive_impl_unittest.cc +@@ -160,7 +160,7 @@ TEST_F(KeepAliveTest, UnloadExtension) { + run_loop.Run(); + } + +-TEST_F(KeepAliveTest, Shutdown) { ++TEST_F(KeepAliveTest, ShutdownExtensionRegistry) { + mojo::Remote keep_alive; + CreateKeepAlive(keep_alive.BindNewPipeAndPassReceiver()); + EXPECT_EQ(1, GetKeepAliveCount()); +@@ -178,4 +178,22 @@ TEST_F(KeepAliveTest, Shutdown) { + run_loop.Run(); + } + ++TEST_F(KeepAliveTest, ShutdownProcessManager) { ++ mojo::Remote keep_alive; ++ CreateKeepAlive(keep_alive.BindNewPipeAndPassReceiver()); ++ EXPECT_EQ(1, GetKeepAliveCount()); ++ EXPECT_EQ(1u, GetActivities().count(mojo_activity_)); ++ ++ ProcessManager::Get(browser_context())->Shutdown(); ++ // After a shutdown event, the KeepAliveImpl should not access its ++ // ProcessManager and so the keep-alive count should remain unchanged. ++ EXPECT_EQ(1, GetKeepAliveCount()); ++ EXPECT_EQ(1u, GetActivities().count(mojo_activity_)); ++ ++ // Wait for |keep_alive| to disconnect. ++ base::RunLoop run_loop; ++ keep_alive.set_disconnect_handler(run_loop.QuitClosure()); ++ run_loop.Run(); ++} ++ + } // namespace extensions diff --git a/patches/chromium/cherry-pick-1028ffc9bd83.patch b/patches/chromium/cherry-pick-1028ffc9bd83.patch new file mode 100644 index 0000000000000..f623de049a423 --- /dev/null +++ b/patches/chromium/cherry-pick-1028ffc9bd83.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bill Budge +Date: Tue, 20 Apr 2021 15:22:33 +0000 +Subject: M86-LTS: [GeneratedCodeCache] Copy large data before hashing and + writing + +- Makes a copy before hashing and writing large code entries. + +(cherry picked from commit cea0cb8eee9900308d9b43661e9faca449086940) + +Bug: chromium:1194046 +Change-Id: Id5a6e6d3a04c83cfed2f18db53587d654d642fc0 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2807255 +Reviewed-by: Nasko Oskov +Reviewed-by: Mythri Alle +Commit-Queue: Bill Budge +Cr-Original-Commit-Position: refs/heads/master@{#870064} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2838077 +Reviewed-by: Bill Budge +Commit-Queue: Achuith Bhandarkar +Owners-Override: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1612} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/content/browser/code_cache/generated_code_cache.cc b/content/browser/code_cache/generated_code_cache.cc +index dd5c28f92503ce95082b9b6b6254f6922e5b81ac..4b71cde691a7a89344a556396780ce71cf7aebf7 100644 +--- a/content/browser/code_cache/generated_code_cache.cc ++++ b/content/browser/code_cache/generated_code_cache.cc +@@ -382,9 +382,18 @@ void GeneratedCodeCache::WriteEntry(const GURL& url, + // [stream1] + // [stream0 (checksum key entry)] + // [stream1 (checksum key entry)] data ++ ++ // Make a copy of the data before hashing. A compromised renderer could ++ // change shared memory before we can compute the hash and write the data. ++ // TODO(1135729) Eliminate this copy when the shared memory can't be written ++ // by the sender. ++ mojo_base::BigBuffer copy({data.data(), data.size()}); ++ if (copy.size() != data.size()) ++ return; ++ data = mojo_base::BigBuffer(); // Release the old buffer. + uint8_t result[crypto::kSHA256Length]; + crypto::SHA256HashString( +- base::StringPiece(reinterpret_cast(data.data()), data.size()), ++ base::StringPiece(reinterpret_cast(copy.data()), copy.size()), + result, base::size(result)); + std::string checksum_key = base::HexEncode(result, base::size(result)); + small_buffer = base::MakeRefCounted( +@@ -399,7 +408,7 @@ void GeneratedCodeCache::WriteEntry(const GURL& url, + // Issue another write operation for the code, with the checksum as the key + // and nothing in the header. + auto small_buffer2 = base::MakeRefCounted(0); +- auto large_buffer2 = base::MakeRefCounted(std::move(data)); ++ auto large_buffer2 = base::MakeRefCounted(std::move(copy)); + auto op2 = std::make_unique(Operation::kWriteWithSHAKey, + checksum_key, small_buffer2, + large_buffer2); diff --git a/patches/chromium/cherry-pick-19aeffd4d93f.patch b/patches/chromium/cherry-pick-19aeffd4d93f.patch new file mode 100644 index 0000000000000..fed36e0b6164c --- /dev/null +++ b/patches/chromium/cherry-pick-19aeffd4d93f.patch @@ -0,0 +1,124 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Matthew Denton +Date: Tue, 15 Dec 2020 01:09:52 +0000 +Subject: Fix UAF in ~MultiThreadedCertVerifier + +MultiThreadedCertVerifier keeps a list of +MultiThreadedCertVerifier::InternalRequests in order to eagerly reset +callbacks passed to Verify() if the MultiThreadedCertVerifier is +itself deleted (CertVerifier contract guarantees this eager reset +behavior). + +In ~MultiThreadedCertVerifier we loop through this list and reset the +callbacks, but then delete the InternalRequest from the list. However, +the callbacks are allowed to own the InternalRequest, so this leads +to a UaF. + +We don't need to remove the InternalRequest from the list in +~MultiThreadedCertVerifier, because we are not in charge of the +lifetime of the InternalRequest. InternalRequest can remove itself +from the list during ~InternalRequest, or MultiThreadedCertVerifier +can remove it from the list when a CertVerification job is complete. +The former is safe because ~InternalRequest won't remove itself from +the list if the MultiThreadedCertVerifier is already destructed. +The latter is obviously safe because if the request was cancelled, +then InternalRequest::OnJobCompleted will never run, so +|this| is always valid to remove from the list during +InternalRequest::OnJobCompleted. + +The added test reproduces the UAF without the fix. + +Bug: 1157562 +Change-Id: I92d0dc6ca6df084f55ea511ea692853ee63f5033 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2587560 +Reviewed-by: Ryan Sleevi +Commit-Queue: Matthew Denton +Cr-Commit-Position: refs/heads/master@{#836903} + +diff --git a/net/cert/multi_threaded_cert_verifier.cc b/net/cert/multi_threaded_cert_verifier.cc +index f2546d9187dc6aedbf5eab055d2017939df6a705..b46dbf68d0f239c2d002f9dedeecd6f10709f9c4 100644 +--- a/net/cert/multi_threaded_cert_verifier.cc ++++ b/net/cert/multi_threaded_cert_verifier.cc +@@ -202,10 +202,13 @@ MultiThreadedCertVerifier::~MultiThreadedCertVerifier() { + DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); + // Reset the callbacks for each InternalRequest to fulfill the respective + // net::CertVerifier contract. +- while (!request_list_.empty()) { +- base::LinkNode* curr = request_list_.head(); +- curr->value()->ResetCallback(); +- curr->RemoveFromList(); ++ for (base::LinkNode* node = request_list_.head(); ++ node != request_list_.end();) { ++ // Resetting the callback may delete the request, so save a pointer to the ++ // next node first. ++ base::LinkNode* next_node = node->next(); ++ node->value()->ResetCallback(); ++ node = next_node; + } + } + +diff --git a/net/cert/multi_threaded_cert_verifier.h b/net/cert/multi_threaded_cert_verifier.h +index 82b750a42f8eb99675e35aa41ef167c1e7896a33..05c5463abffc61644e31293b6876801efc6138fb 100644 +--- a/net/cert/multi_threaded_cert_verifier.h ++++ b/net/cert/multi_threaded_cert_verifier.h +@@ -50,6 +50,10 @@ class NET_EXPORT_PRIVATE MultiThreadedCertVerifier : public CertVerifier { + Config config_; + scoped_refptr verify_proc_; + ++ // Holds a list of CertVerifier::Requests that have not yet completed or been ++ // deleted. It is used to ensure that when the MultiThreadedCertVerifier is ++ // deleted, we eagerly reset all of the callbacks provided to Verify(), and ++ // don't call them later, as required by the CertVerifier contract. + base::LinkedList request_list_; + + #if defined(USE_NSS_CERTS) +diff --git a/net/cert/multi_threaded_cert_verifier_unittest.cc b/net/cert/multi_threaded_cert_verifier_unittest.cc +index 89c394541a94697036e34c7430e982c4eeb1a1f7..b5cf4bbaf3648f8b562fffd3804b65ab5b9379ab 100644 +--- a/net/cert/multi_threaded_cert_verifier_unittest.cc ++++ b/net/cert/multi_threaded_cert_verifier_unittest.cc +@@ -152,6 +152,45 @@ TEST_F(MultiThreadedCertVerifierTest, DeleteVerifier) { + RunUntilIdle(); + } + ++namespace { ++ ++struct CertVerifyResultHelper { ++ void FailTest(int /* result */) { FAIL(); } ++ std::unique_ptr request; ++}; ++ ++} // namespace ++ ++// The same as the above "DeleteVerifier" test, except the callback provided ++// will own the CertVerifier::Request as allowed by the CertVerifier contract. ++// This is a regression test for https://crbug.com/1157562. ++TEST_F(MultiThreadedCertVerifierTest, DeleteVerifierCallbackOwnsResult) { ++ base::FilePath certs_dir = GetTestCertsDirectory(); ++ scoped_refptr test_cert( ++ ImportCertFromFile(certs_dir, "ok_cert.pem")); ++ ASSERT_NE(static_cast(nullptr), test_cert.get()); ++ ++ int error; ++ CertVerifyResult verify_result; ++ std::unique_ptr result_helper = ++ std::make_unique(); ++ CertVerifyResultHelper* result_helper_ptr = result_helper.get(); ++ CompletionOnceCallback callback = base::BindOnce( ++ &CertVerifyResultHelper::FailTest, std::move(result_helper)); ++ ++ error = verifier_->Verify( ++ CertVerifier::RequestParams(test_cert, "www.example.com", 0, ++ /*ocsp_response=*/std::string(), ++ /*sct_list=*/std::string()), ++ &verify_result, std::move(callback), &result_helper_ptr->request, ++ NetLogWithSource()); ++ ASSERT_THAT(error, IsError(ERR_IO_PENDING)); ++ ASSERT_TRUE(result_helper_ptr->request); ++ verifier_.reset(); ++ ++ RunUntilIdle(); ++} ++ + // Tests that a canceled request is not leaked. + TEST_F(MultiThreadedCertVerifierTest, CancelRequestThenQuit) { + base::FilePath certs_dir = GetTestCertsDirectory(); diff --git a/patches/chromium/cherry-pick-1ed869ad4bb3.patch b/patches/chromium/cherry-pick-1ed869ad4bb3.patch new file mode 100644 index 0000000000000..feb9a3baeca80 --- /dev/null +++ b/patches/chromium/cherry-pick-1ed869ad4bb3.patch @@ -0,0 +1,160 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Reilly Grant +Date: Wed, 7 Oct 2020 23:26:36 +0000 +Subject: usb: Prevent parallel calls to UsbDevice::Open + +This change adds a check to prevent a Mojo client from calling Open() +multiple times while the open is in progress. + +Bug: 1135857 +Change-Id: Ib467de9129673710b883d9e186c32c359f8592d8 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2454846 +Auto-Submit: Reilly Grant +Reviewed-by: Matt Reynolds +Commit-Queue: Reilly Grant +Cr-Commit-Position: refs/heads/master@{#814940} + +diff --git a/services/device/usb/mojo/device_impl.cc b/services/device/usb/mojo/device_impl.cc +index 1e7a6a798cf43d24a6dcb42e4ac676098908e0a1..bc7081486476045ed7d957ba8cdec8a52227c517 100644 +--- a/services/device/usb/mojo/device_impl.cc ++++ b/services/device/usb/mojo/device_impl.cc +@@ -160,6 +160,7 @@ void DeviceImpl::OnOpen(base::WeakPtr self, + return; + } + ++ self->opening_ = false; + self->device_handle_ = std::move(handle); + if (self->device_handle_ && self->client_) + self->client_->OnDeviceOpened(); +@@ -175,16 +176,19 @@ void DeviceImpl::OnPermissionGrantedForOpen(OpenCallback callback, + device_->Open(base::BindOnce( + &DeviceImpl::OnOpen, weak_factory_.GetWeakPtr(), std::move(callback))); + } else { ++ opening_ = false; + std::move(callback).Run(mojom::UsbOpenDeviceError::ACCESS_DENIED); + } + } + + void DeviceImpl::Open(OpenCallback callback) { +- if (device_handle_) { ++ if (opening_ || device_handle_) { + std::move(callback).Run(mojom::UsbOpenDeviceError::ALREADY_OPEN); + return; + } + ++ opening_ = true; ++ + if (!device_->permission_granted()) { + device_->RequestPermission( + base::BindOnce(&DeviceImpl::OnPermissionGrantedForOpen, +diff --git a/services/device/usb/mojo/device_impl.h b/services/device/usb/mojo/device_impl.h +index ca3bffe4392f7607f7cf7445e2f21320fc02a82f..0965ca72663e14463aedfcff813c8307a1ebd447 100644 +--- a/services/device/usb/mojo/device_impl.h ++++ b/services/device/usb/mojo/device_impl.h +@@ -106,7 +106,9 @@ class DeviceImpl : public mojom::UsbDevice, public device::UsbDevice::Observer { + ScopedObserver observer_; + + // The device handle. Will be null before the device is opened and after it +- // has been closed. ++ // has been closed. |opening_| is set to true while the asynchronous open is ++ // in progress. ++ bool opening_ = false; + scoped_refptr device_handle_; + + mojo::SelfOwnedReceiverRef receiver_; +diff --git a/services/device/usb/mojo/device_impl_unittest.cc b/services/device/usb/mojo/device_impl_unittest.cc +index f0cd0eab4cf04d6f922c1748274d8f5f07a9452e..81045373bb3fa306b78f580f87424eacba36cd3d 100644 +--- a/services/device/usb/mojo/device_impl_unittest.cc ++++ b/services/device/usb/mojo/device_impl_unittest.cc +@@ -22,6 +22,7 @@ + #include "base/memory/ref_counted_memory.h" + #include "base/run_loop.h" + #include "base/stl_util.h" ++#include "base/test/bind_test_util.h" + #include "base/test/task_environment.h" + #include "mojo/public/cpp/bindings/interface_request.h" + #include "mojo/public/cpp/bindings/receiver.h" +@@ -265,7 +266,10 @@ class USBDeviceImplTest : public testing::Test { + void OpenMockHandle(UsbDevice::OpenCallback& callback) { + EXPECT_FALSE(is_device_open_); + is_device_open_ = true; +- std::move(callback).Run(mock_handle_); ++ // Simulate the asynchronous device opening process. ++ base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( ++ FROM_HERE, base::BindOnce(std::move(callback), mock_handle_), ++ base::TimeDelta::FromMilliseconds(1)); + } + + void CloseMockHandle() { +@@ -515,17 +519,39 @@ TEST_F(USBDeviceImplTest, OpenFailure) { + GetMockDeviceProxy(device_client.CreateInterfacePtrAndBind()); + + EXPECT_CALL(mock_device(), OpenInternal(_)) +- .WillOnce(Invoke([](UsbDevice::OpenCallback& callback) { ++ .WillOnce([](UsbDevice::OpenCallback& callback) { + std::move(callback).Run(nullptr); +- })); ++ }); + EXPECT_CALL(device_client, OnDeviceOpened()).Times(0); + EXPECT_CALL(device_client, OnDeviceClosed()).Times(0); + +- base::RunLoop loop; +- device->Open(base::BindOnce(&ExpectOpenAndThen, +- mojom::UsbOpenDeviceError::ACCESS_DENIED, +- loop.QuitClosure())); +- loop.Run(); ++ { ++ base::RunLoop loop; ++ device->Open( ++ base::BindLambdaForTesting([&](mojom::UsbOpenDeviceError result) { ++ EXPECT_EQ(result, mojom::UsbOpenDeviceError::ACCESS_DENIED); ++ loop.Quit(); ++ })); ++ loop.Run(); ++ } ++ ++ // A second attempt can succeed. ++ EXPECT_CALL(mock_device(), OpenInternal(_)); ++ EXPECT_CALL(device_client, OnDeviceOpened()); ++ EXPECT_CALL(device_client, OnDeviceClosed()); ++ ++ { ++ base::RunLoop loop; ++ device->Open( ++ base::BindLambdaForTesting([&](mojom::UsbOpenDeviceError result) { ++ EXPECT_EQ(result, mojom::UsbOpenDeviceError::OK); ++ loop.Quit(); ++ })); ++ loop.Run(); ++ } ++ ++ device.reset(); ++ base::RunLoop().RunUntilIdle(); + } + + TEST_F(USBDeviceImplTest, OpenDelayedFailure) { +@@ -549,6 +575,24 @@ TEST_F(USBDeviceImplTest, OpenDelayedFailure) { + std::move(saved_callback).Run(nullptr); + } + ++TEST_F(USBDeviceImplTest, MultipleOpenNotAllowed) { ++ MockUsbDeviceClient device_client; ++ mojo::Remote device = ++ GetMockDeviceProxy(device_client.CreateInterfacePtrAndBind()); ++ ++ base::RunLoop loop; ++ device->Open( ++ base::BindLambdaForTesting([&](mojom::UsbOpenDeviceError result) { ++ EXPECT_EQ(result, mojom::UsbOpenDeviceError::OK); ++ })); ++ device->Open( ++ base::BindLambdaForTesting([&](mojom::UsbOpenDeviceError result) { ++ EXPECT_EQ(result, mojom::UsbOpenDeviceError::ALREADY_OPEN); ++ loop.Quit(); ++ })); ++ loop.Run(); ++} ++ + TEST_F(USBDeviceImplTest, Close) { + MockUsbDeviceClient device_client; + mojo::Remote device = diff --git a/patches/chromium/cherry-pick-229fdaf8fc05.patch b/patches/chromium/cherry-pick-229fdaf8fc05.patch new file mode 100644 index 0000000000000..66d712782e1bf --- /dev/null +++ b/patches/chromium/cherry-pick-229fdaf8fc05.patch @@ -0,0 +1,64 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Guido Urdaneta +Date: Wed, 14 Oct 2020 19:40:12 +0000 +Subject: Validate input of MediaStreamDispatcherHost::OpenDevice() + +This method forwards to MediaStreamManager::OpenDevice(), which +DCHECKs for the stream type to be device video or audio capture +(i.e., webcam or mic). However, MSDH admits other stream types, +which cause MSM::OpenDevice to hit this DCHECK. + +This CL ensures that a message containing an incorrect stream type, +which could be sent by a malicious renderer, results in killing the +renderer process. + +Bug: 1135018 +Change-Id: I3884dde95d92c41f44966a8ab1dd7bdfd4b23b9b +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2472397 +Auto-Submit: Guido Urdaneta +Commit-Queue: Guido Urdaneta +Reviewed-by: Avi Drissman +Cr-Commit-Position: refs/heads/master@{#817151} + +diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h +index 0d5ef5f9d79dbc82a325f91b36350d01d760a064..ab0a195c2da772ea5825bb97e54743318bd06841 100644 +--- a/content/browser/bad_message.h ++++ b/content/browser/bad_message.h +@@ -255,6 +255,7 @@ enum BadMessageReason { + RFH_INVALID_CALL_FROM_NOT_MAIN_FRAME = 227, + INPUT_ROUTER_INVALID_EVENT_SOURCE = 228, + RWH_CLOSE_PORTAL = 233, ++ MSDH_INVALID_STREAM_TYPE = 234, + + // Please add new elements here. The naming convention is abbreviated class + // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the +diff --git a/content/browser/renderer_host/media/media_stream_dispatcher_host.cc b/content/browser/renderer_host/media/media_stream_dispatcher_host.cc +index 3e43ba7ad5c9e7223df587f3126807618da17245..835c9be6981b0b0fa3548a4fa7629d19280aec76 100644 +--- a/content/browser/renderer_host/media/media_stream_dispatcher_host.cc ++++ b/content/browser/renderer_host/media/media_stream_dispatcher_host.cc +@@ -196,6 +196,13 @@ void MediaStreamDispatcherHost::OpenDevice(int32_t page_request_id, + blink::mojom::MediaStreamType type, + OpenDeviceCallback callback) { + DCHECK_CURRENTLY_ON(BrowserThread::IO); ++ // OpenDevice is only supported for microphone or webcam capture. ++ if (type != blink::mojom::MediaStreamType::DEVICE_AUDIO_CAPTURE && ++ type != blink::mojom::MediaStreamType::DEVICE_VIDEO_CAPTURE) { ++ bad_message::ReceivedBadMessage( ++ render_process_id_, bad_message::MDDH_INVALID_DEVICE_TYPE_REQUEST); ++ return; ++ } + + base::PostTaskAndReplyWithResult( + GetUIThreadTaskRunner({}).get(), FROM_HERE, +diff --git a/tools/metrics/histograms/enums.xml b/tools/metrics/histograms/enums.xml +index c394f02fb8d8150689109bc2c853f63af965fc29..e6c4aadf3dd94baec25aa3e6ce24aae191a6d145 100644 +--- a/tools/metrics/histograms/enums.xml ++++ b/tools/metrics/histograms/enums.xml +@@ -6154,6 +6154,7 @@ Unknown properties are collapsed to zero. --> + + + ++ + + + diff --git a/patches/chromium/cherry-pick-2d18de63acf1.patch b/patches/chromium/cherry-pick-2d18de63acf1.patch new file mode 100644 index 0000000000000..6fc0a6d4f83af --- /dev/null +++ b/patches/chromium/cherry-pick-2d18de63acf1.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: danakj +Date: Tue, 17 Nov 2020 21:47:27 +0000 +Subject: Convert strides with padding in skia::SkBitmapToN32OpaqueOrPremul(). + +Code using bitmaps converted with SkBitmapToN32OpaqueOrPremul() can +easily assume that the pixels are one contiguous (width*4*height)-sized +buffer. If it's not then out-of-bounds read/write can occur. + +Also adds tests for SkBitmapToN32OpaqueOrPremul(). + +R=fmalita@chromium.org + +Bug: 1147431, 1144462 +Change-Id: I21f7a958a8c9231bf5f052f8ff246f2c249bd70b +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2544032 +Commit-Queue: danakj +Reviewed-by: Florin Malita +Cr-Commit-Position: refs/heads/master@{#828406} + +diff --git a/skia/ext/skia_utils_base.cc b/skia/ext/skia_utils_base.cc +index 516ad7ea1e3a0acb1c8b207f98f6daf534262cbc..f9e622eff3ec6c3287138d7cdf68814b8535a338 100644 +--- a/skia/ext/skia_utils_base.cc ++++ b/skia/ext/skia_utils_base.cc +@@ -85,7 +85,8 @@ void WriteSkFontStyle(base::Pickle* pickle, SkFontStyle style) { + bool SkBitmapToN32OpaqueOrPremul(const SkBitmap& in, SkBitmap* out) { + DCHECK(out); + const SkImageInfo& info = in.info(); +- if (info.colorType() == kN32_SkColorType && ++ const bool stride_matches_width = in.rowBytes() == info.minRowBytes(); ++ if (stride_matches_width && info.colorType() == kN32_SkColorType && + (info.alphaType() == kPremul_SkAlphaType || + info.alphaType() == kOpaque_SkAlphaType)) { + // Shallow copy if the data is already in the right format. +diff --git a/skia/ext/skia_utils_base.h b/skia/ext/skia_utils_base.h +index 2a1eca124e91695ddec635e593ad1e9b650aa156..40401bb2fe0e484fae64490757d63f85e5c5ffea 100644 +--- a/skia/ext/skia_utils_base.h ++++ b/skia/ext/skia_utils_base.h +@@ -42,9 +42,10 @@ SK_API void WriteSkFontIdentity( + // Writes style into the request pickle. + SK_API void WriteSkFontStyle(base::Pickle* pickle, SkFontStyle style); + +-// Converts an SkBitmap to an Opaque or Premul N32 SkBitmap. If the input is in +-// the right format (N32 Opaque or Premul) already, points |out| directly at +-// |in|. |out| may or may not be GPU-backed. ++// Converts an SkBitmap to an Opaque or Premul N32 SkBitmap with stride matching ++// the width of each row. If the input is has the right format (N32 Opaque or ++// Premul) without stride padding already, this assigns `in` to `out`, sharing ++// the backing pixels. `out` may or may not be GPU-backed. + // + // If unsuccessful, returns false, but |out| may be modified. + SK_API bool SkBitmapToN32OpaqueOrPremul(const SkBitmap& in, SkBitmap* out); diff --git a/patches/chromium/cherry-pick-30261f9de11e.patch b/patches/chromium/cherry-pick-30261f9de11e.patch new file mode 100644 index 0000000000000..bd024f6e3c06d --- /dev/null +++ b/patches/chromium/cherry-pick-30261f9de11e.patch @@ -0,0 +1,29 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lei Zhang +Date: Thu, 1 Oct 2020 19:18:54 +0000 +Subject: Check RF is alive In PrintRenderFrameHelper::PreviewPageRendered(). + +Do not take an accessibility snapshot if the RenderFrame is gone. + +Bug: 1133983 +Change-Id: I612cc72936a1dcedc5180c24eae067e47237b09b +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2442375 +Reviewed-by: Dominic Mazzoni +Commit-Queue: Lei Zhang +Cr-Commit-Position: refs/heads/master@{#812851} + +diff --git a/components/printing/renderer/print_render_frame_helper.cc b/components/printing/renderer/print_render_frame_helper.cc +index c0107f561f444d537735d4725c6dbecbcaf45226..1f7b7294aa5afb7f967ec260ab9f62d8b27d6827 100644 +--- a/components/printing/renderer/print_render_frame_helper.cc ++++ b/components/printing/renderer/print_render_frame_helper.cc +@@ -2463,6 +2463,10 @@ bool PrintRenderFrameHelper::PreviewPageRendered( + "page_number", page_number); + + #if BUILDFLAG(ENABLE_TAGGED_PDF) ++ // Make sure the RenderFrame is alive before taking the snapshot. ++ if (render_frame_gone_) ++ snapshotter_.reset(); ++ + // For tagged PDF exporting, send a snapshot of the accessibility tree + // along with page 0. The accessibility tree contains the content for + // all of the pages of the main frame. diff --git a/patches/chromium/cherry-pick-37210e5ab006.patch b/patches/chromium/cherry-pick-37210e5ab006.patch new file mode 100644 index 0000000000000..b64186ffe7809 --- /dev/null +++ b/patches/chromium/cherry-pick-37210e5ab006.patch @@ -0,0 +1,148 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Antonio Sartori +Date: Mon, 8 Mar 2021 10:28:40 +0000 +Subject: Strip url to origin in X-Frame-Options violation messages + +X-Frame-Options violations are logged via a console message in the +parent frame. To avoid leaking sensitive data to the parent frame, +let's report as "blocked url" just the origin of the blocked frame's +url, as we are already doing for the frame-ancestors CSP directive. + +[M86 Merge]: ancestor_throttle.cc was moved. + +(cherry picked from commit 93ce5606cd9a9597993ba70670b4092ab6722281) + +Bug: 1146651 +Change-Id: If5e5ac62f7e44e714b109e6adc389f11999e0f8b +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2534851 +Commit-Queue: Antonio Sartori +Reviewed-by: Charlie Reis +Reviewed-by: Arthur Sonzogni +Cr-Original-Commit-Position: refs/heads/master@{#828651} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2731577 +Reviewed-by: Achuith Bhandarkar +Commit-Queue: Victor-Gabriel Savu +Cr-Commit-Position: refs/branch-heads/4240@{#1563} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/android_webview/javatests/src/org/chromium/android_webview/test/ConsoleMessagesForBlockedLoadsTest.java b/android_webview/javatests/src/org/chromium/android_webview/test/ConsoleMessagesForBlockedLoadsTest.java +index 7394abb2e639a8f3b25cca0a568135cd4e8bd0d8..c746763be2a77a87a52e9ef31bbbd25de7ba03ae 100644 +--- a/android_webview/javatests/src/org/chromium/android_webview/test/ConsoleMessagesForBlockedLoadsTest.java ++++ b/android_webview/javatests/src/org/chromium/android_webview/test/ConsoleMessagesForBlockedLoadsTest.java +@@ -94,7 +94,7 @@ public class ConsoleMessagesForBlockedLoadsTest { + mActivityTestRule.loadUrlSync( + mAwContents, mContentsClient.getOnPageFinishedHelper(), pageUrl); + AwConsoleMessage errorMessage = getSingleErrorMessage(); +- assertNotEquals(errorMessage.message().indexOf(iframeUrl), -1); ++ assertNotEquals(errorMessage.message().indexOf(mWebServer.getBaseUrl()), -1); + } + + @Test +diff --git a/content/browser/frame_host/ancestor_throttle.cc b/content/browser/frame_host/ancestor_throttle.cc +index 8630ace7189d465cea8360a55acf899fa801f15e..75269311d73c1d9b881bc40f320265772b074d63 100644 +--- a/content/browser/frame_host/ancestor_throttle.cc ++++ b/content/browser/frame_host/ancestor_throttle.cc +@@ -239,12 +239,20 @@ void AncestorThrottle::ParseXFrameOptionsError(const std::string& value, + "Refused to display '%s' in a frame because it set multiple " + "'X-Frame-Options' headers with conflicting values " + "('%s'). Falling back to 'deny'.", +- navigation_handle()->GetURL().spec().c_str(), value.c_str()); ++ url::Origin::Create(navigation_handle()->GetURL()) ++ .GetURL() ++ .spec() ++ .c_str(), ++ value.c_str()); + } else { + message = base::StringPrintf( + "Invalid 'X-Frame-Options' header encountered when loading '%s': " + "'%s' is not a recognized directive. The header will be ignored.", +- navigation_handle()->GetURL().spec().c_str(), value.c_str()); ++ url::Origin::Create(navigation_handle()->GetURL()) ++ .GetURL() ++ .spec() ++ .c_str(), ++ value.c_str()); + } + + // Log a console error in the parent of the current RenderFrameHost (as +@@ -265,11 +273,19 @@ void AncestorThrottle::ConsoleErrorXFrameOptions( + std::string message = base::StringPrintf( + "Refused to display '%s' in a frame because it set 'X-Frame-Options' " + "to '%s'.", +- navigation_handle()->GetURL().spec().c_str(), ++ url::Origin::Create(navigation_handle()->GetURL()) ++ .GetURL() ++ .spec() ++ .c_str(), + disposition == HeaderDisposition::DENY ? "deny" : "sameorigin"); + + // Log a console error in the parent of the current RenderFrameHost (as + // the current RenderFrameHost itself doesn't yet have a document). ++ // ++ // TODO(https://crbug.com/1146651): We should not leak any information at all ++ // to the parent frame. Send a message directly to Devtools instead (without ++ // passing through a renderer): that can also contain more information (like ++ // the full blocked url). + auto* frame = static_cast( + navigation_handle()->GetRenderFrameHost()); + ParentForAncestorThrottle(frame)->AddMessageToConsole( +diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc +index d81076da5ef7701475ef1158b528d02d3992ff0d..10d1a58f143906a77df33ae8da3cc1df7605c07b 100644 +--- a/content/browser/site_per_process_browsertest.cc ++++ b/content/browser/site_per_process_browsertest.cc +@@ -7109,12 +7109,26 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, + "document.querySelector('iframe').onload = " + " function() { document.title = 'loaded'; };")); + ++ // The blocked url reported in the console message should only contain the ++ // origin, in order to avoid sensitive data being leaked to the parent frame. ++ // ++ // TODO(https://crbug.com/1146651): We should not leak any information at all ++ // to the parent frame. Instead, we should send a message directly to Devtools ++ // (without passing through a renderer): that can also contain more ++ // information (like the full blocked url). ++ GURL reported_blocked_url = embedded_test_server()->GetURL("b.com", "/"); + const struct { + const char* url; + bool use_error_page; ++ std::string expected_console_message; + } kTestCases[] = { +- {"/frame-ancestors-none.html", false}, +- {"/x-frame-options-deny.html", true}, ++ {"/frame-ancestors-none.html", false, ++ "Refused to frame '" + reported_blocked_url.spec() + ++ "' because an ancestor violates the following Content Security " ++ "Policy directive: \"frame-ancestors 'none'\".\n"}, ++ {"/x-frame-options-deny.html", true, ++ "Refused to display '" + reported_blocked_url.spec() + ++ "' in a frame because it set 'X-Frame-Options' to 'deny'."}, + }; + + for (const auto& test : kTestCases) { +@@ -7123,6 +7137,9 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, + base::string16 expected_title(base::UTF8ToUTF16("loaded")); + TitleWatcher title_watcher(shell()->web_contents(), expected_title); + ++ WebContentsConsoleObserver console_observer(shell()->web_contents()); ++ console_observer.SetPattern("Refused to*"); ++ + // Navigate the subframe to a blocked URL. + TestNavigationObserver load_observer(shell()->web_contents()); + EXPECT_TRUE(ExecuteScript( +@@ -7150,6 +7167,8 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, + // The blocked frame should still fire a load event in its parent's process. + EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle()); + ++ EXPECT_EQ(console_observer.GetMessageAt(0u), test.expected_console_message); ++ + // Check that the current RenderFrameHost has stopped loading. + EXPECT_FALSE(root->child_at(0)->current_frame_host()->is_loading()); + +diff --git a/third_party/blink/web_tests/http/tests/security/XFrameOptions/x-frame-options-deny-delete-frame-in-load-event-expected.txt b/third_party/blink/web_tests/http/tests/security/XFrameOptions/x-frame-options-deny-delete-frame-in-load-event-expected.txt +index f2e5b68c997ca33da841aa7ba5795ef3b96fa02f..f7eea4a189ae8913921444428e26389dfd4de4da 100644 +--- a/third_party/blink/web_tests/http/tests/security/XFrameOptions/x-frame-options-deny-delete-frame-in-load-event-expected.txt ++++ b/third_party/blink/web_tests/http/tests/security/XFrameOptions/x-frame-options-deny-delete-frame-in-load-event-expected.txt +@@ -1,2 +1,2 @@ +-CONSOLE ERROR: Refused to display 'http://127.0.0.1:8000/security/XFrameOptions/resources/x-frame-options-deny.cgi' in a frame because it set 'X-Frame-Options' to 'deny'. ++CONSOLE ERROR: Refused to display 'http://127.0.0.1:8000/' in a frame because it set 'X-Frame-Options' to 'deny'. + Test that if an iframe is denied, we don't crash if the load event detaches the frame. diff --git a/patches/chromium/cherry-pick-3910c9f5cde6.patch b/patches/chromium/cherry-pick-3910c9f5cde6.patch new file mode 100644 index 0000000000000..48d9712e083be --- /dev/null +++ b/patches/chromium/cherry-pick-3910c9f5cde6.patch @@ -0,0 +1,224 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Raymond Toy +Date: Tue, 2 Mar 2021 15:15:29 +0000 +Subject: Convert AudioParam NaN values to the default value + +If any output value of an AudioParam (including the intrinsic values +and any inputs to the AudioParam), should be NaN, replace the NaN +value with the associated defaultValue. + +This causes some slowdowns so SIMD/NEON code was added to mitigate the +degradation. There is still some slowdown, but the worst case is now +about 7% slower on x86 and 10% on arm. Generally, the slowdown is less +than 2% and 5%, respectively. (Perversely, some results got faster, +and the differences are statistically significant.) + +Full details can be found at +https://docs.google.com/spreadsheets/d/1EhbLHm-9cUoEO5aj1vYemVBLQ3Dh4dCJPPLTfZPrZt4/edit?usp=sharing + +Manually tested the test case from the bug and the issue no longer +occurs. + +(cherry picked from commit ab1862017b5717271a28376659944dddc602195c) + +(cherry picked from commit eb0c0353bf245885797d8ce0d1b864d88a381fbb) + +Bug: 1170531 +Change-Id: I00d902b40a9ef9da990c6d68b664b1dcfc31b091 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2658724 +Commit-Queue: Raymond Toy +Reviewed-by: Hongchan Choi +Cr-Original-Original-Commit-Position: refs/heads/master@{#851733} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2686369 +Reviewed-by: Raymond Toy +Cr-Original-Commit-Position: refs/branch-heads/4389@{#880} +Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2727697 +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1551} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/modules/webaudio/audio_param.cc b/third_party/blink/renderer/modules/webaudio/audio_param.cc +index c5d329479a412d52ee39167ff841b1cea417a217..135588f56ebceabd3e0a12f9f506955bb58b20ca 100644 +--- a/third_party/blink/renderer/modules/webaudio/audio_param.cc ++++ b/third_party/blink/renderer/modules/webaudio/audio_param.cc +@@ -25,6 +25,7 @@ + + #include "third_party/blink/renderer/modules/webaudio/audio_param.h" + ++#include "build/build_config.h" + #include "third_party/blink/renderer/core/inspector/console_message.h" + #include "third_party/blink/renderer/modules/webaudio/audio_graph_tracer.h" + #include "third_party/blink/renderer/modules/webaudio/audio_node.h" +@@ -235,6 +236,49 @@ void AudioParamHandler::CalculateSampleAccurateValues( + CalculateFinalValues(values, number_of_values, IsAudioRate()); + } + ++// Replace NaN values in |values| with |default_value|. ++static void HandleNaNValues(float* values, ++ unsigned number_of_values, ++ float default_value) { ++ unsigned k = 0; ++#if defined(ARCH_CPU_X86_FAMILY) ++ if (number_of_values >= 4) { ++ __m128 defaults = _mm_set1_ps(default_value); ++ for (k = 0; k < number_of_values; k += 4) { ++ __m128 v = _mm_loadu_ps(values + k); ++ // cmpuord returns all 1's if v is NaN for each elmeent of v. ++ __m128 isnan = _mm_cmpunord_ps(v, v); ++ // Replace NaN parts with default. ++ __m128 result = _mm_and_ps(isnan, defaults); ++ // Merge in the parts that aren't NaN ++ result = _mm_or_ps(_mm_andnot_ps(isnan, v), result); ++ _mm_storeu_ps(values + k, result); ++ } ++ } ++#elif defined(CPU_ARM_NEON) ++ if (number_of_values >= 4) { ++ uint32x4_t defaults = static_cast(vdupq_n_f32(default_value)); ++ for (k = 0; k < number_of_values; k += 4) { ++ float32x4_t v = vld1q_f32(values + k); ++ // Returns true (all ones) if v is not NaN ++ uint32x4_t is_not_nan = vceqq_f32(v, v); ++ // Get the parts that are not NaN ++ uint32x4_t result = vandq_u32(is_not_nan, v); ++ // Replace the parts that are NaN with the default and merge with previous ++ // result. (Note: vbic_u32(x, y) = x and not y) ++ result = vorrq_u32(result, vbicq_u32(defaults, is_not_nan)); ++ vst1q_f32(values + k, static_cast(result)); ++ } ++ } ++#endif ++ ++ for (; k < number_of_values; ++k) { ++ if (std::isnan(values[k])) { ++ values[k] = default_value; ++ } ++ } ++} ++ + void AudioParamHandler::CalculateFinalValues(float* values, + unsigned number_of_values, + bool sample_accurate) { +@@ -297,10 +341,21 @@ void AudioParamHandler::CalculateFinalValues(float* values, + } + } + +- // Clamp the values now to the nominal range + float min_value = MinValue(); + float max_value = MaxValue(); + ++ if (NumberOfRenderingConnections() > 0) { ++ // AudioParams by themselves don't produce NaN because of the finite min ++ // and max values. But an input to an AudioParam could have NaNs. ++ // ++ // NaN values in AudioParams must be replaced by the AudioParam's ++ // defaultValue. Then these values must be clamped to lie in the nominal ++ // range between the AudioParam's minValue and maxValue. ++ // ++ // See https://webaudio.github.io/web-audio-api/#computation-of-value. ++ HandleNaNValues(values, number_of_values, DefaultValue()); ++ } ++ + vector_math::Vclip(values, 1, &min_value, &max_value, values, 1, + number_of_values); + } +diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/nan-param.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/nan-param.html +new file mode 100644 +index 0000000000000000000000000000000000000000..e9b8f0accbd1b0359275615f3ef12bd7e9317c4f +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioparam-interface/nan-param.html +@@ -0,0 +1,92 @@ ++ ++ ++ ++ Test Flushing of NaN to Zero in AudioParams ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --git a/patches/chromium/cherry-pick-3abc372c9c00.patch b/patches/chromium/cherry-pick-3abc372c9c00.patch new file mode 100644 index 0000000000000..d0bb9c06a4d40 --- /dev/null +++ b/patches/chromium/cherry-pick-3abc372c9c00.patch @@ -0,0 +1,61 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Xiaocheng Hu +Date: Tue, 3 Nov 2020 23:00:29 +0000 +Subject: Apply markup sanitizer in CompositeEditCommand::MoveParagraphs() + +CompositeEditCommand::MoveParagraphs() serailizes part of the DOM and +then re-parse it and insert it at some other place of the document. This +is essentially a copy-and-paste, and can be exploited in the same way +how copy-and-paste is exploited. So we should also sanitize markup in +the function. + +(cherry picked from commit c529cbcc1bb0f72af944c30f03c2b3b435317bc7) + +Bug: 1141350 +Change-Id: I25c1dfc61c20b9134b23e057c5a3a0f56c190b5c +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2500633 +Commit-Queue: Yoshifumi Inoue +Reviewed-by: Yoshifumi Inoue +Cr-Original-Commit-Position: refs/heads/master@{#821098} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2518088 +Reviewed-by: Xiaocheng Hu +Commit-Queue: Xiaocheng Hu +Cr-Commit-Position: refs/branch-heads/4280@{#1099} +Cr-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852} + +diff --git a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc +index 08a19440da1bff652481c3cedae2f1edb2b58246..0ba9af6cff6dd6eaa373a0f4dca37226aee85ca0 100644 +--- a/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc ++++ b/third_party/blink/renderer/core/editing/commands/composite_edit_command.cc +@@ -1507,19 +1507,18 @@ void CompositeEditCommand::MoveParagraphs( + // FIXME: This is an inefficient way to preserve style on nodes in the + // paragraph to move. It shouldn't matter though, since moved paragraphs will + // usually be quite small. +- DocumentFragment* fragment = +- start_of_paragraph_to_move.DeepEquivalent() != +- end_of_paragraph_to_move.DeepEquivalent() +- ? CreateFragmentFromMarkup( +- GetDocument(), +- CreateMarkup(start.ParentAnchoredEquivalent(), +- end.ParentAnchoredEquivalent(), +- CreateMarkupOptions::Builder() +- .SetShouldConvertBlocksToInlines(true) +- .SetConstrainingAncestor(constraining_ancestor) +- .Build()), +- "", kDisallowScriptingAndPluginContent) +- : nullptr; ++ DocumentFragment* fragment = nullptr; ++ if (start_of_paragraph_to_move.DeepEquivalent() != ++ end_of_paragraph_to_move.DeepEquivalent()) { ++ const String paragraphs_markup = CreateMarkup( ++ start.ParentAnchoredEquivalent(), end.ParentAnchoredEquivalent(), ++ CreateMarkupOptions::Builder() ++ .SetShouldConvertBlocksToInlines(true) ++ .SetConstrainingAncestor(constraining_ancestor) ++ .Build()); ++ fragment = CreateSanitizedFragmentFromMarkupWithContext( ++ GetDocument(), paragraphs_markup, 0, paragraphs_markup.length(), ""); ++ } + + // A non-empty paragraph's style is moved when we copy and move it. We don't + // move anything if we're given an empty paragraph, but an empty paragraph can diff --git a/patches/chromium/cherry-pick-3c80bb2a594f.patch b/patches/chromium/cherry-pick-3c80bb2a594f.patch new file mode 100644 index 0000000000000..e8f0e9fcd93dc --- /dev/null +++ b/patches/chromium/cherry-pick-3c80bb2a594f.patch @@ -0,0 +1,46 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jana Grill +Date: Wed, 14 Apr 2021 08:40:10 +0000 +Subject: Forbid script execution while updating the paint lifecycle. + +(cherry picked from commit 5425d3b100fab533ea9ddc2ed8fbfc4870db0587) + +Bug: 1196781 +Change-Id: Idc8d24792d5c413691977b09ca821de4e13887ad +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2812000 +Commit-Queue: Adrian Taylor +Commit-Queue: Robert Flack +Reviewed-by: Xianzhu Wang +Cr-Original-Commit-Position: refs/heads/master@{#870275} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2821879 +Reviewed-by: Robert Flack +Reviewed-by: Achuith Bhandarkar +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Jana Grill +Cr-Commit-Position: refs/branch-heads/4240@{#1601} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/core/frame/local_frame_view.cc b/third_party/blink/renderer/core/frame/local_frame_view.cc +index 9a4c7a5249424b021759bf7895dd3f343b9641e6..37054d34157e7f4b4d65b022cdb83c832deb26a8 100644 +--- a/third_party/blink/renderer/core/frame/local_frame_view.cc ++++ b/third_party/blink/renderer/core/frame/local_frame_view.cc +@@ -2648,11 +2648,14 @@ void LocalFrameView::RunPaintLifecyclePhase() { + for (PaintLayerScrollableArea* area : *animating_scrollable_areas) + area->UpdateCompositorScrollAnimations(); + } +- frame_view.GetLayoutView() +- ->GetDocument() +- .GetDocumentAnimations() +- .UpdateAnimations(DocumentLifecycle::kPaintClean, +- paint_artifact_compositor_.get()); ++ { ++ ScriptForbiddenScope forbid_script; ++ frame_view.GetLayoutView() ++ ->GetDocument() ++ .GetDocumentAnimations() ++ .UpdateAnimations(DocumentLifecycle::kPaintClean, ++ paint_artifact_compositor_.get()); ++ } + }); + + // Initialize animation properties in the newly created paint property diff --git a/patches/chromium/cherry-pick-3ca3d70c7af5.patch b/patches/chromium/cherry-pick-3ca3d70c7af5.patch new file mode 100644 index 0000000000000..40023c2b2685a --- /dev/null +++ b/patches/chromium/cherry-pick-3ca3d70c7af5.patch @@ -0,0 +1,56 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Yuri Wiitala +Date: Thu, 10 Dec 2020 18:07:39 +0000 +Subject: Minor UI logic changes to prevent a UAF bug when starting tab + capture. + +See discussion in crbug 1155426 for details. Changes: + +MediaStreamCaptureIndicator::UIDelegate: Ignore multiple calls to +OnStarted(). + +TabSharingUIViews: Unconditionally execute clean-up tasks in destructor. + +Bug: 1155426 +Change-Id: I392fba38118ce51744ba36b4dec19ebfe39f1fbe +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2581028 +Reviewed-by: Guido Urdaneta +Reviewed-by: Marina Ciocea +Commit-Queue: Yuri Wiitala +Cr-Commit-Position: refs/heads/master@{#835736} + +diff --git a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc +index ce93d308a3d1099d9edbeea88ce8d05e60158117..99203ebb36edc21697518d172d67831deed8ba04 100644 +--- a/chrome/browser/media/webrtc/media_stream_capture_indicator.cc ++++ b/chrome/browser/media/webrtc/media_stream_capture_indicator.cc +@@ -186,7 +186,12 @@ class MediaStreamCaptureIndicator::UIDelegate : public content::MediaStreamUI { + gfx::NativeViewId OnStarted( + base::OnceClosure stop_callback, + content::MediaStreamUI::SourceCallback source_callback) override { +- DCHECK(!started_); ++ if (started_) { ++ // Ignore possibly-compromised renderers that might call ++ // MediaStreamDispatcherHost::OnStreamStarted() more than once. ++ // See: https://crbug.com/1155426 ++ return 0; ++ } + started_ = true; + + if (device_usage_) { +diff --git a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc +index 1582ccedd3fac5368e7adf94ec222e5d85b18aab..35e4f3e93c41f52fb50599da4050c0f3c25dd0d4 100644 +--- a/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc ++++ b/chrome/browser/ui/views/tab_sharing/tab_sharing_ui_views.cc +@@ -134,8 +134,10 @@ TabSharingUIViews::TabSharingUIViews(const content::DesktopMediaID& media_id, + } + + TabSharingUIViews::~TabSharingUIViews() { +- if (!infobars_.empty()) +- StopSharing(); ++ // Unconditionally call StopSharing(), to ensure all clean-up has been ++ // performed if tasks race (e.g., OnStarted() is called after ++ // OnInfoBarRemoved()). See: https://crbug.com/1155426 ++ StopSharing(); + } + + gfx::NativeViewId TabSharingUIViews::OnStarted( diff --git a/patches/chromium/cherry-pick-406ae3e8a9a8.patch b/patches/chromium/cherry-pick-406ae3e8a9a8.patch new file mode 100644 index 0000000000000..a922984741905 --- /dev/null +++ b/patches/chromium/cherry-pick-406ae3e8a9a8.patch @@ -0,0 +1,100 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ken Rockot +Date: Tue, 20 Apr 2021 15:46:33 +0000 +Subject: M86-LTS: Mojo: Properly validate broadcast events + +This corrects broadcast event deserialization by adding a missing +validation step when decoding the outer message header. + +(cherry picked from commit 6740adb28374ddeee13febfd5e5d20cb8a365979) + +Fixed: 1195308 +Change-Id: Ia67a20e48614e7ef00b1b32f7f4e5f20235be310 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2808678 +Reviewed-by: Daniel Cheng +Commit-Queue: Ken Rockot +Cr-Original-Commit-Position: refs/heads/master@{#870238} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2837712 +Owners-Override: Achuith Bhandarkar +Auto-Submit: Achuith Bhandarkar +Reviewed-by: Artem Sumaneev +Commit-Queue: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1614} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/mojo/core/node_channel.cc b/mojo/core/node_channel.cc +index 061ea1026e95d1b1f80a762ce377aebdd97e1b42..07e3b8b21f7ef70b64d214ec03e0dd1eb807fad6 100644 +--- a/mojo/core/node_channel.cc ++++ b/mojo/core/node_channel.cc +@@ -191,13 +191,16 @@ Channel::MessagePtr NodeChannel::CreateEventMessage(size_t capacity, + } + + // static +-void NodeChannel::GetEventMessageData(Channel::Message* message, ++bool NodeChannel::GetEventMessageData(Channel::Message& message, + void** data, + size_t* num_data_bytes) { +- // NOTE: OnChannelMessage guarantees that we never accept a Channel::Message +- // with a payload of fewer than |sizeof(Header)| bytes. +- *data = reinterpret_cast(message->mutable_payload()) + 1; +- *num_data_bytes = message->payload_size() - sizeof(Header); ++ // NOTE: Callers must guarantee that the payload in `message` must be at least ++ // large enough to hold a Header. ++ if (message.payload_size() < sizeof(Header)) ++ return false; ++ *data = reinterpret_cast(message.mutable_payload()) + 1; ++ *num_data_bytes = message.payload_size() - sizeof(Header); ++ return true; + } + + void NodeChannel::Start() { +diff --git a/mojo/core/node_channel.h b/mojo/core/node_channel.h +index 58ab42bd01fc856856d171985dac50934d4e00b2..7ae08e3e73110667f0eafe0fe4f70242bfeece39 100644 +--- a/mojo/core/node_channel.h ++++ b/mojo/core/node_channel.h +@@ -90,7 +90,9 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeChannel + void** payload, + size_t num_handles); + +- static void GetEventMessageData(Channel::Message* message, ++ // Retrieves address and size of an Event message's underlying message data. ++ // Returns `false` if the message is not a valid Event message. ++ static bool GetEventMessageData(Channel::Message& message, + void** data, + size_t* num_data_bytes); + +diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc +index c333ed64f71f0dfe5d0012b07bcedccfd94cd5e9..a8b8520729510408dc822532271d0ff4a36a7151 100644 +--- a/mojo/core/node_controller.cc ++++ b/mojo/core/node_controller.cc +@@ -76,7 +76,9 @@ ports::ScopedEvent DeserializeEventMessage( + Channel::MessagePtr channel_message) { + void* data; + size_t size; +- NodeChannel::GetEventMessageData(channel_message.get(), &data, &size); ++ bool valid = NodeChannel::GetEventMessageData(*channel_message, &data, &size); ++ if (!valid) ++ return nullptr; + auto event = ports::Event::Deserialize(data, size); + if (!event) + return nullptr; +diff --git a/mojo/core/user_message_impl.cc b/mojo/core/user_message_impl.cc +index 2f1665e55cf0af69c58c21f2e0d602a93e79052e..a6b35b2cd812bb0da7026b088aa0d96acbbc6a2f 100644 +--- a/mojo/core/user_message_impl.cc ++++ b/mojo/core/user_message_impl.cc +@@ -415,7 +415,14 @@ Channel::MessagePtr UserMessageImpl::FinalizeEventMessage( + if (channel_message) { + void* data; + size_t size; +- NodeChannel::GetEventMessageData(channel_message.get(), &data, &size); ++ // The `channel_message` must either be produced locally or must have ++ // already been validated by the caller, as is done for example by ++ // NodeController::DeserializeEventMessage before ++ // NodeController::OnBroadcast re-serializes each copy of the message it ++ // received. ++ bool result = ++ NodeChannel::GetEventMessageData(*channel_message, &data, &size); ++ DCHECK(result); + message_event->Serialize(data); + } + diff --git a/patches/chromium/cherry-pick-4794770cf175.patch b/patches/chromium/cherry-pick-4794770cf175.patch new file mode 100644 index 0000000000000..85b614d9a1528 --- /dev/null +++ b/patches/chromium/cherry-pick-4794770cf175.patch @@ -0,0 +1,326 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Adam Rice +Date: Fri, 4 Dec 2020 10:19:12 +0000 +Subject: Correctly handle detach during (de)compression + +Sometimes CompressionStream and DecompressionStream enqueue multiple +output chunks for a single input chunk. When this happens, JavaScript +code can detach the input ArrayBuffer while the stream is processing it. +This will cause an error when zlib tries to read the buffer again +afterwards. + +To prevent this, buffer output chunks until the entire input chunk has +been processed, and then enqueue them all at once. + +Bug: 1151298 +Change-Id: I03fca26fc641d54b09067e3994b76ee8efca6839 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2567539 +Commit-Queue: Adam Rice +Reviewed-by: Yutaka Hirano +Cr-Commit-Position: refs/heads/master@{#833659} + +diff --git a/third_party/blink/renderer/modules/compression/deflate_transformer.cc b/third_party/blink/renderer/modules/compression/deflate_transformer.cc +index e663e563d58e90cdce5185636d1b1aa75491195c..e806b84afbb6cd82c53832e0c0b455ae0767a34a 100644 +--- a/third_party/blink/renderer/modules/compression/deflate_transformer.cc ++++ b/third_party/blink/renderer/modules/compression/deflate_transformer.cc +@@ -111,6 +111,10 @@ void DeflateTransformer::Deflate(const uint8_t* start, + // Zlib treats this pointer as const, so this cast is safe. + stream_.next_in = const_cast(start); + ++ // enqueue() may execute JavaScript which may invalidate the input buffer. So ++ // accumulate all the output before calling enqueue(). ++ HeapVector, 1u> buffers; ++ + do { + stream_.avail_out = out_buffer_.size(); + stream_.next_out = out_buffer_.data(); +@@ -120,16 +124,21 @@ void DeflateTransformer::Deflate(const uint8_t* start, + + wtf_size_t bytes = out_buffer_.size() - stream_.avail_out; + if (bytes) { +- controller->enqueue( +- script_state_, +- ScriptValue::From(script_state_, +- DOMUint8Array::Create(out_buffer_.data(), bytes)), +- exception_state); +- if (exception_state.HadException()) { +- return; +- } ++ buffers.push_back(DOMUint8Array::Create(out_buffer_.data(), bytes)); + } + } while (stream_.avail_out == 0); ++ ++ DCHECK_EQ(stream_.avail_in, 0u); ++ ++ // JavaScript may be executed inside this loop, however it is safe because ++ // |buffers| is a local variable that JavaScript cannot modify. ++ for (DOMUint8Array* buffer : buffers) { ++ controller->enqueue(script_state_, ScriptValue::From(script_state_, buffer), ++ exception_state); ++ if (exception_state.HadException()) { ++ return; ++ } ++ } + } + + void DeflateTransformer::Trace(Visitor* visitor) const { +diff --git a/third_party/blink/renderer/modules/compression/inflate_transformer.cc b/third_party/blink/renderer/modules/compression/inflate_transformer.cc +index f348a5086e4254bc972dd321109d9fbf676f1d5e..29667b5752250466cd71542ab8868d6e14961e81 100644 +--- a/third_party/blink/renderer/modules/compression/inflate_transformer.cc ++++ b/third_party/blink/renderer/modules/compression/inflate_transformer.cc +@@ -20,6 +20,7 @@ + #include "third_party/blink/renderer/platform/bindings/exception_state.h" + #include "third_party/blink/renderer/platform/bindings/to_v8.h" + #include "third_party/blink/renderer/platform/wtf/text/wtf_string.h" ++#include "third_party/blink/renderer/platform/wtf/vector.h" + #include "v8/include/v8.h" + + namespace blink { +@@ -93,11 +94,15 @@ ScriptPromise InflateTransformer::Flush( + TransformStreamDefaultController* controller, + ExceptionState& exception_state) { + DCHECK(!was_flush_called_); ++ was_flush_called_ = true; + Inflate(nullptr, 0u, IsFinished(true), controller, exception_state); + inflateEnd(&stream_); +- was_flush_called_ = true; + out_buffer_.clear(); + ++ if (exception_state.HadException()) { ++ return ScriptPromise(); ++ } ++ + if (!reached_end_) { + exception_state.ThrowTypeError("Compressed input was truncated."); + } +@@ -121,12 +126,22 @@ void InflateTransformer::Inflate(const uint8_t* start, + // Zlib treats this pointer as const, so this cast is safe. + stream_.next_in = const_cast(start); + ++ // enqueue() may execute JavaScript which may invalidate the input buffer. So ++ // accumulate all the output before calling enqueue(). ++ HeapVector, 1u> buffers; ++ + do { + stream_.avail_out = out_buffer_.size(); + stream_.next_out = out_buffer_.data(); + const int err = inflate(&stream_, finished ? Z_FINISH : Z_NO_FLUSH); + if (err != Z_OK && err != Z_STREAM_END && err != Z_BUF_ERROR) { + DCHECK_NE(err, Z_STREAM_ERROR); ++ ++ EnqueueBuffers(controller, std::move(buffers), exception_state); ++ if (exception_state.HadException()) { ++ return; ++ } ++ + if (err == Z_DATA_ERROR) { + exception_state.ThrowTypeError( + String("The compressed data was not valid: ") + stream_.msg + "."); +@@ -138,25 +153,44 @@ void InflateTransformer::Inflate(const uint8_t* start, + + wtf_size_t bytes = out_buffer_.size() - stream_.avail_out; + if (bytes) { +- controller->enqueue( +- script_state_, +- ScriptValue::From(script_state_, +- DOMUint8Array::Create(out_buffer_.data(), bytes)), +- exception_state); +- if (exception_state.HadException()) { +- return; +- } ++ buffers.push_back(DOMUint8Array::Create(out_buffer_.data(), bytes)); + } + + if (err == Z_STREAM_END) { + reached_end_ = true; +- if (stream_.next_in < start + length) { ++ const bool junk_found = stream_.avail_in > 0; ++ ++ EnqueueBuffers(controller, std::move(buffers), exception_state); ++ if (exception_state.HadException()) { ++ return; ++ } ++ ++ if (junk_found) { + exception_state.ThrowTypeError( + "Junk found after end of compressed data."); + } + return; + } + } while (stream_.avail_out == 0); ++ ++ DCHECK_EQ(stream_.avail_in, 0u); ++ ++ EnqueueBuffers(controller, std::move(buffers), exception_state); ++} ++ ++void InflateTransformer::EnqueueBuffers( ++ TransformStreamDefaultController* controller, ++ HeapVector, 1u> buffers, ++ ExceptionState& exception_state) { ++ // JavaScript may be executed inside this loop, however it is safe because ++ // |buffers| is a local variable that JavaScript cannot modify. ++ for (DOMUint8Array* buffer : buffers) { ++ controller->enqueue(script_state_, ScriptValue::From(script_state_, buffer), ++ exception_state); ++ if (exception_state.HadException()) { ++ return; ++ } ++ } + } + + void InflateTransformer::Trace(Visitor* visitor) const { +diff --git a/third_party/blink/renderer/modules/compression/inflate_transformer.h b/third_party/blink/renderer/modules/compression/inflate_transformer.h +index 290e91808dfcc16ed7ff6ec9e6e42eb412dfc969..c5df437f1684e6ca1201d9b3d32dff19903a0b5e 100644 +--- a/third_party/blink/renderer/modules/compression/inflate_transformer.h ++++ b/third_party/blink/renderer/modules/compression/inflate_transformer.h +@@ -41,6 +41,10 @@ class InflateTransformer final : public TransformStreamTransformer { + TransformStreamDefaultController*, + ExceptionState&); + ++ void EnqueueBuffers(TransformStreamDefaultController*, ++ HeapVector, 1u> buffers, ++ ExceptionState&); ++ + Member script_state_; + + z_stream stream_; +diff --git a/third_party/blink/web_tests/external/wpt/compression/compression-with-detach.tentative.any.js b/third_party/blink/web_tests/external/wpt/compression/compression-with-detach.tentative.any.js +new file mode 100644 +index 0000000000000000000000000000000000000000..786bba21c800ca9f067a6d033f0345a52bfbb218 +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/compression/compression-with-detach.tentative.any.js +@@ -0,0 +1,55 @@ ++// META: global=window,worker ++// META: script=resources/concatenate-stream.js ++ ++'use strict'; ++ ++const kInputLength = 500000; ++ ++function createLargeRandomInput() { ++ const buffer = new ArrayBuffer(kInputLength); ++ // The getRandomValues API will only let us get 65536 bytes at a time, so call ++ // it multiple times. ++ const kChunkSize = 65536; ++ for (let offset = 0; offset < kInputLength; offset += kChunkSize) { ++ const length = ++ offset + kChunkSize > kInputLength ? kInputLength - offset : kChunkSize; ++ const view = new Uint8Array(buffer, offset, length); ++ crypto.getRandomValues(view); ++ } ++ return new Uint8Array(buffer); ++} ++ ++function decompress(view) { ++ const ds = new DecompressionStream('deflate'); ++ const writer = ds.writable.getWriter(); ++ writer.write(view); ++ writer.close(); ++ return concatenateStream(ds.readable); ++} ++ ++promise_test(async () => { ++ const input = createLargeRandomInput(); ++ const inputCopy = input.slice(0, input.byteLength); ++ const cs = new CompressionStream('deflate'); ++ const writer = cs.writable.getWriter(); ++ writer.write(input); ++ writer.close(); ++ // Object.prototype.then will be looked up synchronously when the promise ++ // returned by read() is resolved. ++ Object.defineProperty(Object.prototype, 'then', { ++ get() { ++ // Cause input to become detached and unreferenced. ++ try { ++ postMessage(undefined, 'nowhere', [input.buffer]); ++ } catch (e) { ++ // It's already detached. ++ } ++ } ++ }); ++ const output = await concatenateStream(cs.readable); ++ // Perform the comparison as strings since this is reasonably fast even when ++ // JITted JavaScript is running under an emulator. ++ assert_equals( ++ inputCopy.toString(), (await decompress(output)).toString(), ++ 'decompressing the output should return the input'); ++}, 'data should be correctly compressed even if input is detached partway'); +diff --git a/third_party/blink/web_tests/external/wpt/compression/decompression-with-detach.tentative.any.js b/third_party/blink/web_tests/external/wpt/compression/decompression-with-detach.tentative.any.js +new file mode 100644 +index 0000000000000000000000000000000000000000..a2f8bda09148f0d323022b1f93be78d83c4aa654 +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/compression/decompression-with-detach.tentative.any.js +@@ -0,0 +1,41 @@ ++// META: global=window,worker ++// META: script=resources/concatenate-stream.js ++ ++'use strict'; ++ ++const kInputLength = 1000000; ++ ++async function createLargeCompressedInput() { ++ const cs = new CompressionStream('deflate'); ++ // The input has to be large enough that it won't fit in a single chunk when ++ // decompressed. ++ const writer = cs.writable.getWriter(); ++ writer.write(new Uint8Array(kInputLength)); ++ writer.close(); ++ return concatenateStream(cs.readable); ++} ++ ++promise_test(async () => { ++ const input = await createLargeCompressedInput(); ++ const ds = new DecompressionStream('deflate'); ++ const writer = ds.writable.getWriter(); ++ writer.write(input); ++ writer.close(); ++ // Object.prototype.then will be looked up synchronously when the promise ++ // returned by read() is resolved. ++ Object.defineProperty(Object.prototype, 'then', { ++ get() { ++ // Cause input to become detached and unreferenced. ++ try { ++ postMessage(undefined, 'nowhere', [input.buffer]); ++ } catch (e) { ++ // It's already detached. ++ } ++ } ++ }); ++ const output = await concatenateStream(ds.readable); ++ // If output successfully decompressed and gave the right length, we can be ++ // reasonably confident that no data corruption happened. ++ assert_equals( ++ output.byteLength, kInputLength, 'output should be the right length'); ++}, 'data should be correctly decompressed even if input is detached partway'); +diff --git a/third_party/blink/web_tests/external/wpt/compression/resources/concatenate-stream.js b/third_party/blink/web_tests/external/wpt/compression/resources/concatenate-stream.js +new file mode 100644 +index 0000000000000000000000000000000000000000..a35bb1416e754893e331c0089d97720ae3b5af8e +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/compression/resources/concatenate-stream.js +@@ -0,0 +1,25 @@ ++'use strict'; ++ ++// Read all the chunks from a stream that returns BufferSource objects and ++// concatenate them into a single Uint8Array. ++async function concatenateStream(readableStream) { ++ const reader = readableStream.getReader(); ++ let totalSize = 0; ++ const buffers = []; ++ while (true) { ++ const { value, done } = await reader.read(); ++ if (done) { ++ break; ++ } ++ buffers.push(value); ++ totalSize += value.byteLength; ++ } ++ reader.releaseLock(); ++ const concatenated = new Uint8Array(totalSize); ++ let offset = 0; ++ for (const buffer of buffers) { ++ concatenated.set(buffer, offset); ++ offset += buffer.byteLength; ++ } ++ return concatenated; ++} diff --git a/patches/chromium/cherry-pick-5651fb858b75.patch b/patches/chromium/cherry-pick-5651fb858b75.patch new file mode 100644 index 0000000000000..577b06a742817 --- /dev/null +++ b/patches/chromium/cherry-pick-5651fb858b75.patch @@ -0,0 +1,45 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Harald Alvestrand +Date: Thu, 11 Mar 2021 18:54:23 +0000 +Subject: Iterate more carefully over DTLS transports at close +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Ensure that even if the set of DTLS transports is modified during +callbacks called from close, the process will be well-defined. + +(cherry picked from commit 4f62c7bb28b0ce77b773a611c6ba02b361db1c85) + +Bug: chromium:1167357 +Change-Id: I712280e7382a647027912178156127831b437f75 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2639893 +Reviewed-by: Henrik Boström +Commit-Queue: Harald Alvestrand +Cr-Original-Commit-Position: refs/heads/master@{#845122} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2752880 +Reviewed-by: Adrian Taylor +Cr-Commit-Position: refs/branch-heads/4389@{#1521} +Cr-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} + +diff --git a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc +index 1503444ac2931a4205afe990f2e2dac11206de48..5d0b7e9cbe1f3459057b50879ab88d5a2ae12c20 100644 +--- a/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc ++++ b/third_party/blink/renderer/modules/peerconnection/rtc_peer_connection.cc +@@ -3438,8 +3438,14 @@ void RTCPeerConnection::CloseInternal() { + if (sctp_transport_) { + sctp_transport_->Close(); + } +- for (auto& dtls_transport_iter : dtls_transports_by_native_transport_) { +- dtls_transport_iter.value->Close(); ++ // Since Close() can trigger JS-level callbacks, iterate over a copy ++ // of the transports list. ++ auto dtls_transports_copy = dtls_transports_by_native_transport_; ++ for (auto& dtls_transport_iter : dtls_transports_copy) { ++ // Since "value" is a WeakPtr, check if it's still valid. ++ if (dtls_transport_iter.value) { ++ dtls_transport_iter.value->Close(); ++ } + } + + feature_handle_for_scheduler_.reset(); diff --git a/patches/chromium/cherry-pick-5745eaf16077.patch b/patches/chromium/cherry-pick-5745eaf16077.patch new file mode 100644 index 0000000000000..d2789fddf0efb --- /dev/null +++ b/patches/chromium/cherry-pick-5745eaf16077.patch @@ -0,0 +1,89 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Asanka Herath +Date: Wed, 31 Mar 2021 16:33:46 +0000 +Subject: Remove unnecessary kCanvasReadback metrics. + +The identifiability metrics recorded under kCanvasReadback surface type +used two conflicting sources as inputs: the CanvasRenderingContext +type, and the paint-op digest. + +There are known collisions between resulting IdentifiableSurface values +from the two sources, which makes it impossible to losslessly separate +the two during analysis. + +While the fact that a canvas readback happened is interesting, it +doesn't help determine the observed diversity of clients. Hence this +change removes one of those sources: the CanvasRenderingContext type. + +M86 merge conflicts and resolution: +* third_party/blink/renderer/core/offscreencanvas/offscreen_canvas.cc + M86 does not have the code removed in original CL. +* third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc + third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc + Removed corresponding code, kept old API. + +(cherry picked from commit 809231f0c9fdc6180b6a99cf067d0a32db053034) + +(cherry picked from commit b206b57b96985713ad167738f6839a8d32db78f2) + +Bug: 1161379, 1186641 +Change-Id: I770cb631c9c4afe4c36d1b129aaf61410db25d43 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2600386 +Commit-Queue: Asanka Herath +Reviewed-by: Caleb Raitto +Reviewed-by: Kentaro Hara +Cr-Original-Original-Commit-Position: refs/heads/master@{#847480} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2785145 +Reviewed-by: Justin Novosad +Reviewed-by: Juanmi Huertas +Reviewed-by: Asanka Herath +Commit-Queue: Yi Xu +Cr-Original-Commit-Position: refs/branch-heads/4389@{#1599} +Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2794506 +Reviewed-by: Artem Sumaneev +Reviewed-by: Victor-Gabriel Savu +Auto-Submit: Artem Sumaneev +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1586} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc +index d4e0d5d0657e89f531d810f407a232248e0fca6f..95c891fc795cb0024e561ee522da79a82b8f14ce 100644 +--- a/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc ++++ b/third_party/blink/renderer/modules/canvas/canvas2d/canvas_rendering_context_2d.cc +@@ -680,12 +680,6 @@ ImageData* CanvasRenderingContext2D::getImageData( + int sw, + int sh, + ExceptionState& exception_state) { +- blink::IdentifiabilityMetricBuilder(ukm_source_id_) +- .Set(blink::IdentifiableSurface::FromTypeAndInput( +- blink::IdentifiableSurface::Type::kCanvasReadback, +- GetContextType()), +- 0) +- .Record(ukm_recorder_); + return BaseRenderingContext2D::getImageData(sx, sy, sw, sh, exception_state); + } + +diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +index d7445bf31a391bea3742327c34dc3eb46c72513b..32e945f0692b66de95735a3cd949943243ec5a7b 100644 +--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc ++++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +@@ -4508,17 +4508,6 @@ void WebGLRenderingContextBase::readPixels( + GLenum format, + GLenum type, + MaybeShared pixels) { +- if (IsUserInIdentifiabilityStudy()) { +- base::Optional ukm_params = ukm_parameters(); +- if (ukm_params) { +- blink::IdentifiabilityMetricBuilder(ukm_params->source_id) +- .Set(blink::IdentifiableSurface::FromTypeAndInput( +- blink::IdentifiableSurface::Type::kCanvasReadback, +- GetContextType()), +- 0) +- .Record(ukm_params->ukm_recorder); +- } +- } + ReadPixelsHelper(x, y, width, height, format, type, pixels.View(), 0); + } + diff --git a/patches/chromium/cherry-pick-5ffbb7ed173a.patch b/patches/chromium/cherry-pick-5ffbb7ed173a.patch new file mode 100644 index 0000000000000..4a137f83d8bf8 --- /dev/null +++ b/patches/chromium/cherry-pick-5ffbb7ed173a.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Darwin Huang +Date: Fri, 13 Nov 2020 10:07:05 +0000 +Subject: Pepper: Ensure weak pointer is still valid before use (M86). + +TBR=bbudge@chromium.org +(cherry picked from commit f24c213293752250db05e11c5e4b77adce002d38) + +Bug: 1146675 +Change-Id: I382dcb5c0b09a26e3c397ebef46947f626e2aef9 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2527065 +Reviewed-by: Bill Budge +Commit-Queue: Darwin Huang +Cr-Original-Commit-Position: refs/heads/master@{#825558} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2536757 +Reviewed-by: Darwin Huang +Cr-Commit-Position: refs/branch-heads/4240@{#1448} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/content/browser/renderer_host/pepper/pepper_file_io_host.cc b/content/browser/renderer_host/pepper/pepper_file_io_host.cc +index 28fba2fc56ddb7f42f3390db99999998ba912867..74bb7c539f8f05971b020e1d370098f5825e0ac2 100644 +--- a/content/browser/renderer_host/pepper/pepper_file_io_host.cc ++++ b/content/browser/renderer_host/pepper/pepper_file_io_host.cc +@@ -248,7 +248,12 @@ void PepperFileIOHost::GotUIThreadStuffForInternalFileSystems( + return; + } + +- DCHECK(file_system_host_.get()); ++ if (!file_system_host_.get()) { ++ reply_context.params.set_result(PP_ERROR_FAILED); ++ SendOpenErrorReply(reply_context); ++ return; ++ } ++ + DCHECK(file_system_host_->GetFileSystemOperationRunner()); + + file_system_host_->GetFileSystemOperationRunner()->OpenFile( diff --git a/patches/chromium/cherry-pick-668cf831e912.patch b/patches/chromium/cherry-pick-668cf831e912.patch new file mode 100644 index 0000000000000..e724f55a883e0 --- /dev/null +++ b/patches/chromium/cherry-pick-668cf831e912.patch @@ -0,0 +1,57 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ken Rockot +Date: Tue, 23 Mar 2021 21:13:00 +0000 +Subject: Never fail in ReceiverSet::Add + +Because of how UniqueReceiverSet is implemented and used, it is +dangerous to allow Add() to fail: callers reasonably assume that added +objects are still alive immediately after the Add() call. + +This changes ReceiverId to a uint64 and simply CHECK-fails on +insert collision. + +This fundamentally increases binary size of 32-bit builds, because +a widely used 32-bit data type is expanding to 64 bits for the sake +of security and stability. It is effectively unavoidable for now, and +also just barely above the tolerable threshold. + +A follow-up (but less backwards-mergeable) change should be able to +reduce binary size beyond this increase by consolidating shared +code among ReceiverSet template instantiations. + +Fixed: 1185732 +Change-Id: I9acf6aaaa36e10fdce5aa49a890173caddc13c52 +Binary-Size: Unavoidable (see above) +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2778871 +Commit-Queue: Ken Rockot +Auto-Submit: Ken Rockot +Reviewed-by: Robert Sesek +Cr-Commit-Position: refs/heads/master@{#865815} + +diff --git a/mojo/public/cpp/bindings/receiver_set.h b/mojo/public/cpp/bindings/receiver_set.h +index 8d7d73231543c70b67913fdf735c1a16cc6170b1..56027d1f3e6393f739c3b51330137d54ae3fc0d2 100644 +--- a/mojo/public/cpp/bindings/receiver_set.h ++++ b/mojo/public/cpp/bindings/receiver_set.h +@@ -24,7 +24,7 @@ + + namespace mojo { + +-using ReceiverId = size_t; ++using ReceiverId = uint64_t; + + template + struct ReceiverSetTraits; +@@ -359,11 +359,11 @@ class ReceiverSetBase { + Context context, + scoped_refptr task_runner) { + ReceiverId id = next_receiver_id_++; +- DCHECK_GE(next_receiver_id_, 0u); + auto entry = + std::make_unique(std::move(impl), std::move(receiver), this, id, + std::move(context), std::move(task_runner)); +- receivers_.insert(std::make_pair(id, std::move(entry))); ++ auto result = receivers_.insert(std::make_pair(id, std::move(entry))); ++ CHECK(result.second) << "ReceiverId overflow with collision"; + return id; + } + diff --git a/patches/chromium/cherry-pick-6a6361c9f31c.patch b/patches/chromium/cherry-pick-6a6361c9f31c.patch new file mode 100644 index 0000000000000..d761e3dfe0c03 --- /dev/null +++ b/patches/chromium/cherry-pick-6a6361c9f31c.patch @@ -0,0 +1,48 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Palak Agarwal +Date: Wed, 31 Mar 2021 16:10:26 +0000 +Subject: WebContents bug fix: Device capture only if web contents is valid + +(cherry picked from commit a462be0883486431086c5f07cdafbd3607005a59) + +(cherry picked from commit e6f11cafde08981e47ba77e71abf99a271f7a042) + +Bug: 1181228 +Change-Id: I0a4c9718a3c0ccb52cefa4565b9787e6912554c9 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2752235 +Reviewed-by: Guido Urdaneta +Commit-Queue: Palak Agarwal +Cr-Original-Original-Commit-Position: refs/heads/master@{#863828} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2782122 +Auto-Submit: Guido Urdaneta +Commit-Queue: Rubber Stamper +Bot-Commit: Rubber Stamper +Cr-Original-Commit-Position: refs/branch-heads/4389@{#1586} +Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2795101 +Reviewed-by: Victor-Gabriel Savu +Reviewed-by: Artem Sumaneev +Auto-Submit: Artem Sumaneev +Commit-Queue: Guido Urdaneta +Cr-Commit-Position: refs/branch-heads/4240@{#1585} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc +index e3f7e784f0339581a1c8a50301f8dbfd465abbfd..1f0811610e155715cc5cd72bbaa7e703728a1fe5 100644 +--- a/chrome/browser/media/webrtc/desktop_capture_access_handler.cc ++++ b/chrome/browser/media/webrtc/desktop_capture_access_handler.cc +@@ -248,6 +248,14 @@ void DesktopCaptureAccessHandler::ProcessScreenCaptureAccessRequest( + const bool display_notification = + display_notification_ && ShouldDisplayNotification(extension); + ++ if (!content::WebContents::FromRenderFrameHost( ++ content::RenderFrameHost::FromID(request.render_process_id, ++ request.render_frame_id))) { ++ std::move(callback).Run( ++ devices, blink::mojom::MediaStreamRequestResult::INVALID_STATE, ++ std::move(ui)); ++ return; ++ } + ui = GetDevicesForDesktopCapture( + web_contents, &devices, screen_id, + blink::mojom::MediaStreamType::GUM_DESKTOP_VIDEO_CAPTURE, diff --git a/patches/chromium/cherry-pick-6b84dc72351b.patch b/patches/chromium/cherry-pick-6b84dc72351b.patch new file mode 100644 index 0000000000000..caec3db896ac6 --- /dev/null +++ b/patches/chromium/cherry-pick-6b84dc72351b.patch @@ -0,0 +1,82 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Brendon Tiszka +Date: Tue, 20 Apr 2021 15:45:03 +0000 +Subject: M86-LTS: Ensure that BrowserContext is not used after it has been + freed +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Previously, it was possible for the BrowserContext to be destroyed +before ReportAnchorElementMetricsOnClick attempted to access it. + +The fix uses the fact that NavigationPredictor extends +WebContentsObserver and checks that web_contents is still alive +before dereferencing BrowserContext. WebContents will always +outlive BrowserContext. + +R=​lukasza@chromium.org, ryansturm@chromium.org + +(cherry picked from commit 7313a810ae0b1361cbe8453bc5496654dee24c76) + +Bug: 1197904 +Change-Id: Iee4f126e92670a84d57c7a4ec7d6f702fb975c7e +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2821639 +Reviewed-by: Ryan Sturm +Reviewed-by: Łukasz Anforowicz +Commit-Queue: Łukasz Anforowicz +Cr-Original-Commit-Position: refs/heads/master@{#872021} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2838328 +Owners-Override: Achuith Bhandarkar +Auto-Submit: Achuith Bhandarkar +Reviewed-by: Artem Sumaneev +Commit-Queue: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1613} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/AUTHORS b/AUTHORS +index 3aa101a8d38a899fefcca149e4ac8e658188e590..cccc1f6d1407183806e78cb99e56abe7bd93de82 100644 +--- a/AUTHORS ++++ b/AUTHORS +@@ -145,6 +145,7 @@ Bobby Powers + Branden Archer + Brendan Kirby + Brendan Long ++Brendon Tiszka + Brian Clifton + Brian G. Merrell + Brian Konzman, SJ +diff --git a/chrome/browser/navigation_predictor/navigation_predictor.cc b/chrome/browser/navigation_predictor/navigation_predictor.cc +index 495bb165a30f2b1bf690e6d0724ad8f347a76d44..b62a97501565555493f4db82ce4a1ababff19eb6 100644 +--- a/chrome/browser/navigation_predictor/navigation_predictor.cc ++++ b/chrome/browser/navigation_predictor/navigation_predictor.cc +@@ -506,6 +506,9 @@ void NavigationPredictor::ReportAnchorElementMetricsOnClick( + DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); + DCHECK(base::FeatureList::IsEnabled(blink::features::kNavigationPredictor)); + ++ if (!web_contents()) ++ return; ++ + if (browser_context_->IsOffTheRecord()) + return; + +@@ -652,6 +655,9 @@ void NavigationPredictor::ReportAnchorElementMetricsOnLoad( + // Each document should only report metrics once when page is loaded. + DCHECK(navigation_scores_map_.empty()); + ++ if (!web_contents()) ++ return; ++ + if (browser_context_->IsOffTheRecord()) + return; + +@@ -897,6 +903,9 @@ void NavigationPredictor::MaybeTakeActionOnLoad( + } + + void NavigationPredictor::MaybePrefetch() { ++ if (!web_contents()) ++ return; ++ + // If prefetches aren't allowed here, this URL has already + // been prefetched, or the current tab is hidden, + // we shouldn't prefetch again. diff --git a/patches/chromium/cherry-pick-6e8856624cbb.patch b/patches/chromium/cherry-pick-6e8856624cbb.patch new file mode 100644 index 0000000000000..5a89360074372 --- /dev/null +++ b/patches/chromium/cherry-pick-6e8856624cbb.patch @@ -0,0 +1,71 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Koji Ishii +Date: Thu, 11 Mar 2021 17:45:46 +0000 +Subject: Mark additional RootInlineBox dirty when culled inline box is removed + +When a |LayoutInline| is removed, |LineBoxList:: +DirtyLinesFromChangedChild| tries to mark affected +|RootInlineBox| dirty. + +When the |LayoutInline| to be removed is culled, it tries to +find the |RootInlineBox| from its previous siblings, then look +for its previous and next |RootInlineBox|es. + +Occasionally, the next next line of the previous sibling is +wrapped at the |LayoutInline|, and that its |LineBreakObj()| +holds the reference to the |LayoutInline|. This patch marks +such |RootInlineBox| dirty. + +(cherry picked from commit 2dbdabb28d647c8ee20cbe36e3c957e74aff663b) + +Bug: 1186287 +Change-Id: I8ca73ebb4f5e4f13e997662fffd803d6a74ef49a +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2748756 +Auto-Submit: Koji Ishii +Reviewed-by: Ian Kilpatrick +Commit-Queue: Ian Kilpatrick +Cr-Original-Commit-Position: refs/heads/master@{#861724} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2749769 +Commit-Queue: Krishna Govind +Cr-Commit-Position: refs/branch-heads/4389@{#1518} +Cr-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} + +diff --git a/third_party/blink/renderer/core/layout/line/line_box_list.cc b/third_party/blink/renderer/core/layout/line/line_box_list.cc +index 9d9d861f00cb9784041796acb91604d64dab1cb7..ed929849f721fbae25fd8c1106c95e563aca289b 100644 +--- a/third_party/blink/renderer/core/layout/line/line_box_list.cc ++++ b/third_party/blink/renderer/core/layout/line/line_box_list.cc +@@ -359,14 +359,32 @@ void LineBoxList::DirtyLinesFromChangedChild(LineLayoutItem container, + // findNextLineBreak. findNextLineBreak, despite the name, actually returns + // the first LayoutObject after the BR. "Typing + // after pasting line does not appear until after window resize." +- if (RootInlineBox* prev_root_box = box->PrevRootBox()) ++ if (RootInlineBox* prev_root_box = box->PrevRootBox()) { + prev_root_box->MarkDirty(); ++#if DCHECK_IS_ON() ++ for (; prev_root_box; prev_root_box = prev_root_box->PrevRootBox()) { ++ DCHECK(prev_root_box->IsDirty() || ++ prev_root_box->LineBreakObj() != child); ++ } ++#endif ++ } + // If |child| or any of its immediately previous siblings with culled + // lineboxes is the object after a line-break in |box| or the linebox after + // it then that means |child| actually sits on the linebox after |box| (or + // is its line-break object) and so we need to dirty it as well. +- if (RootInlineBox* next_root_box = box->NextRootBox()) ++ if (RootInlineBox* next_root_box = box->NextRootBox()) { + next_root_box->MarkDirty(); ++ ++ next_root_box = next_root_box->NextRootBox(); ++ if (next_root_box && next_root_box->LineBreakObj() == child) ++ next_root_box->MarkDirty(); ++#if DCHECK_IS_ON() ++ for (; next_root_box; next_root_box = next_root_box->NextRootBox()) { ++ DCHECK(next_root_box->IsDirty() || ++ next_root_box->LineBreakObj() != child); ++ } ++#endif ++ } + } + } + diff --git a/patches/chromium/cherry-pick-76cb1cc32baa.patch b/patches/chromium/cherry-pick-76cb1cc32baa.patch new file mode 100644 index 0000000000000..c3011770b1d68 --- /dev/null +++ b/patches/chromium/cherry-pick-76cb1cc32baa.patch @@ -0,0 +1,67 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Sergei Glazunov +Date: Fri, 12 Feb 2021 16:37:12 +0000 +Subject: Use a copy for transferring non detachable buffers + +Currently, |DOMArrayBuffer::Transfer()| makes a copy, but still uses +the original buffer for transferring, thus making it possible to share a +regular ArrayBuffer (not SAB) with multiple threads. + +(cherry picked from commit 0d289da12075592372940a366ad565b9a13d57ce) + +Bug: 1177341 +Change-Id: Idb48deb1698fe555f32531bc04b55dd3e1fb0a06 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2690630 +Reviewed-by: Bill Budge +Reviewed-by: Andreas Haas +Reviewed-by: Daniel Cheng +Commit-Queue: Sergei Glazunov +Commit-Queue: Daniel Cheng +Cr-Original-Commit-Position: refs/heads/master@{#853272} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2691251 +Reviewed-by: Krishna Govind +Commit-Queue: Krishna Govind +Cr-Commit-Position: refs/branch-heads/4389@{#980} +Cr-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} + +diff --git a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc +index 17fcf0f9034d09a53376ebb380c98589d52de8f4..c456d15f2f5084d7592326e151c1a478bc2ac1fc 100644 +--- a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc ++++ b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.cc +@@ -47,6 +47,13 @@ bool DOMArrayBuffer::Transfer(v8::Isolate* isolate, + DOMArrayBuffer::Create(Content()->Data(), ByteLengthAsSizeT()); + } + ++ return to_transfer->TransferDetachable(isolate, result); ++} ++ ++bool DOMArrayBuffer::TransferDetachable(v8::Isolate* isolate, ++ ArrayBufferContents& result) { ++ DCHECK(IsDetachable(isolate)); ++ + if (IsDetached()) { + result.Detach(); + return false; +@@ -62,7 +69,7 @@ bool DOMArrayBuffer::Transfer(v8::Isolate* isolate, + + Vector, 4> buffer_handles; + v8::HandleScope handle_scope(isolate); +- AccumulateArrayBuffersForAllWorlds(isolate, to_transfer, buffer_handles); ++ AccumulateArrayBuffersForAllWorlds(isolate, this, buffer_handles); + + for (const auto& buffer_handle : buffer_handles) + buffer_handle->Detach(); +diff --git a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h +index 00ba385dafcfd476805e39e4c138cdac8f071ef6..e9a85d38d4d46d26a41cf4d394a92d1a7b511c02 100644 +--- a/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h ++++ b/third_party/blink/renderer/core/typed_arrays/dom_array_buffer.h +@@ -78,6 +78,9 @@ class CORE_EXPORT DOMArrayBuffer final : public DOMArrayBufferBase { + + v8::Local Wrap(v8::Isolate*, + v8::Local creation_context) override; ++ ++ private: ++ bool TransferDetachable(v8::Isolate*, ArrayBufferContents& result); + }; + + } // namespace blink diff --git a/patches/chromium/cherry-pick-79440c3a0675.patch b/patches/chromium/cherry-pick-79440c3a0675.patch new file mode 100644 index 0000000000000..f31bc9a56717b --- /dev/null +++ b/patches/chromium/cherry-pick-79440c3a0675.patch @@ -0,0 +1,197 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hongchan Choi +Date: Tue, 8 Dec 2020 01:42:34 +0000 +Subject: Fail gracefully if |parameter| object has an invalid array topology + +AudioWorkletProcessor::CopyParamValueMapToObject() assumed the array +topology is always valid (DCHECK), but if the user code actively mangles +the parameter descriptor getter the array to return invalid content +this assumption becomes invalid. + +The fix is to fail gracefully when the object type or the array content +is not correct. + +Bug: 1151069 +Test: The repro case does not reproduce any more after 1 hour run. +Change-Id: I3f8decd3721e9b00ba201e2f76751e4bc941e05d +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2569788 +Reviewed-by: Raymond Toy +Commit-Queue: Hongchan Choi +Cr-Commit-Position: refs/heads/master@{#834503} + +diff --git a/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc b/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc +index 9ac9bcc91967e2228516e6cdc7065b83316c25e7..3518f24ea248862ea9d96ab8e6bbfecfe001fb33 100644 +--- a/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc ++++ b/third_party/blink/renderer/modules/webaudio/audio_worklet_processor.cc +@@ -107,8 +107,13 @@ bool AudioWorkletProcessor::Process( + DCHECK(!params_.IsEmpty()); + DCHECK(params_.NewLocal(isolate)->IsObject()); + +- // Copies |param_value_map| to the internal |params_| object; +- CopyParamValueMapToObject(isolate, context, *param_value_map, params_); ++ // Copies |param_value_map| to the internal |params_| object. This operation ++ // could fail if the getter of parameterDescriptors is overridden by user code ++ // and returns incompatible data. (crbug.com/1151069) ++ if (!CopyParamValueMapToObject(isolate, context, *param_value_map, params_)) { ++ SetErrorState(AudioWorkletProcessorErrorState::kProcessError); ++ return false; ++ } + + // Performs the user-defined AudioWorkletProcessor.process() function. + v8::TryCatch try_catch(isolate); +@@ -444,6 +449,7 @@ bool AudioWorkletProcessor::CloneParamValueMapToObject( + break; + } + } ++ DCHECK(array_size == 1 || array_size == param_float_array->size()); + + v8::Local array_buffer = + v8::ArrayBuffer::New(isolate, array_size * sizeof(float)); +@@ -482,18 +488,23 @@ bool AudioWorkletProcessor::CopyParamValueMapToObject( + + v8::Local param_array_value; + if (!params_object->Get(context, V8String(isolate, param_name)) +- .ToLocal(¶m_array_value)) { ++ .ToLocal(¶m_array_value) || ++ !param_array_value->IsFloat32Array()) { + return false; + } +- DCHECK(param_array_value->IsFloat32Array()); ++ + v8::Local float32_array = + param_array_value.As(); ++ size_t array_length = float32_array->Length(); ++ ++ // The |float32_array| is neither 1 nor 128 frames, or the array buffer is ++ // trasnferred/detached, do not proceed. ++ if ((array_length != 1 && array_length != param_array->size()) || ++ float32_array->Buffer()->ByteLength() == 0) ++ return false; + +- // The Float32Array is either 1 or 128 frames, but it always should be +- // less than equal to the size of the given AudioFloatArray. +- DCHECK_LE(float32_array->Length(), param_array->size()); + memcpy(float32_array->Buffer()->GetContents().Data(), param_array->Data(), +- float32_array->Length() * sizeof(float)); ++ array_length * sizeof(float)); + } + + return true; +diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-param-getter-overridden.https.html b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-param-getter-overridden.https.html +new file mode 100644 +index 0000000000000000000000000000000000000000..e3fb6e533df8ccaa88564f836239e0c9e9c1d5e4 +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/audioworkletprocessor-param-getter-overridden.https.html +@@ -0,0 +1,59 @@ ++ ++ ++ ++ ++ Test if AudioWorkletProcessor with invalid parameters array getter ++ ++ ++ ++ ++ ++ ++ ++ ++ +diff --git a/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/processors/invalid-param-array-processor.js b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/processors/invalid-param-array-processor.js +new file mode 100644 +index 0000000000000000000000000000000000000000..e4a5dc39ba16b282e254eba84e8038ae59a6471d +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/webaudio/the-audio-api/the-audioworklet-interface/processors/invalid-param-array-processor.js +@@ -0,0 +1,47 @@ ++/** ++ * @class InvalidParamArrayProcessor ++ * @extends AudioWorkletProcessor ++ * ++ * This processor intentionally returns an array with an invalid size when the ++ * processor's getter is queried. ++ */ ++let singleton = undefined; ++let secondFetch = false; ++let useDescriptor = false; ++let processCounter = 0; ++ ++class InvalidParamArrayProcessor extends AudioWorkletProcessor { ++ static get parameterDescriptors() { ++ if (useDescriptor) ++ return [{name: 'invalidParam'}]; ++ useDescriptor = true; ++ return []; ++ } ++ ++ constructor() { ++ super(); ++ if (singleton === undefined) ++ singleton = this; ++ return singleton; ++ } ++ ++ process(inputs, outputs, parameters) { ++ const output = outputs[0]; ++ for (let channel = 0; channel < output.length; ++channel) ++ output[channel].fill(1); ++ return false; ++ } ++} ++ ++// This overridden getter is invoked under the hood before process() gets ++// called. After this gets called, process() method above will be invalidated, ++// and mark the worklet node non-functional. (i.e. in an error state) ++Object.defineProperty(Object.prototype, 'invalidParam', {'get': () => { ++ if (secondFetch) ++ return new Float32Array(256); ++ secondFetch = true; ++ return new Float32Array(128); ++}}); ++ ++registerProcessor('invalid-param-array-1', InvalidParamArrayProcessor); ++registerProcessor('invalid-param-array-2', InvalidParamArrayProcessor); diff --git a/patches/chromium/cherry-pick-7dd3b1c86795.patch b/patches/chromium/cherry-pick-7dd3b1c86795.patch new file mode 100644 index 0000000000000..ff5e93109ae42 --- /dev/null +++ b/patches/chromium/cherry-pick-7dd3b1c86795.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Wez +Date: Thu, 15 Apr 2021 18:24:27 +0000 +Subject: Handle window deletion during HandleDisplayChange. + +In principle there is no reason why the HWNDMessageHandler shouldn't be +deleted by a HandleDisplayChange() call out to the delegate, e.g. if the +change results in a change in window layout. + +(cherry picked from commit 299155e5e37a77670b7969771e09e9a16b1f5612) + +Bug: 1192552 +Change-Id: I9fca35ff32e7037c6492f4cee7069e272059b920 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2804382 +Auto-Submit: Wez +Commit-Queue: Scott Violet +Reviewed-by: Scott Violet +Cr-Original-Commit-Position: refs/heads/master@{#869603} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2826321 +Cr-Commit-Position: refs/branch-heads/4430@{#1291} +Cr-Branched-From: e5ce7dc4f7518237b3d9bb93cccca35d25216cbe-refs/heads/master@{#857950} + +diff --git a/ui/views/win/hwnd_message_handler.cc b/ui/views/win/hwnd_message_handler.cc +index f6e9f97e47b44fa47e8e6c237be371754a5204bd..39518ac65d091bc52a26cf84b89fa350459e874b 100644 +--- a/ui/views/win/hwnd_message_handler.cc ++++ b/ui/views/win/hwnd_message_handler.cc +@@ -1670,7 +1670,13 @@ void HWNDMessageHandler::OnDisplayChange(UINT bits_per_pixel, + const gfx::Size& screen_size) { + TRACE_EVENT0("ui", "HWNDMessageHandler::OnDisplayChange"); + ++ base::WeakPtr ref(msg_handler_weak_factory_.GetWeakPtr()); + delegate_->HandleDisplayChange(); ++ ++ // HandleDisplayChange() may result in |this| being deleted. ++ if (!ref) ++ return; ++ + // Force a WM_NCCALCSIZE to occur to ensure that we handle auto hide + // taskbars correctly. + SendFrameChanged(); diff --git a/patches/chromium/cherry-pick-7e0e52df283c.patch b/patches/chromium/cherry-pick-7e0e52df283c.patch new file mode 100644 index 0000000000000..e4b53046f7409 --- /dev/null +++ b/patches/chromium/cherry-pick-7e0e52df283c.patch @@ -0,0 +1,54 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Marijn Kruisselbrink +Date: Tue, 23 Feb 2021 22:17:17 +0000 +Subject: Don't store BlobStorageLimits as a reference in transport strategy. + +Rather than storing a const reference to something of unclear lifetime, +just make a copy. We could just copy the specific limits we need, but +there shouldn't be many TransportStrategy instances alive at the same +time anyway, so the cost of duplicating shouldn't be too high. + +(cherry picked from commit 9a10c68a381d78088532953aa8e0de0a5ff47316) + +(cherry picked from commit 7b51cb5e4e2c6cf9dcf19bd9d7599735efd48110) + +Bug: 1180871 +Change-Id: Ie1e31728b18f02c5d35df0ac0f285eb8f70cb268 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2713912 +Reviewed-by: Olivier Yiptong +Reviewed-by: Darwin Huang +Reviewed-by: Victor Costan +Commit-Queue: Marijn Kruisselbrink +Cr-Original-Original-Commit-Position: refs/heads/master@{#856503} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2713891 +Reviewed-by: Krishna Govind +Reviewed-by: Srinivas Sista +Cr-Original-Commit-Position: refs/branch-heads/4425@{#2} +Cr-Original-Branched-From: 4a7d24ec28ccb96c5a1cfd7b4b40b17070f2c396-refs/heads/master@{#856252} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2713958 +Commit-Queue: Krishna Govind +Cr-Commit-Position: refs/branch-heads/4389@{#1327} +Cr-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} + +diff --git a/storage/browser/blob/blob_transport_strategy.cc b/storage/browser/blob/blob_transport_strategy.cc +index eb66014792246254db1c825b61bd18e3190970de..5ae41639f149f8ff3ead9633d3aa834ac05a8e5f 100644 +--- a/storage/browser/blob/blob_transport_strategy.cc ++++ b/storage/browser/blob/blob_transport_strategy.cc +@@ -239,7 +239,7 @@ class DataPipeTransportStrategy : public BlobTransportStrategy { + } + } + +- const BlobStorageLimits& limits_; ++ const BlobStorageLimits limits_; + base::circular_deque requests_; + + mojo::ScopedDataPipeConsumerHandle consumer_handle_; +@@ -336,7 +336,7 @@ class FileTransportStrategy : public BlobTransportStrategy { + std::move(result_callback_).Run(BlobStatus::DONE); + } + +- const BlobStorageLimits& limits_; ++ const BlobStorageLimits limits_; + + // State used to assign bytes elements to individual files. + // The index of the first file that still has available space. diff --git a/patches/chromium/cherry-pick-861253f1de98.patch b/patches/chromium/cherry-pick-861253f1de98.patch new file mode 100644 index 0000000000000..24fa43af3c2e8 --- /dev/null +++ b/patches/chromium/cherry-pick-861253f1de98.patch @@ -0,0 +1,78 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Adam Rice +Date: Sat, 5 Dec 2020 03:19:09 +0000 +Subject: Update the restricted port list + +Add ports 69, 137, 161, 554, 1719, 1720, 1723, 6566 to the restricted +ports list to match Firefox. See +https://hg.mozilla.org/mozilla-central/file/tip/netwerk/base/nsIOService.cpp. + +Leave out port 10080 for now as it seems likely to cause compatibility +problems. + +BUG=1148309 + +Change-Id: I16f9a61068dbe35334fd5ca2bf55b3ab0287df74 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2562905 +Reviewed-by: David Schinazi +Commit-Queue: Adam Rice +Cr-Commit-Position: refs/heads/master@{#832169} +(cherry picked from commit c36c5078c41bd1a9e2455d747d69ac1703d977d3) + +TBR=ricea@chromium.org + +Change-Id: I9cc989e46ac63b3c656eb2eaed825add9b8346f8 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2574877 +Reviewed-by: Adam Rice +Commit-Queue: Adam Rice +Cr-Commit-Position: refs/branch-heads/4324@{#611} +Cr-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} + +diff --git a/net/base/port_util.cc b/net/base/port_util.cc +index 72af86dd6acf2db65a14b2f1bb2e3a241a1d043d..12bfd9a36a0e8e9331420c597faf670477100e47 100644 +--- a/net/base/port_util.cc ++++ b/net/base/port_util.cc +@@ -37,6 +37,7 @@ const int kRestrictedPorts[] = { + 42, // name + 43, // nicname + 53, // domain ++ 69, // tftp + 77, // priv-rjs + 79, // finger + 87, // ttylink +@@ -54,8 +55,10 @@ const int kRestrictedPorts[] = { + 119, // nntp + 123, // NTP + 135, // loc-srv /epmap ++ 137, // netbios + 139, // netbios + 143, // imap2 ++ 161, // snmp + 179, // BGP + 389, // ldap + 427, // SLP (Also used by Apple Filing Protocol) +@@ -70,6 +73,7 @@ const int kRestrictedPorts[] = { + 532, // netnews + 540, // uucp + 548, // AFP (Apple Filing Protocol) ++ 554, // rtsp + 556, // remotefs + 563, // nntp+ssl + 587, // smtp (rfc6409) +@@ -77,12 +81,16 @@ const int kRestrictedPorts[] = { + 636, // ldap+ssl + 993, // ldap+ssl + 995, // pop3+ssl ++ 1719, // h323gatestat ++ 1720, // h323hostcall ++ 1723, // pptp + 2049, // nfs + 3659, // apple-sasl / PasswordServer + 4045, // lockd + 5060, // sip + 5061, // sips + 6000, // X11 ++ 6566, // sane-port + 6665, // Alternate IRC [Apple addition] + 6666, // Alternate IRC [Apple addition] + 6667, // Standard IRC [Apple addition] diff --git a/patches/chromium/cherry-pick-8629cd7f8af3.patch b/patches/chromium/cherry-pick-8629cd7f8af3.patch new file mode 100644 index 0000000000000..a5c9713e6f19e --- /dev/null +++ b/patches/chromium/cherry-pick-8629cd7f8af3.patch @@ -0,0 +1,291 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Raymond Toy +Date: Tue, 29 Sep 2020 17:56:16 +0000 +Subject: Add mutex for allowing the graph to be pulled + +Basically add a mutex to protect access to +allow_pulling_audio_graph_. The main thread waits until it has the +lock before setting this. This prevents the main thread from deleting +things while the audio thread is pulling on the graph. + +A try lock is used so as not to block the audio thread if it can't get +lock. + +This is applied to both real time and offline contexts which required +moving the original real-time-only implementation to +audio_destination_node so we can use the same methods for the offline +context. + +Tested the repro case from 1125635, and the issue does not reproduce. +We're assuming this will fix 1115901, but I've not been able to +reproduce that locally. + +(cherry picked from commit 6785a406fd652f7a2f40933620ef4c979643c56b) + +Bug: 1125635, 1115901 +Change-Id: I1037d8c44225c6dcc8fe906c29a5a86740a15e1d +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2410743 +Reviewed-by: Hongchan Choi +Commit-Queue: Raymond Toy +Cr-Original-Commit-Position: refs/heads/master@{#809393} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2436324 +Reviewed-by: Raymond Toy +Cr-Commit-Position: refs/branch-heads/4240@{#1083} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/modules/webaudio/audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/audio_destination_node.cc +index c85f6d2e26f2e34b8633c29754028afbd8f2cbb9..9b1a5ec495f8f3614e8e77fdeaf9eaa159b3e3ae 100644 +--- a/third_party/blink/renderer/modules/webaudio/audio_destination_node.cc ++++ b/third_party/blink/renderer/modules/webaudio/audio_destination_node.cc +@@ -30,7 +30,8 @@ + namespace blink { + + AudioDestinationHandler::AudioDestinationHandler(AudioNode& node) +- : AudioHandler(kNodeTypeDestination, node, 0) { ++ : AudioHandler(kNodeTypeDestination, node, 0), ++ allow_pulling_audio_graph_(false) { + AddInput(); + } + +diff --git a/third_party/blink/renderer/modules/webaudio/audio_destination_node.h b/third_party/blink/renderer/modules/webaudio/audio_destination_node.h +index c35722c9296b6c98832444799124334d382225df..2d1a60c5bb5f3b6c75998b7ecbce1c94f3d68a2d 100644 +--- a/third_party/blink/renderer/modules/webaudio/audio_destination_node.h ++++ b/third_party/blink/renderer/modules/webaudio/audio_destination_node.h +@@ -71,6 +71,53 @@ class AudioDestinationHandler : public AudioHandler { + return is_execution_context_destroyed_; + } + ++ // Should only be called from ++ // RealtimeAudioDestinationHandler::StartPlatformDestination for a realtime ++ // context or OfflineAudioDestinationHandler::StartRendering for an offline ++ // context---basically wherever the context has started rendering. ++ // TODO(crbug.com/1128121): Consider removing this if possible ++ void EnablePullingAudioGraph() { ++ MutexLocker lock(allow_pulling_audio_graph_mutex_); ++ allow_pulling_audio_graph_.store(true, std::memory_order_release); ++ } ++ ++ // Should only be called from ++ // RealtimeAudioDestinationHandler::StopPlatformDestination for a realtime ++ // context or from OfflineAudioDestinationHandler::Uninitialize for an offline ++ // context---basically whenever the context is being stopped. ++ // TODO(crbug.com/1128121): Consider removing this if possible ++ void DisablePullingAudioGraph() { ++ MutexLocker lock(allow_pulling_audio_graph_mutex_); ++ allow_pulling_audio_graph_.store(false, std::memory_order_release); ++ } ++ ++ // TODO(crbug.com/1128121): Consider removing this if possible ++ bool IsPullingAudioGraphAllowed() const { ++ return allow_pulling_audio_graph_.load(std::memory_order_acquire); ++ } ++ ++ // If true, the audio graph will be pulled to get new data. Otherwise, the ++ // graph is not pulled, even if the audio thread is still running and ++ // requesting data. ++ // ++ // For an AudioContext, this MUST be modified only in ++ // RealtimeAudioDestinationHandler::StartPlatformDestination (via ++ // AudioDestinationHandler::EnablePullingAudioGraph) or ++ // RealtimeAudioDestinationHandler::StopPlatformDestination (via ++ // AudioDestinationHandler::DisablePullingAudioGraph), including destroying ++ // the the realtime context. ++ // ++ // For an OfflineAudioContext, this MUST be modified only when the the context ++ // is started or it has stopped rendering, including destroying the offline ++ // context. ++ ++ // TODO(crbug.com/1128121): Consider removing this if possible ++ std::atomic_bool allow_pulling_audio_graph_; ++ ++ // Protects allow_pulling_audio_graph_ from race conditions. Must use try ++ // lock on the audio thread. ++ mutable Mutex allow_pulling_audio_graph_mutex_; ++ + protected: + void AdvanceCurrentSampleFrame(size_t number_of_frames) { + current_sample_frame_.fetch_add(number_of_frames, +diff --git a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc +index 73494d0842432b466625a99f0114256fb6ab2025..4160bf9099a7ec06cfc075cb2d27d3392ebf3d12 100644 +--- a/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc ++++ b/third_party/blink/renderer/modules/webaudio/offline_audio_destination_node.cc +@@ -90,6 +90,7 @@ void OfflineAudioDestinationHandler::Uninitialize() { + if (!IsInitialized()) + return; + ++ DisablePullingAudioGraph(); + AudioHandler::Uninitialize(); + } + +@@ -109,6 +110,7 @@ void OfflineAudioDestinationHandler::StartRendering() { + // Rendering was not started. Starting now. + if (!is_rendering_started_) { + is_rendering_started_ = true; ++ EnablePullingAudioGraph(); + PostCrossThreadTask( + *render_thread_task_runner_, FROM_HERE, + CrossThreadBindOnce( +@@ -290,20 +292,34 @@ bool OfflineAudioDestinationHandler::RenderIfNotSuspended( + + DCHECK_GE(NumberOfInputs(), 1u); + +- // This will cause the node(s) connected to us to process, which in turn will +- // pull on their input(s), all the way backwards through the rendering graph. +- scoped_refptr rendered_bus = +- Input(0).Pull(destination_bus, number_of_frames); ++ { ++ // The entire block that relies on |IsPullingAudioGraphAllowed| needs ++ // locking to prevent pulling audio graph being disallowed (i.e. a ++ // destruction started) in the middle of processing ++ MutexTryLocker try_locker(allow_pulling_audio_graph_mutex_); ++ ++ if (IsPullingAudioGraphAllowed() && try_locker.Locked()) { ++ // This will cause the node(s) connected to us to process, which in turn ++ // will pull on their input(s), all the way backwards through the ++ // rendering graph. ++ scoped_refptr rendered_bus = ++ Input(0).Pull(destination_bus, number_of_frames); ++ ++ if (!rendered_bus) { ++ destination_bus->Zero(); ++ } else if (rendered_bus != destination_bus) { ++ // in-place processing was not possible - so copy ++ destination_bus->CopyFrom(*rendered_bus); ++ } + +- if (!rendered_bus) { +- destination_bus->Zero(); +- } else if (rendered_bus != destination_bus) { +- // in-place processing was not possible - so copy +- destination_bus->CopyFrom(*rendered_bus); ++ } else { ++ // Not allowed to pull on the graph or couldn't get the lock. ++ destination_bus->Zero(); ++ } + } + +- // Process nodes which need a little extra help because they are not connected +- // to anything, but still need to process. ++ // Process nodes which need a little extra help because they are not ++ // connected to anything, but still need to process. + Context()->GetDeferredTaskHandler().ProcessAutomaticPullNodes( + number_of_frames); + +diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc +index 4d3666b37d0fab3aa76541bad7f8f6b40c5e1f3d..71507ed2ccec3e3fd90602e21995ee477dff5748 100644 +--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc ++++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.cc +@@ -53,7 +53,6 @@ RealtimeAudioDestinationHandler::RealtimeAudioDestinationHandler( + : AudioDestinationHandler(node), + latency_hint_(latency_hint), + sample_rate_(sample_rate), +- allow_pulling_audio_graph_(false), + task_runner_(Context()->GetExecutionContext()->GetTaskRunner( + TaskType::kInternalMediaRealTime)) { + // Node-specific default channel count and mixing rules. +@@ -200,28 +199,38 @@ void RealtimeAudioDestinationHandler::Render( + // Only pull on the audio graph if we have not stopped the destination. It + // takes time for the destination to stop, but we want to stop pulling before + // the destination has actually stopped. +- if (IsPullingAudioGraphAllowed()) { +- // Renders the graph by pulling all the inputs to this node. This will in +- // turn pull on their inputs, all the way backwards through the graph. +- scoped_refptr rendered_bus = +- Input(0).Pull(destination_bus, number_of_frames); +- +- DCHECK(rendered_bus); +- if (!rendered_bus) { +- // AudioNodeInput might be in the middle of destruction. Then the internal +- // summing bus will return as nullptr. Then zero out the output. ++ { ++ // The entire block that relies on |IsPullingAudioGraphAllowed| needs ++ // locking to prevent pulling audio graph being disallowed (i.e. a ++ // destruction started) in the middle of processing. ++ MutexTryLocker try_locker(allow_pulling_audio_graph_mutex_); ++ ++ if (IsPullingAudioGraphAllowed() && try_locker.Locked()) { ++ // Renders the graph by pulling all the inputs to this node. This will in ++ // turn pull on their inputs, all the way backwards through the graph. ++ scoped_refptr rendered_bus = ++ Input(0).Pull(destination_bus, number_of_frames); ++ ++ DCHECK(rendered_bus); ++ if (!rendered_bus) { ++ // AudioNodeInput might be in the middle of destruction. Then the ++ // internal summing bus will return as nullptr. Then zero out the ++ // output. ++ destination_bus->Zero(); ++ } else if (rendered_bus != destination_bus) { ++ // In-place processing was not possible. Copy the rendered result to the ++ // given |destination_bus| buffer. ++ destination_bus->CopyFrom(*rendered_bus); ++ } ++ } else { ++ // Not allowed to pull on the graph or couldn't get the lock. + destination_bus->Zero(); +- } else if (rendered_bus != destination_bus) { +- // In-place processing was not possible. Copy the rendered result to the +- // given |destination_bus| buffer. +- destination_bus->CopyFrom(*rendered_bus); + } +- } else { +- destination_bus->Zero(); + } + +- // Processes "automatic" nodes that are not connected to anything. This can +- // be done after copying because it does not affect the rendered result. ++ // Processes "automatic" nodes that are not connected to anything. This ++ // can be done after copying because it does not affect the rendered ++ // result. + context->GetDeferredTaskHandler().ProcessAutomaticPullNodes(number_of_frames); + + context->HandlePostRenderTasks(); +diff --git a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h +index a0a3baefe5aaa7ed77c08273188d2a39da8a3cd1..638d364afa38fd3a1d5d34b6cd32acc7ab4de58e 100644 +--- a/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h ++++ b/third_party/blink/renderer/modules/webaudio/realtime_audio_destination_node.h +@@ -84,10 +84,6 @@ class RealtimeAudioDestinationHandler final + // Returns a given frames-per-buffer size from audio infra. + int GetFramesPerBuffer() const; + +- bool IsPullingAudioGraphAllowed() const { +- return allow_pulling_audio_graph_.load(std::memory_order_acquire); +- } +- + // Sets the detect silence flag for the platform destination. + void SetDetectSilence(bool detect_silence); + +@@ -105,16 +101,6 @@ class RealtimeAudioDestinationHandler final + // Render() method. + void SetDetectSilenceIfNecessary(bool has_automatic_pull_nodes); + +- // Should only be called from StartPlatformDestination. +- void EnablePullingAudioGraph() { +- allow_pulling_audio_graph_.store(true, std::memory_order_release); +- } +- +- // Should only be called from StopPlatformDestination. +- void DisablePullingAudioGraph() { +- allow_pulling_audio_graph_.store(false, std::memory_order_release); +- } +- + const WebAudioLatencyHint latency_hint_; + + // Holds the audio device thread that runs the real time audio context. +@@ -122,16 +108,6 @@ class RealtimeAudioDestinationHandler final + + base::Optional sample_rate_; + +- // If true, the audio graph will be pulled to get new data. Otherwise, the +- // graph is not pulled, even if the audio thread is still running and +- // requesting data. +- // +- // Must be modified only in StartPlatformDestination (via +- // EnablePullingAudioGraph) or StopPlatformDestination (via +- // DisablePullingAudioGraph) . This is modified only by the main threda and +- // the audio thread only reads this. +- std::atomic_bool allow_pulling_audio_graph_; +- + scoped_refptr task_runner_; + + // Represents the current condition of silence detection. By default, the diff --git a/patches/chromium/cherry-pick-88f263f401b4.patch b/patches/chromium/cherry-pick-88f263f401b4.patch new file mode 100644 index 0000000000000..15a5aa459e517 --- /dev/null +++ b/patches/chromium/cherry-pick-88f263f401b4.patch @@ -0,0 +1,315 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Will Cassella +Date: Thu, 24 Sep 2020 22:54:27 +0000 +Subject: Add callback to WebMediaPlayerImpl to notify when a redirect occurs + +(cherry picked from commit 8b18bcfd9aa8096c4551ec34c0225b6017cd211e) + +Bug: 1128657 +Change-Id: I9548e1f3bfe5693871a56e23c3373f45147e52e0 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2422091 +Reviewed-by: Dale Curtis +Reviewed-by: Guido Urdaneta +Commit-Queue: Guido Urdaneta +Cr-Original-Commit-Position: refs/heads/master@{#809217} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2425223 +Commit-Queue: Will Cassella +Cr-Commit-Position: refs/branch-heads/4240@{#993} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/media/blink/multibuffer_data_source.cc b/media/blink/multibuffer_data_source.cc +index 0f6ae1fb8b4ff9f24ce3f407b7359e016fc6de5f..4ec77cf8a6b56002e601fb89eb0dee059f3ab31b 100644 +--- a/media/blink/multibuffer_data_source.cc ++++ b/media/blink/multibuffer_data_source.cc +@@ -145,7 +145,7 @@ MultibufferDataSource::MultibufferDataSource( + DCHECK(url_data_.get()); + url_data_->Use(); + url_data_->OnRedirect( +- base::BindOnce(&MultibufferDataSource::OnRedirect, weak_ptr_)); ++ base::BindOnce(&MultibufferDataSource::OnRedirected, weak_ptr_)); + } + + MultibufferDataSource::~MultibufferDataSource() { +@@ -219,10 +219,10 @@ void MultibufferDataSource::Initialize(InitializeCB init_cb) { + } + } + +-void MultibufferDataSource::OnRedirect( +- const scoped_refptr& destination) { +- if (!destination) { +- // A failure occured. ++void MultibufferDataSource::OnRedirected( ++ const scoped_refptr& new_destination) { ++ if (!new_destination) { ++ // A failure occurred. + failed_ = true; + if (init_cb_) { + render_task_runner_->PostTask( +@@ -235,38 +235,39 @@ void MultibufferDataSource::OnRedirect( + StopLoader(); + return; + } +- if (url_data_->url().GetOrigin() != destination->url().GetOrigin()) { ++ if (url_data_->url().GetOrigin() != new_destination->url().GetOrigin()) { + single_origin_ = false; + } + SetReader(nullptr); +- url_data_ = std::move(destination); ++ url_data_ = std::move(new_destination); + +- if (url_data_) { +- url_data_->OnRedirect( +- base::BindOnce(&MultibufferDataSource::OnRedirect, weak_ptr_)); ++ url_data_->OnRedirect( ++ base::BindOnce(&MultibufferDataSource::OnRedirected, weak_ptr_)); + +- if (init_cb_) { +- CreateResourceLoader(0, kPositionNotSpecified); +- if (reader_->Available()) { +- render_task_runner_->PostTask( +- FROM_HERE, +- base::BindOnce(&MultibufferDataSource::StartCallback, weak_ptr_)); +- } else { +- reader_->Wait(1, base::BindOnce(&MultibufferDataSource::StartCallback, +- weak_ptr_)); +- } +- } else if (read_op_) { +- CreateResourceLoader(read_op_->position(), kPositionNotSpecified); +- if (reader_->Available()) { +- render_task_runner_->PostTask( +- FROM_HERE, +- base::BindOnce(&MultibufferDataSource::ReadTask, weak_ptr_)); +- } else { +- reader_->Wait( +- 1, base::BindOnce(&MultibufferDataSource::ReadTask, weak_ptr_)); +- } ++ if (init_cb_) { ++ CreateResourceLoader(0, kPositionNotSpecified); ++ if (reader_->Available()) { ++ render_task_runner_->PostTask( ++ FROM_HERE, ++ base::BindOnce(&MultibufferDataSource::StartCallback, weak_ptr_)); ++ } else { ++ reader_->Wait( ++ 1, base::BindOnce(&MultibufferDataSource::StartCallback, weak_ptr_)); ++ } ++ } else if (read_op_) { ++ CreateResourceLoader(read_op_->position(), kPositionNotSpecified); ++ if (reader_->Available()) { ++ render_task_runner_->PostTask( ++ FROM_HERE, ++ base::BindOnce(&MultibufferDataSource::ReadTask, weak_ptr_)); ++ } else { ++ reader_->Wait( ++ 1, base::BindOnce(&MultibufferDataSource::ReadTask, weak_ptr_)); + } + } ++ ++ if (redirect_cb_) ++ redirect_cb_.Run(); + } + + void MultibufferDataSource::SetPreload(Preload preload) { +@@ -287,6 +288,10 @@ bool MultibufferDataSource::IsCorsCrossOrigin() const { + return url_data_->is_cors_cross_origin(); + } + ++void MultibufferDataSource::OnRedirect(RedirectCB callback) { ++ redirect_cb_ = std::move(callback); ++} ++ + bool MultibufferDataSource::HasAccessControl() const { + return url_data_->has_access_control(); + } +diff --git a/media/blink/multibuffer_data_source.h b/media/blink/multibuffer_data_source.h +index 3da5a7bba5e7cc0f54998a81649f4dd9d78aa7be..da316964ff1543086865d7c85597f381ca8a3296 100644 +--- a/media/blink/multibuffer_data_source.h ++++ b/media/blink/multibuffer_data_source.h +@@ -38,6 +38,7 @@ class MultiBufferReader; + class MEDIA_BLINK_EXPORT MultibufferDataSource : public DataSource { + public: + using DownloadingCB = base::RepeatingCallback; ++ using RedirectCB = base::RepeatingCallback; + + // Used to specify video preload states. They are "hints" to the browser about + // how aggressively the browser should load and buffer data. +@@ -82,6 +83,9 @@ class MEDIA_BLINK_EXPORT MultibufferDataSource : public DataSource { + // This must be called after the response arrives. + bool IsCorsCrossOrigin() const; + ++ // Provides a callback to be run when the underlying url is redirected. ++ void OnRedirect(RedirectCB callback); ++ + // Returns true if the response includes an Access-Control-Allow-Origin + // header (that is not "null"). + bool HasAccessControl() const; +@@ -128,7 +132,7 @@ class MEDIA_BLINK_EXPORT MultibufferDataSource : public DataSource { + bool cancel_on_defer_for_testing() const { return cancel_on_defer_; } + + protected: +- void OnRedirect(const scoped_refptr& destination); ++ void OnRedirected(const scoped_refptr& new_destination); + + // A factory method to create a BufferedResourceLoader based on the read + // parameters. +@@ -243,6 +247,9 @@ class MEDIA_BLINK_EXPORT MultibufferDataSource : public DataSource { + // go between different origins. + bool single_origin_; + ++ // Callback used when a redirect occurs. ++ RedirectCB redirect_cb_; ++ + // Close the connection when we have enough data. + bool cancel_on_defer_; + +diff --git a/media/blink/webmediaplayer_impl.cc b/media/blink/webmediaplayer_impl.cc +index 8c769e69efad504f15a0fc79a2efc0cecf91f0a8..b234635ad78f6a22c91ecf854e7ae2f468315cc4 100644 +--- a/media/blink/webmediaplayer_impl.cc ++++ b/media/blink/webmediaplayer_impl.cc +@@ -799,6 +799,8 @@ void WebMediaPlayerImpl::DoLoad(LoadType load_type, + base::BindRepeating(&WebMediaPlayerImpl::NotifyDownloading, + weak_this_)); + data_source_.reset(mb_data_source_); ++ mb_data_source_->OnRedirect(base::BindRepeating( ++ &WebMediaPlayerImpl::OnDataSourceRedirected, weak_this_)); + mb_data_source_->SetPreload(preload_); + mb_data_source_->SetIsClientAudioElement(client_->IsAudioElement()); + mb_data_source_->Initialize( +@@ -2642,6 +2644,16 @@ void WebMediaPlayerImpl::DataSourceInitialized(bool success) { + StartPipeline(); + } + ++void WebMediaPlayerImpl::OnDataSourceRedirected() { ++ DVLOG(1) << __func__; ++ DCHECK(main_task_runner_->BelongsToCurrentThread()); ++ DCHECK(mb_data_source_); ++ ++ if (WouldTaintOrigin()) { ++ audio_source_provider_->TaintOrigin(); ++ } ++} ++ + void WebMediaPlayerImpl::NotifyDownloading(bool is_downloading) { + DVLOG(1) << __func__ << "(" << is_downloading << ")"; + if (!is_downloading && network_state_ == WebMediaPlayer::kNetworkStateLoading) +diff --git a/media/blink/webmediaplayer_impl.h b/media/blink/webmediaplayer_impl.h +index f4e48c46f6217b218f39b96d40220f040bdb556b..d2ee61bae7e1b30d0eac4a14437e00bbdc8a6e68 100644 +--- a/media/blink/webmediaplayer_impl.h ++++ b/media/blink/webmediaplayer_impl.h +@@ -382,6 +382,9 @@ class MEDIA_BLINK_EXPORT WebMediaPlayerImpl + // Called after asynchronous initialization of a data source completed. + void DataSourceInitialized(bool success); + ++ // Called if the |MultiBufferDataSource| is redirected. ++ void OnDataSourceRedirected(); ++ + // Called when the data source is downloading or paused. + void NotifyDownloading(bool is_downloading); + +diff --git a/third_party/blink/public/platform/webaudiosourceprovider_impl.h b/third_party/blink/public/platform/webaudiosourceprovider_impl.h +index 5383074dcbffbb77a102de6fb551628ff5b964b8..bbf2a0b3d28282587625d6a5cd0103f6a5378b54 100644 +--- a/third_party/blink/public/platform/webaudiosourceprovider_impl.h ++++ b/third_party/blink/public/platform/webaudiosourceprovider_impl.h +@@ -75,6 +75,7 @@ class BLINK_PLATFORM_EXPORT WebAudioSourceProviderImpl + bool CurrentThreadIsRenderingThread() override; + void SwitchOutputDevice(const std::string& device_id, + media::OutputDeviceStatusCB callback) override; ++ void TaintOrigin(); + + // These methods allow a client to get a copy of the rendered audio. + void SetCopyAudioCallback(CopyAudioCB callback); +diff --git a/third_party/blink/renderer/modules/mediacapturefromelement/html_audio_element_capturer_source_unittest.cc b/third_party/blink/renderer/modules/mediacapturefromelement/html_audio_element_capturer_source_unittest.cc +index 48092cc09148886bf74e544f640fb373bb374102..2ffaca2d399daba2d59f6dd218ce84e5b9c6d880 100644 +--- a/third_party/blink/renderer/modules/mediacapturefromelement/html_audio_element_capturer_source_unittest.cc ++++ b/third_party/blink/renderer/modules/mediacapturefromelement/html_audio_element_capturer_source_unittest.cc +@@ -102,7 +102,7 @@ class HTMLAudioElementCapturerSourceTest : public testing::Test { + media_stream_component_ = MakeGarbageCollected( + media_stream_source_->Id(), media_stream_source_); + +- // |media_stream_source_| takes wnership of ++ // |media_stream_source_| takes ownership of + // HtmlAudioElementCapturerSource. + auto capture_source = std::make_unique( + audio_source_, blink::scheduler::GetSingleThreadTaskRunnerForTesting()); +@@ -183,4 +183,36 @@ TEST_F(HTMLAudioElementCapturerSourceTest, + track()->RemoveSink(&sink); + } + ++TEST_F(HTMLAudioElementCapturerSourceTest, TaintedPlayerDeliversMutedAudio) { ++ testing::InSequence s; ++ ++ base::RunLoop run_loop; ++ base::OnceClosure quit_closure = run_loop.QuitClosure(); ++ ++ MockMediaStreamAudioSink sink; ++ track()->AddSink(&sink); ++ EXPECT_CALL(sink, OnSetFormat(testing::_)).Times(1); ++ EXPECT_CALL( ++ sink, ++ OnData(testing::AllOf( ++ testing::Property(&media::AudioBus::channels, ++ kNumChannelsForTest), ++ testing::Property(&media::AudioBus::frames, ++ kAudioTrackSamplesPerBuffer), ++ testing::Property(&media::AudioBus::AreFramesZero, true)), ++ testing::_)) ++ .Times(1) ++ .WillOnce([&](const auto&, auto) { std::move(quit_closure).Run(); }); ++ ++ audio_source_->TaintOrigin(); ++ ++ std::unique_ptr bus = ++ media::AudioBus::Create(kNumChannelsForTest, kAudioTrackSamplesPerBuffer); ++ InjectAudio(bus.get()); ++ run_loop.Run(); ++ ++ track()->Stop(); ++ track()->RemoveSink(&sink); ++} ++ + } // namespace blink +diff --git a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc +index a1e014ceb2ebc1496dd5712f2ed863a56e455af6..c54e1768886a7d2d922f13596a9fb3b456ba6611 100644 +--- a/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc ++++ b/third_party/blink/renderer/platform/media/webaudiosourceprovider_impl.cc +@@ -81,6 +81,10 @@ class WebAudioSourceProviderImpl::TeeFilter + const int num_rendered_frames = renderer_->Render( + delay, delay_timestamp, prior_frames_skipped, audio_bus); + ++ // Zero out frames after rendering ++ if (origin_tainted_.IsSet()) ++ audio_bus->Zero(); ++ + // Avoid taking the copy lock for the vast majority of cases. + if (copy_required_) { + base::AutoLock auto_lock(copy_lock_); +@@ -114,11 +118,18 @@ class WebAudioSourceProviderImpl::TeeFilter + copy_audio_bus_callback_ = std::move(callback); + } + ++ void TaintOrigin() { origin_tainted_.Set(); } ++ + private: + AudioRendererSink::RenderCallback* renderer_ = nullptr; + int channels_ = 0; + int sample_rate_ = 0; + ++ // Indicates whether the audio source is tainted, and output should be muted. ++ // This can happen if the media element source is a cross-origin source which ++ // the page is not allowed to access due to CORS restrictions. ++ base::AtomicFlag origin_tainted_; ++ + // The vast majority of the time we're operating in passthrough mode. So only + // acquire a lock to read |copy_audio_bus_callback_| when necessary. + std::atomic copy_required_; +@@ -318,6 +329,10 @@ void WebAudioSourceProviderImpl::SwitchOutputDevice( + sink_->SwitchOutputDevice(device_id, std::move(callback)); + } + ++void WebAudioSourceProviderImpl::TaintOrigin() { ++ tee_filter_->TaintOrigin(); ++} ++ + void WebAudioSourceProviderImpl::SetCopyAudioCallback(CopyAudioCB callback) { + DCHECK(!callback.is_null()); + tee_filter_->SetCopyAudioCallback(std::move(callback)); diff --git a/patches/chromium/cherry-pick-8c3eb9d1c409.patch b/patches/chromium/cherry-pick-8c3eb9d1c409.patch new file mode 100644 index 0000000000000..e401c6f81f04d --- /dev/null +++ b/patches/chromium/cherry-pick-8c3eb9d1c409.patch @@ -0,0 +1,143 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Scott Violet +Date: Wed, 31 Mar 2021 13:28:05 +0000 +Subject: x11/ozone: fix two edge cases + +WindowTreeHost::OnHostMovedInPixels() may trigger a nested message +loop (tab dragging), which when the stack unravels means this may +be deleted. This adds an early out if this happens. + +X11WholeScreenMoveLoop has a similar issue, in so far as notifying +the delegate may delete this. + +BUG=1185482 +TEST=WindowTreeHostPlatform.DeleteHostFromOnHostMovedInPixels + +(cherry picked from commit 5e3a738b1204941aab9f15c0eb3d06e20fefd96e) + +(cherry picked from commit 8ad84a8e7882275fb32f938fd0adc04d1a2a5773) + +Change-Id: Ieca1c90b3e4358da50b332abe2941fdbb50c5c25 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2743555 +Reviewed-by: Thomas Anderson +Commit-Queue: Scott Violet +Cr-Original-Original-Commit-Position: refs/heads/master@{#860852} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2779886 +Cr-Original-Commit-Position: refs/branch-heads/4389@{#1583} +Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2794391 +Reviewed-by: Scott Violet +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1583} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/ui/aura/window_tree_host_platform.cc b/ui/aura/window_tree_host_platform.cc +index 917b45bf3f8554c63886af30b1483cd97670299c..c9053e7c7e7abb1cdbaf114028579a0484b1d4a9 100644 +--- a/ui/aura/window_tree_host_platform.cc ++++ b/ui/aura/window_tree_host_platform.cc +@@ -214,13 +214,21 @@ void WindowTreeHostPlatform::OnBoundsChanged(const gfx::Rect& new_bounds) { + float current_scale = compositor()->device_scale_factor(); + float new_scale = ui::GetScaleFactorForNativeView(window()); + gfx::Rect old_bounds = bounds_in_pixels_; ++ auto weak_ref = GetWeakPtr(); + bounds_in_pixels_ = new_bounds; +- if (bounds_in_pixels_.origin() != old_bounds.origin()) ++ if (bounds_in_pixels_.origin() != old_bounds.origin()) { + OnHostMovedInPixels(bounds_in_pixels_.origin()); ++ // Changing the bounds may destroy this. ++ if (!weak_ref) ++ return; ++ } + if (bounds_in_pixels_.size() != old_bounds.size() || + current_scale != new_scale) { + pending_size_ = gfx::Size(); + OnHostResizedInPixels(bounds_in_pixels_.size()); ++ // Changing the size may destroy this. ++ if (!weak_ref) ++ return; + } + DCHECK_GT(on_bounds_changed_recursion_depth_, 0); + if (--on_bounds_changed_recursion_depth_ == 0) { +diff --git a/ui/aura/window_tree_host_platform_unittest.cc b/ui/aura/window_tree_host_platform_unittest.cc +index eda14e2f0cdf5015f366aa70ea68ae2a2c2b431e..4de039c88af8a6f0ac03df2f772cfea2dfe3514f 100644 +--- a/ui/aura/window_tree_host_platform_unittest.cc ++++ b/ui/aura/window_tree_host_platform_unittest.cc +@@ -34,7 +34,7 @@ class TestWindowTreeHost : public WindowTreeHostPlatform { + // OnHostWill/DidProcessBoundsChange. Additionally, this triggers a bounds + // change from within OnHostResized(). Such a scenario happens in production + // code. +-class TestWindowTreeHostObserver : public aura::WindowTreeHostObserver { ++class TestWindowTreeHostObserver : public WindowTreeHostObserver { + public: + TestWindowTreeHostObserver(WindowTreeHostPlatform* host, + ui::PlatformWindow* platform_window) +@@ -51,7 +51,7 @@ class TestWindowTreeHostObserver : public aura::WindowTreeHostObserver { + return on_host_will_process_bounds_change_count_; + } + +- // aura::WindowTreeHostObserver: ++ // WindowTreeHostObserver: + void OnHostResized(WindowTreeHost* host) override { + if (!should_change_bounds_in_on_resized_) + return; +@@ -92,5 +92,41 @@ TEST_F(WindowTreeHostPlatformTest, HostWillProcessBoundsChangeRecursion) { + EXPECT_EQ(1, observer.on_host_will_process_bounds_change_count()); + } + ++// Deletes WindowTreeHostPlatform from OnHostMovedInPixels(). ++class DeleteHostWindowTreeHostObserver : public WindowTreeHostObserver { ++ public: ++ explicit DeleteHostWindowTreeHostObserver( ++ std::unique_ptr host) ++ : host_(std::move(host)) { ++ host_->AddObserver(this); ++ } ++ ~DeleteHostWindowTreeHostObserver() override = default; ++ ++ TestWindowTreeHost* host() { return host_.get(); } ++ ++ // WindowTreeHostObserver: ++ void OnHostMovedInPixels(WindowTreeHost* host, ++ const gfx::Point& new_origin_in_pixels) override { ++ host_->RemoveObserver(this); ++ host_.reset(); ++ } ++ ++ private: ++ std::unique_ptr host_; ++ ++ DISALLOW_COPY_AND_ASSIGN(DeleteHostWindowTreeHostObserver); ++}; ++ ++// Verifies WindowTreeHostPlatform can be safely deleted when calling ++// OnHostMovedInPixels(). ++// Regression test for https://crbug.com/1185482 ++TEST_F(WindowTreeHostPlatformTest, DeleteHostFromOnHostMovedInPixels) { ++ std::unique_ptr host = ++ std::make_unique(); ++ DeleteHostWindowTreeHostObserver observer(std::move(host)); ++ observer.host()->SetBoundsInPixels(gfx::Rect(1, 2, 3, 4)); ++ EXPECT_EQ(nullptr, observer.host()); ++} ++ + } // namespace + } // namespace aura +diff --git a/ui/base/x/x11_whole_screen_move_loop.cc b/ui/base/x/x11_whole_screen_move_loop.cc +index 39f4d0c12aa1feb1702c3f4aa4ba0f62be591197..2bbb1f035b0db8de218ce629cc16aab91cf8519b 100644 +--- a/ui/base/x/x11_whole_screen_move_loop.cc ++++ b/ui/base/x/x11_whole_screen_move_loop.cc +@@ -62,9 +62,13 @@ X11WholeScreenMoveLoop::~X11WholeScreenMoveLoop() = default; + void X11WholeScreenMoveLoop::DispatchMouseMovement() { + if (!last_motion_in_screen_) + return; ++ auto weak_ref = weak_factory_.GetWeakPtr(); + delegate_->OnMouseMovement(last_motion_in_screen_->root_location(), + last_motion_in_screen_->flags(), + last_motion_in_screen_->time_stamp()); ++ // The delegate may delete this during dispatch. ++ if (!weak_ref) ++ return; + last_motion_in_screen_.reset(); + } + diff --git a/patches/chromium/cherry-pick-8f24f935c903.patch b/patches/chromium/cherry-pick-8f24f935c903.patch new file mode 100644 index 0000000000000..9a9d329b17320 --- /dev/null +++ b/patches/chromium/cherry-pick-8f24f935c903.patch @@ -0,0 +1,40 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Adrian Taylor +Date: Thu, 5 Nov 2020 08:50:39 +0000 +Subject: Prevent overflow of drag image on Windows. + +(cherry picked from commit 236b1a349111fc945c741f85e1b1e2e04d9c42ff) + +(cherry picked from commit 5f61af8f3af5efd0d915a51da6df822678d959b9) + +Bug: 1144489 +Change-Id: I130adffc1c69073295537aaff3ce7054260064fc +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2513345 +Reviewed-by: Krishna Govind +Cr-Original-Original-Commit-Position: refs/branch-heads/4310@{#4} +Cr-Original-Original-Branched-From: 3e31ebb7467fdc4295f123385825b8c95ef13332-refs/heads/master@{#822916} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2513349 +Reviewed-by: Adrian Taylor +Commit-Queue: Krishna Govind +Cr-Original-Commit-Position: refs/branch-heads/4240@{#1373} +Cr-Original-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2517728 +Reviewed-by: Achuith Bhandarkar +Commit-Queue: Victor-Gabriel Savu +Cr-Commit-Position: refs/branch-heads/4240_112@{#18} +Cr-Branched-From: 427c00d3874b6abcf4c4c2719768835fc3ef26d6-refs/branch-heads/4240@{#1291} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/ui/base/dragdrop/os_exchange_data_provider_win.cc b/ui/base/dragdrop/os_exchange_data_provider_win.cc +index b51a0161fdf028ac2a4f079b813d6622602f980c..41f163a3424e89a9370faebb77ac0058da9b01f6 100644 +--- a/ui/base/dragdrop/os_exchange_data_provider_win.cc ++++ b/ui/base/dragdrop/os_exchange_data_provider_win.cc +@@ -714,7 +714,7 @@ void OSExchangeDataProviderWin::SetDragImage( + int width = unpremul_bitmap.width(); + int height = unpremul_bitmap.height(); + size_t rowbytes = unpremul_bitmap.rowBytes(); +- DCHECK_EQ(rowbytes, static_cast(width) * 4u); ++ CHECK_EQ(rowbytes, static_cast(width) * 4u); + + void* bits; + HBITMAP hbitmap; diff --git a/patches/chromium/cherry-pick-9afec1792cfc.patch b/patches/chromium/cherry-pick-9afec1792cfc.patch new file mode 100644 index 0000000000000..1f24f9d49fddf --- /dev/null +++ b/patches/chromium/cherry-pick-9afec1792cfc.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Xiaocheng Hu +Date: Wed, 3 Feb 2021 14:56:15 +0000 +Subject: Move FontPreloadManager to Oilpan + +Currently, TimerBase cannot correctly track the lifetime of objects +embedded in GC-ed objects, like FontPreloadManager which is embedded in +Document. This causes some memory safety issues. + +This patch moves FontPreloadManager to Oilpan to avoid the issue. + +(cherry picked from commit d31bbf2910ee44e4a206d926ddae6827d16a2754) + +(cherry picked from commit cdab130053839ffae4a02d00812c1c3a0ecf01bd) + +Bug: 1154965 +Change-Id: I490b416abc6a997034baaa7994cd3a50bca7e055 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2611755 +Reviewed-by: Kentaro Hara +Commit-Queue: Xiaocheng Hu +Cr-Original-Original-Commit-Position: refs/heads/master@{#841039} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2653946 +Commit-Queue: Kentaro Hara +Auto-Submit: Xiaocheng Hu +Cr-Original-Commit-Position: refs/branch-heads/4324@{#2045} +Cr-Original-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2665939 +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1536} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/core/dom/document.cc b/third_party/blink/renderer/core/dom/document.cc +index 2fea6ad5cb19e42dbb51a6432ce119b935c23d8f..f84f12cfa8ea70051bcdc82e723816d4ae266b5d 100644 +--- a/third_party/blink/renderer/core/dom/document.cc ++++ b/third_party/blink/renderer/core/dom/document.cc +@@ -791,7 +791,7 @@ Document::Document(const DocumentInit& initializer, + MakeGarbageCollected(this)), + permission_service_(GetExecutionContext()), + has_trust_tokens_answerer_(GetExecutionContext()), +- font_preload_manager_(*this) { ++ font_preload_manager_(MakeGarbageCollected(*this)) { + GetOriginTrialContext()->BindExecutionContext(GetExecutionContext()); + + if (dom_window_) { +@@ -6892,7 +6892,7 @@ void Document::BeginLifecycleUpdatesIfRenderingReady() { + return; + if (!HaveRenderBlockingResourcesLoaded()) + return; +- font_preload_manager_.WillBeginRendering(); ++ font_preload_manager_->WillBeginRendering(); + View()->BeginLifecycleUpdates(); + } + +@@ -7660,7 +7660,7 @@ bool Document::HaveScriptBlockingStylesheetsLoaded() const { + bool Document::HaveRenderBlockingResourcesLoaded() const { + return HaveImportsLoaded() && + style_engine_->HaveRenderBlockingStylesheetsLoaded() && +- !font_preload_manager_.HasPendingRenderBlockingFonts(); ++ !font_preload_manager_->HasPendingRenderBlockingFonts(); + } + + Locale& Document::GetCachedLocale(const AtomicString& locale) { +@@ -8503,7 +8503,7 @@ void Document::ClearUseCounterForTesting(mojom::WebFeature feature) { + } + + void Document::FontPreloadingFinishedOrTimedOut() { +- DCHECK(!font_preload_manager_.HasPendingRenderBlockingFonts()); ++ DCHECK(!font_preload_manager_->HasPendingRenderBlockingFonts()); + if (IsA(this) && body()) { + // For HTML, we resume only when we're past the body tag, so that we should + // have something to paint now. +diff --git a/third_party/blink/renderer/core/dom/document.h b/third_party/blink/renderer/core/dom/document.h +index b197ad6e06c6ba1b609607a78aa798f1cc397a3e..b092be4c8bd55567c14d81c9bee343da432747a0 100644 +--- a/third_party/blink/renderer/core/dom/document.h ++++ b/third_party/blink/renderer/core/dom/document.h +@@ -1647,7 +1647,7 @@ class CORE_EXPORT Document : public ContainerNode, + unsigned new_length); + void NotifyChangeChildren(const ContainerNode& container); + +- FontPreloadManager& GetFontPreloadManager() { return font_preload_manager_; } ++ FontPreloadManager& GetFontPreloadManager() { return *font_preload_manager_; } + void FontPreloadingFinishedOrTimedOut(); + + void IncrementAsyncScriptCount() { async_script_count_++; } +@@ -2230,7 +2230,7 @@ class CORE_EXPORT Document : public ContainerNode, + HeapHashSet> + pending_has_trust_tokens_resolvers_; + +- FontPreloadManager font_preload_manager_; ++ Member font_preload_manager_; + + int async_script_count_ = 0; + bool first_paint_recorded_ = false; +diff --git a/third_party/blink/renderer/core/loader/font_preload_manager.h b/third_party/blink/renderer/core/loader/font_preload_manager.h +index 3e98fc931a205b7e20b119e7af5a5bbac1eee2b5..631f0f95e7e70d3e1bf73c78f725fdf756a721eb 100644 +--- a/third_party/blink/renderer/core/loader/font_preload_manager.h ++++ b/third_party/blink/renderer/core/loader/font_preload_manager.h +@@ -20,9 +20,8 @@ class ResourceFinishObserver; + // API) and notifies the relevant document, so that it can manage the first + // rendering timing to work with preloaded fonts. + // Design doc: https://bit.ly/36E8UKB +-class CORE_EXPORT FontPreloadManager final { +- DISALLOW_NEW(); +- ++class CORE_EXPORT FontPreloadManager final ++ : public GarbageCollected { + public: + explicit FontPreloadManager(Document&); + ~FontPreloadManager() = default; diff --git a/patches/chromium/cherry-pick-9ec949913373.patch b/patches/chromium/cherry-pick-9ec949913373.patch new file mode 100644 index 0000000000000..4bca05c8f8559 --- /dev/null +++ b/patches/chromium/cherry-pick-9ec949913373.patch @@ -0,0 +1,195 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ken Rockot +Date: Tue, 12 Jan 2021 18:36:22 +0000 +Subject: Mojo: Fix UAF on NodeChannel + +TBR=rockot@google.com + +(cherry picked from commit 9c8a98b3983dd1c7828ceae2fc8a5a2e9bad1f68) + +(cherry picked from commit 06fe641d21bda1b5869a46e59b13873762ce1324) + +Fixed: 1162198 +Change-Id: Ief850903a7e6ba3d7c5c0129704d1f80aa3467ce +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2612085 +Commit-Queue: Ken Rockot +Reviewed-by: Ken Rockot +Reviewed-by: Robert Sesek +Cr-Original-Original-Commit-Position: refs/heads/master@{#840942} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2621996 +Cr-Original-Commit-Position: refs/branch-heads/4324@{#1637} +Cr-Original-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2623929 +Reviewed-by: Achuith Bhandarkar +Commit-Queue: Victor-Gabriel Savu +Cr-Commit-Position: refs/branch-heads/4240@{#1517} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/mojo/core/BUILD.gn b/mojo/core/BUILD.gn +index c42daf24d62b3a1c8479466d1bc4efe753099136..a7f574455ac02b656a451257cdc11a2968dd5b68 100644 +--- a/mojo/core/BUILD.gn ++++ b/mojo/core/BUILD.gn +@@ -309,6 +309,7 @@ source_set("test_sources") { + "data_pipe_unittest.cc", + "invitation_unittest.cc", + "multiprocess_message_pipe_unittest.cc", ++ "node_controller_unittest.cc", + "platform_wrapper_unittest.cc", + ] + } +diff --git a/mojo/core/core.cc b/mojo/core/core.cc +index 6d06ab1fdee75c8fc8670ff7a35923aa90c4bf8c..5eed4e1be13df656897c60378dde8ec024e81a59 100644 +--- a/mojo/core/core.cc ++++ b/mojo/core/core.cc +@@ -137,7 +137,7 @@ void Core::SetIOTaskRunner( + NodeController* Core::GetNodeController() { + base::AutoLock lock(node_controller_lock_); + if (!node_controller_) +- node_controller_.reset(new NodeController(this)); ++ node_controller_ = std::make_unique(); + return node_controller_.get(); + } + +diff --git a/mojo/core/node_controller.cc b/mojo/core/node_controller.cc +index ff93f0c3a6b2bf4a340e027ee803b9006152f031..c7646fa4dc5c5062e8a8a620e55839301af51bed 100644 +--- a/mojo/core/node_controller.cc ++++ b/mojo/core/node_controller.cc +@@ -21,7 +21,6 @@ + #include "mojo/core/broker.h" + #include "mojo/core/broker_host.h" + #include "mojo/core/configuration.h" +-#include "mojo/core/core.h" + #include "mojo/core/request_context.h" + #include "mojo/core/user_message_impl.h" + #include "mojo/public/cpp/platform/named_platform_channel.h" +@@ -146,10 +145,8 @@ class ThreadDestructionObserver + + NodeController::~NodeController() = default; + +-NodeController::NodeController(Core* core) +- : core_(core), +- name_(GetRandomNodeName()), +- node_(new ports::Node(name_, this)) { ++NodeController::NodeController() ++ : name_(GetRandomNodeName()), node_(new ports::Node(name_, this)) { + DVLOG(1) << "Initializing node " << name_; + } + +@@ -587,10 +584,17 @@ void NodeController::AddPeer(const ports::NodeName& name, + } + } + +-void NodeController::DropPeer(const ports::NodeName& name, ++void NodeController::DropPeer(const ports::NodeName& node_name, + NodeChannel* channel) { + DCHECK(io_task_runner_->RunsTasksInCurrentSequence()); + ++ // NOTE: Either the `peers_` erasure or the `pending_invitations_` erasure ++ // below, if executed, may drop the last reference to the named NodeChannel ++ // and thus result in its deletion. The passed `node_name` argument may be ++ // owned by that same NodeChannel, so we make a copy of it here to avoid ++ // potentially unsafe references further below. ++ ports::NodeName name = node_name; ++ + { + base::AutoLock lock(peers_lock_); + auto it = peers_.find(name); +diff --git a/mojo/core/node_controller.h b/mojo/core/node_controller.h +index 9494de5b809e0216fde750960cd5f86c2f14b46e..6f9f0680062db679587ab1846c768846f0fbcb13 100644 +--- a/mojo/core/node_controller.h ++++ b/mojo/core/node_controller.h +@@ -52,11 +52,10 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate, + }; + + // |core| owns and out-lives us. +- explicit NodeController(Core* core); ++ NodeController(); + ~NodeController() override; + + const ports::NodeName& name() const { return name_; } +- Core* core() const { return core_; } + ports::Node* node() const { return node_.get(); } + scoped_refptr io_task_runner() const { + return io_task_runner_; +@@ -135,6 +134,8 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate, + base::span data); + static void DeserializeMessageAsEventForFuzzer(Channel::MessagePtr message); + ++ scoped_refptr GetBrokerChannel(); ++ + private: + friend Core; + +@@ -176,7 +177,6 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate, + + scoped_refptr GetPeerChannel(const ports::NodeName& name); + scoped_refptr GetInviterChannel(); +- scoped_refptr GetBrokerChannel(); + + void AddPeer(const ports::NodeName& name, + scoped_refptr channel, +@@ -254,7 +254,6 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate, + void ForceDisconnectProcessForTestingOnIOThread(base::ProcessId process_id); + + // These are safe to access from any thread as long as the Node is alive. +- Core* const core_; + const ports::NodeName name_; + const std::unique_ptr node_; + scoped_refptr io_task_runner_; +@@ -319,7 +318,7 @@ class MOJO_SYSTEM_IMPL_EXPORT NodeController : public ports::NodeDelegate, + AtomicFlag shutdown_callback_flag_; + + // All other fields below must only be accessed on the I/O thread, i.e., the +- // thread on which core_->io_task_runner() runs tasks. ++ // thread on which `io_task_runner_` runs tasks. + + // Channels to invitees during handshake. + NodeMap pending_invitations_; +diff --git a/mojo/core/node_controller_unittest.cc b/mojo/core/node_controller_unittest.cc +new file mode 100644 +index 0000000000000000000000000000000000000000..316e162376323f27f83868d6b943c40e395511bb +--- /dev/null ++++ b/mojo/core/node_controller_unittest.cc +@@ -0,0 +1,42 @@ ++// Copyright 2013 The Chromium Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++#include "base/logging.h" ++#include "mojo/core/core.h" ++#include "mojo/core/test/mojo_test_base.h" ++#include "mojo/public/c/system/types.h" ++#include "testing/gtest/include/gtest/gtest.h" ++ ++namespace mojo { ++namespace core { ++namespace { ++ ++using NodeControllerTest = test::MojoTestBase; ++ ++TEST_F(NodeControllerTest, AcceptInvitationFailure) { ++ // Spawn a child process that will send an invalid AcceptInvitation ++ // NodeChannel message. This is a regression test for ++ // https://crbug.com/1162198. ++ RunTestClient("SendInvalidAcceptInvitation", ++ [&](MojoHandle h) { WriteMessage(h, "hi"); }); ++} ++ ++DEFINE_TEST_CLIENT_TEST_WITH_PIPE(SendInvalidAcceptInvitation, ++ NodeControllerTest, ++ h) { ++ // A little communication to synchronize against Mojo bringup. By the time ++ // this read completes, we must have an internal NodeController with the ++ // parent test process connected as its broker. ++ EXPECT_EQ("hi", ReadMessage(h)); ++ ++ // Send an unexpected AcceptInvitation message to the parent process. This ++ // exercises the regression code path in the parent process. ++ NodeController* controller = Core::Get()->GetNodeController(); ++ scoped_refptr channel = controller->GetBrokerChannel(); ++ channel->AcceptInvitation(ports::NodeName{0, 0}, ports::NodeName{0, 0}); ++} ++ ++} // namespace ++} // namespace core ++} // namespace mojo diff --git a/patches/chromium/cherry-pick-a4faa754a9ef.patch b/patches/chromium/cherry-pick-a4faa754a9ef.patch new file mode 100644 index 0000000000000..137e600d3d2d0 --- /dev/null +++ b/patches/chromium/cherry-pick-a4faa754a9ef.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Harald Alvestrand +Date: Tue, 2 Mar 2021 17:33:49 +0000 +Subject: Fix GetP2PSocketManager ownership + +Let it return a mojo::SharedRemote<> instead of a raw pointer - this is +a decoration around a shared_refptr. + +(cherry picked from commit 82cdc0781ceb4c22ef5903cf3115bea518a5523b) + +(cherry picked from commit 6ed1c0c425e03172c77ba0f1465fe3ade79f2b2a) + +Bug: chromium:1172054 +Change-Id: I49bd22a0dc949bf869744d2ad25c1afcaea7fdbc +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2692532 +Reviewed-by: Guido Urdaneta +Commit-Queue: Harald Alvestrand +Cr-Original-Original-Commit-Position: refs/heads/master@{#854050} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2709590 +Reviewed-by: Harald Alvestrand +Cr-Original-Commit-Position: refs/branch-heads/4389@{#1280} +Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2726713 +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1555} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/platform/p2p/socket_dispatcher.cc b/third_party/blink/renderer/platform/p2p/socket_dispatcher.cc +index 29884a255f24556f4dc8b41b22304938a4f0d775..72ec477f57871b460adf83ea9e1a4bd217d5eebe 100644 +--- a/third_party/blink/renderer/platform/p2p/socket_dispatcher.cc ++++ b/third_party/blink/renderer/platform/p2p/socket_dispatcher.cc +@@ -36,7 +36,7 @@ void P2PSocketDispatcher::RemoveNetworkListObserver( + network_list_observers_->RemoveObserver(network_list_observer); + } + +-network::mojom::blink::P2PSocketManager* ++mojo::SharedRemote + P2PSocketDispatcher::GetP2PSocketManager() { + base::AutoLock lock(p2p_socket_manager_lock_); + if (!p2p_socket_manager_) { +@@ -56,7 +56,7 @@ P2PSocketDispatcher::GetP2PSocketManager() { + *main_task_runner_.get(), FROM_HERE, + CrossThreadBindOnce(&P2PSocketDispatcher::RequestInterfaceIfNecessary, + scoped_refptr(this))); +- return p2p_socket_manager_.get(); ++ return p2p_socket_manager_; + } + + void P2PSocketDispatcher::NetworkListChanged( +diff --git a/third_party/blink/renderer/platform/p2p/socket_dispatcher.h b/third_party/blink/renderer/platform/p2p/socket_dispatcher.h +index c250d2af99d5291f34a7e3bfdb63fcbb70a5bd73..84e9c34c0896db5039d9cf8f51167d69ceec9be2 100644 +--- a/third_party/blink/renderer/platform/p2p/socket_dispatcher.h ++++ b/third_party/blink/renderer/platform/p2p/socket_dispatcher.h +@@ -66,7 +66,8 @@ class PLATFORM_EXPORT P2PSocketDispatcher + void RemoveNetworkListObserver( + blink::NetworkListObserver* network_list_observer) override; + +- network::mojom::blink::P2PSocketManager* GetP2PSocketManager(); ++ mojo::SharedRemote ++ GetP2PSocketManager(); + + private: + friend class base::RefCountedThreadSafe; +@@ -95,7 +96,7 @@ class PLATFORM_EXPORT P2PSocketDispatcher + mojo::PendingReceiver + p2p_socket_manager_receiver_; + mojo::SharedRemote +- p2p_socket_manager_; ++ p2p_socket_manager_ GUARDED_BY(p2p_socket_manager_lock_); + base::Lock p2p_socket_manager_lock_; + + // Cached from last |NetworkListChanged| call. diff --git a/patches/chromium/cherry-pick-b3dc4c4b349d.patch b/patches/chromium/cherry-pick-b3dc4c4b349d.patch new file mode 100644 index 0000000000000..a052c3a25bad1 --- /dev/null +++ b/patches/chromium/cherry-pick-b3dc4c4b349d.patch @@ -0,0 +1,171 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hongchan Choi +Date: Tue, 2 Mar 2021 15:17:19 +0000 +Subject: Introduce AudioBuffers for user access in ScriptProcessorNode + +This CL adds new AudioBuffers for the access from the user code. + +(cherry picked from commit b9e60ddc7606689e508f295077656389380288ba) + +(cherry picked from commit c281886ca9ff22f6e75c8c1967dab9bf18b9942d) + +(cherry picked from commit 33861dcf6d15415b6abf2152440906fafb74a27a) + +Bug: 1177465 +Test: The local ASAN build doesn't reproduce on given POCs. +Change-Id: Id9a3505ddb9ab61b4442385d0b830ef56f65f797 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2718543 +Auto-Submit: Hongchan Choi +Reviewed-by: Raymond Toy +Commit-Queue: Hongchan Choi +Cr-Original-Original-Original-Commit-Position: refs/heads/master@{#857817} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2719239 +Reviewed-by: Krishna Govind +Cr-Original-Original-Commit-Position: refs/branch-heads/4429@{#2} +Cr-Original-Original-Branched-From: 19b974fae7ec51a60e2f1044d81e2e1b32be179b-refs/heads/master@{#857666} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2721252 +Reviewed-by: Adrian Taylor +Commit-Queue: Krishna Govind +Cr-Original-Commit-Position: refs/branch-heads/4324@{#2250} +Cr-Original-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2727696 +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1553} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc +index 6e80b23a32dd1895a0d51d08ee16c8cb2d44fc55..d46e17440a9f3c2de488432bf3efdba5ac01f193 100644 +--- a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc ++++ b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc +@@ -42,6 +42,28 @@ + + namespace blink { + ++namespace { ++ ++bool IsAudioBufferDetached(AudioBuffer* buffer) { ++ bool is_buffer_detached = false; ++ for (unsigned channel = 0; channel < buffer->numberOfChannels(); ++channel) { ++ if (buffer->getChannelData(channel)->buffer()->IsDetached()) { ++ is_buffer_detached = true; ++ break; ++ } ++ } ++ ++ return is_buffer_detached; ++} ++ ++bool BufferTopologyMatches(AudioBuffer* buffer_1, AudioBuffer* buffer_2) { ++ return (buffer_1->numberOfChannels() == buffer_2->numberOfChannels()) && ++ (buffer_1->length() == buffer_2->length()) && ++ (buffer_1->sampleRate() == buffer_2->sampleRate()); ++} ++ ++} // namespace ++ + ScriptProcessorHandler::ScriptProcessorHandler( + AudioNode& node, + float sample_rate, +@@ -335,6 +357,12 @@ ScriptProcessorNode::ScriptProcessorNode(BaseAudioContext& context, + input_buffers_.push_back(input_buffer); + output_buffers_.push_back(output_buffer); + } ++ ++ external_input_buffer_ = AudioBuffer::Create( ++ number_of_input_channels, buffer_size, sample_rate); ++ external_output_buffer_ = AudioBuffer::Create( ++ number_of_output_channels, buffer_size, sample_rate); ++ + SetHandler(ScriptProcessorHandler::Create( + *this, sample_rate, buffer_size, number_of_input_channels, + number_of_output_channels, input_buffers_, output_buffers_)); +@@ -476,11 +504,62 @@ uint32_t ScriptProcessorNode::bufferSize() const { + + void ScriptProcessorNode::DispatchEvent(double playback_time, + uint32_t double_buffer_index) { +- AudioBuffer* input_buffer = input_buffers_.at(double_buffer_index).Get(); +- AudioBuffer* output_buffer = output_buffers_.at(double_buffer_index).Get(); +- DCHECK(output_buffer); ++ DCHECK(IsMainThread()); ++ ++ AudioBuffer* backing_input_buffer = ++ input_buffers_.at(double_buffer_index).Get(); ++ ++ // The backing buffer can be nullptr, when the number of input channels is 0. ++ if (backing_input_buffer) { ++ // Also the author code might have transferred |external_input_buffer| to ++ // other threads or replaced it with a different AudioBuffer object. Then ++ // re-create a new buffer instance. ++ if (IsAudioBufferDetached(external_input_buffer_) || ++ !BufferTopologyMatches(backing_input_buffer, ++ external_input_buffer_)) { ++ external_input_buffer_ = AudioBuffer::Create( ++ backing_input_buffer->numberOfChannels(), ++ backing_input_buffer->length(), ++ backing_input_buffer->sampleRate()); ++ } ++ ++ for (unsigned channel = 0; ++ channel < backing_input_buffer->numberOfChannels(); ++channel) { ++ const float* source = static_cast( ++ backing_input_buffer->getChannelData(channel)->buffer()->Data()); ++ float* destination = static_cast( ++ external_input_buffer_->getChannelData(channel)->buffer()->Data()); ++ memcpy(destination, source, ++ backing_input_buffer->length() * sizeof(float)); ++ } ++ } ++ + AudioNode::DispatchEvent(*AudioProcessingEvent::Create( +- input_buffer, output_buffer, playback_time)); ++ external_input_buffer_, external_output_buffer_, playback_time)); ++ ++ AudioBuffer* backing_output_buffer = ++ output_buffers_.at(double_buffer_index).Get(); ++ ++ if (backing_output_buffer) { ++ if (IsAudioBufferDetached(external_output_buffer_) || ++ !BufferTopologyMatches(backing_output_buffer, ++ external_output_buffer_)) { ++ external_output_buffer_ = AudioBuffer::Create( ++ backing_output_buffer->numberOfChannels(), ++ backing_output_buffer->length(), ++ backing_output_buffer->sampleRate()); ++ } ++ ++ for (unsigned channel = 0; ++ channel < backing_output_buffer->numberOfChannels(); ++channel) { ++ const float* source = static_cast( ++ external_output_buffer_->getChannelData(channel)->buffer()->Data()); ++ float* destination = static_cast( ++ backing_output_buffer->getChannelData(channel)->buffer()->Data()); ++ memcpy(destination, source, ++ backing_output_buffer->length() * sizeof(float)); ++ } ++ } + } + + bool ScriptProcessorNode::HasPendingActivity() const { +@@ -499,6 +578,8 @@ bool ScriptProcessorNode::HasPendingActivity() const { + void ScriptProcessorNode::Trace(Visitor* visitor) const { + visitor->Trace(input_buffers_); + visitor->Trace(output_buffers_); ++ visitor->Trace(external_input_buffer_); ++ visitor->Trace(external_output_buffer_); + AudioNode::Trace(visitor); + } + +diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node.h b/third_party/blink/renderer/modules/webaudio/script_processor_node.h +index a93dbacc70709c063b452be3e5fe4b56fcaf7c4a..ea5ad1c326d6eb202ba6b8eba0ac5b5d0f1f61b1 100644 +--- a/third_party/blink/renderer/modules/webaudio/script_processor_node.h ++++ b/third_party/blink/renderer/modules/webaudio/script_processor_node.h +@@ -169,6 +169,8 @@ class ScriptProcessorNode final + private: + HeapVector> input_buffers_; + HeapVector> output_buffers_; ++ Member external_input_buffer_; ++ Member external_output_buffer_; + }; + + } // namespace blink diff --git a/patches/chromium/cherry-pick-b772b48067c4.patch b/patches/chromium/cherry-pick-b772b48067c4.patch new file mode 100644 index 0000000000000..b6ef28a3c693b --- /dev/null +++ b/patches/chromium/cherry-pick-b772b48067c4.patch @@ -0,0 +1,163 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Christoph Schwering +Date: Thu, 4 Mar 2021 17:21:46 +0000 +Subject: Limit preview and filling only for non-state fields. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +The number of times a value is filled into different fields is limited. +The exception are state fields because websites sometimes have one +state select box for each country and display the relevant select +box once the respective country has been selected. + +This CL simplifies this mechanism and makes it more explicit by +encoding the type-dependent limits in TypeValueFormFillingLimit(). +As a side effect, the limits apply not just to filled fields but also +unfilled fields of the same type. + +(cherry picked from commit 18d3f86206e88156e2eb20c1f691b3b40a779150) + +Bug: 1075734, 1084903 +Change-Id: Icc5e8e082850ed44d9c7fbbc911d03a95033d81f +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2557977 +Commit-Queue: Matthias Körber +Reviewed-by: Matthias Körber +Auto-Submit: Christoph Schwering +Cr-Original-Commit-Position: refs/heads/master@{#830778} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2731409 +Reviewed-by: Achuith Bhandarkar +Commit-Queue: Victor-Gabriel Savu +Cr-Commit-Position: refs/branch-heads/4240@{#1560} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/components/autofill/core/browser/autofill_manager.cc b/components/autofill/core/browser/autofill_manager.cc +index 4e8321b85e329139012a5026b044d2c070750151..ded9c514894eee34a34eb562b08e7484673dfc4a 100644 +--- a/components/autofill/core/browser/autofill_manager.cc ++++ b/components/autofill/core/browser/autofill_manager.cc +@@ -20,6 +20,7 @@ + #include "base/check_op.h" + #include "base/command_line.h" + #include "base/containers/adapters.h" ++#include "base/containers/flat_map.h" + #include "base/feature_list.h" + #include "base/files/file_util.h" + #include "base/guid.h" +@@ -424,9 +425,15 @@ const char* SubmissionSourceToString(SubmissionSource source) { + + // Returns how many fields with type |field_type| may be filled in a form at + // maximum. +-int TypeValueFormFillingLimit(ServerFieldType field_type) { +- return field_type == CREDIT_CARD_NUMBER ? kCreditCardTypeValueFormFillingLimit +- : kTypeValueFormFillingLimit; ++size_t TypeValueFormFillingLimit(ServerFieldType field_type) { ++ switch (field_type) { ++ case CREDIT_CARD_NUMBER: ++ return kCreditCardTypeValueFormFillingLimit; ++ case ADDRESS_HOME_STATE: ++ return kStateTypeValueFormFillingLimit; ++ default: ++ return kTypeValueFormFillingLimit; ++ } + } + + } // namespace +@@ -1757,7 +1764,8 @@ void AutofillManager::FillOrPreviewDataModelForm( + + // Count the number of times the value of a specific type was filled into the + // form. +- std::map type_filling_count; ++ base::flat_map type_filling_count; ++ type_filling_count.reserve(form_structure->field_count()); + + for (size_t i = 0; i < form_structure->field_count(); ++i) { + std::string field_number = base::StringPrintf("Field %zu", i); +@@ -1841,7 +1849,7 @@ void AutofillManager::FillOrPreviewDataModelForm( + + // A field with a specific type is only allowed to be filled a limited + // number of times given by |TypeValueFormFillingLimit(field_type)|. +- if (type_filling_count[field_type] >= ++ if (++type_filling_count[field_type] > + TypeValueFormFillingLimit(field_type)) { + buffer << Tr{} << field_number + << "Skipped: field-type filling-limit reached"; +@@ -1877,10 +1885,6 @@ void AutofillManager::FillOrPreviewDataModelForm( + bool has_value_after = !result.fields[i].value.empty(); + bool is_autofilled_after = result.fields[i].is_autofilled; + +- // If the field was actually filled, increment the filling counter. +- if (is_autofilled_after) +- type_filling_count[field_type]++; +- + buffer << Tr{} << field_number + << base::StringPrintf( + "Fillable - has value: %d->%d; autofilled: %d->%d. %s", +diff --git a/components/autofill/core/browser/autofill_manager_unittest.cc b/components/autofill/core/browser/autofill_manager_unittest.cc +index 79f827e5c8ea473d8c6e9bcef42d58427e278fc7..fd5973da9c997dc4c74489dd112b541f86d9bf57 100644 +--- a/components/autofill/core/browser/autofill_manager_unittest.cc ++++ b/components/autofill/core/browser/autofill_manager_unittest.cc +@@ -2903,6 +2903,14 @@ TEST_F(AutofillManagerTest, OnlyCountFilledSelectionBoxesForTypeFillingLimit) { + form.fields.push_back(field); + } + ++ // Create a selection box for the state that hat the correct entry to be ++ // filled with user data. Note, TN is the official abbreviation for Tennessee. ++ for (int i = 0; i < 20; ++i) { ++ test::CreateTestSelectField("Country", "country", "", {"DE", "FR", "US"}, ++ {"DE", "FR", "US"}, 3, &field); ++ form.fields.push_back(field); ++ } ++ + std::vector forms(1, form); + FormsSeen(forms); + +@@ -2939,17 +2947,18 @@ TEST_F(AutofillManagerTest, OnlyCountFilledSelectionBoxesForTypeFillingLimit) { + response_data.fields[4 + i]); + } + +- // Verify that the next 8 selection boxes are correctly filled again. +- for (int i = 0; i < 8; i++) { ++ // Verify that the remaining selection boxes are correctly filled again ++ // because there's no limit on filling ADDRESS_HOME_STATE fields. ++ for (int i = 0; i < 20; i++) { + ExpectFilledField("State", "state", "TN", "select-one", + response_data.fields[24 + i]); + } + +- // Verify that the last 12 boxes are not filled because the filling limit for +- // the state type is already reached. +- for (int i = 0; i < 12; i++) { +- ExpectFilledField("State", "state", "", "select-one", +- response_data.fields[32 + i]); ++ // Verify that only the first 9 of the remaining selection boxes are ++ // correctly filled due to the limit on filling ADDRESS_HOME_COUNTRY fields. ++ for (int i = 0; i < 20; i++) { ++ ExpectFilledField("Country", "country", i < 9 ? "US" : "", "select-one", ++ response_data.fields[44 + i]); + } + } + +diff --git a/components/autofill/core/common/autofill_constants.h b/components/autofill/core/common/autofill_constants.h +index 84c5b916b0330ab8305db7b1831b42fa42843708..2d6ade5e6bea81ec4383d0345863514e1b551ea8 100644 +--- a/components/autofill/core/common/autofill_constants.h ++++ b/components/autofill/core/common/autofill_constants.h +@@ -68,12 +68,14 @@ const int64_t kAutocompleteRetentionPolicyPeriodInDays = 14 * 31; + + // Limits the number of times the value of a specific type can be filled into a + // form. +-constexpr int kTypeValueFormFillingLimit = 9; +- + // Credit card numbers are sometimes distributed between up to 19 individual +-// fields. Therefore, credit cards need a higher limit compared to +-// |kTypeValueFormFillingLimit|. +-constexpr int kCreditCardTypeValueFormFillingLimit = 19; ++// fields. Therefore, credit cards need a higher limit. ++// State fields are effecectively unlimited because there are sometimes hidden ++// fields select boxes, each with a list of states for one specific countries, ++// which are displayed only upon country selection. ++constexpr size_t kTypeValueFormFillingLimit = 9; ++constexpr size_t kCreditCardTypeValueFormFillingLimit = 19; ++constexpr size_t kStateTypeValueFormFillingLimit = 1000; + + } // namespace autofill + diff --git a/patches/chromium/cherry-pick-bbb64b5c6916.patch b/patches/chromium/cherry-pick-bbb64b5c6916.patch new file mode 100644 index 0000000000000..d9c5f1c48fa99 --- /dev/null +++ b/patches/chromium/cherry-pick-bbb64b5c6916.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Daniel Cheng +Date: Sat, 7 Nov 2020 19:07:34 +0000 +Subject: Prevent UB if a WeakPtr to an already-destroyed object is + dereferenced. +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +If a WeakPtr references an already-destroyed object, operator-> and +operator* end up simply dereferencing nullptr. However, dereferencing +nullptr is undefined behavior and can be optimized in surprising ways +by compilers. To prevent this from happening, add a defence of last +resort and CHECK that the WeakPtr is still valid. + +(cherry picked from commit 0b308a0e37b9d14a335c3b487511b7117c98d74b) + +Bug: 817982 +Change-Id: Ib3a025c18fbd9d5db88770fced2063135086847b +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2463857 +Commit-Queue: Daniel Cheng +Reviewed-by: Wez +Reviewed-by: Jan Wilken Dörrie +Cr-Original-Commit-Position: refs/heads/master@{#816701} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2524521 +Reviewed-by: Adrian Taylor +Reviewed-by: Krishna Govind +Commit-Queue: Krishna Govind +Commit-Queue: Adrian Taylor +Cr-Commit-Position: refs/branch-heads/4240@{#1410} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/base/memory/weak_ptr.h b/base/memory/weak_ptr.h +index 42aa3412c5ed13c8cdf94584e65e2549548ed125..63508a6043dd55572f0683681f01b238751c360f 100644 +--- a/base/memory/weak_ptr.h ++++ b/base/memory/weak_ptr.h +@@ -248,11 +248,11 @@ class WeakPtr : public internal::WeakPtrBase { + } + + T& operator*() const { +- DCHECK(get() != nullptr); ++ CHECK(ref_.IsValid()); + return *get(); + } + T* operator->() const { +- DCHECK(get() != nullptr); ++ CHECK(ref_.IsValid()); + return get(); + } + +diff --git a/base/memory/weak_ptr_unittest.cc b/base/memory/weak_ptr_unittest.cc +index 6b1e9bf3c50d3da276c0d9d9de5322355719a61e..6425a88a36f716c58951f0bd52ef1979f598e895 100644 +--- a/base/memory/weak_ptr_unittest.cc ++++ b/base/memory/weak_ptr_unittest.cc +@@ -798,4 +798,26 @@ TEST(WeakPtrDeathTest, NonOwnerThreadReferencesObjectAfterDeletion) { + ASSERT_DCHECK_DEATH(arrow.target.get()); + } + ++TEST(WeakPtrDeathTest, ArrowOperatorChecksOnBadDereference) { ++ // The default style "fast" does not support multi-threaded tests ++ // (introduces deadlock on Linux). ++ ::testing::FLAGS_gtest_death_test_style = "threadsafe"; ++ ++ auto target = std::make_unique(); ++ WeakPtr weak = target->AsWeakPtr(); ++ target.reset(); ++ EXPECT_CHECK_DEATH(weak->AsWeakPtr()); ++} ++ ++TEST(WeakPtrDeathTest, StarOperatorChecksOnBadDereference) { ++ // The default style "fast" does not support multi-threaded tests ++ // (introduces deadlock on Linux). ++ ::testing::FLAGS_gtest_death_test_style = "threadsafe"; ++ ++ auto target = std::make_unique(); ++ WeakPtr weak = target->AsWeakPtr(); ++ target.reset(); ++ EXPECT_CHECK_DEATH((*weak).AsWeakPtr()); ++} ++ + } // namespace base diff --git a/patches/chromium/cherry-pick-bbc6ab5bb49c.patch b/patches/chromium/cherry-pick-bbc6ab5bb49c.patch new file mode 100644 index 0000000000000..8f84fdec21aaf --- /dev/null +++ b/patches/chromium/cherry-pick-bbc6ab5bb49c.patch @@ -0,0 +1,187 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Adam Rice +Date: Wed, 2 Dec 2020 20:26:52 +0000 +Subject: Add ports 5060 and 5061 to the restricted list + +Some NAT devices examine traffic on port 5060 to look for a valid SIP +message. If they find one, they will forward a port back to the origin +host. A carefully crafted HTTP request can trick these NAT devices into +forwarding an arbitrary port. See https://samy.pl/slipstream for more +details on the attack and sample code. + +Block port 5060 for HTTP. Out of an abundance of caution, and to match +the Fetch standard (https://github.com/whatwg/fetch/pull/1109), also +block port 5061 (SIP over TLS). + +Also reduce the whitespace before protocol description comments. This +was insisted on by clang-format and is not worth fighting. + +BUG=1145680 + +(cherry picked from commit 90d1302aec437166b383eabc08af741bf24f7ea8) + +(cherry picked from commit dbb0452e69a49e803e0e4cbb6921d5ccad338716) + +Change-Id: I3a556fbbb4dc6099caa4418addaf1e89bf254ae3 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2519174 +Reviewed-by: Matt Menke +Commit-Queue: Adam Rice +Cr-Original-Original-Commit-Position: refs/heads/master@{#824254} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2525474 +Reviewed-by: Adam Rice +Cr-Original-Commit-Position: refs/branch-heads/4280@{#1247} +Cr-Original-Branched-From: ea420fb963f9658c9969b6513c56b8f47efa1a2a-refs/heads/master@{#812852} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2560585 +Reviewed-by: Achuith Bhandarkar +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1474} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/net/base/port_util.cc b/net/base/port_util.cc +index a10a4b4038171b317a2a9ae6cd42aeb1166f5552..72af86dd6acf2db65a14b2f1bb2e3a241a1d043d 100644 +--- a/net/base/port_util.cc ++++ b/net/base/port_util.cc +@@ -20,73 +20,75 @@ namespace { + // The general list of blocked ports. Will be blocked unless a specific + // protocol overrides it. (Ex: ftp can use ports 20 and 21) + const int kRestrictedPorts[] = { +- 1, // tcpmux +- 7, // echo +- 9, // discard +- 11, // systat +- 13, // daytime +- 15, // netstat +- 17, // qotd +- 19, // chargen +- 20, // ftp data +- 21, // ftp access +- 22, // ssh +- 23, // telnet +- 25, // smtp +- 37, // time +- 42, // name +- 43, // nicname +- 53, // domain +- 77, // priv-rjs +- 79, // finger +- 87, // ttylink +- 95, // supdup +- 101, // hostriame +- 102, // iso-tsap +- 103, // gppitnp +- 104, // acr-nema +- 109, // pop2 +- 110, // pop3 +- 111, // sunrpc +- 113, // auth +- 115, // sftp +- 117, // uucp-path +- 119, // nntp +- 123, // NTP +- 135, // loc-srv /epmap +- 139, // netbios +- 143, // imap2 +- 179, // BGP +- 389, // ldap +- 427, // SLP (Also used by Apple Filing Protocol) +- 465, // smtp+ssl +- 512, // print / exec +- 513, // login +- 514, // shell +- 515, // printer +- 526, // tempo +- 530, // courier +- 531, // chat +- 532, // netnews +- 540, // uucp +- 548, // AFP (Apple Filing Protocol) +- 556, // remotefs +- 563, // nntp+ssl +- 587, // smtp (rfc6409) +- 601, // syslog-conn (rfc3195) +- 636, // ldap+ssl +- 993, // ldap+ssl +- 995, // pop3+ssl +- 2049, // nfs +- 3659, // apple-sasl / PasswordServer +- 4045, // lockd +- 6000, // X11 +- 6665, // Alternate IRC [Apple addition] +- 6666, // Alternate IRC [Apple addition] +- 6667, // Standard IRC [Apple addition] +- 6668, // Alternate IRC [Apple addition] +- 6669, // Alternate IRC [Apple addition] +- 6697, // IRC + TLS ++ 1, // tcpmux ++ 7, // echo ++ 9, // discard ++ 11, // systat ++ 13, // daytime ++ 15, // netstat ++ 17, // qotd ++ 19, // chargen ++ 20, // ftp data ++ 21, // ftp access ++ 22, // ssh ++ 23, // telnet ++ 25, // smtp ++ 37, // time ++ 42, // name ++ 43, // nicname ++ 53, // domain ++ 77, // priv-rjs ++ 79, // finger ++ 87, // ttylink ++ 95, // supdup ++ 101, // hostriame ++ 102, // iso-tsap ++ 103, // gppitnp ++ 104, // acr-nema ++ 109, // pop2 ++ 110, // pop3 ++ 111, // sunrpc ++ 113, // auth ++ 115, // sftp ++ 117, // uucp-path ++ 119, // nntp ++ 123, // NTP ++ 135, // loc-srv /epmap ++ 139, // netbios ++ 143, // imap2 ++ 179, // BGP ++ 389, // ldap ++ 427, // SLP (Also used by Apple Filing Protocol) ++ 465, // smtp+ssl ++ 512, // print / exec ++ 513, // login ++ 514, // shell ++ 515, // printer ++ 526, // tempo ++ 530, // courier ++ 531, // chat ++ 532, // netnews ++ 540, // uucp ++ 548, // AFP (Apple Filing Protocol) ++ 556, // remotefs ++ 563, // nntp+ssl ++ 587, // smtp (rfc6409) ++ 601, // syslog-conn (rfc3195) ++ 636, // ldap+ssl ++ 993, // ldap+ssl ++ 995, // pop3+ssl ++ 2049, // nfs ++ 3659, // apple-sasl / PasswordServer ++ 4045, // lockd ++ 5060, // sip ++ 5061, // sips ++ 6000, // X11 ++ 6665, // Alternate IRC [Apple addition] ++ 6666, // Alternate IRC [Apple addition] ++ 6667, // Standard IRC [Apple addition] ++ 6668, // Alternate IRC [Apple addition] ++ 6669, // Alternate IRC [Apple addition] ++ 6697, // IRC + TLS + }; + + // FTP overrides the following restricted port. diff --git a/patches/chromium/cherry-pick-c6d6f7aee733.patch b/patches/chromium/cherry-pick-c6d6f7aee733.patch new file mode 100644 index 0000000000000..d6aad978db3e3 --- /dev/null +++ b/patches/chromium/cherry-pick-c6d6f7aee733.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Lukasz Anforowicz +Date: Thu, 4 Mar 2021 17:07:16 +0000 +Subject: M86-LTS: Destroy `url_loader_factories_` before other NetworkContext + fields +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +[M86 Merge]: Fixed conflict in network_context.h. + +(cherry picked from commit f2b091f02593c67fd67db936452f363102b8d035) + +(cherry picked from commit ffeb0731f83f8c4fa72776b658df45f0e6da041c) + +Bug: 1174943 +Change-Id: I7488c7779f51a3f0d82ecad3d65446032c065b26 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2679230 +Commit-Queue: Łukasz Anforowicz +Reviewed-by: Matt Menke +Cr-Original-Original-Commit-Position: refs/heads/master@{#852311} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2692489 +Reviewed-by: Łukasz Anforowicz +Bot-Commit: Rubber Stamper +Cr-Original-Commit-Position: refs/branch-heads/4389@{#986} +Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2731372 +Commit-Queue: Victor-Gabriel Savu +Reviewed-by: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1559} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/services/network/network_context.h b/services/network/network_context.h +index 0d9c4cce09db485b5e750921eed703c75ffdf15a..3a88fd24b421411f086a31db34c07ed0f175d14f 100644 +--- a/services/network/network_context.h ++++ b/services/network/network_context.h +@@ -604,13 +604,6 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext + std::set, base::UniquePtrComparator> + proxy_lookup_requests_; + +- // This must be below |url_request_context_| so that the URLRequestContext +- // outlives all the URLLoaderFactories and URLLoaders that depend on it; +- // for the same reason, it must also be below |network_context_|. +- std::set, +- base::UniquePtrComparator> +- url_loader_factories_; +- + std::set, base::UniquePtrComparator> + quic_transports_; + +@@ -721,6 +714,19 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext + // HttpAuthHandle via |NetworkContext::CreateHttpAuthHandlerFactory|. + net::HttpAuthPreferences http_auth_merged_preferences_; + ++ // CorsURLLoaderFactory assumes that fields owned by the NetworkContext always ++ // live longer than the factory. Therefore we want the factories to be ++ // destroyed before other fields above. In particular: ++ // - This must be below |url_request_context_| so that the URLRequestContext ++ // outlives all the URLLoaderFactories and URLLoaders that depend on it; ++ // for the same reason, it must also be below |network_context_|. ++ // - This must be below |loader_count_per_process_| that is touched by ++ // CorsURLLoaderFactory::DestroyURLLoader (see also ++ // https://crbug.com/1174943). ++ std::set, ++ base::UniquePtrComparator> ++ url_loader_factories_; ++ + base::WeakPtrFactory weak_factory_{this}; + + DISALLOW_COPY_AND_ASSIGN(NetworkContext); diff --git a/patches/chromium/cherry-pick-d74ba931c4b7.patch b/patches/chromium/cherry-pick-d74ba931c4b7.patch new file mode 100644 index 0000000000000..c8d9a2708ad58 --- /dev/null +++ b/patches/chromium/cherry-pick-d74ba931c4b7.patch @@ -0,0 +1,110 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Achuith Bhandarkar +Date: Wed, 13 Jan 2021 21:27:05 +0000 +Subject: content-visibility: Don't adjust position of a locked hittest result + node. + +This patch ensures that if we have a hittest result that has a locked +node, we don't try to recurse into its subtree. This can happen when we +do a PositionWithAffinity check. + +R=chrishtr@chromium.org + +(cherry picked from commit 8483cf6944e38203c3b247163c54cfa105e89c56) + +(cherry picked from commit 3f7b67374a1121b6756ccfd2e4e414987f167489) + +Bug: 1162131 +Change-Id: I357bd7032c6c2b6c9405bf26c49a36bda22d6a0d +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2611453 +Reviewed-by: Chris Harrelson +Commit-Queue: vmpstr +Cr-Original-Original-Commit-Position: refs/heads/master@{#840727} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2618603 +Reviewed-by: Xianzhu Wang +Reviewed-by: Krishna Govind +Cr-Original-Commit-Position: refs/branch-heads/4324@{#1566} +Cr-Original-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2618982 +Commit-Queue: Achuith Bhandarkar +Reviewed-by: Victor-Gabriel Savu +Cr-Commit-Position: refs/branch-heads/4240@{#1520} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/core/layout/hit_test_result.cc b/third_party/blink/renderer/core/layout/hit_test_result.cc +index 1359aa6bdd532028d8280b093a4381a70e4a7577..9d02cabafa3b8acc19ae2c0007c0eb6b86f1fb5d 100644 +--- a/third_party/blink/renderer/core/layout/hit_test_result.cc ++++ b/third_party/blink/renderer/core/layout/hit_test_result.cc +@@ -21,12 +21,14 @@ + + #include "third_party/blink/renderer/core/layout/hit_test_result.h" + ++#include "third_party/blink/renderer/core/display_lock/display_lock_utilities.h" + #include "third_party/blink/renderer/core/dom/flat_tree_traversal.h" + #include "third_party/blink/renderer/core/dom/pseudo_element.h" + #include "third_party/blink/renderer/core/dom/shadow_root.h" + #include "third_party/blink/renderer/core/editing/editing_utilities.h" + #include "third_party/blink/renderer/core/editing/frame_selection.h" + #include "third_party/blink/renderer/core/editing/position_with_affinity.h" ++#include "third_party/blink/renderer/core/editing/text_affinity.h" + #include "third_party/blink/renderer/core/editing/visible_units.h" + #include "third_party/blink/renderer/core/frame/local_frame.h" + #include "third_party/blink/renderer/core/frame/visual_viewport.h" +@@ -144,6 +146,20 @@ PositionWithAffinity HitTestResult::GetPosition() const { + LayoutObject* layout_object = GetLayoutObject(); + if (!layout_object) + return PositionWithAffinity(); ++ ++ // We should never have a layout object that is within a locked subtree. ++ CHECK(!DisplayLockUtilities::NearestLockedExclusiveAncestor(*layout_object)); ++ ++ // If the layout object is blocked by display lock, we return the beginning of ++ // the node as the position. This is because we don't paint contents of the ++ // element. Furthermore, any caret adjustments below can access layout-dirty ++ // state in the subtree of this object. ++ if (layout_object->PaintBlockedByDisplayLock( ++ DisplayLockLifecycleTarget::kChildren)) { ++ return PositionWithAffinity(Position(*inner_node_, 0), ++ TextAffinity::kDefault); ++ } ++ + if (inner_possibly_pseudo_node_->IsPseudoElement() && + inner_possibly_pseudo_node_->GetPseudoId() == kPseudoIdBefore) { + return PositionWithAffinity(MostForwardCaretPosition( +diff --git a/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-080.html b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-080.html +new file mode 100644 +index 0000000000000000000000000000000000000000..d3cea5fb83767ddfc236850097387644e0f74c8e +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/css/css-contain/content-visibility/content-visibility-080.html +@@ -0,0 +1,31 @@ ++ ++ ++ ++Content Visibility: caret position with html hidden ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ diff --git a/patches/chromium/cherry-pick-d866af575997.patch b/patches/chromium/cherry-pick-d866af575997.patch new file mode 100644 index 0000000000000..329b3f6c951c9 --- /dev/null +++ b/patches/chromium/cherry-pick-d866af575997.patch @@ -0,0 +1,39 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Raymond Toy +Date: Mon, 7 Dec 2020 17:55:30 +0000 +Subject: Clear handlers when the base context goes away. + +Previously, in BaseAudioContext::Clear() we called +GetDeferredTaskHandler().ClearHandlersToBeDeleted(). But this was +also called in DeferredTaskHandler::ContextWillBeDestroyed(), which is +called in BaseAudioContext::~BaseAudioContext(). + +There's no need to call this twice while handling the audio context +going away. + +Manually verified that the tests from issue 1125635 and 1153658 work, +and the deadlock in issue 1136571 is gone. + +Bug: 1150065, 1153658 +Change-Id: Iee15c31dc637bf82d66bfd79d5238b1f80813153 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2575418 +Commit-Queue: Raymond Toy +Reviewed-by: Hongchan Choi +Cr-Commit-Position: refs/heads/master@{#834265} + +diff --git a/third_party/blink/renderer/modules/webaudio/base_audio_context.cc b/third_party/blink/renderer/modules/webaudio/base_audio_context.cc +index e203973a516c630af3decaddc1080ab9697c634a..cd16a1f31fb4d8469f35a8c5e08c51d15efb0cf8 100644 +--- a/third_party/blink/renderer/modules/webaudio/base_audio_context.cc ++++ b/third_party/blink/renderer/modules/webaudio/base_audio_context.cc +@@ -142,9 +142,8 @@ void BaseAudioContext::Initialize() { + + void BaseAudioContext::Clear() { + destination_node_.Clear(); +- // The audio rendering thread is dead. Nobody will schedule AudioHandler +- // deletion. Let's do it ourselves. +- GetDeferredTaskHandler().ClearHandlersToBeDeleted(); ++ // Make a note that we've cleared out the context so that there's no pending ++ // activity. + is_cleared_ = true; + } + diff --git a/patches/chromium/cherry-pick-d8d64b7cd244.patch b/patches/chromium/cherry-pick-d8d64b7cd244.patch new file mode 100644 index 0000000000000..ce242772d748d --- /dev/null +++ b/patches/chromium/cherry-pick-d8d64b7cd244.patch @@ -0,0 +1,132 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Xianzhu Wang +Date: Mon, 16 Nov 2020 17:26:33 +0000 +Subject: Ensure change type for OverflowControlsClip is returned + +This at least ensures that we will update the paint properites for the +composited overflow control layers in pre-CompositeAfterPaint to avoid +stale properties on the layers. + +The test doesn't actually reproduce the bug because any test simpler +than the bug case couldn't reproduce the bug as the update would be +triggered in other code paths (any style change, layout change, etc.). + +Anyway this CL does fix the bug case. + +TBR=wangxianzhu@chromium.org + +(cherry picked from commit c20bb9897ef6d26a46391a4dc1658c5d33e0c100) + +(cherry picked from commit cfb81e677a508871f56d8bec958d0b585298ae0c) + +Bug: 1137603 +Change-Id: I5cca970bcf8cda6085527f79a97f269c4e3e9986 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2500264 +Reviewed-by: Stefan Zager +Commit-Queue: Xianzhu Wang +Cr-Original-Original-Commit-Position: refs/heads/master@{#820986} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2536910 +Reviewed-by: Xianzhu Wang +Cr-Original-Commit-Position: refs/branch-heads/4240@{#1446} +Cr-Original-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2540592 +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Jana Grill +Cr-Commit-Position: refs/branch-heads/4240_112@{#26} +Cr-Branched-From: 427c00d3874b6abcf4c4c2719768835fc3ef26d6-refs/branch-heads/4240@{#1291} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater_test.cc b/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater_test.cc +index e991dfac93cfa90f46b92e00c4f29318736bc7ba..b42f1d6bd9b04f293034ee97c8eaa7aa10390ac9 100644 +--- a/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater_test.cc ++++ b/third_party/blink/renderer/core/paint/compositing/compositing_layer_property_updater_test.cc +@@ -174,4 +174,56 @@ TEST_F(CompositingLayerPropertyUpdaterTest, + } + } + ++TEST_F(CompositingLayerPropertyUpdaterTest, OverflowControlsClip) { ++ SetBodyInnerHTML(R"HTML( ++ ++
++
++
++ )HTML"); ++ ++ // Initially the vertical scrollbar overflows the narrow border box. ++ auto* container = GetDocument().getElementById("container"); ++ auto* target = ToLayoutBox(GetLayoutObjectByElementId("target")); ++ auto* scrollbar_layer = ++ target->GetScrollableArea()->GraphicsLayerForVerticalScrollbar(); ++ auto target_state = target->FirstFragment().LocalBorderBoxProperties(); ++ auto scrollbar_state = target_state; ++ auto* overflow_controls_clip = ++ target->FirstFragment().PaintProperties()->OverflowControlsClip(); ++ ASSERT_TRUE(overflow_controls_clip); ++ scrollbar_state.SetClip(*overflow_controls_clip); ++ EXPECT_EQ(scrollbar_state, scrollbar_layer->GetPropertyTreeState()); ++ ++ // Widen target to make the vertical scrollbar contained by the border box. ++ container->setAttribute(html_names::kStyleAttr, "width: 100px"); ++ UpdateAllLifecyclePhasesForTest(); ++ LOG(ERROR) << target->Size(); ++ EXPECT_FALSE( ++ target->FirstFragment().PaintProperties()->OverflowControlsClip()); ++ EXPECT_EQ(target_state, scrollbar_layer->GetPropertyTreeState()); ++ ++ // Narrow down target back. ++ container->removeAttribute(html_names::kStyleAttr); ++ UpdateAllLifecyclePhasesForTest(); ++ scrollbar_state = target_state; ++ overflow_controls_clip = ++ target->FirstFragment().PaintProperties()->OverflowControlsClip(); ++ ASSERT_TRUE(overflow_controls_clip); ++ scrollbar_state.SetClip(*overflow_controls_clip); ++ EXPECT_EQ(scrollbar_state, scrollbar_layer->GetPropertyTreeState()); ++} ++ + } // namespace blink +diff --git a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc +index 0702a0fc65a69ab1da2cceeb4e6dcb9be30c8d3b..527024f5b1e9c71d7ad5311805c81c3587a7dc32 100644 +--- a/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc ++++ b/third_party/blink/renderer/core/paint/paint_property_tree_builder.cc +@@ -1554,21 +1554,21 @@ void FragmentPaintPropertyTreeBuilder::UpdateOverflowControlsClip() { + + if (NeedsOverflowControlsClip()) { + // Clip overflow controls to the border box rect. Not wrapped with +- // OnUpdateClip() because this clip doesn't affect descendants. ++ // OnUpdateClip() because this clip doesn't affect descendants. Wrap with ++ // OnUpdate() to let PrePaintTreeWalk see the change. This may cause ++ // unnecessary subtree update, but is not a big deal because it is rare. + const auto& clip_rect = PhysicalRect(context_.current.paint_offset, + ToLayoutBox(object_).Size()); +- properties_->UpdateOverflowControlsClip( ++ OnUpdate(properties_->UpdateOverflowControlsClip( + *context_.current.clip, + ClipPaintPropertyNode::State(context_.current.transform, + FloatRoundedRect(FloatRect(clip_rect)), +- ToSnappedClipRect(clip_rect))); ++ ToSnappedClipRect(clip_rect)))); + } else { +- properties_->ClearOverflowControlsClip(); ++ OnClear(properties_->ClearOverflowControlsClip()); + } + +- // No need to set force_subtree_update_reasons and clip_changed because +- // OverflowControlsClip applies to overflow controls only, not descendants. +- // We also don't walk into custom scrollbars in PrePaintTreeWalk and ++ // We don't walk into custom scrollbars in PrePaintTreeWalk because + // LayoutObjects under custom scrollbars don't support paint properties. + } + diff --git a/patches/chromium/cherry-pick-da9b5ec032ad.patch b/patches/chromium/cherry-pick-da9b5ec032ad.patch new file mode 100644 index 0000000000000..4522e347fa3eb --- /dev/null +++ b/patches/chromium/cherry-pick-da9b5ec032ad.patch @@ -0,0 +1,81 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Daniele Castagna +Date: Mon, 14 Dec 2020 23:03:31 +0000 +Subject: viz: Destroy |gpu_memory_buffer_factory_| on IOThread + +|gpu_memory_buffer_factory_| weak pointers are checked on the +IOThread. +Weak pointers should be invalidated on the same thread that +checks them. + +This CL moves the destruction of |gpu_memory_buffer_factory_| +on the IOThread to avoid possible use after free issues. + +Bug: 1152645 + +Change-Id: I0d42814f0e435a3746728515da1f32d08a1252cf +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2563077 +Commit-Queue: Daniele Castagna +Reviewed-by: Andres Calderon Jaramillo +Cr-Commit-Position: refs/heads/master@{#836827} + +diff --git a/components/viz/service/gl/gpu_service_impl.cc b/components/viz/service/gl/gpu_service_impl.cc +index 98cdbff9bf021f9665abdb339a25753b2e7b4807..6ab1e86035089b748eb636cc5e654702ee19fd9e 100644 +--- a/components/viz/service/gl/gpu_service_impl.cc ++++ b/components/viz/service/gl/gpu_service_impl.cc +@@ -428,16 +428,18 @@ GpuServiceImpl::~GpuServiceImpl() { + GetLogMessageManager()->ShutdownLogging(); + + // Destroy the receiver on the IO thread. +- base::WaitableEvent wait; +- auto destroy_receiver_task = base::BindOnce( +- [](mojo::Receiver* receiver, +- base::WaitableEvent* wait) { +- receiver->reset(); +- wait->Signal(); +- }, +- &receiver_, &wait); +- if (io_runner_->PostTask(FROM_HERE, std::move(destroy_receiver_task))) +- wait.Wait(); ++ { ++ base::WaitableEvent wait; ++ auto destroy_receiver_task = base::BindOnce( ++ [](mojo::Receiver* receiver, ++ base::WaitableEvent* wait) { ++ receiver->reset(); ++ wait->Signal(); ++ }, ++ &receiver_, base::Unretained(&wait)); ++ if (io_runner_->PostTask(FROM_HERE, std::move(destroy_receiver_task))) ++ wait.Wait(); ++ } + + if (watchdog_thread_) + watchdog_thread_->OnGpuProcessTearDown(); +@@ -445,6 +447,26 @@ GpuServiceImpl::~GpuServiceImpl() { + media_gpu_channel_manager_.reset(); + gpu_channel_manager_.reset(); + ++ // Destroy |gpu_memory_buffer_factory_| on the IO thread since its weakptrs ++ // are checked there. ++ { ++ base::WaitableEvent wait; ++ auto destroy_gmb_factory = base::BindOnce( ++ [](std::unique_ptr gmb_factory, ++ base::WaitableEvent* wait) { ++ gmb_factory.reset(); ++ wait->Signal(); ++ }, ++ std::move(gpu_memory_buffer_factory_), base::Unretained(&wait)); ++ ++ if (io_runner_->PostTask(FROM_HERE, std::move(destroy_gmb_factory))) { ++ // |gpu_memory_buffer_factory_| holds a raw pointer to ++ // |vulkan_context_provider_|. Waiting here enforces the correct order ++ // of destruction. ++ wait.Wait(); ++ } ++ } ++ + // Scheduler must be destroyed before sync point manager is destroyed. + scheduler_.reset(); + owned_sync_point_manager_.reset(); diff --git a/patches/chromium/cherry-pick-dea071d8b30f.patch b/patches/chromium/cherry-pick-dea071d8b30f.patch new file mode 100644 index 0000000000000..0664e2e2efde4 --- /dev/null +++ b/patches/chromium/cherry-pick-dea071d8b30f.patch @@ -0,0 +1,121 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Hongchan Choi +Date: Tue, 2 Mar 2021 15:15:59 +0000 +Subject: Prevent accessing shared buffers from audio rendering thread + +The shared buffer in ScriptProcessorNode can be accessed by the +audio rendering thread when it is held by the main thread. + +The solution suggested here is simply to expand the scope of +the mutex to minimize the code change. This is a deprecated +feature in Web Audio, so making significant changes is not +sensible. By locking the entire scope of Process() call, this +area would be immune to the similar problems in the future. + +(cherry picked from commit 60987aa224f369fc0ea38c56e498389440921356) + +(cherry picked from commit aeb6bc551b607e0c80c232ed4817c0ff5e9a7784) + +Bug: 1174582 +Test: The repro case doesn't crash on ASAN. +Change-Id: I2b292f94be65e6ec26c6eb0e0ed32b3fb2d88466 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2681193 +Commit-Queue: Hongchan Choi +Reviewed-by: Raymond Toy +Cr-Original-Original-Commit-Position: refs/heads/master@{#852240} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2715585 +Commit-Queue: Krishna Govind +Reviewed-by: Srinivas Sista +Cr-Original-Commit-Position: refs/branch-heads/4324@{#2238} +Cr-Original-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2726911 +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1552} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc +index b1ca691b07b53b927a92753906f7f25edebac919..6e80b23a32dd1895a0d51d08ee16c8cb2d44fc55 100644 +--- a/third_party/blink/renderer/modules/webaudio/script_processor_node.cc ++++ b/third_party/blink/renderer/modules/webaudio/script_processor_node.cc +@@ -110,6 +110,14 @@ void ScriptProcessorHandler::Initialize() { + } + + void ScriptProcessorHandler::Process(uint32_t frames_to_process) { ++ // The main thread might be accessing the shared buffers. If so, silience ++ // the output and return. ++ MutexTryLocker try_locker(process_event_lock_); ++ if (!try_locker.Locked()) { ++ Output(0).Bus()->Zero(); ++ return; ++ } ++ + // Discussion about inputs and outputs: + // As in other AudioNodes, ScriptProcessorNode uses an AudioBus for its input + // and output (see inputBus and outputBus below). Additionally, there is a +@@ -181,47 +189,26 @@ void ScriptProcessorHandler::Process(uint32_t frames_to_process) { + buffer_read_write_index_ = + (buffer_read_write_index_ + frames_to_process) % BufferSize(); + +- // m_bufferReadWriteIndex will wrap back around to 0 when the current input +- // and output buffers are full. +- // When this happens, fire an event and swap buffers. ++ // Fire an event and swap buffers when |buffer_read_write_index_| wraps back ++ // around to 0. It means the current input and output buffers are full. + if (!buffer_read_write_index_) { +- // Avoid building up requests on the main thread to fire process events when +- // they're not being handled. This could be a problem if the main thread is +- // very busy doing other things and is being held up handling previous +- // requests. The audio thread can't block on this lock, so we call +- // tryLock() instead. +- MutexTryLocker try_locker(process_event_lock_); +- if (!try_locker.Locked()) { +- // We're late in handling the previous request. The main thread must be +- // very busy. The best we can do is clear out the buffer ourself here. +- shared_output_buffer->Zero(); ++ if (Context()->HasRealtimeConstraint()) { ++ // For a realtime context, fire an event and do not wait. ++ PostCrossThreadTask( ++ *task_runner_, FROM_HERE, ++ CrossThreadBindOnce(&ScriptProcessorHandler::FireProcessEvent, ++ AsWeakPtr(), double_buffer_index_)); + } else { +- // With the realtime context, execute the script code asynchronously +- // and do not wait. +- if (Context()->HasRealtimeConstraint()) { +- // Fire the event on the main thread with the appropriate buffer +- // index. +- PostCrossThreadTask( +- *task_runner_, FROM_HERE, +- CrossThreadBindOnce(&ScriptProcessorHandler::FireProcessEvent, +- AsWeakPtr(), double_buffer_index_)); +- } else { +- // If this node is in the offline audio context, use the +- // waitable event to synchronize to the offline rendering thread. +- std::unique_ptr waitable_event = +- std::make_unique(); +- +- PostCrossThreadTask( +- *task_runner_, FROM_HERE, +- CrossThreadBindOnce( +- &ScriptProcessorHandler::FireProcessEventForOfflineAudioContext, +- AsWeakPtr(), double_buffer_index_, +- CrossThreadUnretained(waitable_event.get()))); +- +- // Okay to block the offline audio rendering thread since it is +- // not the actual audio device thread. +- waitable_event->Wait(); +- } ++ // For an offline context, wait until the script execution is finished. ++ std::unique_ptr waitable_event = ++ std::make_unique(); ++ PostCrossThreadTask( ++ *task_runner_, FROM_HERE, ++ CrossThreadBindOnce( ++ &ScriptProcessorHandler::FireProcessEventForOfflineAudioContext, ++ AsWeakPtr(), double_buffer_index_, ++ CrossThreadUnretained(waitable_event.get()))); ++ waitable_event->Wait(); + } + + SwapBuffers(); diff --git a/patches/chromium/cherry-pick-df438f22f7d2.patch b/patches/chromium/cherry-pick-df438f22f7d2.patch new file mode 100644 index 0000000000000..7931fef0fb12b --- /dev/null +++ b/patches/chromium/cherry-pick-df438f22f7d2.patch @@ -0,0 +1,146 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Artem Sumaneev +Date: Wed, 3 Feb 2021 15:00:25 +0000 +Subject: Fix navigation request reset logic +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Do not delete navigation request which has started upon receiving +notification about beforeunload dialog being cancelled, as a) this +navigation request is not waiting for beforeunload and b) it might have +been this navigation request which canceled this beforeunload dialog. + +M86 merge conflicts and resolution: +* content/browser/frame_host/* + In ToT files the affected under frame_host directory are moved to + renderer_host dir. Applied patch to frame_host, no further conflicts. + +R=​alexmos@chromium.org +BUG=1161705 + +(cherry picked from commit 23c110b5b81dc401ded5d4dcecfab65d5d88fdfa) + +(cherry picked from commit 87550e04d9fed4bbedff4546f4161e3c02415d7e) + +Change-Id: I7d385d4326fac6f67d17a003679471806b5ad3b2 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2624733 +Commit-Queue: Alexander Timin +Reviewed-by: Alex Moshchuk +Cr-Original-Original-Commit-Position: refs/heads/master@{#843343} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2652791 +Commit-Queue: Alex Moshchuk +Auto-Submit: Alexander Timin +Cr-Original-Commit-Position: refs/branch-heads/4324@{#2040} +Cr-Original-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2666397 +Reviewed-by: Victor-Gabriel Savu +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1537} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/content/browser/frame_host/frame_tree_node.cc b/content/browser/frame_host/frame_tree_node.cc +index a1c72899b940d942bccf25c617ded87081f67901..754563aa43df66edc473e58106200a4b6923716a 100644 +--- a/content/browser/frame_host/frame_tree_node.cc ++++ b/content/browser/frame_host/frame_tree_node.cc +@@ -578,10 +578,12 @@ void FrameTreeNode::BeforeUnloadCanceled() { + render_manager_.speculative_frame_host(); + if (speculative_frame_host) + speculative_frame_host->ResetLoadingState(); +- // Note: there is no need to set an error code on the NavigationHandle here +- // as it has not been created yet. It is only created when the +- // BeforeUnloadCompleted callback is invoked. +- if (navigation_request_) ++ // Note: there is no need to set an error code on the NavigationHandle as ++ // the observers have not been notified about its creation. ++ // We also reset navigation request only when this navigation request was ++ // responsible for this dialog, as a new navigation request might cancel ++ // existing unrelated dialog. ++ if (navigation_request_ && navigation_request_->IsWaitingForBeforeUnload()) + ResetNavigationRequest(false); + } + +diff --git a/content/browser/frame_host/navigation_request.cc b/content/browser/frame_host/navigation_request.cc +index 764e1f007b44bda5191e9021be59ab21446f6e55..498a8c3d8bcbea3b411901eaca9544ac5037308e 100644 +--- a/content/browser/frame_host/navigation_request.cc ++++ b/content/browser/frame_host/navigation_request.cc +@@ -4899,4 +4899,8 @@ void NavigationRequest::UpdateCoopStatus( + } + } + ++bool NavigationRequest::IsWaitingForBeforeUnload() { ++ return state_ < WILL_START_NAVIGATION; ++} ++ + } // namespace content +diff --git a/content/browser/frame_host/navigation_request.h b/content/browser/frame_host/navigation_request.h +index 9db96ba9e042507b465821dbb0632317f1f0de70..e6f6e83e7013bd13b504969a8ebfbf5f1428eb2f 100644 +--- a/content/browser/frame_host/navigation_request.h ++++ b/content/browser/frame_host/navigation_request.h +@@ -670,6 +670,10 @@ class CONTENT_EXPORT NavigationRequest + // navigation or an error page. + bool IsWaitingToCommit(); + ++ // Whether this navigation request waits for the result of beforeunload before ++ // proceeding. ++ bool IsWaitingForBeforeUnload(); ++ + private: + friend class NavigationRequestTest; + +diff --git a/content/browser/frame_host/render_frame_host_impl_browsertest.cc b/content/browser/frame_host/render_frame_host_impl_browsertest.cc +index 089a0f08c96e7cb366659c633e2fdf5f61904f4c..bf7e8c0706f7694878b817312f4b7f8339d6645e 100644 +--- a/content/browser/frame_host/render_frame_host_impl_browsertest.cc ++++ b/content/browser/frame_host/render_frame_host_impl_browsertest.cc +@@ -1113,6 +1113,51 @@ IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBeforeUnloadBrowserTest, + + namespace { + ++class OnDidStartNavigation : public WebContentsObserver { ++ public: ++ OnDidStartNavigation(WebContents* web_contents, ++ base::RepeatingClosure callback) ++ : WebContentsObserver(web_contents), callback_(callback) {} ++ ++ void DidStartNavigation(NavigationHandle* navigation) override { ++ callback_.Run(); ++ } ++ ++ private: ++ base::RepeatingClosure callback_; ++}; ++ ++} // namespace ++ ++// This test closes beforeunload dialog due to a new navigation starting from ++// within WebContentsObserver::DidStartNavigation. This test succeeds if it ++// doesn't crash with a UAF while loading the second page. ++IN_PROC_BROWSER_TEST_F(RenderFrameHostImplBeforeUnloadBrowserTest, ++ DidStartNavigationClosesDialog) { ++ GURL url1 = embedded_test_server()->GetURL("a.com", "/title1.html"); ++ GURL url2 = embedded_test_server()->GetURL("b.com", "/title1.html"); ++ ++ EXPECT_TRUE(NavigateToURL(shell(), url1)); ++ ++ // This matches the behaviour of TabModalDialogManager in ++ // components/javascript_dialogs. ++ OnDidStartNavigation close_dialog(web_contents(), ++ base::BindLambdaForTesting([&]() { ++ CloseDialogAndCancel(); ++ ++ // Check that web_contents() were not ++ // deleted. ++ DCHECK(web_contents()->GetMainFrame()); ++ })); ++ ++ web_contents()->GetMainFrame()->RunBeforeUnloadConfirm(true, ++ base::DoNothing()); ++ ++ EXPECT_TRUE(NavigateToURL(shell(), url2)); ++} ++ ++namespace { ++ + // A helper to execute some script in a frame just before it is deleted, such + // that no message loops are pumped and no sync IPC messages are processed + // between script execution and the destruction of the RenderFrameHost . diff --git a/patches/chromium/cherry-pick-ecdec1fb0f42.patch b/patches/chromium/cherry-pick-ecdec1fb0f42.patch new file mode 100644 index 0000000000000..fd8c5df0803b9 --- /dev/null +++ b/patches/chromium/cherry-pick-ecdec1fb0f42.patch @@ -0,0 +1,70 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Etienne Bergeron +Date: Fri, 13 Nov 2020 17:40:59 +0000 +Subject: Fix text eliding for single-codepoint text with BiDi + +This CL is fixing a corner case where RenderText::Elide(...) may +produce a text with more codepoints than the original one. This is +an issue since the breaklists are not resized and the overflow +will lead the render_text code to perfoarm an out-of-bound memory +access by deferencing breaks_.end() while rendering the text. This +is causing chrome to crash. + +See crbug/1142020 for details. + +The bug was happening when: + 1) The text to elide was a single codepoint + 2) The width of the glyph of the codepoint is larger than the width + of the ellipsis glyph + 3) Eliding is set to ELIDING_TAIL + 4) The display_rect width will trigger eliding + (smaller than codepoint width, but larger than ellipsis width) + 5) The render text is set to RTL + +A possible solution is to adjust the breaklist but this required +larger refactoring and cannot be a minimal patch to be merge on +other channels. + +TBR=msw@chromium.org +(cherry picked from commit e54920751871321474e0b953329c8aedcc8702c3) + +Bug: 1142020 +Change-Id: I9854651175562ec5f0d0bf7083a8da99fede0e29 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2522892 +Commit-Queue: Etienne Bergeron +Reviewed-by: Michael Wasserman +Cr-Original-Commit-Position: refs/heads/master@{#824878} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2537931 +Reviewed-by: Etienne Bergeron +Cr-Commit-Position: refs/branch-heads/4240@{#1450} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/ui/gfx/render_text.cc b/ui/gfx/render_text.cc +index 383943809398a59380518f00d84275f493f008c3..2887ea638e3843e61ca2a2935f52d08fe2bbe45e 100644 +--- a/ui/gfx/render_text.cc ++++ b/ui/gfx/render_text.cc +@@ -2060,8 +2060,10 @@ base::string16 RenderText::Elide(const base::string16& text, + } + + // Append the ellipsis and the optional directional marker characters. ++ // Do not append the BiDi marker if the only codepoint in the text is ++ // an ellipsis. + new_text.append(ellipsis); +- if (trailing_text_direction != text_direction) { ++ if (new_text.size() != 1 && trailing_text_direction != text_direction) { + if (trailing_text_direction == base::i18n::LEFT_TO_RIGHT) + new_text += base::i18n::kLeftToRightMark; + else +diff --git a/ui/gfx/render_text_unittest.cc b/ui/gfx/render_text_unittest.cc +index 2fd63b1b75fc0fdec9772a9d6b74a13423bf7221..42827f318c8b1896c021f30895072e1add742d32 100644 +--- a/ui/gfx/render_text_unittest.cc ++++ b/ui/gfx/render_text_unittest.cc +@@ -1901,7 +1901,7 @@ const ElideTextCase kElideTailTextCases[] = { + {"ltr_0", L"abc", L""}, + {"rtl_3", L"\u05d0\u05d1\u05d2", L"\u05d0\u05d1\u05d2"}, + {"rtl_2", L"\u05d0\u05d1\u05d2", L"\u05d0\u2026"}, +- {"rtl_1", L"\u05d0\u05d1\u05d2", L"\u2026\x200E"}, ++ {"rtl_1", L"\u05d0\u05d1\u05d2", L"\u2026"}, + {"rtl_0", L"\u05d0\u05d1\u05d2", L""}, + {"ltr_rtl_5", L"abc\u05d0\u05d1\u05d2", L"abc\u05d0\u2026\x200F"}, + {"ltr_rtl_4", L"abc\u05d0\u05d1\u05d2", L"abc\u2026"}, diff --git a/patches/chromium/cherry-pick-eec5025668f8.patch b/patches/chromium/cherry-pick-eec5025668f8.patch new file mode 100644 index 0000000000000..3179c84f5fd17 --- /dev/null +++ b/patches/chromium/cherry-pick-eec5025668f8.patch @@ -0,0 +1,77 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Bill Budge +Date: Fri, 13 Nov 2020 09:02:09 +0000 +Subject: Merged: [wasm][code cache] Match response to cached raw resource + +- Verifies that the retrieved resource has the same response time, and + that the source matches (i.e. both are from service worker, or both + are not). + +Bug: chromium:1146673 + +(cherry picked from commit a8b46244ecaa1647ee2d70304878c0365ee04087) + +Change-Id: I6243ec9017b2405687056aa6ea199c67b1c16063 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2526802 +Reviewed-by: Yutaka Hirano +Reviewed-by: Andreas Haas +Commit-Queue: Bill Budge +Cr-Original-Commit-Position: refs/heads/master@{#826277} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2537531 +Commit-Queue: Clemens Backes +Commit-Queue: Andreas Haas +Auto-Submit: Clemens Backes +Cr-Commit-Position: refs/branch-heads/4240@{#1447} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc b/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc +index 1010b8be1364fc500b1bb20338e8959d683df5e8..0fffec5084a09cb1af18e619cbad7cc1dd73c447 100644 +--- a/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc ++++ b/third_party/blink/renderer/bindings/core/v8/v8_wasm_response_extensions.cc +@@ -210,7 +210,8 @@ class ExceptionToAbortStreamingScope { + }; + + RawResource* GetRawResource(ScriptState* script_state, +- const String& url_string) { ++ const String& url_string, ++ Response* response) { + ExecutionContext* execution_context = ExecutionContext::From(script_state); + if (!execution_context) + return nullptr; +@@ -224,6 +225,18 @@ RawResource* GetRawResource(ScriptState* script_state, + if (!resource) + return nullptr; + ++ // Make sure the resource matches the |response|. To check that, we make sure ++ // the response times match, and the response sources match. ++ const ResourceResponse& resource_response = resource->GetResponse(); ++ const FetchResponseData* fetch_response_data = ++ response->GetResponse()->InternalResponse(); ++ if (resource_response.ResponseTime() != fetch_response_data->ResponseTime()) ++ return nullptr; ++ bool from_service_worker = fetch_response_data->ResponseSource() == ++ network::mojom::FetchResponseSource::kUnspecified; ++ if (resource_response.WasFetchedViaServiceWorker() != from_service_worker) ++ return nullptr; ++ + // Wasm modules should be fetched as raw resources. + DCHECK_EQ(ResourceType::kRaw, resource->GetType()); + return ToRawResource(resource); +@@ -347,13 +360,12 @@ void StreamFromResponseCallback( + String url = response->url(); + const std::string& url_utf8 = url.Utf8(); + streaming->SetUrl(url_utf8.c_str(), url_utf8.size()); +- RawResource* raw_resource = GetRawResource(script_state, url); +- if (raw_resource) { +- SingleCachedMetadataHandler* cache_handler = +- raw_resource->ScriptCacheHandler(); ++ RawResource* resource = GetRawResource(script_state, url, response); ++ if (resource) { ++ SingleCachedMetadataHandler* cache_handler = resource->ScriptCacheHandler(); + if (cache_handler) { + auto client = std::make_shared( +- url, raw_resource->GetResponse().ResponseTime()); ++ url, resource->GetResponse().ResponseTime()); + streaming->SetClient(client); + scoped_refptr cached_module = + cache_handler->GetCachedMetadata(kWasmModuleTag); diff --git a/patches/chromium/cherry-pick-f37149c4434f.patch b/patches/chromium/cherry-pick-f37149c4434f.patch new file mode 100644 index 0000000000000..a72ab6bd60189 --- /dev/null +++ b/patches/chromium/cherry-pick-f37149c4434f.patch @@ -0,0 +1,314 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jana Grill +Date: Mon, 19 Apr 2021 10:55:05 +0000 +Subject: Don't show autofill dropdown for element outside the viewport + +Details are in the linked bug. + +Also cherry pick crrev/c/2682341 and a part of crrev/c/2628287 to fix +failing tests and compile errors. + +(cherry picked from commit 53a4f38ee5d44bd935d176cc89e3e59fd0a3970e) + +Bug: 1172533,1173297 +Change-Id: Iee429cac167ccdb0cd74acf57fc5f7c3821883b1 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2675932 +Commit-Queue: Mohamed Amir Yosef +Reviewed-by: Vasilii Sukhanov +Reviewed-by: Evan Stade +Cr-Original-Commit-Position: refs/heads/master@{#851718} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2822155 +Commit-Queue: Jana Grill +Reviewed-by: Achuith Bhandarkar +Reviewed-by: Victor-Gabriel Savu +Owners-Override: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1611} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/chrome/browser/autofill/autofill_interactive_uitest.cc b/chrome/browser/autofill/autofill_interactive_uitest.cc +index e28756cffe70f5f62630e54f4d74230ca21e477f..45be9ac71dc96783bfd0402f5012f94d02f9ab93 100644 +--- a/chrome/browser/autofill/autofill_interactive_uitest.cc ++++ b/chrome/browser/autofill/autofill_interactive_uitest.cc +@@ -2771,7 +2771,14 @@ class AutofillInteractiveIsolationTest : public AutofillInteractiveTestBase { + } + }; + +-IN_PROC_BROWSER_TEST_F(AutofillInteractiveIsolationTest, SimpleCrossSiteFill) { ++// Flaky on ChromeOS http://crbug.com/1175735 ++#if defined(OS_CHROMEOS) ++#define MAYBE_SimpleCrossSiteFill DISABLED_SimpleCrossSiteFill ++#else ++#define MAYBE_SimpleCrossSiteFill SimpleCrossSiteFill ++#endif ++IN_PROC_BROWSER_TEST_F(AutofillInteractiveIsolationTest, ++ MAYBE_SimpleCrossSiteFill) { + CreateTestProfile(); + + // Main frame is on a.com, iframe is on b.com. +@@ -2814,7 +2821,8 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveIsolationTest, SimpleCrossSiteFill) { + // This test verifies that credit card (payment card list) popup works when the + // form is inside an OOPIF. + // Flaky on Windows http://crbug.com/728488 +-#if defined(OS_WIN) ++// Flaky on ChromeOS http://crbug.com/1175735 ++#if defined(OS_WIN) || defined(OS_CHROMEOS) + #define MAYBE_CrossSitePaymentForms DISABLED_CrossSitePaymentForms + #else + #define MAYBE_CrossSitePaymentForms CrossSitePaymentForms +@@ -2852,8 +2860,14 @@ IN_PROC_BROWSER_TEST_F(AutofillInteractiveTest, MAYBE_CrossSitePaymentForms) { + {ObservedUiEvents::kSuggestionShown}); + } + ++// Flaky on ChromeOS http://crbug.com/1175735 ++#if defined(OS_CHROMEOS) ++#define MAYBE_DeletingFrameUnderSuggestion DISABLED_DeletingFrameUnderSuggestion ++#else ++#define MAYBE_DeletingFrameUnderSuggestion DeletingFrameUnderSuggestion ++#endif + IN_PROC_BROWSER_TEST_F(AutofillInteractiveIsolationTest, +- DeletingFrameUnderSuggestion) { ++ MAYBE_DeletingFrameUnderSuggestion) { + CreateTestProfile(); + + // Main frame is on a.com, iframe is on b.com. +diff --git a/chrome/browser/autofill/autofill_uitest.cc b/chrome/browser/autofill/autofill_uitest.cc +index b405c197f6a51bfe28aea9790fc73025238be8e2..f136fd6ac034316822d71d3ccaafb906c43a5139 100644 +--- a/chrome/browser/autofill/autofill_uitest.cc ++++ b/chrome/browser/autofill/autofill_uitest.cc +@@ -8,6 +8,7 @@ + #include "base/macros.h" + #include "base/run_loop.h" + #include "chrome/browser/autofill/autofill_uitest.h" ++#include "chrome/browser/autofill/autofill_uitest_util.h" + #include "chrome/browser/autofill/personal_data_manager_factory.h" + #include "chrome/browser/profiles/profile.h" + #include "chrome/browser/ui/browser.h" +@@ -78,6 +79,10 @@ void AutofillUiTest::SetUpOnMainThread() { + /* new_host = */ GetWebContents()->GetMainFrame()); + Observe(GetWebContents()); + ++ // Wait for Personal Data Manager to be fully loaded to prevent that ++ // spurious notifications deceive the tests. ++ WaitForPersonalDataManagerToBeLoaded(browser()); ++ + disable_animation_ = std::make_unique( + ui::ScopedAnimationDurationScaleMode::ZERO_DURATION); + +diff --git a/chrome/browser/autofill/autofill_uitest_util.cc b/chrome/browser/autofill/autofill_uitest_util.cc +index 5bee77a2b6b451c4e15b0329379961164ac4f973..69d62b4f506e83e46db57d7afbff5ddf3911fd79 100644 +--- a/chrome/browser/autofill/autofill_uitest_util.cc ++++ b/chrome/browser/autofill/autofill_uitest_util.cc +@@ -113,4 +113,12 @@ void WaitForPersonalDataChange(Browser* browser) { + observer.Wait(); + } + ++// Adjusted from crrev/c/2628287 to fix failure in crrev/c/2822155 ++void WaitForPersonalDataManagerToBeLoaded(Browser* browser) { ++ PersonalDataManager* pdm = ++ autofill::PersonalDataManagerFactory::GetForProfile(browser->profile()); ++ while (!pdm->IsDataLoaded()) ++ WaitForPersonalDataChange(browser); ++} ++ + } // namespace autofill +diff --git a/chrome/browser/autofill/autofill_uitest_util.h b/chrome/browser/autofill/autofill_uitest_util.h +index df333ecf76a98def05aa55fa0c332010fb4eebd0..c95a3888563d43fa26196b7164c73db96cb80343 100644 +--- a/chrome/browser/autofill/autofill_uitest_util.h ++++ b/chrome/browser/autofill/autofill_uitest_util.h +@@ -24,6 +24,9 @@ void AddTestAutofillData(Browser* browser, + const CreditCard& card); + void WaitForPersonalDataChange(Browser* browser); + ++// Adjusted from crrev/c/2628287 to fix failure in crrev/c/2822155 ++void WaitForPersonalDataManagerToBeLoaded(Browser* browser); ++ + } // namespace autofill + + #endif // CHROME_BROWSER_AUTOFILL_AUTOFILL_UITEST_UTIL_H_ +diff --git a/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc b/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc +index 7fbfcfe10bb7ed8987aaddabf3562c7a25efbaf4..31cb5347160cc069ac0fd16abd3f76017d82314a 100644 +--- a/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc ++++ b/chrome/browser/ui/passwords/password_generation_popup_view_browsertest.cc +@@ -26,9 +26,15 @@ class TestPasswordGenerationPopupController + explicit TestPasswordGenerationPopupController( + content::WebContents* web_contents) + : PasswordGenerationPopupControllerImpl( +- gfx::RectF(0, 0, 10, 10), ++ gfx::RectF(web_contents->GetContainerBounds().x(), ++ web_contents->GetContainerBounds().y(), ++ 10, ++ 10), + autofill::password_generation::PasswordGenerationUIData( +- /*bounds=*/gfx::RectF(0, 0, 10, 10), ++ /*bounds=*/gfx::RectF(web_contents->GetContainerBounds().x(), ++ web_contents->GetContainerBounds().y(), ++ 10, ++ 10), + /*max_length=*/10, + /*generation_element=*/base::string16(), + autofill::FieldRendererId(100), +@@ -70,7 +76,9 @@ IN_PROC_BROWSER_TEST_F(PasswordGenerationPopupViewTest, + new autofill::TestPasswordGenerationPopupController(GetWebContents()); + controller_->Show(PasswordGenerationPopupController::kEditGeneratedPassword); + +- GetViewTester()->SimulateMouseMovementAt(gfx::Point(1, 1)); ++ GetViewTester()->SimulateMouseMovementAt( ++ gfx::Point(GetWebContents()->GetContainerBounds().x() + 1, ++ GetWebContents()->GetContainerBounds().y() + 1)); + + // This hides the popup and destroys the controller. + GetWebContents()->Close(); +diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc +index cb0c7e0b669fcae0f9c842221be47c2866a18d39..f748f66b78f6880dc72037e09b86697f30b696c2 100644 +--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc ++++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view.cc +@@ -259,8 +259,8 @@ bool AutofillPopupBaseView::DoUpdateBoundsAndRedrawPopup() { + // area so that the user notices the presence of the popup. + int item_height = + children().size() > 0 ? children()[0]->GetPreferredSize().height() : 0; +- if (!HasEnoughHeightForOneRow(item_height, GetContentAreaBounds(), +- element_bounds)) { ++ if (!CanShowDropdownHere(item_height, GetContentAreaBounds(), ++ element_bounds)) { + HideController(PopupHidingReason::kInsufficientSpace); + return false; + } +diff --git a/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc b/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc +index fcf2076ddff00a4a010f8d5c14c9b99070aa6911..92b79871919e971aff7043718b0187d8b9e8972c 100644 +--- a/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc ++++ b/chrome/browser/ui/views/autofill/autofill_popup_base_view_browsertest.cc +@@ -53,10 +53,13 @@ class AutofillPopupBaseViewTest : public InProcessBrowserTest { + ~AutofillPopupBaseViewTest() override {} + + void SetUpOnMainThread() override { +- gfx::NativeView native_view = +- browser()->tab_strip_model()->GetActiveWebContents()->GetNativeView(); ++ content::WebContents* web_contents = ++ browser()->tab_strip_model()->GetActiveWebContents(); ++ gfx::NativeView native_view = web_contents->GetNativeView(); + EXPECT_CALL(mock_delegate_, container_view()) + .WillRepeatedly(Return(native_view)); ++ EXPECT_CALL(mock_delegate_, GetWebContents()) ++ .WillRepeatedly(Return(web_contents)); + EXPECT_CALL(mock_delegate_, ViewDestroyed()); + + view_ = new AutofillPopupBaseView( +diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc +index 6622a15d0c10c4a3cc1d4fc69aee682372e42aa6..be6dd2e36d2ba0c9173c158f31c7dae9856adc2c 100644 +--- a/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc ++++ b/chrome/browser/ui/views/autofill/autofill_popup_view_native_views.cc +@@ -1229,8 +1229,9 @@ bool AutofillPopupViewNativeViews::DoUpdateBoundsAndRedrawPopup() { + body_container_ && body_container_->children().size() > 0 + ? body_container_->children()[0]->GetPreferredSize().height() + : 0; +- if (!HasEnoughHeightForOneRow(item_height, GetContentAreaBounds(), +- element_bounds)) { ++ ++ if (!CanShowDropdownHere(item_height, GetContentAreaBounds(), ++ element_bounds)) { + controller_->Hide(PopupHidingReason::kInsufficientSpace); + return false; + } +diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc +index e95fe363ba65c7bf6223820f5f3ec110d395cbeb..82f528c39a6edeb329bc8c19321831caca41e553 100644 +--- a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc ++++ b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.cc +@@ -87,16 +87,26 @@ gfx::Rect CalculatePopupBounds(const gfx::Size& desired_size, + return popup_bounds; + } + +-bool HasEnoughHeightForOneRow(int item_height, +- const gfx::Rect& content_area_bounds, +- const gfx::Rect& element_bounds) { +- // Ensure that at least one row of the popup can be displayed within the ++bool CanShowDropdownHere(int item_height, ++ const gfx::Rect& content_area_bounds, ++ const gfx::Rect& element_bounds) { ++ // Ensure that at least one row of the popup will be displayed within the + // bounds of the content area so that the user notices the presence of the + // popup. + bool enough_space_for_one_item_in_content_area_above_element = + element_bounds.y() - content_area_bounds.y() >= item_height; ++ bool element_top_is_within_content_area_bounds = ++ element_bounds.y() > content_area_bounds.y() && ++ element_bounds.y() < content_area_bounds.bottom(); ++ + bool enough_space_for_one_item_in_content_area_below_element = + content_area_bounds.bottom() - element_bounds.bottom() >= item_height; +- return enough_space_for_one_item_in_content_area_above_element || +- enough_space_for_one_item_in_content_area_below_element; ++ bool element_bottom_is_within_content_area_bounds = ++ element_bounds.bottom() > content_area_bounds.y() && ++ element_bounds.bottom() < content_area_bounds.bottom(); ++ ++ return (enough_space_for_one_item_in_content_area_above_element && ++ element_top_is_within_content_area_bounds) || ++ (enough_space_for_one_item_in_content_area_below_element && ++ element_bottom_is_within_content_area_bounds); + } +diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h +index 990d67cababe9363fe5ee1b91bee81986846f807..bab1a26c38f7fcbea7da51e92e0d8e3dfe97622b 100644 +--- a/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h ++++ b/chrome/browser/ui/views/autofill/autofill_popup_view_utils.h +@@ -34,9 +34,10 @@ gfx::Rect CalculatePopupBounds(const gfx::Size& desired_size, + bool is_rtl); + + // Returns whether there is enough height within |content_area_bounds| above or +-// below |element_bounds| to display |item_height|. +-bool HasEnoughHeightForOneRow(int item_height, +- const gfx::Rect& content_area_bounds, +- const gfx::Rect& element_bounds); ++// below |element_bounds| to display |item_height|, and that the first dropdown ++// item will actually be visible within the bounds of the content area. ++bool CanShowDropdownHere(int item_height, ++ const gfx::Rect& content_area_bounds, ++ const gfx::Rect& element_bounds); + + #endif // CHROME_BROWSER_UI_VIEWS_AUTOFILL_AUTOFILL_POPUP_VIEW_UTILS_H_ +diff --git a/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc b/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc +index 78fa8a09a4791bde5825935e8f2b7e805c100b1f..cecd8d2c3173ea7b48ebebf5ae33fd2c531e6551 100644 +--- a/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc ++++ b/chrome/browser/ui/views/autofill/autofill_popup_view_utils_unittest.cc +@@ -1,3 +1,4 @@ ++ + // Copyright 2020 The Chromium Authors. All rights reserved. + // Use of this source code is governed by a BSD-style license that can be + // found in the LICENSE file. +@@ -115,7 +116,34 @@ TEST(AutofillPopupViewUtilsTest, NotEnoughHeightForAnItem) { + gfx::Rect content_area_bounds(x, window_y + 2, width, height - 2); + gfx::Rect element_bounds(x, window_y + 3, width, height - 3); + +- bool enough_height_for_item = HasEnoughHeightForOneRow( +- item_height, content_area_bounds, element_bounds); +- EXPECT_FALSE(enough_height_for_item); ++ EXPECT_FALSE( ++ CanShowDropdownHere(item_height, content_area_bounds, element_bounds)); ++} ++ ++TEST(AutofillPopupViewUtilsTest, ElementOutOfContentAreaBounds) { ++ // In this test, each row of the popup has a height of 8 pixels, and there is ++ // no enough height in the content area to show one row. ++ // ++ // |---------------------| ---> y = 5 ++ // | Window | ++ // | |-----------------| | ---> y = 7 ++ // | | | | ++ // | | Content Area | | ++ // | | | | ++ // |-|-----------------|-| ---> y = 50 ++ // |-------------| ---> y = 53 ++ // | Element | ++ // |-------------| ---> y = 63 ++ ++ constexpr int item_height = 8; ++ constexpr int window_y = 5; ++ constexpr int x = 10; ++ constexpr int width = 5; ++ constexpr int height = 46; ++ ++ gfx::Rect content_area_bounds(x, window_y + 2, width, height - 2); ++ gfx::Rect element_bounds(x, window_y + height + 3, width, 10); ++ ++ EXPECT_FALSE( ++ CanShowDropdownHere(item_height, content_area_bounds, element_bounds)); + } diff --git a/patches/chromium/cherry-pick-f440137cd96a.patch b/patches/chromium/cherry-pick-f440137cd96a.patch new file mode 100644 index 0000000000000..547eb6eb0a61b --- /dev/null +++ b/patches/chromium/cherry-pick-f440137cd96a.patch @@ -0,0 +1,155 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Stephen Chenney +Date: Wed, 14 Oct 2020 02:52:47 +0000 +Subject: Implement WebGL image-orientation + +M-86 merge. + +When creating textures for WebGL, always orient images +with EXIF orientation data. + +This change also corrects the transposed size reported by +ImageBitmap. And it removes superfluous arguments from +CopyImageData. + +(cherry picked from commit f373458c504c2d115c42f31b29ff5c19674acbbc) + +Bug: 1100470, 1125337 +Change-Id: I79aa798327a3582939aa574723926b3325c80e7c +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2459400 +Reviewed-by: Kenneth Russell +Commit-Queue: Stephen Chenney +Cr-Original-Commit-Position: refs/heads/master@{#815359} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2469776 +Reviewed-by: Stephen Chenney +Cr-Commit-Position: refs/branch-heads/4240@{#1237} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc +index b1cdb23186256ca2b4082d2acf556d98c910f145..3e2b5373fdba7bcd0abe866644e17fcb3cee167e 100644 +--- a/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc ++++ b/third_party/blink/renderer/core/imagebitmap/image_bitmap.cc +@@ -189,14 +189,16 @@ SkImageInfo GetSkImageInfo(const scoped_refptr& input) { + + // This function results in a readback due to using SkImage::readPixels(). + // Returns transparent black pixels if the input SkImageInfo.bounds() does +-// not intersect with the input image boundaries. ++// not intersect with the input image boundaries. When apply_orientation ++// is true this method will orient the data according to the source's EXIF ++// information. + Vector CopyImageData(const scoped_refptr& input, + const SkImageInfo& info, +- const unsigned x = 0, +- const unsigned y = 0) { ++ bool apply_orientation = true) { + if (info.isEmpty()) + return {}; +- sk_sp sk_image = input->PaintImageForCurrentFrame().GetSkImage(); ++ PaintImage paint_image = input->PaintImageForCurrentFrame(); ++ sk_sp sk_image = paint_image.GetSkImage(); + if (sk_image->bounds().isEmpty()) + return {}; + +@@ -205,16 +207,30 @@ Vector CopyImageData(const scoped_refptr& input, + Vector dst_buffer(byte_length); + + bool read_pixels_successful = +- sk_image->readPixels(info, dst_buffer.data(), info.minRowBytes(), x, y); ++ sk_image->readPixels(info, dst_buffer.data(), info.minRowBytes(), 0, 0); + DCHECK(read_pixels_successful); + if (!read_pixels_successful) + return {}; ++ ++ // Orient the data, and re-read the pixels. ++ if (apply_orientation && !input->HasDefaultOrientation()) { ++ paint_image = Image::ResizeAndOrientImage( ++ paint_image, input->CurrentFrameOrientation(), FloatSize(1, 1), 1, ++ kInterpolationNone); ++ sk_image = paint_image.GetSkImage(); ++ read_pixels_successful = sk_image->readPixels(info, dst_buffer.data(), ++ info.minRowBytes(), 0, 0); ++ DCHECK(read_pixels_successful); ++ if (!read_pixels_successful) ++ return {}; ++ } ++ + return dst_buffer; + } + + Vector CopyImageData(const scoped_refptr& input) { + SkImageInfo info = GetSkImageInfo(input); +- return CopyImageData(std::move(input), info); ++ return CopyImageData(std::move(input), info, false); + } + + static inline bool ShouldAvoidPremul( +@@ -1053,12 +1069,13 @@ Vector ImageBitmap::CopyBitmapData(AlphaDisposition alpha_op, + auto color_type = info.colorType(); + if (color_type == kN32_SkColorType && u8_color_type == kRGBAColorType) + color_type = kRGBA_8888_SkColorType; ++ // Note that width() and height() here apply EXIF orientation + info = + SkImageInfo::Make(width(), height(), color_type, + (alpha_op == kPremultiplyAlpha) ? kPremul_SkAlphaType + : kUnpremul_SkAlphaType, + info.refColorSpace()); +- return CopyImageData(image_, info); ++ return CopyImageData(image_, info, true); + } + + Vector ImageBitmap::CopyBitmapData() { +@@ -1090,7 +1107,7 @@ IntSize ImageBitmap::Size() const { + return IntSize(); + DCHECK_GT(image_->width(), 0); + DCHECK_GT(image_->height(), 0); +- return IntSize(image_->width(), image_->height()); ++ return image_->SizeRespectingOrientation(); + } + + ScriptPromise ImageBitmap::CreateImageBitmap(ScriptState* script_state, +diff --git a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +index 457b7c3d2bef75bb378f8dfc5b61b932b6941fb6..d7445bf31a391bea3742327c34dc3eb46c72513b 100644 +--- a/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc ++++ b/third_party/blink/renderer/modules/webgl/webgl_rendering_context_base.cc +@@ -5307,10 +5307,12 @@ void WebGLRenderingContextBase::TexImageHelperHTMLImageElement( + return; + + scoped_refptr image_for_render = image->CachedImage()->GetImage(); +- if (IsA(image_for_render.get())) { +- if (canvas()) { ++ bool have_svg_image = IsA(image_for_render.get()); ++ if (have_svg_image || !image_for_render->HasDefaultOrientation()) { ++ if (have_svg_image && canvas()) { + UseCounter::Count(canvas()->GetDocument(), WebFeature::kSVGInWebGL); + } ++ // DrawImageIntoBuffer always respects orientation + image_for_render = + DrawImageIntoBuffer(std::move(image_for_render), image->width(), + image->height(), func_name); +@@ -5846,6 +5848,7 @@ void WebGLRenderingContextBase::TexImageHelperImageBitmap( + level, internalformat, width, height, depth, 0, format, + type, xoffset, yoffset, zoffset)) + return; ++ + scoped_refptr image = bitmap->BitmapImage(); + DCHECK(image); + +@@ -5872,9 +5875,16 @@ void WebGLRenderingContextBase::TexImageHelperImageBitmap( + return; + } + ++ // Apply orientation if necessary ++ PaintImage paint_image = bitmap->BitmapImage()->PaintImageForCurrentFrame(); ++ if (!image->HasDefaultOrientation()) { ++ paint_image = Image::ResizeAndOrientImage( ++ paint_image, image->CurrentFrameOrientation(), FloatSize(1, 1), 1, ++ kInterpolationNone); ++ } ++ + // TODO(kbr): refactor this away to use TexImageImpl on image. +- sk_sp sk_image = +- bitmap->BitmapImage()->PaintImageForCurrentFrame().GetSkImage(); ++ sk_sp sk_image = paint_image.GetSkImage(); + if (!sk_image) { + SynthesizeGLError(GL_OUT_OF_MEMORY, func_name, + "ImageBitmap unexpectedly empty"); diff --git a/patches/chromium/cherry-pick-fe20b05a0e5e.patch b/patches/chromium/cherry-pick-fe20b05a0e5e.patch new file mode 100644 index 0000000000000..ece508e56af62 --- /dev/null +++ b/patches/chromium/cherry-pick-fe20b05a0e5e.patch @@ -0,0 +1,140 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Jana Grill +Date: Tue, 20 Apr 2021 18:23:33 +0000 +Subject: M86-LTS: DevTools: expect PageHandler may be destroyed during + Page.navigate + +(cherry picked from commit ff5e70191ec701cce4f84aaa25cd676376253a8a) + +Bug: 1188889 +Change-Id: I5c2fcca84834d66c46d77a70683212c2330177a5 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2787756 +Commit-Queue: Andrey Kosyakov +Reviewed-by: Dmitry Gozman +Reviewed-by: Karan Bhatia +Cr-Original-Commit-Position: refs/heads/master@{#867507} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2821536 +Commit-Queue: Achuith Bhandarkar +Reviewed-by: Achuith Bhandarkar +Reviewed-by: Victor-Gabriel Savu +Owners-Override: Achuith Bhandarkar +Cr-Commit-Position: refs/branch-heads/4240@{#1618} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/chrome/browser/extensions/api/debugger/debugger_apitest.cc b/chrome/browser/extensions/api/debugger/debugger_apitest.cc +index 71ce5a3399db29451e990d530736460aa28eeec0..b35accc8ce46f3465624898fe18d463529498d07 100644 +--- a/chrome/browser/extensions/api/debugger/debugger_apitest.cc ++++ b/chrome/browser/extensions/api/debugger/debugger_apitest.cc +@@ -24,6 +24,7 @@ + #include "components/sessions/content/session_tab_helper.h" + #include "content/public/test/browser_test.h" + #include "content/public/test/browser_test_utils.h" ++#include "content/public/test/no_renderer_crashes_assertion.h" + #include "extensions/browser/extension_function.h" + #include "extensions/common/extension.h" + #include "extensions/common/extension_builder.h" +@@ -353,6 +354,19 @@ IN_PROC_BROWSER_TEST_F(DebuggerExtensionApiTest, + << message_; + } + ++// Tests that navigation to a forbidden URL is properly denied and ++// does not cause a crash. ++// This is a regression test for https://crbug.com/1188889. ++IN_PROC_BROWSER_TEST_F(DebuggerExtensionApiTest, DISABLED_NavigateToForbiddenUrl) { ++ content::ScopedAllowRendererCrashes scoped_allow_renderer_crashes; ++ // We don't send a DevTools command callback before disconnecting the session, ++ // so the extension does not receive a callback either. ++ base::AutoReset ignore_did_respond( ++ &ExtensionFunction::ignore_all_did_respond_for_testing_do_not_use, true); ++ ASSERT_TRUE(RunExtensionTest("debugger_navigate_to_forbidden_url")) ++ << message_; ++} ++ + class SitePerProcessDebuggerExtensionApiTest : public DebuggerExtensionApiTest { + public: + void SetUpCommandLine(base::CommandLine* command_line) override { +diff --git a/chrome/test/data/extensions/api_test/debugger_navigate_to_forbidden_url/background.js b/chrome/test/data/extensions/api_test/debugger_navigate_to_forbidden_url/background.js +new file mode 100644 +index 0000000000000000000000000000000000000000..e2ef32fffd3e5d49e7dc10d53f8c891ddb0f3872 +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/debugger_navigate_to_forbidden_url/background.js +@@ -0,0 +1,28 @@ ++// Copyright 2021 The Chromium Authors. All rights reserved. ++// Use of this source code is governed by a BSD-style license that can be ++// found in the LICENSE file. ++ ++const protocolVersion = '1.3'; ++const DETACHED_WHILE_HANDLING = 'Detached while handling command.'; ++ ++chrome.test.runTests([ ++ async function testNavigateToForbiddenUrl() { ++ const {openTab} = await import('/_test_resources/test_util/tabs_util.js'); ++ const tab = await openTab('about:blank'); ++ const debuggee = {tabId: tab.id}; ++ await new Promise(resolve => ++ chrome.debugger.attach(debuggee, protocolVersion, resolve)); ++ chrome.debugger.sendCommand(debuggee, 'Page.crash'); ++ await new Promise(resolve => ++ chrome.debugger.onEvent.addListener((source, method, params) => { ++ if (method === 'Inspector.targetCrashed') ++ resolve(); ++ })); ++ const result = await new Promise(resolve => ++ chrome.debugger.sendCommand(debuggee, 'Page.navigate', { ++ url: 'chrome://version' ++ }, resolve)); ++ chrome.test.assertLastError(DETACHED_WHILE_HANDLING); ++ chrome.test.succeed(); ++ } ++]); +diff --git a/chrome/test/data/extensions/api_test/debugger_navigate_to_forbidden_url/manifest.json b/chrome/test/data/extensions/api_test/debugger_navigate_to_forbidden_url/manifest.json +new file mode 100644 +index 0000000000000000000000000000000000000000..05db294ed7f49893431b0039a5f338d20e08f27d +--- /dev/null ++++ b/chrome/test/data/extensions/api_test/debugger_navigate_to_forbidden_url/manifest.json +@@ -0,0 +1,11 @@ ++{ ++ "name": "Debugger API test for CDP-initiated navigation to forbidden URLs", ++ "version": "1.0", ++ "manifest_version": 2, ++ "background": { ++ "scripts": ["background.js"] ++ }, ++ "permissions": [ ++ "debugger" ++ ] ++} +diff --git a/content/browser/devtools/protocol/page_handler.cc b/content/browser/devtools/protocol/page_handler.cc +index 630de0dd016fd3d054bcd40b22d75a242eeaa23e..a340d3e4519ada9edba279090ea11b57521ef0f4 100644 +--- a/content/browser/devtools/protocol/page_handler.cc ++++ b/content/browser/devtools/protocol/page_handler.cc +@@ -496,7 +496,12 @@ void PageHandler::Navigate(const std::string& url, + params.referrer = Referrer(GURL(referrer.fromMaybe("")), policy); + params.transition_type = type; + params.frame_tree_node_id = frame_tree_node->frame_tree_node_id(); ++ // Handler may be destroyed while navigating if the session ++ // gets disconnected as a result of access checks. ++ base::WeakPtr weak_self = weak_factory_.GetWeakPtr(); + frame_tree_node->navigator().GetController()->LoadURLWithParams(params); ++ if (!weak_self) ++ return; + + base::UnguessableToken frame_token = frame_tree_node->devtools_frame_token(); + auto navigate_callback = navigate_callbacks_.find(frame_token); +diff --git a/content/browser/devtools/render_frame_devtools_agent_host.cc b/content/browser/devtools/render_frame_devtools_agent_host.cc +index 52fdd0f1066699cc019c33de2517c23f12b4a616..8795c547717b206f4e459f655f6e62a7ba9229e0 100644 +--- a/content/browser/devtools/render_frame_devtools_agent_host.cc ++++ b/content/browser/devtools/render_frame_devtools_agent_host.cc +@@ -472,8 +472,11 @@ void RenderFrameDevToolsAgentHost::UpdateFrameHost( + if (!ShouldAllowSession(session)) + restricted_sessions.push_back(session); + } +- if (!restricted_sessions.empty()) ++ scoped_refptr protect; ++ if (!restricted_sessions.empty()) { ++ protect = this; + ForceDetachRestrictedSessions(restricted_sessions); ++ } + + UpdateFrameAlive(); + } diff --git a/patches/chromium/cherry-pick-fe85e04a1797.patch b/patches/chromium/cherry-pick-fe85e04a1797.patch new file mode 100644 index 0000000000000..2fae048bacdd8 --- /dev/null +++ b/patches/chromium/cherry-pick-fe85e04a1797.patch @@ -0,0 +1,291 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Ken Rockot +Date: Wed, 31 Mar 2021 18:44:06 +0000 +Subject: Don't use BigBuffer for IPC::Message transport + +M86 merge conflicts and resolution: +* ipc/ipc_message_pipe_reader.cc + Fixed extra include. + +(cherry picked from commit 85bd7c88523545ab0e497d5e7b3e929793813358) + +(cherry picked from commit fad3b9ffe7c7ff82909d911c573bd185aa3b3b50) + +Fixed: 1184399 +Change-Id: Iddd91ae8d7ae63022b61c96239f5e39261dfb735 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2737012 +Commit-Queue: Ken Rockot +Reviewed-by: Daniel Cheng +Cr-Original-Original-Commit-Position: refs/heads/master@{#860010} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2779918 +Auto-Submit: Ken Rockot +Reviewed-by: Adrian Taylor +Reviewed-by: Alex Gough +Commit-Queue: Alex Gough +Cr-Original-Commit-Position: refs/branch-heads/4389@{#1597} +Cr-Original-Branched-From: 9251c5db2b6d5a59fe4eac7aafa5fed37c139bb7-refs/heads/master@{#843830} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2794488 +Reviewed-by: Victor-Gabriel Savu +Reviewed-by: Artem Sumaneev +Reviewed-by: Ken Rockot +Auto-Submit: Artem Sumaneev +Commit-Queue: Artem Sumaneev +Cr-Commit-Position: refs/branch-heads/4240@{#1587} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/ipc/BUILD.gn b/ipc/BUILD.gn +index 994d45e84502670f544742a3011a8f9381a711bc..281e84df83ae73e8c4716fda1624cb4065342c76 100644 +--- a/ipc/BUILD.gn ++++ b/ipc/BUILD.gn +@@ -187,10 +187,7 @@ mojom_component("mojom") { + output_prefix = "ipc_mojom" + macro_prefix = "IPC_MOJOM" + sources = [ "ipc.mojom" ] +- public_deps = [ +- "//mojo/public/interfaces/bindings", +- "//mojo/public/mojom/base", +- ] ++ public_deps = [ "//mojo/public/interfaces/bindings" ] + + cpp_typemaps = [ + { +@@ -207,10 +204,7 @@ mojom_component("mojom") { + "//ipc/message_view.cc", + "//ipc/message_view.h", + ] +- traits_public_deps = [ +- "//ipc:message_support", +- "//mojo/public/cpp/base:shared_typemap_traits", +- ] ++ traits_public_deps = [ "//ipc:message_support" ] + }, + ] + +diff --git a/ipc/ipc.mojom b/ipc/ipc.mojom +index c66799642fbee2cef3449ff5d52cd5f187808cfe..4606022b28bca1df06ba6eb8eaac025573475b10 100644 +--- a/ipc/ipc.mojom ++++ b/ipc/ipc.mojom +@@ -4,7 +4,6 @@ + + module IPC.mojom; + +-import "mojo/public/mojom/base/big_buffer.mojom"; + import "mojo/public/interfaces/bindings/native_struct.mojom"; + + // A placeholder interface type since we don't yet support generic associated +@@ -14,7 +13,7 @@ interface GenericInterface {}; + // Typemapped such that arbitrarily large IPC::Message objects can be sent and + // received with minimal copying. + struct Message { +- mojo_base.mojom.BigBuffer buffer; ++ array bytes; + array? handles; + }; + +@@ -24,6 +23,7 @@ interface Channel { + SetPeerPid(int32 pid); + + // Transmits a classical Chrome IPC message. ++ [UnlimitedSize] + Receive(Message message); + + // Requests a Channel-associated interface. +diff --git a/ipc/ipc_message_pipe_reader.cc b/ipc/ipc_message_pipe_reader.cc +index bdc5dd680d0f9107719765334d0a1ea3e864e200..cbf0363a9d941db1ab34ae835e707b7825447659 100644 +--- a/ipc/ipc_message_pipe_reader.cc ++++ b/ipc/ipc_message_pipe_reader.cc +@@ -10,6 +10,7 @@ + + #include "base/bind.h" + #include "base/bind_helpers.h" ++#include "base/containers/span.h" + #include "base/location.h" + #include "base/logging.h" + #include "base/macros.h" +@@ -62,7 +63,9 @@ bool MessagePipeReader::Send(std::unique_ptr message) { + if (!sender_) + return false; + +- sender_->Receive(MessageView(*message, std::move(handles))); ++ base::span bytes(static_cast(message->data()), ++ message->size()); ++ sender_->Receive(MessageView(bytes, std::move(handles))); + DVLOG(4) << "Send " << message->type() << ": " << message->size(); + return true; + } +@@ -82,11 +85,12 @@ void MessagePipeReader::SetPeerPid(int32_t peer_pid) { + } + + void MessagePipeReader::Receive(MessageView message_view) { +- if (!message_view.size()) { ++ if (message_view.bytes().empty()) { + delegate_->OnBrokenDataReceived(); + return; + } +- Message message(message_view.data(), message_view.size()); ++ Message message(reinterpret_cast(message_view.bytes().data()), ++ message_view.bytes().size()); + if (!message.IsValid()) { + delegate_->OnBrokenDataReceived(); + return; +diff --git a/ipc/ipc_mojo_bootstrap_unittest.cc b/ipc/ipc_mojo_bootstrap_unittest.cc +index 47a7ad79a30165c76041075be10b9be8c13f5e75..b32941da752a54ba7317e439150982adbb9fbcad 100644 +--- a/ipc/ipc_mojo_bootstrap_unittest.cc ++++ b/ipc/ipc_mojo_bootstrap_unittest.cc +@@ -77,7 +77,9 @@ class PeerPidReceiver : public IPC::mojom::Channel { + ASSERT_NE(MessageExpectation::kNotExpected, message_expectation_); + received_message_ = true; + +- IPC::Message message(message_view.data(), message_view.size()); ++ IPC::Message message( ++ reinterpret_cast(message_view.bytes().data()), ++ message_view.bytes().size()); + bool expected_valid = + message_expectation_ == MessageExpectation::kExpectedValid; + EXPECT_EQ(expected_valid, message.IsValid()); +@@ -196,8 +198,7 @@ MULTIPROCESS_TEST_MAIN_WITH_SETUP( + + uint8_t data = 0; + sender->Receive( +- IPC::MessageView(mojo_base::BigBufferView(base::make_span(&data, 0)), +- base::nullopt /* handles */)); ++ IPC::MessageView(base::make_span(&data, 0), base::nullopt /* handles */)); + + base::RunLoop run_loop; + PeerPidReceiver impl(std::move(receiver), run_loop.QuitClosure()); +diff --git a/ipc/message_mojom_traits.cc b/ipc/message_mojom_traits.cc +index 4aab9248e9ff6ca8e2d7d085ae3e996ac04666e8..d8ad4a2f919b01362e3e2746bfb7f4fae77b059d 100644 +--- a/ipc/message_mojom_traits.cc ++++ b/ipc/message_mojom_traits.cc +@@ -4,15 +4,13 @@ + + #include "ipc/message_mojom_traits.h" + +-#include "mojo/public/cpp/base/big_buffer_mojom_traits.h" +- + namespace mojo { + + // static +-mojo_base::BigBufferView +-StructTraits::buffer( ++base::span ++StructTraits::bytes( + IPC::MessageView& view) { +- return view.TakeBufferView(); ++ return view.bytes(); + } + + // static +@@ -26,14 +24,14 @@ StructTraits::handles( + bool StructTraits::Read( + IPC::mojom::MessageDataView data, + IPC::MessageView* out) { +- mojo_base::BigBufferView buffer_view; +- if (!data.ReadBuffer(&buffer_view)) +- return false; ++ mojo::ArrayDataView bytes; ++ data.GetBytesDataView(&bytes); ++ + base::Optional> handles; + if (!data.ReadHandles(&handles)) + return false; + +- *out = IPC::MessageView(std::move(buffer_view), std::move(handles)); ++ *out = IPC::MessageView(bytes, std::move(handles)); + return true; + } + +diff --git a/ipc/message_mojom_traits.h b/ipc/message_mojom_traits.h +index 617ffbe37309946464e3f180a0ebde97f56dbd75..6b5064a12191e9a663519e7b5cb7c5f907a75054 100644 +--- a/ipc/message_mojom_traits.h ++++ b/ipc/message_mojom_traits.h +@@ -7,10 +7,10 @@ + + #include + ++#include "base/containers/span.h" + #include "base/optional.h" + #include "ipc/ipc.mojom-shared.h" + #include "ipc/message_view.h" +-#include "mojo/public/cpp/base/big_buffer.h" + #include "mojo/public/cpp/bindings/struct_traits.h" + #include "mojo/public/interfaces/bindings/native_struct.mojom.h" + +@@ -19,7 +19,7 @@ namespace mojo { + template <> + class StructTraits { + public: +- static mojo_base::BigBufferView buffer(IPC::MessageView& view); ++ static base::span bytes(IPC::MessageView& view); + static base::Optional> handles( + IPC::MessageView& view); + +diff --git a/ipc/message_view.cc b/ipc/message_view.cc +index 49a80878e7a92cda13105ea0f2fea36ad7ed05e6..39c6608dd507c3ca051b619d966ae521e95fe8e2 100644 +--- a/ipc/message_view.cc ++++ b/ipc/message_view.cc +@@ -11,16 +11,9 @@ namespace IPC { + MessageView::MessageView() = default; + + MessageView::MessageView( +- const Message& message, ++ base::span bytes, + base::Optional> handles) +- : buffer_view_(base::make_span(static_cast(message.data()), +- message.size())), +- handles_(std::move(handles)) {} +- +-MessageView::MessageView( +- mojo_base::BigBufferView buffer_view, +- base::Optional> handles) +- : buffer_view_(std::move(buffer_view)), handles_(std::move(handles)) {} ++ : bytes_(bytes), handles_(std::move(handles)) {} + + MessageView::MessageView(MessageView&&) = default; + +diff --git a/ipc/message_view.h b/ipc/message_view.h +index 4ec059bf3639b9c75178f2300d0796b433e1d2ed..c7801bb963f06b03c51ba87bffc307792b592dae 100644 +--- a/ipc/message_view.h ++++ b/ipc/message_view.h +@@ -11,7 +11,6 @@ + #include "base/containers/span.h" + #include "base/macros.h" + #include "ipc/ipc_message.h" +-#include "mojo/public/cpp/base/big_buffer.h" + #include "mojo/public/interfaces/bindings/native_struct.mojom-forward.h" + + namespace IPC { +@@ -20,30 +19,18 @@ class COMPONENT_EXPORT(IPC_MOJOM) MessageView { + public: + MessageView(); + MessageView( +- const Message& message, +- base::Optional> handles); +- MessageView( +- mojo_base::BigBufferView buffer_view, ++ base::span bytes, + base::Optional> handles); + MessageView(MessageView&&); + ~MessageView(); + + MessageView& operator=(MessageView&&); + +- const char* data() const { +- return reinterpret_cast(buffer_view_.data().data()); +- } +- +- uint32_t size() const { +- return static_cast(buffer_view_.data().size()); +- } +- +- mojo_base::BigBufferView TakeBufferView() { return std::move(buffer_view_); } +- ++ base::span bytes() const { return bytes_; } + base::Optional> TakeHandles(); + + private: +- mojo_base::BigBufferView buffer_view_; ++ base::span bytes_; + base::Optional> handles_; + + DISALLOW_COPY_AND_ASSIGN(MessageView); diff --git a/patches/chromium/chore_expose_v8_initialization_isolate_callbacks.patch b/patches/chromium/chore_expose_v8_initialization_isolate_callbacks.patch new file mode 100644 index 0000000000000..401d3003e62e1 --- /dev/null +++ b/patches/chromium/chore_expose_v8_initialization_isolate_callbacks.patch @@ -0,0 +1,37 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shelley Vohr +Date: Mon, 5 Oct 2020 13:43:59 -0700 +Subject: chore: expose v8 initialization isolate callbacks + +This commit is necessary in order to ensure consistent behavior from +v8 Isolate callbacks in contexts which Node.js does not control. If +we're running with contextIsolation enabled, we should be falling back +to Blink's logic. This will be upstreamed in some form. + +diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc +index 21504ce01403d20067c8439c0c61ee0d71de84a5..13855078e4e9531304d30ec46cd2bb79798623ad 100644 +--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc ++++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.cc +@@ -452,7 +452,7 @@ CodeGenerationCheckCallbackInMainThread(v8::Local context, + return {true, std::move(stringified_source)}; + } + +-static bool WasmCodeGenerationCheckCallbackInMainThread( ++bool V8Initializer::WasmCodeGenerationCheckCallbackInMainThread( + v8::Local context, + v8::Local source) { + if (ExecutionContext* execution_context = ToExecutionContext(context)) { +diff --git a/third_party/blink/renderer/bindings/core/v8/v8_initializer.h b/third_party/blink/renderer/bindings/core/v8/v8_initializer.h +index e7cbc5db7d15aa0fcfb37ba261673b973827296a..6b93aa449a005e06862a99ea0c9b751ffff2d6ec 100644 +--- a/third_party/blink/renderer/bindings/core/v8/v8_initializer.h ++++ b/third_party/blink/renderer/bindings/core/v8/v8_initializer.h +@@ -67,6 +67,9 @@ class CORE_EXPORT V8Initializer { + v8::Local); + static void MessageHandlerInWorker(v8::Local, + v8::Local); ++ static bool WasmCodeGenerationCheckCallbackInMainThread( ++ v8::Local context, ++ v8::Local source); + }; + + } // namespace blink diff --git a/patches/chromium/crashpad-initialize-logging.patch b/patches/chromium/crashpad-initialize-logging.patch new file mode 100644 index 0000000000000..32156ea3e570e --- /dev/null +++ b/patches/chromium/crashpad-initialize-logging.patch @@ -0,0 +1,146 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Joshua Peraza +Date: Wed, 21 Oct 2020 11:10:25 -0700 +Subject: Initialize logging for crashpad + +Although logging to files is not yet supported by mini_chromium, it is +the default behavior for OS_WIN in chromium. This change should +cause crashpad to log via OutputDebugString() on Windows, instead of +debug.log files. Future work (crbug.com/crashpad/26) should arrange for +logs to be uploaded with reports, embedded in associated minidumps or as +file attachments. + +Bug: chromium:711159 +Change-Id: I0f9004f7de94dd29d555cc7d23c48a63da6b4bba +Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/2425108 +Reviewed-by: Mark Mentovai + +diff --git a/base/logging.cc b/base/logging.cc +index 2da71c73819b34a45911131e2cc8bff861178789..6b701b7a8dee0d015758e11771c5c1f3a6b8d13b 100644 +--- a/base/logging.cc ++++ b/base/logging.cc +@@ -421,21 +421,23 @@ bool BaseInitLoggingImpl(const LoggingSettings& settings) { + 0u); + #endif + +- base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); +- // Don't bother initializing |g_vlog_info| unless we use one of the +- // vlog switches. +- if (command_line->HasSwitch(switches::kV) || +- command_line->HasSwitch(switches::kVModule)) { +- // NOTE: If |g_vlog_info| has already been initialized, it might be in use +- // by another thread. Don't delete the old VLogInfo, just create a second +- // one. We keep track of both to avoid memory leak warnings. +- CHECK(!g_vlog_info_prev); +- g_vlog_info_prev = g_vlog_info; +- +- g_vlog_info = +- new VlogInfo(command_line->GetSwitchValueASCII(switches::kV), +- command_line->GetSwitchValueASCII(switches::kVModule), +- &g_min_log_level); ++ if (base::CommandLine::InitializedForCurrentProcess()) { ++ base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); ++ // Don't bother initializing |g_vlog_info| unless we use one of the ++ // vlog switches. ++ if (command_line->HasSwitch(switches::kV) || ++ command_line->HasSwitch(switches::kVModule)) { ++ // NOTE: If |g_vlog_info| has already been initialized, it might be in use ++ // by another thread. Don't delete the old VLogInfo, just create a second ++ // one. We keep track of both to avoid memory leak warnings. ++ CHECK(!g_vlog_info_prev); ++ g_vlog_info_prev = g_vlog_info; ++ ++ g_vlog_info = ++ new VlogInfo(command_line->GetSwitchValueASCII(switches::kV), ++ command_line->GetSwitchValueASCII(switches::kVModule), ++ &g_min_log_level); ++ } + } + + g_logging_destination = settings.logging_dest; +@@ -446,7 +448,10 @@ bool BaseInitLoggingImpl(const LoggingSettings& settings) { + config.min_severity = FX_LOG_INFO; + config.console_fd = -1; + config.log_service_channel = ZX_HANDLE_INVALID; +- std::string log_tag = command_line->GetProgram().BaseName().AsUTF8Unsafe(); ++ std::string log_tag = base::CommandLine::ForCurrentProcess() ++ ->GetProgram() ++ .BaseName() ++ .AsUTF8Unsafe(); + const char* log_tag_data = log_tag.data(); + config.tags = &log_tag_data; + config.num_tags = 1; +diff --git a/third_party/crashpad/crashpad/DEPS b/third_party/crashpad/crashpad/DEPS +index ccb447cde9a9ee3c1fe29419abfa8aa63d777455..6ee9db5400cf12ae4812a261e78234581b036c25 100644 +--- a/third_party/crashpad/crashpad/DEPS ++++ b/third_party/crashpad/crashpad/DEPS +@@ -42,7 +42,7 @@ deps = { + '7bde79cc274d06451bf65ae82c012a5d3e476b5a', + 'crashpad/third_party/mini_chromium/mini_chromium': + Var('chromium_git') + '/chromium/mini_chromium@' + +- 'ae14a14ab4cea36db9c446741581d427a7fc7f89', ++ '5fc64bfbf1c000161445c586de45e40464ff2314', + 'crashpad/third_party/libfuzzer/src': + Var('chromium_git') + '/chromium/llvm-project/compiler-rt/lib/fuzzer.git@' + + 'fda403cf93ecb8792cb1d061564d89a6553ca020', +diff --git a/third_party/crashpad/crashpad/handler/handler_main.cc b/third_party/crashpad/crashpad/handler/handler_main.cc +index d56857ff9042a0e4ed55bb101e419948f4028305..579a7c364077d2b6a78803fa75f7180109b37c89 100644 +--- a/third_party/crashpad/crashpad/handler/handler_main.cc ++++ b/third_party/crashpad/crashpad/handler/handler_main.cc +@@ -494,16 +494,26 @@ class ScopedStoppable { + DISALLOW_COPY_AND_ASSIGN(ScopedStoppable); + }; + ++void InitCrashpadLogging() { ++ logging::LoggingSettings settings; ++#if defined(OS_CHROMEOS) ++ settings.logging_dest = logging::LOG_TO_FILE; ++ settings.log_file_path = "/var/log/chrome/chrome"; ++#elif defined(OS_WIN) ++ settings.logging_dest = logging::LOG_TO_SYSTEM_DEBUG_LOG; ++#else ++ settings.logging_dest = ++ logging::LOG_TO_SYSTEM_DEBUG_LOG | logging::LOG_TO_STDERR; ++#endif ++ logging::InitLogging(settings); ++} ++ + } // namespace + + int HandlerMain(int argc, + char* argv[], + const UserStreamDataSources* user_stream_sources) { +-#if defined(OS_CHROMEOS) +- if (freopen("/var/log/chrome/chrome", "a", stderr) == nullptr) { +- PLOG(ERROR) << "Failed to redirect stderr to /var/log/chrome/chrome"; +- } +-#endif ++ InitCrashpadLogging(); + + InstallCrashHandler(); + CallMetricsRecordNormalExit metrics_record_normal_exit; +diff --git a/third_party/crashpad/crashpad/test/gtest_main.cc b/third_party/crashpad/crashpad/test/gtest_main.cc +index 67cfa0d72d7eb469775201f3a9df906f27c302a9..c67b8e24bb940935d5da88428ed3058a135f5a57 100644 +--- a/third_party/crashpad/crashpad/test/gtest_main.cc ++++ b/third_party/crashpad/crashpad/test/gtest_main.cc +@@ -12,6 +12,7 @@ + // See the License for the specific language governing permissions and + // limitations under the License. + ++#include "base/logging.h" + #include "build/build_config.h" + #include "gtest/gtest.h" + #include "test/main_arguments.h" +@@ -99,6 +100,12 @@ int main(int argc, char* argv[]) { + + #endif // CRASHPAD_IS_IN_CHROMIUM + ++ // base::TestSuite initializes logging when using Chromium's test launcher. ++ logging::LoggingSettings settings; ++ settings.logging_dest = ++ logging::LOG_TO_STDERR | logging::LOG_TO_SYSTEM_DEBUG_LOG; ++ logging::InitLogging(settings); ++ + #if defined(CRASHPAD_TEST_LAUNCHER_GOOGLEMOCK) + testing::InitGoogleMock(&argc, argv); + #elif defined(CRASHPAD_TEST_LAUNCHER_GOOGLETEST) diff --git a/patches/chromium/css_make_fetches_from_inline_css_use_the_document_s_url_as_referrer.patch b/patches/chromium/css_make_fetches_from_inline_css_use_the_document_s_url_as_referrer.patch new file mode 100644 index 0000000000000..0883a3c9c5561 --- /dev/null +++ b/patches/chromium/css_make_fetches_from_inline_css_use_the_document_s_url_as_referrer.patch @@ -0,0 +1,447 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: David Van Cleve +Date: Thu, 4 Mar 2021 16:50:46 +0000 +Subject: css: Make fetches from inline CSS use the document's URL as referrer + +Right now, fetches from inline CSS use the inline CSS's base URL +instead of the URL from the context that embeds the inline CSS: for +instance, loading a source-site.com page with the following code + + +should lead to the best-sheet.com sheet getting fetched with a +source-site.com referrer, but it will currently provide an +other-site.com referrer. However, if the imported sheet from +best-sheet.com makes more nested fetches, those nested requests should +use best-sheet.com as the basis for their referrers (as they do +currently). + +This CL updates CSSParserContext's referrer setting logic to roughly do +the following: +- inline CSS: use the embedding document's URL as the referrer, or, for +srcdoc iframes, walk up the frame tree until hitting a non-srcdoc frame +- requests from fetched stylesheets: just as currently, use the fetched +sheet's URL as the basis for constructing the referrer + +This seemed like it required refactoring CSSParserContext slightly +because there are constructors that take both a Document and a base URL, +and it's not obvious from the constructor signature whether the +Document or the base URL should be the one that provides the referrer. +To resolve this ambiguity, the refactor updates these CSSParserContext +constructors to take caller-provided Referrer objects. + +(cherry picked from commit 0b1539fcb923056624d4adc84b88140d367d92da) + +Change-Id: If5a99d8057dff5e771e821d0e1f605566e28ff1d +Fixed: 1158645, 1158010 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2592447 +Reviewed-by: Rune Lillesveen +Reviewed-by: Matt Falkenhagen +Commit-Queue: David Van Cleve +Cr-Original-Commit-Position: refs/heads/master@{#841509} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2731576 +Reviewed-by: Achuith Bhandarkar +Commit-Queue: Victor-Gabriel Savu +Cr-Commit-Position: refs/branch-heads/4240@{#1558} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/third_party/blink/renderer/core/css/css_style_sheet.cc b/third_party/blink/renderer/core/css/css_style_sheet.cc +index c168b0a244865e3c390989e3e5af275fdef2a4cd..10efc5bd894c16de745b7f4bb07268719f443e73 100644 +--- a/third_party/blink/renderer/core/css/css_style_sheet.cc ++++ b/third_party/blink/renderer/core/css/css_style_sheet.cc +@@ -37,6 +37,7 @@ + #include "third_party/blink/renderer/core/dom/document.h" + #include "third_party/blink/renderer/core/dom/node.h" + #include "third_party/blink/renderer/core/frame/deprecation.h" ++#include "third_party/blink/renderer/core/frame/local_dom_window.h" + #include "third_party/blink/renderer/core/html/html_link_element.h" + #include "third_party/blink/renderer/core/html/html_style_element.h" + #include "third_party/blink/renderer/core/html_names.h" +@@ -138,9 +139,15 @@ CSSStyleSheet* CSSStyleSheet::CreateInline(Node& owner_node, + const KURL& base_url, + const TextPosition& start_position, + const WTF::TextEncoding& encoding) { ++ Document& owner_node_document = owner_node.GetDocument(); + auto* parser_context = MakeGarbageCollected( +- owner_node.GetDocument(), owner_node.GetDocument().BaseURL(), +- true /* origin_clean */, owner_node.GetDocument().GetReferrerPolicy(), ++ owner_node_document, owner_node_document.BaseURL(), ++ true /* origin_clean */, ++ Referrer( ++ owner_node_document.GetExecutionContext() ++ ? owner_node_document.GetExecutionContext()->OutgoingReferrer() ++ : String(), // GetExecutionContext() only returns null in tests. ++ owner_node.GetDocument().GetReferrerPolicy()), + encoding); + if (AdTracker::IsAdScriptExecutingInDocument(&owner_node.GetDocument())) + parser_context->SetIsAdRelated(); +diff --git a/third_party/blink/renderer/core/css/parser/css_parser_context.cc b/third_party/blink/renderer/core/css/parser/css_parser_context.cc +index 5c1292cf13b4265c3db36fd3c1a71a30b3c81c68..b636bd5388a871fd284a74b7926aeb5922e274e5 100644 +--- a/third_party/blink/renderer/core/css/parser/css_parser_context.cc ++++ b/third_party/blink/renderer/core/css/parser/css_parser_context.cc +@@ -53,27 +53,25 @@ CSSParserContext::CSSParserContext(const CSSParserContext* other, + is_ad_related_ = other->is_ad_related_; + } + +-CSSParserContext::CSSParserContext( +- const CSSParserContext* other, +- const KURL& base_url, +- bool origin_clean, +- network::mojom::ReferrerPolicy referrer_policy, +- const WTF::TextEncoding& charset, +- const Document* use_counter_document) +- : CSSParserContext( +- base_url, +- origin_clean, +- charset, +- other->mode_, +- other->match_mode_, +- other->profile_, +- Referrer(base_url.StrippedForUseAsReferrer(), referrer_policy), +- other->is_html_document_, +- other->use_legacy_background_size_shorthand_behavior_, +- other->secure_context_mode_, +- other->should_check_content_security_policy_, +- use_counter_document, +- other->resource_fetch_restriction_) { ++CSSParserContext::CSSParserContext(const CSSParserContext* other, ++ const KURL& base_url, ++ bool origin_clean, ++ const Referrer& referrer, ++ const WTF::TextEncoding& charset, ++ const Document* use_counter_document) ++ : CSSParserContext(base_url, ++ origin_clean, ++ charset, ++ other->mode_, ++ other->match_mode_, ++ other->profile_, ++ referrer, ++ other->is_html_document_, ++ other->use_legacy_background_size_shorthand_behavior_, ++ other->secure_context_mode_, ++ other->should_check_content_security_policy_, ++ use_counter_document, ++ other->resource_fetch_restriction_) { + is_ad_related_ = other->is_ad_related_; + } + +@@ -96,18 +94,23 @@ CSSParserContext::CSSParserContext(CSSParserMode mode, + ResourceFetchRestriction::kNone) {} + + CSSParserContext::CSSParserContext(const Document& document) +- : CSSParserContext(document, +- document.BaseURL(), +- true /* origin_clean */, +- document.GetReferrerPolicy(), +- WTF::TextEncoding(), +- kLiveProfile) {} ++ : CSSParserContext( ++ document, ++ document.BaseURL(), ++ true /* origin_clean */, ++ Referrer(document.GetExecutionContext() ++ ? document.GetExecutionContext()->OutgoingReferrer() ++ : String(), // GetExecutionContext() only returns null ++ // in tests. ++ document.GetReferrerPolicy()), ++ WTF::TextEncoding(), ++ kLiveProfile) {} + + CSSParserContext::CSSParserContext( + const Document& document, + const KURL& base_url_override, + bool origin_clean, +- network::mojom::ReferrerPolicy referrer_policy_override, ++ const Referrer& referrer, + const WTF::TextEncoding& charset, + SelectorProfile profile, + enum ResourceFetchRestriction resource_fetch_restriction) +@@ -122,8 +125,7 @@ CSSParserContext::CSSParserContext( + : kHTMLStandardMode) + : document.InQuirksMode() ? kHTMLQuirksMode : kHTMLStandardMode, + profile, +- Referrer(base_url_override.StrippedForUseAsReferrer(), +- referrer_policy_override), ++ referrer, + IsA(document), + document.GetSettings() + ? document.GetSettings() +diff --git a/third_party/blink/renderer/core/css/parser/css_parser_context.h b/third_party/blink/renderer/core/css/parser/css_parser_context.h +index 33b458f21910bcbfdb6956b02cd2ef56bb39778b..8d7ec9e6b7a14fffaaf72dcd0d2c0d0f062dbbfa 100644 +--- a/third_party/blink/renderer/core/css/parser/css_parser_context.h ++++ b/third_party/blink/renderer/core/css/parser/css_parser_context.h +@@ -40,10 +40,15 @@ class CORE_EXPORT CSSParserContext final + explicit CSSParserContext(const CSSParserContext* other, + const Document* use_counter_document = nullptr); + ++ // Creates a context with most of its constructor attributes provided by ++ // copying from |other|, except that the remaining constructor arguments take ++ // precedence over the corresponding characteristics of |other|. This is ++ // useful for initializing @imported sheets' contexts, which inherit most of ++ // their characteristics from their parents. + CSSParserContext(const CSSParserContext* other, + const KURL& base_url_override, + bool origin_clean, +- network::mojom::ReferrerPolicy referrer_policy_override, ++ const Referrer& referrer, + const WTF::TextEncoding& charset_override, + const Document* use_counter_document); + CSSParserContext(CSSParserMode, +@@ -54,7 +59,7 @@ class CORE_EXPORT CSSParserContext final + CSSParserContext(const Document&, + const KURL& base_url_override, + bool origin_clean, +- network::mojom::ReferrerPolicy referrer_policy_override, ++ const Referrer& referrer, + const WTF::TextEncoding& charset = WTF::TextEncoding(), + SelectorProfile = kLiveProfile, + ResourceFetchRestriction resource_fetch_restriction = +@@ -69,7 +74,7 @@ class CORE_EXPORT CSSParserContext final + CSSParserMode, + CSSParserMode match_mode, + SelectorProfile, +- const Referrer&, ++ const Referrer& referrer, + bool is_html_document, + bool use_legacy_background_size_shorthand_behavior, + SecureContextMode, +diff --git a/third_party/blink/renderer/core/css/selector_query.cc b/third_party/blink/renderer/core/css/selector_query.cc +index 722b751f19207b070664d79c5a9cc758f1c044f4..35d9d926f2e1ac0c2d032817b87c1079af9408ec 100644 +--- a/third_party/blink/renderer/core/css/selector_query.cc ++++ b/third_party/blink/renderer/core/css/selector_query.cc +@@ -535,9 +535,8 @@ SelectorQuery* SelectorQueryCache::Add(const AtomicString& selectors, + + CSSSelectorList selector_list = CSSParser::ParseSelector( + MakeGarbageCollected( +- document, document.BaseURL(), true /* origin_clean */, +- document.GetReferrerPolicy(), WTF::TextEncoding(), +- CSSParserContext::kSnapshotProfile), ++ document, document.BaseURL(), true /* origin_clean */, Referrer(), ++ WTF::TextEncoding(), CSSParserContext::kSnapshotProfile), + nullptr, selectors); + + if (!selector_list.First()) { +diff --git a/third_party/blink/renderer/core/css/selector_query_test.cc b/third_party/blink/renderer/core/css/selector_query_test.cc +index 8d701d91372e5c2fb4b1a30f190f629f95e1b0b2..bf14850baf8dd2e92f74b89b78963a367440a704 100644 +--- a/third_party/blink/renderer/core/css/selector_query_test.cc ++++ b/third_party/blink/renderer/core/css/selector_query_test.cc +@@ -72,9 +72,8 @@ TEST(SelectorQueryTest, NotMatchingPseudoElement) { + + CSSSelectorList selector_list = CSSParser::ParseSelector( + MakeGarbageCollected( +- *document, NullURL(), true /* origin_clean */, +- network::mojom::ReferrerPolicy::kDefault, WTF::TextEncoding(), +- CSSParserContext::kSnapshotProfile), ++ *document, NullURL(), true /* origin_clean */, Referrer(), ++ WTF::TextEncoding(), CSSParserContext::kSnapshotProfile), + nullptr, "span::before"); + std::unique_ptr query = + SelectorQuery::Adopt(std::move(selector_list)); +@@ -83,9 +82,8 @@ TEST(SelectorQueryTest, NotMatchingPseudoElement) { + + selector_list = CSSParser::ParseSelector( + MakeGarbageCollected( +- *document, NullURL(), true /* origin_clean */, +- network::mojom::ReferrerPolicy::kDefault, WTF::TextEncoding(), +- CSSParserContext::kSnapshotProfile), ++ *document, NullURL(), true /* origin_clean */, Referrer(), ++ WTF::TextEncoding(), CSSParserContext::kSnapshotProfile), + nullptr, "span"); + query = SelectorQuery::Adopt(std::move(selector_list)); + elm = query->QueryFirst(*document); +@@ -103,9 +101,8 @@ TEST(SelectorQueryTest, LastOfTypeNotFinishedParsing) { + + CSSSelectorList selector_list = CSSParser::ParseSelector( + MakeGarbageCollected( +- *document, NullURL(), true /* origin_clean */, +- network::mojom::ReferrerPolicy::kDefault, WTF::TextEncoding(), +- CSSParserContext::kSnapshotProfile), ++ *document, NullURL(), true /* origin_clean */, Referrer(), ++ WTF::TextEncoding(), CSSParserContext::kSnapshotProfile), + nullptr, "p:last-of-type"); + std::unique_ptr query = + SelectorQuery::Adopt(std::move(selector_list)); +diff --git a/third_party/blink/renderer/core/css/style_rule_import.cc b/third_party/blink/renderer/core/css/style_rule_import.cc +index 447d130a9c29a698c81c0436b318058172b3a7ef..857fb15c74063613d467c29829eda0a8ea18b9bb 100644 +--- a/third_party/blink/renderer/core/css/style_rule_import.cc ++++ b/third_party/blink/renderer/core/css/style_rule_import.cc +@@ -83,8 +83,9 @@ void StyleRuleImport::NotifyFinished(Resource* resource) { + CSSParserContext* context = MakeGarbageCollected( + parent_context, cached_style_sheet->GetResponse().ResponseUrl(), + cached_style_sheet->GetResponse().IsCorsSameOrigin(), +- cached_style_sheet->GetReferrerPolicy(), cached_style_sheet->Encoding(), +- document); ++ Referrer(cached_style_sheet->GetResponse().ResponseUrl(), ++ cached_style_sheet->GetReferrerPolicy()), ++ cached_style_sheet->Encoding(), document); + if (cached_style_sheet->GetResourceRequest().IsAdResource()) + context->SetIsAdRelated(); + +diff --git a/third_party/blink/renderer/core/dom/processing_instruction.cc b/third_party/blink/renderer/core/dom/processing_instruction.cc +index 739226d95f964ebf4ae35983c6e1cd5faa01b324..1a1ead65c7642350f7d13364046f665c021bc3b0 100644 +--- a/third_party/blink/renderer/core/dom/processing_instruction.cc ++++ b/third_party/blink/renderer/core/dom/processing_instruction.cc +@@ -206,7 +206,9 @@ void ProcessingInstruction::NotifyFinished(Resource* resource) { + auto* parser_context = MakeGarbageCollected( + GetDocument(), style_resource->GetResponse().ResponseUrl(), + style_resource->GetResponse().IsCorsSameOrigin(), +- style_resource->GetReferrerPolicy(), style_resource->Encoding()); ++ Referrer(style_resource->GetResponse().ResponseUrl(), ++ style_resource->GetReferrerPolicy()), ++ style_resource->Encoding()); + if (style_resource->GetResourceRequest().IsAdResource()) + parser_context->SetIsAdRelated(); + +diff --git a/third_party/blink/renderer/core/html/link_style.cc b/third_party/blink/renderer/core/html/link_style.cc +index 5036ac1f0cb9613772c704a6dc5c5e2496ab5567..5e3b7e361ae1ebe2ff89f10448eac4fe33352031 100644 +--- a/third_party/blink/renderer/core/html/link_style.cc ++++ b/third_party/blink/renderer/core/html/link_style.cc +@@ -88,7 +88,9 @@ void LinkStyle::NotifyFinished(Resource* resource) { + auto* parser_context = MakeGarbageCollected( + GetDocument(), cached_style_sheet->GetResponse().ResponseUrl(), + cached_style_sheet->GetResponse().IsCorsSameOrigin(), +- cached_style_sheet->GetReferrerPolicy(), cached_style_sheet->Encoding()); ++ Referrer(cached_style_sheet->GetResponse().ResponseUrl(), ++ cached_style_sheet->GetReferrerPolicy()), ++ cached_style_sheet->Encoding()); + if (cached_style_sheet->GetResourceRequest().IsAdResource()) { + parser_context->SetIsAdRelated(); + } +diff --git a/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc b/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc +index 0b6e9b6b8b66029f46ca79f24a2519f13e611005..e492003c470af4c7b96551f6dfee79d26f8b7c77 100644 +--- a/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc ++++ b/third_party/blink/renderer/core/html/track/vtt/vtt_parser.cc +@@ -244,9 +244,8 @@ VTTParser::ParseState VTTParser::CollectRegionSettings(const String& line) { + VTTParser::ParseState VTTParser::CollectStyleSheet(const String& line) { + if (line.IsEmpty() || line.Contains("-->")) { + auto* parser_context = MakeGarbageCollected( +- *document_, NullURL(), true /* origin_clean */, +- document_->GetReferrerPolicy(), UTF8Encoding(), +- CSSParserContext::kLiveProfile, ++ *document_, NullURL(), true /* origin_clean */, Referrer(), ++ UTF8Encoding(), CSSParserContext::kLiveProfile, + ResourceFetchRestriction::kOnlyDataUrls); + auto* style_sheet_contents = + MakeGarbageCollected(parser_context); +diff --git a/third_party/blink/web_tests/TestExpectations b/third_party/blink/web_tests/TestExpectations +index acf5033054cecc1099068e2452cfbef8f1ffbd95..0e9ac683c48befe5f3e740e0632bf93e7569c686 100644 +--- a/third_party/blink/web_tests/TestExpectations ++++ b/third_party/blink/web_tests/TestExpectations +@@ -3245,6 +3245,7 @@ virtual/webrtc-wpt-plan-b/external/wpt/webrtc/RTCPeerConnection-restartIce-onneg + # See also crbug.com/920100 (sheriff 2019-01-09). + crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/external-stylesheet.html [ Timeout Failure ] + crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/inline-style.html [ Timeout Failure ] ++crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/inline-style-with-differentorigin-base-tag.tentative.html [ Timeout Failure ] + crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/internal-stylesheet.html [ Timeout Failure ] + crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/presentation-attribute.html [ Timeout Failure ] + crbug.com/626703 external/wpt/referrer-policy/css-integration/svg/processing-instruction.html [ Timeout Failure ] +diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/css-integration/image/inline-style-with-differentorigin-base-tag.tentative.html b/third_party/blink/web_tests/external/wpt/referrer-policy/css-integration/image/inline-style-with-differentorigin-base-tag.tentative.html +new file mode 100644 +index 0000000000000000000000000000000000000000..091afd832ab35a76136b4242df1c1ec73aee109d +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/referrer-policy/css-integration/image/inline-style-with-differentorigin-base-tag.tentative.html +@@ -0,0 +1,45 @@ ++ ++CSS integration - image from inline style from document with base tag ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++

Check that resources from inline styles are loaded with ++ the referrer and referrer policy from the document and, in ++ particular, not with the different base URL set in the base tag.

++ ++
++ ++ ++ ++
++ ++ +diff --git a/third_party/blink/web_tests/external/wpt/referrer-policy/css-integration/svg/inline-style-with-differentorigin-base-tag.tentative.html b/third_party/blink/web_tests/external/wpt/referrer-policy/css-integration/svg/inline-style-with-differentorigin-base-tag.tentative.html +new file mode 100644 +index 0000000000000000000000000000000000000000..9a8bc6da418bc7302138daba8cf06cb449bd2dfe +--- /dev/null ++++ b/third_party/blink/web_tests/external/wpt/referrer-policy/css-integration/svg/inline-style-with-differentorigin-base-tag.tentative.html +@@ -0,0 +1,40 @@ ++ ++ ++ ++ ++ CSS integration - styling SVG from inline style on page with different-origin base tag ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++

Check that resources from inline styles are loaded with ++ the referrer and referrer policy from the document and, in ++ particular, not from the document's overridden base URL.

++ ++ ++
++ ++ ++ +diff --git a/third_party/blink/web_tests/http/tests/css/resources/referrer-check.php b/third_party/blink/web_tests/http/tests/css/resources/referrer-check.php +index 69483e01544c842f56a51d00d1b2ee5dc24b4162..7a517de692f418c3c8b365d0f7aefb9e585c9da0 100644 +--- a/third_party/blink/web_tests/http/tests/css/resources/referrer-check.php ++++ b/third_party/blink/web_tests/http/tests/css/resources/referrer-check.php +@@ -31,7 +31,7 @@ $expectedReferrerPaths = array( + "document" => "/css/css-resources-referrer.html", + "sheet" => "/css/resources/css-resources-referrer.css", + "importedSheet" => "/css/resources/css-resources-referrer-import.css", +- "iframe" => "/from/iframe.html" ++ "iframe" => "/css/css-resources-referrer-srcdoc.html" + ); + + $from = $_GET["from"]; diff --git a/patches/chromium/disable_color_correct_rendering.patch b/patches/chromium/disable_color_correct_rendering.patch index ef5ea0e1fe7ce..dacdd0b5fc81f 100644 --- a/patches/chromium/disable_color_correct_rendering.patch +++ b/patches/chromium/disable_color_correct_rendering.patch @@ -20,7 +20,7 @@ to deal with color spaces. That is being tracked at https://crbug.com/634542 and https://crbug.com/711107. diff --git a/cc/trees/layer_tree_host_impl.cc b/cc/trees/layer_tree_host_impl.cc -index 2c8bb4fe9c8cf592cc83fce164f21209e5dd1465..a6fde7b42fd854a56226acb4766274621aa7b3e6 100644 +index 0fbcdf35620861da00da59808d8350d80a8b48f0..d16c76fe5e315047532d2d724f2665efb2b53ea6 100644 --- a/cc/trees/layer_tree_host_impl.cc +++ b/cc/trees/layer_tree_host_impl.cc @@ -1861,6 +1861,10 @@ void LayerTreeHostImpl::SetIsLikelyToRequireADraw( @@ -231,10 +231,10 @@ index 9f9d3422ad128fe3bfc305baa57971bd03e1b676..02747c0d4d7f0bb7ba034f35bcce86a9 service_manager::switches::kGpuSandboxAllowSysVShm, service_manager::switches::kGpuSandboxFailuresFatal, diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc -index e5a82f46cd58b0da9959930ee85303e4a1530425..753b926fbe0c6a3ef2b2e244d7f80f4f4574bc5c 100644 +index 305ffb2b7a1830c93f97450b3b79d5448cb9fad4..3832bd759c65aeccbe44c1b80f1291586177fb36 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc -@@ -227,6 +227,7 @@ +@@ -228,6 +228,7 @@ #include "ui/accessibility/accessibility_switches.h" #include "ui/base/ui_base_switches.h" #include "ui/display/display_switches.h" @@ -242,7 +242,7 @@ index e5a82f46cd58b0da9959930ee85303e4a1530425..753b926fbe0c6a3ef2b2e244d7f80f4f #include "ui/gl/gl_switches.h" #include "ui/native_theme/native_theme_features.h" #include "url/origin.h" -@@ -3283,6 +3284,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer( +@@ -3285,6 +3286,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer( // Propagate the following switches to the renderer command line (along // with any associated values) if present in the browser command line. static const char* const kSwitchNames[] = { diff --git a/patches/chromium/disable_gpu_acceleration_on_all_mesa_software_rasterizers.patch b/patches/chromium/disable_gpu_acceleration_on_all_mesa_software_rasterizers.patch new file mode 100644 index 0000000000000..bafc3a7aac8b8 --- /dev/null +++ b/patches/chromium/disable_gpu_acceleration_on_all_mesa_software_rasterizers.patch @@ -0,0 +1,73 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Kai Ninomiya +Date: Thu, 11 Feb 2021 02:24:04 +0000 +Subject: Disable GPU acceleration on all Mesa software rasterizers + +The previous entry only disabled acceleration on swrast, but softpipe +and llvmpipe shouldn't be used for "GPU" acceleration either. +This should apply to Linux but not ChromeOS, AFAICT. + +This only improves an existing software rendering list entry, but here +is the rationale: We prefer to rely on our own (domain specific, so more +efficient) software paths, at least for everything other than WebGL. And +for WebGL, SwiftShader avoids unknown factors like +llvmpipe/softpipe/swrast. + +If you are running a Mesa GL driver (not e.g. NVIDIA) then you can force +these configurations with: +- LIBGL_ALWAYS_SOFTWARE=1 + https://docs.mesa3d.org/envvars.html#libgl-environment-variables:~:text=LIBGL_ALWAYS_SOFTWARE +- GALLIUM_DRIVER=llvmpipe, softpipe, or swr (though swr didn't work for me) + https://docs.mesa3d.org/envvars.html#gallium-environment-variables:~:text=GALLIUM_DRIVER + +The GL_RENDERER strings are: +- swrast: "Software Rasterizer" (couldn't test this locally; found this online) +- softpipe: "softpipe" (on one machine) +- llvmpipe: "llvmpipe (LLVM 10.0.0, 256 bits)" (on one machine) + +Drive-by updates the description of another item to be more accurate +(SVGA3D is virtualized over hardware; it's not a software renderer). + +# Unrelated CQ failures on branch +(cherry picked from commit 7c7eccfc85e387a0dcd154a2a9c2389177982837) + +No-Try: True +Bug: 1155974 +Change-Id: I0571c1a1bf526260f7ea6cd53f88eec768973b13 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2645491 +Commit-Queue: Kai Ninomiya +Reviewed-by: Zhenyao Mo +Auto-Submit: Kai Ninomiya +Cr-Original-Commit-Position: refs/heads/master@{#846422} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2651183 +Reviewed-by: Kenneth Russell +Cr-Commit-Position: refs/branch-heads/4324@{#2176} +Cr-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} + +diff --git a/gpu/config/software_rendering_list.json b/gpu/config/software_rendering_list.json +index 5b5c542dbc95e49f9f0e5330f99b94ab4ea72321..dd87f7c0cc928bd330ac156bce9391a36bca2db0 100644 +--- a/gpu/config/software_rendering_list.json ++++ b/gpu/config/software_rendering_list.json +@@ -21,11 +21,11 @@ + { + "id": 3, + "description": "GL driver is software rendered. GPU acceleration is disabled", +- "cr_bugs": [59302, 315217], ++ "cr_bugs": [59302, 315217, 1155974], + "os": { + "type": "linux" + }, +- "gl_renderer": "(?i).*software.*", ++ "gl_renderer": "(?i).*(software|llvmpipe|softpipe).*", + "features": [ + "all" + ] +@@ -353,7 +353,7 @@ + }, + { + "id": 50, +- "description": "Disable VMware software renderer on older Mesa", ++ "description": "Disable VMware virtualized renderer on older Mesa", + "cr_bugs": [145531, 332596, 571899, 629434], + "os": { + "type": "linux" diff --git a/patches/chromium/don_t_create_providers_if_context_is_lost.patch b/patches/chromium/don_t_create_providers_if_context_is_lost.patch new file mode 100644 index 0000000000000..a93d1d885a2cd --- /dev/null +++ b/patches/chromium/don_t_create_providers_if_context_is_lost.patch @@ -0,0 +1,75 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Robert Phillips +Date: Wed, 16 Sep 2020 18:42:32 +0000 +Subject: Don't create providers if context is lost + +CanvasResourceProvider::CreateSharedImageProvider receives a weak pointer +to the ContextProviderWrapper and returns nullptr if it does not exist. + +Unfortunately SharedGpuContext::IsGpuCompositingEnabled can re-create +the ContextProviderWrapper after this check happens, leading to potential +use-after-frees. + +To me it simply makes the most sense to not create a CRP if context is +lost, as the created provider would be invalid and nullptr would get +returned anyway. + +Bug: 1126424 +Change-Id: Ic92709d7a38d94e5e7529efac3a09405d64eaa34 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2417097 +Reviewed-by: Juanmi Huertas +Reviewed-by: Fernando Serboncini +Commit-Queue: Aaron Krajeski +Cr-Commit-Position: refs/heads/master@{#809327} + +diff --git a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc +index 4635d4a38836150f3abafedeffaf31a20d6e77cf..8fb14df8be73ef83fe0e67946b684d2faa825f38 100644 +--- a/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc ++++ b/third_party/blink/renderer/platform/graphics/canvas_resource_provider.cc +@@ -801,7 +801,16 @@ CanvasResourceProvider::CreateSharedImageProvider( + bool is_origin_top_left, + RasterMode raster_mode, + uint32_t shared_image_usage_flags) { +- if (!context_provider_wrapper) ++ // IsGpuCompositingEnabled can re-create the context if it has been lost, do ++ // this up front so that we can fail early and not expose ourselves to ++ // use after free bugs (crbug.com/1126424) ++ const bool is_gpu_compositing_enabled = ++ SharedGpuContext::IsGpuCompositingEnabled(); ++ ++ // If the context is lost we don't want to re-create it here, the resulting ++ // resource provider would be invalid anyway ++ if (!context_provider_wrapper || ++ context_provider_wrapper->ContextProvider()->IsContextLost()) + return nullptr; + + const auto& capabilities = +@@ -817,7 +826,7 @@ CanvasResourceProvider::CreateSharedImageProvider( + } + + const bool is_gpu_memory_buffer_image_allowed = +- SharedGpuContext::IsGpuCompositingEnabled() && ++ is_gpu_compositing_enabled && + IsGMBAllowed(size, color_params, capabilities) && + Platform::Current()->GetGpuMemoryBufferManager(); + +@@ -850,6 +859,9 @@ CanvasResourceProvider::CreatePassThroughProvider( + const CanvasColorParams& color_params, + bool is_origin_top_left, + base::WeakPtr resource_dispatcher) { ++ // SharedGpuContext::IsGpuCompositingEnabled can potentially replace the ++ // context_provider_wrapper, so it's important to call that first as it can ++ // invalidate the weak pointer. + if (!SharedGpuContext::IsGpuCompositingEnabled() || !context_provider_wrapper) + return nullptr; + +@@ -883,6 +895,9 @@ CanvasResourceProvider::CreateSwapChainProvider( + const CanvasColorParams& color_params, + bool is_origin_top_left, + base::WeakPtr resource_dispatcher) { ++ // SharedGpuContext::IsGpuCompositingEnabled can potentially replace the ++ // context_provider_wrapper, so it's important to call that first as it can ++ // invalidate the weak pointer. + DCHECK(is_origin_top_left); + if (!SharedGpuContext::IsGpuCompositingEnabled() || !context_provider_wrapper) + return nullptr; diff --git a/patches/chromium/fileapi_terminate_filereaderloader_before_dispatching_onabort_event.patch b/patches/chromium/fileapi_terminate_filereaderloader_before_dispatching_onabort_event.patch new file mode 100644 index 0000000000000..7adfba2e0c069 --- /dev/null +++ b/patches/chromium/fileapi_terminate_filereaderloader_before_dispatching_onabort_event.patch @@ -0,0 +1,58 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Belenko +Date: Wed, 19 May 2021 12:29:29 +0200 +Subject: FileAPI: Terminate FileReaderLoader before dispatching onabort event. + +Otherwise FileReader could end up in an inconsistent state where a load +is still in progress while the state was set to done. + +(cherry picked from commit a74c980df61dd7367ad1b11e6a735be82d2696f0) + +Bug: 1201073 +Change-Id: Ib2c833537e1badc57d125568d5d35f53f12582a8 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2860442 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2871355 + +diff --git a/third_party/blink/renderer/core/fileapi/file_reader.cc b/third_party/blink/renderer/core/fileapi/file_reader.cc +index 978d71510a5a0a790ea86dcd5413665ff624619c..8549fbef76a79cc4eff0fdc262e39720bfff40ba 100644 +--- a/third_party/blink/renderer/core/fileapi/file_reader.cc ++++ b/third_party/blink/renderer/core/fileapi/file_reader.cc +@@ -337,7 +337,10 @@ void FileReader::abort() { + loading_state_ = kLoadingStateAborted; + + DCHECK_NE(kDone, state_); +- state_ = kDone; ++ // Synchronously cancel the loader before dispatching events. This way we make ++ // sure the FileReader internal state stays consistent even if another load ++ // is started from one of the event handlers, or right after abort returns. ++ Terminate(); + + base::AutoReset firing_events(&still_firing_events_, true); + +@@ -349,15 +352,12 @@ void FileReader::abort() { + ThrottlingController::RemoveReader(GetExecutionContext(), this); + + FireEvent(event_type_names::kAbort); ++ // TODO(https://crbug.com/1204139): Only fire loadend event if no new load was ++ // started from the abort event handler. + FireEvent(event_type_names::kLoadend); + + // All possible events have fired and we're done, no more pending activity. + ThrottlingController::FinishReader(GetExecutionContext(), this, final_step); +- +- // Also synchronously cancel the loader, as script might initiate a new load +- // right after this method returns, in which case an async termination would +- // terminate the wrong loader. +- Terminate(); + } + + void FileReader::result(ScriptState* state, +@@ -429,6 +429,8 @@ void FileReader::DidFinishLoading() { + ThrottlingController::RemoveReader(GetExecutionContext(), this); + + FireEvent(event_type_names::kLoad); ++ // TODO(https://crbug.com/1204139): Only fire loadend event if no new load was ++ // started from the abort event handler. + FireEvent(event_type_names::kLoadend); + + // All possible events have fired and we're done, no more pending activity. diff --git a/patches/chromium/fix_heap_overflow_in_videoframeyuvconverter.patch b/patches/chromium/fix_heap_overflow_in_videoframeyuvconverter.patch new file mode 100644 index 0000000000000..e3854c31aea6c --- /dev/null +++ b/patches/chromium/fix_heap_overflow_in_videoframeyuvconverter.patch @@ -0,0 +1,72 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Nathan Zabriskie +Date: Fri, 5 Feb 2021 20:44:16 +0000 +Subject: Fix heap overflow in VideoFrameYUVConverter + +Currently with some texture sizes GLES2Util::ComputeImageDataSizesES3 +will attempt to add row padding when calculating the size of a +VideoFrame plane. This is because it's currently assumed that each row +aligns on a 4 byte boundary based on GL_UNPACK_ALIGNMENT but +VideoFrames make no such guarantee as they may be densely packed. +This CL removes the GL_UNPACK_ALIGNMENT assumption so that we only use +the VideoFrame's stride when calculating padding. + +(cherry picked from commit 7de5d0ecb5a4f73aeffe15d825bf694d0d8e2a08) + +Bug: 1166504, 1161131 +Change-Id: I2484f5dfd2ad85b088fee57758776a5c9bd01d95 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2642765 +Reviewed-by: Vasiliy Telezhnikov +Commit-Queue: Nathan Zabriskie +Cr-Original-Commit-Position: refs/heads/master@{#846298} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2679121 +Bot-Commit: Rubber Stamper +Auto-Submit: Nathan Zabriskie +Cr-Commit-Position: refs/branch-heads/4324@{#2115} +Cr-Branched-From: c73b5a651d37a6c4d0b8e3262cc4015a5579c6c8-refs/heads/master@{#827102} + +diff --git a/gpu/command_buffer/client/gles2_implementation.cc b/gpu/command_buffer/client/gles2_implementation.cc +index 49d050e7e0d8efabeb7cf1ed46041931394d43b4..4e5f869203d6b99b3368d3264830799d2d62006b 100644 +--- a/gpu/command_buffer/client/gles2_implementation.cc ++++ b/gpu/command_buffer/client/gles2_implementation.cc +@@ -840,7 +840,9 @@ bool GLES2Implementation::GetHelper(GLenum pname, GLint* params) { + case GL_GPU_DISJOINT_EXT: + *params = static_cast(query_tracker_->CheckAndResetDisjoint()); + return true; +- ++ case GL_UNPACK_ALIGNMENT: ++ *params = unpack_alignment_; ++ return true; + case GL_VIEWPORT: + if (state_.viewport_width > 0 && state_.viewport_height > 0 && + capabilities_.max_viewport_width > 0 && +@@ -922,7 +924,6 @@ bool GLES2Implementation::GetHelper(GLenum pname, GLint* params) { + case GL_STENCIL_VALUE_MASK: + case GL_STENCIL_WRITEMASK: + case GL_SUBPIXEL_BITS: +- case GL_UNPACK_ALIGNMENT: + return false; + default: + break; +diff --git a/gpu/command_buffer/client/raster_implementation_gles.cc b/gpu/command_buffer/client/raster_implementation_gles.cc +index 299dd4f9874bf7f68563d524167db7180a200e14..f7d794ffbca21b055a9be91fa4b77be79896a730 100644 +--- a/gpu/command_buffer/client/raster_implementation_gles.cc ++++ b/gpu/command_buffer/client/raster_implementation_gles.cc +@@ -178,6 +178,9 @@ void RasterImplementationGLES::WritePixels(const gpu::Mailbox& dest_mailbox, + BeginSharedImageAccessDirectCHROMIUM( + texture_id, GL_SHARED_IMAGE_ACCESS_MODE_READWRITE_CHROMIUM); + ++ GLint old_align = 0; ++ gl_->GetIntegerv(GL_UNPACK_ALIGNMENT, &old_align); ++ gl_->PixelStorei(GL_UNPACK_ALIGNMENT, 1); + gl_->PixelStorei(GL_UNPACK_ROW_LENGTH, row_bytes / src_info.bytesPerPixel()); + gl_->BindTexture(texture_target, texture_id); + gl_->TexSubImage2D(texture_target, 0, dst_x_offset, dst_y_offset, +@@ -186,6 +189,7 @@ void RasterImplementationGLES::WritePixels(const gpu::Mailbox& dest_mailbox, + SkColorTypeToGLDataType(src_info.colorType()), src_pixels); + gl_->BindTexture(texture_target, 0); + gl_->PixelStorei(GL_UNPACK_ROW_LENGTH, 0); ++ gl_->PixelStorei(GL_UNPACK_ALIGNMENT, old_align); + + EndSharedImageAccessDirectCHROMIUM(texture_id); + DeleteGpuRasterTexture(texture_id); diff --git a/patches/chromium/fix_properly_honor_printing_page_ranges.patch b/patches/chromium/fix_properly_honor_printing_page_ranges.patch new file mode 100644 index 0000000000000..354397b278df8 --- /dev/null +++ b/patches/chromium/fix_properly_honor_printing_page_ranges.patch @@ -0,0 +1,127 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shelley Vohr +Date: Wed, 23 Sep 2020 10:27:57 -0700 +Subject: fix: properly honor printing page ranges + +The print ranges in Chromium's print job settings were not being properly +plumbed through to PMPrintSettings on mcOS. This fixes that by setting +them should they exist. + +diff --git a/printing/printing_context_mac.h b/printing/printing_context_mac.h +index 06cdc0e1a4a50e29a148c97c964c30a939e800da..5db5cdb61be0beee4506313dcde46c499e011383 100644 +--- a/printing/printing_context_mac.h ++++ b/printing/printing_context_mac.h +@@ -81,6 +81,10 @@ class PRINTING_EXPORT PrintingContextMac : public PrintingContext { + // Returns true if the orientation was set. + bool SetOrientationIsLandscape(bool landscape); + ++ // Set the page range in native print info object. ++ // Returns true if the range was set. ++ bool SetPrintRangeInPrintSettings(const PageRanges& ranges); ++ + // Sets duplex mode in PMPrintSettings. + // Returns true if duplex mode is set. + bool SetDuplexModeInPrintSettings(mojom::DuplexMode mode); +diff --git a/printing/printing_context_mac.mm b/printing/printing_context_mac.mm +index 93438609a3a0eaeec0dbc76efe5c46e03fb40e52..dc64546f7d88db3b48f379b34c7dcc49b692a048 100644 +--- a/printing/printing_context_mac.mm ++++ b/printing/printing_context_mac.mm +@@ -186,7 +186,8 @@ PrintingContext::Result PrintingContextMac::UpdatePrinterSettings( + !SetCopiesInPrintSettings(settings_->copies()) || + !SetCollateInPrintSettings(settings_->collate()) || + !SetDuplexModeInPrintSettings(settings_->duplex_mode()) || +- !SetOutputColor(settings_->color())) { ++ !SetOutputColor(settings_->color()) || ++ !SetPrintRangeInPrintSettings(settings_->ranges())) { + return OnError(); + } + } +@@ -339,6 +340,22 @@ bool PrintingContextMac::SetCopiesInPrintSettings(int copies) { + return PMSetCopies(print_settings, copies, false) == noErr; + } + ++bool PrintingContextMac::SetPrintRangeInPrintSettings(const PageRanges& ranges) { ++ // Default is already NSPrintAllPages - we can safely bail. ++ if (ranges.empty()) ++ return true; ++ ++ auto* print_settings = ++ static_cast([print_info_.get() PMPrintSettings]); ++ ++ // macOS does not allow multiple ranges, so pluck the first. ++ auto range = ranges.front(); ++ bool set_first_page = PMSetFirstPage(print_settings, range.from + 1, false) == noErr; ++ bool set_last_page = PMSetLastPage(print_settings, range.to + 1, false) == noErr; ++ ++ return set_first_page && set_last_page; ++} ++ + bool PrintingContextMac::SetCollateInPrintSettings(bool collate) { + PMPrintSettings print_settings = + static_cast([print_info_.get() PMPrintSettings]); +diff --git a/printing/printing_context_system_dialog_win.cc b/printing/printing_context_system_dialog_win.cc +index a53e4045bdc8a4ea68720c7a4792599097792814..1cb0c34dce72f99d8689e37f2a5d587767d224bf 100644 +--- a/printing/printing_context_system_dialog_win.cc ++++ b/printing/printing_context_system_dialog_win.cc +@@ -52,14 +52,28 @@ void PrintingContextSystemDialogWin::AskUserForSettings( + PRINTPAGERANGE ranges[32]; + dialog_options.nStartPage = START_PAGE_GENERAL; + if (max_pages) { +- // Default initialize to print all the pages. + memset(ranges, 0, sizeof(ranges)); +- ranges[0].nFromPage = 1; +- ranges[0].nToPage = max_pages; +- dialog_options.nPageRanges = 1; +- dialog_options.nMaxPageRanges = base::size(ranges); ++ ++ auto page_ranges = settings_->ranges(); ++ if (!page_ranges.empty()) { ++ for (size_t i = 0; i < page_ranges.size(); i++) { ++ auto range = page_ranges[i]; ++ ranges[i].nFromPage = range.from + 1; ++ ranges[i].nToPage = range.to + 1; ++ } ++ dialog_options.nPageRanges = page_ranges.size(); ++ ++ // Ensure the Pages radio button is selected. ++ dialog_options.Flags |= PD_PAGENUMS; ++ } else { ++ ranges[0].nFromPage = 1; ++ ranges[0].nToPage = max_pages; ++ dialog_options.nPageRanges = 1; ++ } ++ + dialog_options.nMinPage = 1; + dialog_options.nMaxPage = max_pages; ++ dialog_options.nMaxPageRanges = base::size(ranges); + dialog_options.lpPageRanges = ranges; + } else { + // No need to bother, we don't know how many pages are available. +diff --git a/ui/gtk/printing/print_dialog_gtk.cc b/ui/gtk/printing/print_dialog_gtk.cc +index 57900d2854d7c3cb427bc3bd8b1742335ab820b8..166e7eeb9c46f04664fe8986767114b9f4809b71 100644 +--- a/ui/gtk/printing/print_dialog_gtk.cc ++++ b/ui/gtk/printing/print_dialog_gtk.cc +@@ -240,6 +240,23 @@ void PrintDialogGtk::UpdateSettings( + gtk_print_settings_set_n_copies(gtk_settings_, settings->copies()); + gtk_print_settings_set_collate(gtk_settings_, settings->collate()); + ++ auto print_ranges = settings->ranges(); ++ if (!print_ranges.empty()) { ++ // Tell the system that we only intend to print a subset of pages. ++ gtk_print_settings_set_print_pages(gtk_settings_, GTK_PRINT_PAGES_RANGES); ++ ++ GtkPageRange* ranges; ++ ranges = g_new(GtkPageRange, print_ranges.size()); ++ for (size_t i = 0; i < print_ranges.size(); i++) { ++ auto range = print_ranges[i]; ++ ranges[i].start = range.from; ++ ranges[i].end = range.to; ++ } ++ ++ gtk_print_settings_set_page_ranges(gtk_settings_, ranges, 1); ++ g_free(ranges); ++ } ++ + #if defined(USE_CUPS) + // Set advanced settings first so they can be overridden by user applied + // settings. diff --git a/patches/chromium/fix_setparentacessibile_crash_win.patch b/patches/chromium/fix_setparentacessibile_crash_win.patch new file mode 100644 index 0000000000000..9e1a8c3b403f7 --- /dev/null +++ b/patches/chromium/fix_setparentacessibile_crash_win.patch @@ -0,0 +1,28 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Biru Mohanathas +Date: Thu, 10 Dec 2020 19:02:37 +0200 +Subject: fix crash in NativeViewHost::SetParentAccessible + +This fixes random crashes on Windows 10. It presumably started happening +after the changes in +https://chromium.googlesource.com/chromium/src.git/+/5c6c8e994bce2bfb867279ae5068e9f9134e70c3%5E!/#F15 + +For context, see: https://github.com/electron/electron/issues/26905 + +This patch can likely be upstreamed. The crash cannot be fixed without +patching something in Chromium - this is the least invasive change. + +diff --git a/ui/views/controls/native/native_view_host.cc b/ui/views/controls/native/native_view_host.cc +index 4779e4f07d923b5af9ba05c2765cf294e75dcc14..6112217d532251f7f6850c23be5c312a908df1e2 100644 +--- a/ui/views/controls/native/native_view_host.cc ++++ b/ui/views/controls/native/native_view_host.cc +@@ -54,6 +54,9 @@ void NativeViewHost::Detach() { + } + + void NativeViewHost::SetParentAccessible(gfx::NativeViewAccessible accessible) { ++ if (!native_wrapper_.get()) ++ return; ++ + native_wrapper_->SetParentAccessible(accessible); + } + diff --git a/patches/chromium/fix_use_electron_generated_resources.patch b/patches/chromium/fix_use_electron_generated_resources.patch new file mode 100644 index 0000000000000..bd59ee8c468db --- /dev/null +++ b/patches/chromium/fix_use_electron_generated_resources.patch @@ -0,0 +1,52 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Shelley Vohr +Date: Thu, 24 Sep 2020 11:10:41 -0700 +Subject: fix: use electron generated resources + +This patch fixes a few instances where we need to use Electron generated +resources for IDS strings, or the IDs will be wrong and cause DCHECKS +as they will loaded as empty strings. + +* IDS_UTILITY_PROCESS_UTILITY_WIN_NAME on Windows +* IDR_PDF_MANIFEST on Linux +* IDS_UTILITY_PROCESS_PRINTING_SERVICE_NAME on Windows + +diff --git a/chrome/browser/pdf/pdf_extension_util.cc b/chrome/browser/pdf/pdf_extension_util.cc +index 9fd9791fd923d6c5e741cb7dc064ababf9a7995a..0a5534f2f09224196971076794daa3c805c91606 100644 +--- a/chrome/browser/pdf/pdf_extension_util.cc ++++ b/chrome/browser/pdf/pdf_extension_util.cc +@@ -8,7 +8,7 @@ + #include "base/values.h" + #include "chrome/browser/browser_process.h" + #include "chrome/common/chrome_content_client.h" +-#include "chrome/grit/browser_resources.h" ++#include "electron/grit/electron_resources.h" + #include "components/strings/grit/components_strings.h" + #include "components/zoom/page_zoom_constants.h" + #include "pdf/pdf_features.h" +diff --git a/chrome/browser/printing/printing_service.cc b/chrome/browser/printing/printing_service.cc +index 2b73b110049b5e8d28b52656bbd2423e18ba07a0..8fd868b39d64c74aa189b8ca3e24c8537d91b1ba 100644 +--- a/chrome/browser/printing/printing_service.cc ++++ b/chrome/browser/printing/printing_service.cc +@@ -6,7 +6,7 @@ + + #include "base/no_destructor.h" + #include "chrome/browser/service_sandbox_type.h" +-#include "chrome/grit/generated_resources.h" ++#include "electron/grit/electron_resources.h" + #include "content/public/browser/service_process_host.h" + + const mojo::Remote& GetPrintingService() { +diff --git a/chrome/browser/win/icon_reader_service.cc b/chrome/browser/win/icon_reader_service.cc +index 721e1a863cc6925908f8343002df056f2373bf0b..10b2a95162541a8ff4d010c7be864f3f41dae378 100644 +--- a/chrome/browser/win/icon_reader_service.cc ++++ b/chrome/browser/win/icon_reader_service.cc +@@ -5,7 +5,7 @@ + #include "chrome/browser/win/icon_reader_service.h" + + #include "chrome/browser/service_sandbox_type.h" +-#include "chrome/grit/generated_resources.h" ++#include "electron/grit/electron_resources.h" + #include "content/public/browser/service_process_host.h" + + mojo::Remote LaunchIconReaderInstance() { diff --git a/patches/chromium/gpu_notify_when_dxdiag_request_fails.patch b/patches/chromium/gpu_notify_when_dxdiag_request_fails.patch index 6df36ebd40698..f4db5eaf00107 100644 --- a/patches/chromium/gpu_notify_when_dxdiag_request_fails.patch +++ b/patches/chromium/gpu_notify_when_dxdiag_request_fails.patch @@ -12,10 +12,10 @@ rendering and there is no signal from browser process on this event to identify it. diff --git a/content/browser/gpu/gpu_data_manager_impl.cc b/content/browser/gpu/gpu_data_manager_impl.cc -index 0f73a8d0da336036e663927424a3a0ccba9e4f7b..e2fe4908e8e459bb277dfd6becdc486714e814ea 100644 +index f266f1b45a423db92f59e2d6acbea659cef92de7..2f6b86f3f77e0fe8b1d86831b0f492cd35627f98 100644 --- a/content/browser/gpu/gpu_data_manager_impl.cc +++ b/content/browser/gpu/gpu_data_manager_impl.cc -@@ -188,6 +188,11 @@ void GpuDataManagerImpl::TerminateInfoCollectionGpuProcess() { +@@ -229,6 +229,11 @@ void GpuDataManagerImpl::TerminateInfoCollectionGpuProcess() { base::AutoLock auto_lock(lock_); private_->TerminateInfoCollectionGpuProcess(); } @@ -28,10 +28,10 @@ index 0f73a8d0da336036e663927424a3a0ccba9e4f7b..e2fe4908e8e459bb277dfd6becdc4867 void GpuDataManagerImpl::UpdateGpuFeatureInfo( diff --git a/content/browser/gpu/gpu_data_manager_impl.h b/content/browser/gpu/gpu_data_manager_impl.h -index 2e069f00bb0f2c263b60662988548b246b74e5d9..82e9a19b6502aeab8f5d14098f2e5c5f995c3425 100644 +index b1cb1060511872c07ffd7e7ccfebdbe31233e677..5cdcc8f6445afbe2a46946d7342fd386ccaa6177 100644 --- a/content/browser/gpu/gpu_data_manager_impl.h +++ b/content/browser/gpu/gpu_data_manager_impl.h -@@ -95,6 +95,7 @@ class CONTENT_EXPORT GpuDataManagerImpl : public GpuDataManager, +@@ -97,6 +97,7 @@ class CONTENT_EXPORT GpuDataManagerImpl : public GpuDataManager, // Called from BrowserMainLoop::BrowserThreadsStarted(). void OnBrowserThreadsStarted(); void TerminateInfoCollectionGpuProcess(); @@ -40,7 +40,7 @@ index 2e069f00bb0f2c263b60662988548b246b74e5d9..82e9a19b6502aeab8f5d14098f2e5c5f // Update the GPU feature info. This updates the blacklist and enabled status // of GPU rasterization. In the future this will be used for more features. diff --git a/content/browser/gpu/gpu_data_manager_impl_private.cc b/content/browser/gpu/gpu_data_manager_impl_private.cc -index 54a2d002794e3cf3c40799e437456dd37df51503..4faf21eab883aa7095d712678ceea95975bdd18f 100644 +index e88f18d51f0844cb46e991be695b1637c564defe..a19d9391241a3cd0bbf907675c3f3c7a91aee0ad 100644 --- a/content/browser/gpu/gpu_data_manager_impl_private.cc +++ b/content/browser/gpu/gpu_data_manager_impl_private.cc @@ -1063,6 +1063,11 @@ void GpuDataManagerImplPrivate::TerminateInfoCollectionGpuProcess() { @@ -56,7 +56,7 @@ index 54a2d002794e3cf3c40799e437456dd37df51503..4faf21eab883aa7095d712678ceea959 void GpuDataManagerImplPrivate::UpdateGpuFeatureInfo( diff --git a/content/browser/gpu/gpu_data_manager_impl_private.h b/content/browser/gpu/gpu_data_manager_impl_private.h -index ef74bd7541a2a47e71289b154221a5f5adf1e90b..100d393550d57c24bb0c9dc6d604ed925930bb07 100644 +index 563472dcafa05b827218a301ad621236dbd2d864..8e5ceb626b4690906411ca81b8f89b9c5efeb165 100644 --- a/content/browser/gpu/gpu_data_manager_impl_private.h +++ b/content/browser/gpu/gpu_data_manager_impl_private.h @@ -75,6 +75,7 @@ class CONTENT_EXPORT GpuDataManagerImplPrivate { diff --git a/patches/chromium/guard_webcontents_downloadimage_against_malformed_renderer.patch b/patches/chromium/guard_webcontents_downloadimage_against_malformed_renderer.patch new file mode 100644 index 0000000000000..278e0b8c6a5e3 --- /dev/null +++ b/patches/chromium/guard_webcontents_downloadimage_against_malformed_renderer.patch @@ -0,0 +1,231 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Andrey Belenko +Date: Wed, 19 May 2021 12:19:16 +0200 +Subject: Guard WebContents::DownloadImage() against malformed renderer + response + +Callers expect that ImageDownloadCallback gets invoked with two vectors +having the same number of elements (one containing the bitmaps and the +other one the corresponding sizes). + +However, these vectors are populated directly from the Mojo response, +so there needs to be some browser-process sanitization to protect +against buggy or compromised renderers. + +In this patch, WebContentsImpl::OnDidDownloadImage() mimics a 400 error +if the response is malformed, similarly to how it's done in other edge +cases (renderer process dead upon download). Because this scenario is +a violation of the Mojo API contract, the browser process also issues +a bad message log (newly-introduced WCI_INVALID_DOWNLOAD_IMAGE_RESULT) +and shuts down the renderer process. + +(cherry picked from commit 034ba14e44f08e8ca84b42350f3238f847e08e5f) + +Change-Id: Ic0843e10efc26809fabd8f1bbe506ba1703d1486 +Fixed: 1201446 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2871796 + +diff --git a/components/favicon/core/favicon_handler.cc b/components/favicon/core/favicon_handler.cc +index 64eab051de73b0b259c1f31e93f750d352724521..410ffc64e8f70a29a39cc4fb228dfd720bfe04d7 100644 +--- a/components/favicon/core/favicon_handler.cc ++++ b/components/favicon/core/favicon_handler.cc +@@ -492,6 +492,8 @@ void FaviconHandler::OnDidDownloadFavicon( + const GURL& image_url, + const std::vector& bitmaps, + const std::vector& original_bitmap_sizes) { ++ DCHECK_EQ(bitmaps.size(), original_bitmap_sizes.size()); ++ + // Mark download as finished. + image_download_request_.Cancel(); + +diff --git a/components/favicon/core/favicon_handler.h b/components/favicon/core/favicon_handler.h +index 8a1cf2ef85745711e94ec6e7f6f27acb1482cbd6..4192138711de470eee713fc1fb200cc29d333342 100644 +--- a/components/favicon/core/favicon_handler.h ++++ b/components/favicon/core/favicon_handler.h +@@ -237,7 +237,9 @@ class FaviconHandler { + void ScheduleImageDownload(const GURL& image_url, + favicon_base::IconType icon_type); + +- // Triggered when a download of an image has finished. ++ // Triggered when a download of an image has finished. |bitmaps| and ++ // |original_bitmap_sizes| must contain the same number of elements (i.e. same ++ // vector size). + void OnDidDownloadFavicon( + favicon_base::IconType icon_type, + int id, +diff --git a/components/favicon/ios/web_favicon_driver.mm b/components/favicon/ios/web_favicon_driver.mm +index 6efaf651bdf939d83e1a7f9df339f714410565ad..71ae020efd8862d2b33cb91df9180ef63df1f6f9 100644 +--- a/components/favicon/ios/web_favicon_driver.mm ++++ b/components/favicon/ios/web_favicon_driver.mm +@@ -75,6 +75,7 @@ int WebFaviconDriver::DownloadImage(const GURL& url, + for (const auto& frame : frames) { + sizes.push_back(gfx::Size(frame.width(), frame.height())); + } ++ DCHECK_EQ(frames.size(), sizes.size()); + } + std::move(local_callback) + .Run(local_download_id, metadata.http_response_code, local_url, +diff --git a/components/favicon_base/select_favicon_frames.cc b/components/favicon_base/select_favicon_frames.cc +index 90b58e621492d0e503f7965de91581c0a451d163..73cd7dd5331a818f413d831b4ae596bc88805a8a 100644 +--- a/components/favicon_base/select_favicon_frames.cc ++++ b/components/favicon_base/select_favicon_frames.cc +@@ -216,6 +216,7 @@ gfx::ImageSkia CreateFaviconImageSkia( + const std::vector& original_sizes, + int desired_size_in_dip, + float* score) { ++ DCHECK_EQ(bitmaps.size(), original_sizes.size()); + + const std::vector& favicon_scales = favicon_base::GetFaviconScales(); + std::vector desired_sizes; +diff --git a/components/favicon_base/select_favicon_frames.h b/components/favicon_base/select_favicon_frames.h +index 573a38c79cddf839622488589b71b4d19fbdfba6..eab1e54466763e5a6a6069a29e877da054972fa8 100644 +--- a/components/favicon_base/select_favicon_frames.h ++++ b/components/favicon_base/select_favicon_frames.h +@@ -38,6 +38,8 @@ extern const float kSelectFaviconFramesInvalidScore; + // it inspired by this method. + // If an unsupported scale (not in the favicon_base::GetFaviconScales()) + // is requested, the ImageSkia will automatically scales using lancoz3. ++// |original_sizes| represents the pixel sizes of the favicon bitmaps in ++// |bitmaps|, which also means both vectors must have the same size. + gfx::ImageSkia CreateFaviconImageSkia( + const std::vector& bitmaps, + const std::vector& original_sizes, +diff --git a/content/browser/bad_message.h b/content/browser/bad_message.h +index ab0a195c2da772ea5825bb97e54743318bd06841..efc803efe24f61012882399035f6d96aed799595 100644 +--- a/content/browser/bad_message.h ++++ b/content/browser/bad_message.h +@@ -256,6 +256,15 @@ enum BadMessageReason { + INPUT_ROUTER_INVALID_EVENT_SOURCE = 228, + RWH_CLOSE_PORTAL = 233, + MSDH_INVALID_STREAM_TYPE = 234, ++ RFH_CREATE_CHILD_FRAME_TOKENS_NOT_FOUND = 235, ++ ASGH_ASSOCIATED_INTERFACE_REQUEST = 236, ++ ASGH_RECEIVED_CONTROL_MESSAGE = 237, ++ CSDH_BAD_OWNER = 238, ++ SYNC_COMPOSITOR_NO_LOCAL_SURFACE_ID = 239, ++ WCI_INVALID_FULLSCREEN_OPTIONS = 240, ++ PAYMENTS_WITHOUT_PERMISSION = 241, ++ WEB_BUNDLE_INVALID_NAVIGATION_URL = 242, ++ WCI_INVALID_DOWNLOAD_IMAGE_RESULT = 243, + + // Please add new elements here. The naming convention is abbreviated class + // name (e.g. RenderFrameHost becomes RFH) plus a unique description of the +diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc +index a7c76413b86fac18f6f1f54c87e67218f094e6b2..67deba5a7ab4265c76fe18990ede82c2705efe70 100644 +--- a/content/browser/web_contents/web_contents_impl.cc ++++ b/content/browser/web_contents/web_contents_impl.cc +@@ -160,6 +160,7 @@ + #include "third_party/blink/public/common/security/security_style.h" + #include "third_party/blink/public/mojom/frame/frame.mojom.h" + #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h" ++#include "third_party/blink/public/mojom/image_downloader/image_downloader.mojom.h" + #include "third_party/blink/public/mojom/loader/pause_subresource_loading_handle.mojom.h" + #include "third_party/blink/public/mojom/mediastream/media_stream.mojom-shared.h" + #include "third_party/skia/include/core/SkBitmap.h" +@@ -4295,18 +4296,18 @@ int WebContentsImpl::DownloadImageInFrame( + // respond with a 400 HTTP error code to indicate that something went wrong. + GetUIThreadTaskRunner({})->PostTask( + FROM_HERE, +- base::BindOnce(&WebContentsImpl::OnDidDownloadImage, +- weak_factory_.GetWeakPtr(), std::move(callback), +- download_id, url, 400, std::vector(), +- std::vector())); ++ base::BindOnce( ++ &WebContentsImpl::OnDidDownloadImage, weak_factory_.GetWeakPtr(), ++ initiator_frame->GetWeakPtr(), std::move(callback), download_id, ++ url, 400, std::vector(), std::vector())); + return download_id; + } + + mojo_image_downloader->DownloadImage( + url, is_favicon, preferred_size, max_bitmap_size, bypass_cache, + base::BindOnce(&WebContentsImpl::OnDidDownloadImage, +- weak_factory_.GetWeakPtr(), std::move(callback), +- download_id, url)); ++ weak_factory_.GetWeakPtr(), initiator_frame->GetWeakPtr(), ++ std::move(callback), download_id, url)); + return download_id; + } + +@@ -6829,12 +6830,28 @@ bool WebContentsImpl::CompletedFirstVisuallyNonEmptyPaint() { + } + + void WebContentsImpl::OnDidDownloadImage( ++ base::WeakPtr rfh, + ImageDownloadCallback callback, + int id, + const GURL& image_url, + int32_t http_status_code, + const std::vector& images, + const std::vector& original_image_sizes) { ++ ++ // Guard against buggy or compromised renderers that could violate the API ++ // contract that |images| and |original_image_sizes| must have the same ++ // length. ++ if (images.size() != original_image_sizes.size()) { ++ if (rfh) { ++ ReceivedBadMessage(rfh->GetProcess(), ++ bad_message::WCI_INVALID_DOWNLOAD_IMAGE_RESULT); ++ } ++ // Respond with a 400 to indicate that something went wrong. ++ std::move(callback).Run(id, 400, image_url, std::vector(), ++ std::vector()); ++ return; ++ } ++ + std::move(callback).Run(id, http_status_code, image_url, images, + original_image_sizes); + } +diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h +index 14586316aedab1e99f1b700ecc0e092c86414ee3..ffd53b3dc3ef0553711106f302e9f69b76b49081 100644 +--- a/content/browser/web_contents/web_contents_impl.h ++++ b/content/browser/web_contents/web_contents_impl.h +@@ -1403,7 +1403,8 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents, + std::set& result); + + // Called with the result of a DownloadImage() request. +- void OnDidDownloadImage(ImageDownloadCallback callback, ++ void OnDidDownloadImage(base::WeakPtr rfh, ++ ImageDownloadCallback callback, + int id, + const GURL& image_url, + int32_t http_status_code, +diff --git a/content/public/browser/web_contents.h b/content/public/browser/web_contents.h +index ca7651246f812259e43d5dbc429ae4a95ed60e94..a4572de2ee43ef0c2bb3a785f29974ebb8c1362c 100644 +--- a/content/public/browser/web_contents.h ++++ b/content/public/browser/web_contents.h +@@ -896,8 +896,9 @@ class WebContents : public PageNavigator, + // |bitmaps| will be empty on download failure. + // |sizes| are the sizes in pixels of the bitmaps before they were resized due + // to the max bitmap size passed to DownloadImage(). Each entry in the bitmaps +- // vector corresponds to an entry in the sizes vector. If a bitmap was +- // resized, there should be a single returned bitmap. ++ // vector corresponds to an entry in the sizes vector (both vector sizes are ++ // guaranteed to be equal). If a bitmap was resized, there should be a single ++ // returned bitmap. + using ImageDownloadCallback = + base::OnceCallback& unfiltered, + uint32_t max_image_size, +@@ -202,6 +203,8 @@ void ImageDownloaderImpl::DidDownloadImage( + FilterAndResizeImagesForMaximalSize(images, max_image_size, &result_images, + &result_original_image_sizes); + ++ DCHECK_EQ(result_images.size(), result_original_image_sizes.size()); ++ + std::move(callback).Run(http_status_code, result_images, + result_original_image_sizes); + } diff --git a/patches/chromium/ignore_renderframehostimpl_detach_for_speculative_rfhs.patch b/patches/chromium/ignore_renderframehostimpl_detach_for_speculative_rfhs.patch new file mode 100644 index 0000000000000..873e20771aedb --- /dev/null +++ b/patches/chromium/ignore_renderframehostimpl_detach_for_speculative_rfhs.patch @@ -0,0 +1,163 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Daniel Cheng +Date: Wed, 11 Nov 2020 00:54:41 +0000 +Subject: Ignore RenderFrameHostImpl::Detach() for speculative RFHs. + +Currently, this all happens to work by chance, because the speculative +RFH or the entire FTN happens to be torn down before the browser process +ever processes a Detach() IPC for a speculative RFH. + +However, there are a number of followup CLs that restructure how +provisional RenderFrames are managed and owned in the renderer process. +To simplify those CLs, explicitly branch in Detach() based on whether or +not the RFH is speculative. In the future, additional logic may be added +to the speculative branch (e.g. cancelling the navigation, if +appropriate). + +(cherry picked from commit cf054220a2e1570a9149220494de8826c2e9d4db) + +Bug: 1146709 +Change-Id: I6490a90f7b447422d698676665b52f6f3a6f8ffd +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2524280 +Commit-Queue: Daniel Cheng +Reviewed-by: Nasko Oskov +Cr-Original-Commit-Position: refs/heads/master@{#825903} +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2530189 +Reviewed-by: Adrian Taylor +Cr-Commit-Position: refs/branch-heads/4240@{#1430} +Cr-Branched-From: f297677702651916bbf65e59c0d4bbd4ce57d1ee-refs/heads/master@{#800218} + +diff --git a/content/browser/frame_host/render_frame_host_impl.cc b/content/browser/frame_host/render_frame_host_impl.cc +index c1d7a3052516c6c8ac5a8e65c9688d04b8edec70..ab54a32a17f29e9b5ea9365e92de563737a1e513 100644 +--- a/content/browser/frame_host/render_frame_host_impl.cc ++++ b/content/browser/frame_host/render_frame_host_impl.cc +@@ -2625,6 +2625,9 @@ void RenderFrameHostImpl::UpdateRenderProcessHostFramePriorities() { + } + + void RenderFrameHostImpl::OnDetach() { ++ if (lifecycle_state() == LifecycleState::kSpeculative) ++ return; ++ + if (!parent_) { + bad_message::ReceivedBadMessage(GetProcess(), + bad_message::RFH_DETACH_MAIN_FRAME); +diff --git a/content/browser/site_per_process_browsertest.cc b/content/browser/site_per_process_browsertest.cc +index 7c9902648ab0465f4e0a7b4778b328ad8c1f2fcd..d81076da5ef7701475ef1158b528d02d3992ff0d 100644 +--- a/content/browser/site_per_process_browsertest.cc ++++ b/content/browser/site_per_process_browsertest.cc +@@ -10333,6 +10333,37 @@ IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, + EXPECT_EQ("opener-ping-reply", response); + } + ++IN_PROC_BROWSER_TEST_P(SitePerProcessBrowserTest, ++ DetachSpeculativeRenderFrameHost) { ++ // Commit a page with one iframe. ++ GURL main_url(embedded_test_server()->GetURL( ++ "a.com", "/cross_site_iframe_factory.html?a(a)")); ++ EXPECT_TRUE(NavigateToURL(shell(), main_url)); ++ ++ // Start a cross-site navigation. ++ GURL cross_site_url(embedded_test_server()->GetURL("b.com", "/title2.html")); ++ TestNavigationManager nav_manager(shell()->web_contents(), cross_site_url); ++ BeginNavigateIframeToURL(web_contents(), "child-0", cross_site_url); ++ ++ // Wait for the request, but don't commit it yet. This should create a ++ // speculative RenderFrameHost. ++ ASSERT_TRUE(nav_manager.WaitForRequestStart()); ++ FrameTreeNode* root = web_contents()->GetFrameTree()->root(); ++ RenderFrameHostImpl* speculative_rfh = root->current_frame_host() ++ ->child_at(0) ++ ->render_manager() ++ ->speculative_frame_host(); ++ EXPECT_TRUE(speculative_rfh); ++ ++ // Currently, the browser process never handles an explicit Detach() for a ++ // speculative RFH, since the speculative RFH or the entire FTN is always ++ // destroyed before the renderer sends this IPC. ++ speculative_rfh->Detach(); ++ ++ // Passes if there is no crash. ++} ++ ++ + #if defined(OS_ANDROID) + + namespace { +diff --git a/content/public/test/browser_test_utils.cc b/content/public/test/browser_test_utils.cc +index cbc62fef825815309f417baee931a6c450500f32..d3fa4eb639f58c6eafe01651e643545897dcabcc 100644 +--- a/content/public/test/browser_test_utils.cc ++++ b/content/public/test/browser_test_utils.cc +@@ -603,15 +603,21 @@ bool NavigateToURL(WebContents* web_contents, + bool NavigateIframeToURL(WebContents* web_contents, + const std::string& iframe_id, + const GURL& url) { ++ TestNavigationObserver load_observer(web_contents); ++ bool result = BeginNavigateIframeToURL(web_contents, iframe_id, url); ++ load_observer.Wait(); ++ return result; ++} ++ ++bool BeginNavigateIframeToURL(WebContents* web_contents, ++ const std::string& iframe_id, ++ const GURL& url) { + std::string script = base::StringPrintf( + "setTimeout(\"" + "var iframes = document.getElementById('%s');iframes.src='%s';" + "\",0)", + iframe_id.c_str(), url.spec().c_str()); +- TestNavigationObserver load_observer(web_contents); +- bool result = ExecuteScript(web_contents, script); +- load_observer.Wait(); +- return result; ++ return ExecuteScript(web_contents, script); + } + + void NavigateToURLBlockUntilNavigationsComplete(WebContents* web_contents, +diff --git a/content/public/test/browser_test_utils.h b/content/public/test/browser_test_utils.h +index 275684ba6cf6b6a9f8e5f5b24951d8fb07969767..bc23592ce9b7eab268465fb0e171e8351aa25549 100644 +--- a/content/public/test/browser_test_utils.h ++++ b/content/public/test/browser_test_utils.h +@@ -138,6 +138,12 @@ bool NavigateIframeToURL(WebContents* web_contents, + const std::string& iframe_id, + const GURL& url); + ++// Similar to |NavigateIframeToURL()| but returns as soon as the navigation is ++// initiated. ++bool BeginNavigateIframeToURL(WebContents* web_contents, ++ const std::string& iframe_id, ++ const GURL& url); ++ + // Generate a URL for a file path including a query string. + GURL GetFileUrlWithQuery(const base::FilePath& path, + const std::string& query_string); +diff --git a/content/test/data/cross_site_iframe_factory.html b/content/test/data/cross_site_iframe_factory.html +index 0893a2063585a04520d9bc8f87ee0c1cb726c0d7..78f8126c9cf7f8ae9f2b0d34e17d76ad51784811 100644 +--- a/content/test/data/cross_site_iframe_factory.html ++++ b/content/test/data/cross_site_iframe_factory.html +@@ -10,12 +10,12 @@ Example usage in a browsertest, explained: + When you navigate to the above URL, the outer document (on a.com) will create a + single iframe: + +-