mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
58 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
faa40332ad | ||
|
|
e1ac9d5d1b | ||
|
|
dc254e4bfc | ||
|
|
5631882390 | ||
|
|
e6b53033dd | ||
|
|
fd4f835f37 | ||
|
|
f36da597ff | ||
|
|
87badb84f8 | ||
|
|
2f3a1ca461 | ||
|
|
81ae20905c | ||
|
|
da4a808af7 | ||
|
|
71579e4749 | ||
|
|
06fdee87b3 | ||
|
|
6ce52ad792 | ||
|
|
7784d25821 | ||
|
|
126a32c5d2 | ||
|
|
811b1d6326 | ||
|
|
50fc493ae6 | ||
|
|
9453e8bfe1 | ||
|
|
a4eb213a04 | ||
|
|
81c08e80f6 | ||
|
|
5f630c7de7 | ||
|
|
2dc82ea1f3 | ||
|
|
f57d6f92b6 | ||
|
|
744142fe54 | ||
|
|
b200b8d6c0 | ||
|
|
cdaf0e96b6 | ||
|
|
981df181c1 | ||
|
|
218300e57f | ||
|
|
6ccee512e4 | ||
|
|
b6e4f514d8 | ||
|
|
ade4c00984 | ||
|
|
d8687cfc9d | ||
|
|
2ab4489447 | ||
|
|
ab9b156113 | ||
|
|
35a531953b | ||
|
|
4d18062d0f | ||
|
|
832ffb2330 | ||
|
|
03121eeaef | ||
|
|
8282c07a0f | ||
|
|
f2d1cb21b0 | ||
|
|
ef9b4162af | ||
|
|
6e97bca80d | ||
|
|
c511fc5c3f | ||
|
|
22dfbb0822 | ||
|
|
85913a38da | ||
|
|
a327629ca2 | ||
|
|
7deed2b980 | ||
|
|
65fc06a9f7 | ||
|
|
245e70aedd | ||
|
|
2a8164f499 | ||
|
|
2f7024dbcc | ||
|
|
d53d3bb99e | ||
|
|
c2c1d40294 | ||
|
|
0e9decd459 | ||
|
|
b2e73d28e2 | ||
|
|
aeb5af803f | ||
|
|
53819a8a2a |
@@ -2,7 +2,7 @@ version: '3'
|
||||
|
||||
services:
|
||||
buildtools:
|
||||
image: ghcr.io/electron/devcontainer:933c7d6ff6802706875270bec2e3c891cf8add3f
|
||||
image: ghcr.io/electron/devcontainer:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
|
||||
|
||||
volumes:
|
||||
- ..:/workspaces/gclient/src/electron:cached
|
||||
|
||||
2
.github/actions/build-electron/action.yml
vendored
2
.github/actions/build-electron/action.yml
vendored
@@ -184,7 +184,7 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/yarn create-typescript-definitions
|
||||
node script/yarn.js create-typescript-definitions
|
||||
- name: Publish Electron Dist ${{ inputs.step-suffix }}
|
||||
if: ${{ inputs.is-release == 'true' }}
|
||||
shell: bash
|
||||
|
||||
5
.github/actions/checkout/action.yml
vendored
5
.github/actions/checkout/action.yml
vendored
@@ -143,16 +143,17 @@ runs:
|
||||
echo "No changes to patches detected"
|
||||
fi
|
||||
fi
|
||||
- name: Remove patch conflict problem matcher
|
||||
- name: Remove patch conflict problem matchers
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::remove-matcher owner=merge-conflict::"
|
||||
echo "::remove-matcher owner=patch-conflict::"
|
||||
echo "::remove-matcher owner=patch-needs-update::"
|
||||
- name: Upload patches stats
|
||||
if: ${{ inputs.target-platform == 'linux' && github.ref == 'refs/heads/main' }}
|
||||
shell: bash
|
||||
run: |
|
||||
npx node src/electron/script/patches-stats.mjs --upload-stats || true
|
||||
node src/electron/script/patches-stats.mjs --upload-stats || true
|
||||
# delete all .git directories under src/ except for
|
||||
# third_party/angle/ and third_party/dawn/ because of build time generation of files
|
||||
# gen/angle/commit.h depends on third_party/angle/.git/HEAD
|
||||
|
||||
11
.github/actions/free-space-macos/action.yml
vendored
11
.github/actions/free-space-macos/action.yml
vendored
@@ -64,15 +64,24 @@ runs:
|
||||
sudo rm -rf /Applications/Xcode_16.1.app
|
||||
sudo rm -rf /Applications/Xcode_16.2.app
|
||||
sudo rm -rf /Applications/Xcode_16.3.app
|
||||
sudo rm -rf /Applications/Xcode_26*
|
||||
sudo rm -rf /Applications/Google Chrome.app
|
||||
sudo rm -rf /Applications/Google Chrome for Testing.app
|
||||
sudo rm -rf /Applications/Firefox.app
|
||||
sudo rm -rf /Applications/Firefox.app
|
||||
sudo rm -rf /Applications/Microsoft Edge.app
|
||||
sudo rm -rf ~/project/src/third_party/catapult/tracing/test_data
|
||||
sudo rm -rf ~/project/src/third_party/angle/third_party/VK-GL-CTS
|
||||
sudo rm -rf /Users/runner/Library/Android
|
||||
sudo rm -rf $JAVA_HOME_11_arm64
|
||||
sudo rm -rf $JAVA_HOME_17_arm64
|
||||
sudo rm -rf $JAVA_HOME_21_arm64
|
||||
sudo rm -rf $JAVA_HOME_25_arm64
|
||||
sudo rm -rf /Users/runner/.dotnet/
|
||||
sudo rm -rf /Users/runner/.rustup
|
||||
|
||||
# remove homebrew packages we don't need
|
||||
brew uninstall -f --zap aws-sam-cli session-manager-plugin gcc gcc@13 gcc@14 llvm@18 gradle maven ant azure-cli
|
||||
brew autoremove
|
||||
|
||||
# lipo off some huge binaries arm64 versions to save space
|
||||
strip_universal_deep $(xcode-select -p)/../SharedFrameworks
|
||||
|
||||
14
.github/actions/generate-types/action.yml
vendored
14
.github/actions/generate-types/action.yml
vendored
@@ -13,12 +13,16 @@ runs:
|
||||
- name: Generating Types for SHA in ${{ inputs.sha-file }}
|
||||
shell: bash
|
||||
run: |
|
||||
git checkout $(cat ${{ inputs.sha-file }})
|
||||
rm -rf node_modules
|
||||
yarn install --frozen-lockfile --ignore-scripts
|
||||
export ELECTRON_DIR=$(pwd)
|
||||
if [ "${{ inputs.sha-file }}" == ".dig-old" ]; then
|
||||
cd /tmp
|
||||
git clone https://github.com/electron/electron.git
|
||||
cd electron
|
||||
fi
|
||||
git checkout $(cat $ELECTRON_DIR/${{ inputs.sha-file }})
|
||||
node script/yarn.js install --immutable
|
||||
echo "#!/usr/bin/env node\nglobal.x=1" > node_modules/typescript/bin/tsc
|
||||
node node_modules/.bin/electron-docs-parser --dir=./ --outDir=./ --moduleVersion=0.0.0-development
|
||||
node node_modules/.bin/electron-typescript-definitions --api=electron-api.json --outDir=artifacts
|
||||
mv artifacts/electron.d.ts artifacts/${{ inputs.filename }}
|
||||
git checkout .
|
||||
mv artifacts/electron.d.ts $ELECTRON_DIR/artifacts/${{ inputs.filename }}
|
||||
working-directory: ./electron
|
||||
|
||||
@@ -15,7 +15,7 @@ runs:
|
||||
git config --global core.preloadindex true
|
||||
git config --global core.longpaths true
|
||||
fi
|
||||
export BUILD_TOOLS_SHA=a5d9f9052dcc36ee88bef5c8b13acbefd87b7d8d
|
||||
export BUILD_TOOLS_SHA=4430e4a505e0f4fa2a41b707a10a36f780bbdd26
|
||||
npm i -g @electron/build-tools
|
||||
# Update depot_tools to ensure python
|
||||
e d update_depot_tools
|
||||
|
||||
14
.github/actions/install-dependencies/action.yml
vendored
14
.github/actions/install-dependencies/action.yml
vendored
@@ -6,7 +6,7 @@ runs:
|
||||
- name: Get yarn cache directory path
|
||||
shell: bash
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(node src/electron/script/yarn cache dir)" >> $GITHUB_OUTPUT
|
||||
run: echo "dir=$(node src/electron/script/yarn.js config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
id: yarn-cache
|
||||
with:
|
||||
@@ -18,4 +18,14 @@ runs:
|
||||
shell: bash
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/yarn install --frozen-lockfile --prefer-offline
|
||||
if [ "$TARGET_ARCH" = "x86" ]; then
|
||||
export npm_config_arch="ia32"
|
||||
fi
|
||||
# if running on linux arm skip yarn Builds
|
||||
ARCH=$(uname -m)
|
||||
if [ "$ARCH" = "armv7l" ]; then
|
||||
echo "Skipping yarn build on linux arm"
|
||||
node script/yarn.js install --immutable --mode=skip-build
|
||||
else
|
||||
node script/yarn.js install --immutable
|
||||
fi
|
||||
|
||||
10
.github/problem-matchers/patch-conflict.json
vendored
10
.github/problem-matchers/patch-conflict.json
vendored
@@ -19,6 +19,16 @@
|
||||
"line": 3
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "patch-needs-update",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^((patches\/.*): needs update)$",
|
||||
"message": 1,
|
||||
"file": 2
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
72
.github/workflows/apply-patches.yml
vendored
Normal file
72
.github/workflows/apply-patches.yml
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
name: Apply Patches
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions: {}
|
||||
|
||||
concurrency:
|
||||
group: apply-patches-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
setup:
|
||||
if: github.repository == 'electron/electron'
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: read
|
||||
outputs:
|
||||
has-patches: ${{ steps.filter.outputs.patches }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
# Use dorny/paths-filter instead of the path filter under the on: pull_request: block
|
||||
# so that the output can be used to conditionally run the apply-patches job, which lets
|
||||
# the job be marked as a required status check (conditional skip counts as a success).
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
patches:
|
||||
- DEPS
|
||||
- 'patches/**'
|
||||
|
||||
apply-patches:
|
||||
needs: setup
|
||||
if: ${{ needs.setup.outputs.has-patches == 'true' }}
|
||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||
permissions:
|
||||
contents: read
|
||||
container:
|
||||
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
|
||||
options: --user root
|
||||
volumes:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /var/run/sas:/var/run/sas
|
||||
env:
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
steps:
|
||||
- name: Checkout Electron
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
path: src/electron
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Rebase onto Base Branch
|
||||
working-directory: src/electron
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
run: |
|
||||
git config user.email "electron@github.com"
|
||||
git config user.name "Electron Bot"
|
||||
git fetch origin ${BASE_REF}
|
||||
git rebase origin/${BASE_REF}
|
||||
- name: Checkout & Sync & Save
|
||||
uses: ./src/electron/.github/actions/checkout
|
||||
with:
|
||||
target-platform: linux
|
||||
6
.github/workflows/build-git-cache.yml
vendored
6
.github/workflows/build-git-cache.yml
vendored
@@ -14,7 +14,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
container:
|
||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
||||
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
|
||||
options: --user root
|
||||
volumes:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
@@ -37,7 +37,7 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
container:
|
||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
||||
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
|
||||
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
|
||||
volumes:
|
||||
- /mnt/win-cache:/mnt/win-cache
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
# This job updates the same git cache as linux, so it needs to run after the linux one.
|
||||
needs: build-git-cache-linux
|
||||
container:
|
||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
||||
image: ghcr.io/electron/build:a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb
|
||||
options: --user root
|
||||
volumes:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
|
||||
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
build-image-sha:
|
||||
type: string
|
||||
description: 'SHA for electron/build image'
|
||||
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
|
||||
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
|
||||
required: true
|
||||
skip-macos:
|
||||
type: boolean
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
id: set-output
|
||||
run: |
|
||||
if [ -z "${{ inputs.build-image-sha }}" ]; then
|
||||
echo "build-image-sha=933c7d6ff6802706875270bec2e3c891cf8add3f" >> "$GITHUB_OUTPUT"
|
||||
echo "build-image-sha=a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "build-image-sha=${{ inputs.build-image-sha }}" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
@@ -333,7 +333,7 @@ jobs:
|
||||
build-runs-on: electron-arc-centralus-linux-amd64-32core
|
||||
test-runs-on: electron-arc-centralus-linux-arm64-4core
|
||||
build-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root","volumes":["/mnt/cross-instance-cache:/mnt/cross-instance-cache"]}'
|
||||
test-container: '{"image":"ghcr.io/electron/test:arm32v7-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init","volumes":["/home/runner/externals:/mnt/runner-externals"]}'
|
||||
test-container: '{"image":"ghcr.io/electron/test:arm32v7-${{ needs.checkout-linux.outputs.build-image-sha }}","options":"--user root --privileged --init --memory=12g","volumes":["/home/runner/externals:/mnt/runner-externals"]}'
|
||||
target-platform: linux
|
||||
target-arch: arm
|
||||
is-release: false
|
||||
|
||||
2
.github/workflows/linux-publish.yml
vendored
2
.github/workflows/linux-publish.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
build-image-sha:
|
||||
type: string
|
||||
description: 'SHA for electron/build image'
|
||||
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
|
||||
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
|
||||
upload-to-storage:
|
||||
description: 'Uploads to Azure storage'
|
||||
required: false
|
||||
|
||||
2
.github/workflows/macos-publish.yml
vendored
2
.github/workflows/macos-publish.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
build-image-sha:
|
||||
type: string
|
||||
description: 'SHA for electron/build image'
|
||||
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
|
||||
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
|
||||
required: true
|
||||
upload-to-storage:
|
||||
description: 'Uploads to Azure storage'
|
||||
|
||||
@@ -7,6 +7,8 @@ on:
|
||||
- 'spec/yarn.lock'
|
||||
- '.github/workflows/**'
|
||||
- '.github/actions/**'
|
||||
- '.yarn/**'
|
||||
- '.yarnrc.yml'
|
||||
|
||||
permissions: {}
|
||||
|
||||
|
||||
@@ -54,12 +54,12 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/yarn create-typescript-definitions
|
||||
node script/yarn tsc -p tsconfig.default_app.json --noEmit
|
||||
node script/yarn.js create-typescript-definitions
|
||||
node script/yarn.js tsc -p tsconfig.default_app.json --noEmit
|
||||
for f in build/webpack/*.js
|
||||
do
|
||||
out="${f:29}"
|
||||
if [ "$out" != "base.js" ]; then
|
||||
node script/yarn webpack --config $f --output-filename=$out --output-path=./.tmp --env mode=development
|
||||
node script/yarn.js webpack --config $f --output-filename=$out --output-path=./.tmp --env mode=development
|
||||
fi
|
||||
done
|
||||
|
||||
6
.github/workflows/pipeline-electron-lint.yml
vendored
6
.github/workflows/pipeline-electron-lint.yml
vendored
@@ -78,11 +78,11 @@ jobs:
|
||||
# but then we would lint its contents (at least gn format), and it doesn't pass it.
|
||||
|
||||
cd src/electron
|
||||
node script/yarn install --frozen-lockfile
|
||||
node script/yarn lint
|
||||
node script/yarn.js install --immutable
|
||||
node script/yarn.js lint
|
||||
- name: Run Script Typechecker
|
||||
shell: bash
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/yarn tsc -p tsconfig.script.json
|
||||
node script/yarn.js tsc -p tsconfig.script.json
|
||||
|
||||
|
||||
@@ -196,10 +196,7 @@ jobs:
|
||||
# sudo security authorizationdb write com.apple.trust-settings.admin allow
|
||||
# cd src/electron
|
||||
# ./script/codesign/generate-identity.sh
|
||||
- name: Install Datadog CLI
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/yarn global add @datadog/datadog-ci
|
||||
|
||||
- name: Run Electron Tests
|
||||
shell: bash
|
||||
env:
|
||||
@@ -225,7 +222,7 @@ jobs:
|
||||
export ELECTRON_FORCE_TEST_SUITE_EXIT="true"
|
||||
fi
|
||||
fi
|
||||
node script/yarn test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
|
||||
node script/yarn.js test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
|
||||
else
|
||||
chown :builduser .. && chmod g+w ..
|
||||
chown -R :builduser . && chmod -R g+w .
|
||||
@@ -242,9 +239,14 @@ jobs:
|
||||
export MOCHA_TIMEOUT=180000
|
||||
echo "Piping output to ASAN_SYMBOLIZE ($ASAN_SYMBOLIZE)"
|
||||
cd electron
|
||||
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files | $ASAN_SYMBOLIZE
|
||||
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --runners=main --trace-uncaught --enable-logging --files $tests_files | $ASAN_SYMBOLIZE
|
||||
else
|
||||
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn test --runners=main --trace-uncaught --enable-logging --files $tests_files
|
||||
if [ "${{ inputs.target-arch }}" = "arm" ]; then
|
||||
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --skipYarnInstall --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
|
||||
else
|
||||
runuser -u builduser -- xvfb-run script/actions/run-tests.sh script/yarn.js test --runners=main --enableRerun=3 --trace-uncaught --enable-logging --files $tests_files
|
||||
fi
|
||||
|
||||
fi
|
||||
fi
|
||||
- name: Upload Test results to Datadog
|
||||
@@ -256,9 +258,10 @@ jobs:
|
||||
DD_TAGS: "os.architecture:${{ inputs.target-arch }},os.family:${{ inputs.target-platform }},os.platform:${{ inputs.target-platform }},asan:${{ inputs.is-asan }}"
|
||||
run: |
|
||||
if ! [ -z $DD_API_KEY ] && [ -f src/electron/junit/test-results-main.xml ]; then
|
||||
export DATADOG_PATH=`node src/electron/script/yarn global bin`
|
||||
$DATADOG_PATH/datadog-ci junit upload src/electron/junit/test-results-main.xml
|
||||
fi
|
||||
cd src/electron
|
||||
export DATADOG_PATH=`node script/yarn.js bin datadog-ci`
|
||||
$DATADOG_PATH junit upload junit/test-results-main.xml
|
||||
fi
|
||||
if: always() && !cancelled()
|
||||
- name: Upload Test Artifacts
|
||||
if: always() && !cancelled()
|
||||
|
||||
@@ -138,10 +138,16 @@ jobs:
|
||||
unzip -:o dist.zip
|
||||
- name: Setup Linux for Headless Testing
|
||||
run: sh -e /etc/init.d/xvfb start
|
||||
- name: Add Clang problem matcher
|
||||
shell: bash
|
||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/clang.json"
|
||||
- name: Run Nan Tests
|
||||
run: |
|
||||
cd src
|
||||
node electron/script/nan-spec-runner.js
|
||||
- name: Remove Clang problem matcher
|
||||
shell: bash
|
||||
run: echo "::remove-matcher owner=clang::"
|
||||
- name: Wait for active SSH sessions
|
||||
shell: bash
|
||||
if: always() && !cancelled()
|
||||
|
||||
71
.github/workflows/rerun-apply-patches.yml
vendored
Normal file
71
.github/workflows/rerun-apply-patches.yml
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
name: Rerun PR Apply Patches
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
- '[1-9][0-9]-x-y'
|
||||
paths:
|
||||
- 'DEPS'
|
||||
- 'patches/**'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
rerun-apply-patches:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
actions: write
|
||||
checks: read
|
||||
contents: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Find PRs and Rerun Apply Patches
|
||||
env:
|
||||
GH_REPO: ${{ github.repository }}
|
||||
GH_TOKEN: ${{ github.token }}
|
||||
run: |
|
||||
BRANCH="${GITHUB_REF#refs/heads/}"
|
||||
|
||||
# Find all open PRs targeting this branch
|
||||
PRS=$(gh pr list --base "$BRANCH" --state open --limit 250 --json number)
|
||||
|
||||
echo "$PRS" | jq -c '.[]' | while read -r pr; do
|
||||
PR_NUMBER=$(echo "$pr" | jq -r '.number')
|
||||
echo "Processing PR #${PR_NUMBER}"
|
||||
|
||||
# Find the Apply Patches workflow check for this PR
|
||||
CHECK=$(gh pr checks "$PR_NUMBER" --json link,name,state,workflow --jq '[.[] | select(.workflow == "Apply Patches" and .name == "apply-patches")] | first')
|
||||
|
||||
if [ -z "$CHECK" ] || [ "$CHECK" = "null" ]; then
|
||||
echo " No Apply Patches workflow found for PR #${PR_NUMBER}"
|
||||
continue
|
||||
fi
|
||||
|
||||
STATE=$(echo "$CHECK" | jq -r '.state')
|
||||
if [ "$STATE" = "SKIPPED" ]; then
|
||||
echo " apply-patches job was skipped for PR #${PR_NUMBER} (no patches)"
|
||||
continue
|
||||
fi
|
||||
|
||||
LINK=$(echo "$CHECK" | jq -r '.link')
|
||||
|
||||
# Extract the run ID from the link (format: .../runs/RUN_ID/job/JOB_ID)
|
||||
RUN_ID=$(echo "$LINK" | grep -oE 'runs/[0-9]+' | cut -d'/' -f2)
|
||||
|
||||
if [ -z "$RUN_ID" ]; then
|
||||
echo " Could not extract run ID from link: ${LINK}"
|
||||
continue
|
||||
fi
|
||||
|
||||
# Check if the workflow is currently in progress
|
||||
RUN_STATUS=$(gh run view "$RUN_ID" --json status --jq '.status')
|
||||
|
||||
if [ "$RUN_STATUS" = "in_progress" ] || [ "$RUN_STATUS" = "queued" ] || [ "$RUN_STATUS" = "waiting" ]; then
|
||||
echo " Workflow run ${RUN_ID} is ${RUN_STATUS}, cancelling..."
|
||||
gh run cancel "$RUN_ID" --force
|
||||
gh run watch "$RUN_ID"
|
||||
fi
|
||||
|
||||
gh run rerun "$RUN_ID"
|
||||
done
|
||||
2
.github/workflows/windows-publish.yml
vendored
2
.github/workflows/windows-publish.yml
vendored
@@ -6,7 +6,7 @@ on:
|
||||
build-image-sha:
|
||||
type: string
|
||||
description: 'SHA for electron/build image'
|
||||
default: '933c7d6ff6802706875270bec2e3c891cf8add3f'
|
||||
default: 'a82b87d7a4f5ff0cab61405f8151ac4cf4942aeb'
|
||||
required: true
|
||||
upload-to-storage:
|
||||
description: 'Uploads to Azure storage'
|
||||
|
||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -53,3 +53,5 @@ ts-gen
|
||||
patches/mtime-cache.json
|
||||
|
||||
spec/fixtures/logo.png
|
||||
|
||||
.yarn/install-state.gz
|
||||
942
.yarn/releases/yarn-4.12.0.cjs
vendored
Executable file
942
.yarn/releases/yarn-4.12.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
12
.yarnrc.yml
Normal file
12
.yarnrc.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
enableScripts: false
|
||||
|
||||
nmHoistingLimits: workspaces
|
||||
|
||||
nodeLinker: node-modules
|
||||
|
||||
npmMinimalAgeGate: 10080
|
||||
|
||||
npmPreapprovedPackages:
|
||||
- "@electron/*"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||
32
BUILD.gn
32
BUILD.gn
@@ -420,6 +420,37 @@ action("electron_generate_node_defines") {
|
||||
args = [ rebase_path(target_gen_dir) ] + rebase_path(inputs)
|
||||
}
|
||||
|
||||
# MSIX updater needs to be in a separate source_set because it uses C++/WinRT
|
||||
# headers that require exceptions to be enabled.
|
||||
source_set("electron_msix_updater") {
|
||||
sources = [
|
||||
"shell/browser/api/electron_api_msix_updater.cc",
|
||||
"shell/browser/api/electron_api_msix_updater.h",
|
||||
]
|
||||
|
||||
configs += [ "//third_party/electron_node:node_external_config" ]
|
||||
|
||||
public_configs = [ ":electron_lib_config" ]
|
||||
|
||||
if (is_win) {
|
||||
cflags_cc = [
|
||||
"/EHsc", # Enable C++ exceptions for C++/WinRT
|
||||
"-Wno-c++98-compat-extra-semi", #Suppress C++98 compatibility warnings
|
||||
]
|
||||
|
||||
include_dirs = [ "//third_party/nearby/src/internal/platform/implementation/windows/generated" ]
|
||||
}
|
||||
|
||||
deps = [
|
||||
"//base",
|
||||
"//content/public/browser",
|
||||
"//gin",
|
||||
"//third_party/electron_node/deps/simdjson",
|
||||
"//third_party/electron_node/deps/uv",
|
||||
"//v8",
|
||||
]
|
||||
}
|
||||
|
||||
source_set("electron_lib") {
|
||||
configs += [
|
||||
"//v8:external_startup_data",
|
||||
@@ -435,6 +466,7 @@ source_set("electron_lib") {
|
||||
":electron_fuses",
|
||||
":electron_generate_node_defines",
|
||||
":electron_js2c",
|
||||
":electron_msix_updater",
|
||||
":electron_version_header",
|
||||
":resources",
|
||||
"buildflags",
|
||||
|
||||
9
DEPS
9
DEPS
@@ -2,9 +2,9 @@ gclient_gn_args_from = 'src'
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'142.0.7444.175',
|
||||
'142.0.7444.265',
|
||||
'node_version':
|
||||
'v22.21.1',
|
||||
'v22.22.0',
|
||||
'nan_version':
|
||||
'e14bdcd1f72d62bca1d541b66da43130384ec213',
|
||||
'squirrel.mac_version':
|
||||
@@ -30,9 +30,6 @@ vars = {
|
||||
# The path of the sysroots.json file.
|
||||
'sysroots_json_path': 'electron/script/sysroots.json',
|
||||
|
||||
# KEEP IN SYNC WITH utils.js FILE
|
||||
'yarn_version': '1.22.22',
|
||||
|
||||
# To be able to build clean Chromium from sources.
|
||||
'apply_patches': True,
|
||||
|
||||
@@ -155,7 +152,7 @@ hooks = [
|
||||
'action': [
|
||||
'python3',
|
||||
'-c',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["python3", "script/lib/npx.py", "yarn@' + (Var("yarn_version")) + '", "install", "--frozen-lockfile"]);',
|
||||
'import os, subprocess; os.chdir(os.path.join("src", "electron")); subprocess.check_call(["node", ".yarn/releases/yarn-4.12.0.cjs", "install", "--immutable"]);',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -32,9 +32,19 @@ update process. Apps that need to disable ATS can add the
|
||||
|
||||
### Windows
|
||||
|
||||
On Windows, you have to install your app into a user's machine before you can
|
||||
use the `autoUpdater`, so it is recommended that you use
|
||||
[electron-winstaller][installer-lib] or [Electron Forge's Squirrel.Windows maker][electron-forge-lib] to generate a Windows installer.
|
||||
On Windows, the `autoUpdater` module automatically selects the appropriate update mechanism
|
||||
based on how your app is packaged:
|
||||
|
||||
* **MSIX packages**: If your app is running as an MSIX package (created with [electron-windows-msix][msix-lib] and detected via [`process.windowsStore`](process.md#processwindowsstore-readonly)),
|
||||
the module uses the MSIX updater, which supports direct MSIX file links and JSON update feeds.
|
||||
* **Squirrel.Windows**: For apps installed via traditional installers (created with
|
||||
[electron-winstaller][installer-lib] or [Electron Forge's Squirrel.Windows maker][electron-forge-lib]),
|
||||
the module uses Squirrel.Windows for updates.
|
||||
|
||||
You don't need to configure which updater to use; Electron automatically detects the packaging
|
||||
format and uses the appropriate one.
|
||||
|
||||
#### Squirrel.Windows
|
||||
|
||||
Apps built with Squirrel.Windows will trigger [custom launch events](https://github.com/Squirrel/Squirrel.Windows/blob/51f5e2cb01add79280a53d51e8d0cfa20f8c9f9f/docs/using/custom-squirrel-events-non-cs.md#application-startup-commands)
|
||||
that must be handled by your Electron application to ensure proper setup and teardown.
|
||||
@@ -55,6 +65,14 @@ The installer generated with Squirrel.Windows will create a shortcut icon with a
|
||||
same ID for your app with `app.setAppUserModelId` API, otherwise Windows will
|
||||
not be able to pin your app properly in task bar.
|
||||
|
||||
#### MSIX Packages
|
||||
|
||||
When your app is packaged as an MSIX, the `autoUpdater` module provides additional
|
||||
functionality:
|
||||
|
||||
* Use the `allowAnyVersion` option in `setFeedURL()` to allow updates to older versions (downgrades)
|
||||
* Support for direct MSIX file links or JSON update feeds (similar to Squirrel.Mac format)
|
||||
|
||||
## Events
|
||||
|
||||
The `autoUpdater` object emits the following events:
|
||||
@@ -92,7 +110,7 @@ Returns:
|
||||
|
||||
Emitted when an update has been downloaded.
|
||||
|
||||
On Windows only `releaseName` is available.
|
||||
With Squirrel.Windows only `releaseName` is available.
|
||||
|
||||
> [!NOTE]
|
||||
> It is not strictly necessary to handle this event. A successfully
|
||||
@@ -111,10 +129,12 @@ The `autoUpdater` object has the following methods:
|
||||
### `autoUpdater.setFeedURL(options)`
|
||||
|
||||
* `options` Object
|
||||
* `url` string
|
||||
* `url` string - The update server URL. For _Windows_ MSIX, this can be either a direct link to an MSIX file (e.g., `https://example.com/update.msix`) or a JSON endpoint that returns update information (see the [Squirrel.Mac][squirrel-mac] README for more information).
|
||||
* `headers` Record\<string, string\> (optional) _macOS_ - HTTP request headers.
|
||||
* `serverType` string (optional) _macOS_ - Can be `json` or `default`, see the [Squirrel.Mac][squirrel-mac]
|
||||
README for more information.
|
||||
* `allowAnyVersion` boolean (optional) _Windows_ - If `true`, allows downgrades to older versions for MSIX packages.
|
||||
Defaults to `false`.
|
||||
|
||||
Sets the `url` and initialize the auto updater.
|
||||
|
||||
@@ -151,3 +171,4 @@ closed.
|
||||
[electron-forge-lib]: https://www.electronforge.io/config/makers/squirrel.windows
|
||||
[app-user-model-id]: https://learn.microsoft.com/en-us/windows/win32/shell/appids
|
||||
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
||||
[msix-lib]: https://github.com/electron-userland/electron-windows-msix
|
||||
|
||||
@@ -1252,7 +1252,8 @@ Captures a snapshot of the page within `rect`. Omitting `rect` will capture the
|
||||
|
||||
Returns `Promise<void>` - the promise will resolve when the page has finished loading
|
||||
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
|
||||
if the page fails to load (see [`did-fail-load`](web-contents.md#event-did-fail-load)).
|
||||
if the page fails to load (see
|
||||
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors. If the existing page has a beforeUnload handler, [`did-fail-load`](web-contents.md#event-did-fail-load) will be called unless [`will-prevent-unload`](web-contents.md#event-did-fail-load) is handled.
|
||||
|
||||
Same as [`webContents.loadURL(url[, options])`](web-contents.md#contentsloadurlurl-options).
|
||||
|
||||
|
||||
@@ -159,6 +159,22 @@ Notification activated (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76
|
||||
Notification replied to (com.github.Electron:notification:EAF7B87C-A113-43D7-8E76-F88EC9D73D44)
|
||||
```
|
||||
|
||||
### `ELECTRON_DEBUG_MSIX_UPDATER`
|
||||
|
||||
Adds extra logs to MSIX updater operations on Windows to aid in debugging. Extra logging will be displayed when MSIX update operations are initiated, including package updates, package registration, and restart registration. This helps diagnose issues with MSIX package updates and deployments.
|
||||
|
||||
Sample output:
|
||||
|
||||
```sh
|
||||
UpdateMsix called with URI: https://example.com/app.msix
|
||||
DoUpdateMsix: Starting
|
||||
Calling AddPackageByUriAsync... URI: https://example.com/app.msix
|
||||
Update options - deferRegistration: true, developerMode: false, forceShutdown: false, forceTargetShutdown: false, forceUpdateFromAnyVersion: false
|
||||
Waiting for deployment...
|
||||
Deployment finished.
|
||||
MSIX Deployment completed.
|
||||
```
|
||||
|
||||
### `ELECTRON_LOG_ASAR_READS`
|
||||
|
||||
When Electron reads from an ASAR file, log the read offset and file path to
|
||||
|
||||
@@ -71,7 +71,7 @@ will disable the support for `asar` archives in Node's built-in modules.
|
||||
|
||||
### `process.noDeprecation`
|
||||
|
||||
A `boolean` that controls whether or not deprecation warnings are printed to `stderr`.
|
||||
A `boolean` (optional) that controls whether or not deprecation warnings are printed to `stderr`.
|
||||
Setting this to `true` will silence deprecation warnings. This property is used
|
||||
instead of the `--no-deprecation` command line flag.
|
||||
|
||||
@@ -128,8 +128,8 @@ A `string` representing Electron's version string.
|
||||
|
||||
### `process.windowsStore` _Readonly_
|
||||
|
||||
A `boolean`. If the app is running as a Windows Store app (appx), this property is `true`,
|
||||
for otherwise it is `undefined`.
|
||||
A `boolean`. If the app is running as an MSIX package (including AppX for Windows Store),
|
||||
this property is `true`, otherwise it is `undefined`.
|
||||
|
||||
### `process.contextId` _Readonly_
|
||||
|
||||
|
||||
@@ -58,6 +58,10 @@ Rejects if there was an error while deleting the requested item.
|
||||
This moves a path to the OS-specific trash location (Trash on macOS, Recycle
|
||||
Bin on Windows, and a desktop-environment-specific location on Linux).
|
||||
|
||||
The path must use the default path separator for the platform (backslash on
|
||||
Windows). Use `path.resolve()` from the `node:path` module to ensure correct
|
||||
handling on all filesystems.
|
||||
|
||||
### `shell.beep()`
|
||||
|
||||
Play the beep sound.
|
||||
|
||||
@@ -72,6 +72,9 @@
|
||||
some GTK+3 desktop environments. Default is `false`.
|
||||
* `transparent` boolean (optional) - Makes the window [transparent](../../tutorial/custom-window-styles.md#transparent-windows).
|
||||
Default is `false`. On Windows, does not work unless the window is frameless.
|
||||
When you add a [`View`](../view.md) to a `BaseWindow`, you'll need to call
|
||||
[`view.setBackgroundColor`](../view.md#viewsetbackgroundcolorcolor) with a transparent
|
||||
background color on that view to make its background transparent as well.
|
||||
* `type` string (optional) - The type of window, default is normal window. See more about
|
||||
this below.
|
||||
* `visualEffectState` string (optional) _macOS_ - Specify how the material
|
||||
@@ -99,9 +102,9 @@
|
||||
* `trafficLightPosition` [Point](point.md) (optional) _macOS_ -
|
||||
Set a custom position for the traffic light buttons in frameless windows.
|
||||
* `roundedCorners` boolean (optional) _macOS_ _Windows_ - Whether frameless window
|
||||
should have rounded corners. Default is `true`. Setting this property
|
||||
to `false` will prevent the window from being fullscreenable on macOS.
|
||||
On Windows versions older than Windows 11 Build 22000 this property has no effect, and frameless windows will not have rounded corners.
|
||||
should have rounded corners. Default is `true`. On Windows versions older than
|
||||
Windows 11 Build 22000 this property has no effect, and frameless windows will
|
||||
not have rounded corners.
|
||||
* `thickFrame` boolean (optional) _Windows_ - Use `WS_THICKFRAME` style for
|
||||
frameless windows on Windows, which adds the standard window frame. Setting it
|
||||
to `false` will remove window shadow and window animations, and disable window
|
||||
|
||||
@@ -1079,7 +1079,7 @@ Emitted when the [mainFrame](web-contents.md#contentsmainframe-readonly), an `<i
|
||||
Returns `Promise<void>` - the promise will resolve when the page has finished loading
|
||||
(see [`did-finish-load`](web-contents.md#event-did-finish-load)), and rejects
|
||||
if the page fails to load (see
|
||||
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors.
|
||||
[`did-fail-load`](web-contents.md#event-did-fail-load)). A noop rejection handler is already attached, which avoids unhandled rejection errors. If the existing page has a beforeUnload handler, [`did-fail-load`](web-contents.md#event-did-fail-load) will be called unless [`will-prevent-unload`](web-contents.md#event-did-fail-load) is handled.
|
||||
|
||||
Loads the `url` in the window. The `url` must contain the protocol prefix,
|
||||
e.g. the `http://` or `file://`. If the load should bypass http cache then
|
||||
@@ -2410,7 +2410,8 @@ A [`NavigationHistory`](navigation-history.md) used by this webContents.
|
||||
|
||||
#### `contents.hostWebContents` _Readonly_
|
||||
|
||||
A [`WebContents`](web-contents.md) instance that might own this `WebContents`.
|
||||
A `WebContents | null` property that represents a [`WebContents`](web-contents.md)
|
||||
instance that might own this `WebContents`.
|
||||
|
||||
#### `contents.devToolsWebContents` _Readonly_
|
||||
|
||||
|
||||
@@ -6,77 +6,17 @@ Follow the guidelines below for building **Electron itself** on Linux, for the p
|
||||
|
||||
## Prerequisites
|
||||
|
||||
* At least 25GB disk space and 8GB RAM.
|
||||
* Python >= 3.9.
|
||||
* [Node.js](https://nodejs.org/download/) >= 22.12.0
|
||||
* [clang](https://clang.llvm.org/get_started.html) 3.4 or later.
|
||||
* Development headers of GTK 3 and libnotify.
|
||||
Due to Electron's dependency on Chromium, prerequisites and dependencies for Electron change over time. [Chromium's documentation on building on Linux](https://chromium.googlesource.com/chromium/src/+/HEAD/docs/linux/build_instructions.md) has up to date information for building Chromium on Linux. This documentation can generally
|
||||
be followed for building Electron on Linux as well.
|
||||
|
||||
On Ubuntu >= 20.04, install the following libraries:
|
||||
|
||||
```sh
|
||||
$ sudo apt-get install build-essential clang libdbus-1-dev libgtk-3-dev \
|
||||
libnotify-dev libasound2-dev libcap-dev \
|
||||
libcups2-dev libxtst-dev \
|
||||
libxss1 libnss3-dev gcc-multilib g++-multilib curl \
|
||||
gperf bison python3-dbusmock openjdk-8-jre
|
||||
```
|
||||
|
||||
On Ubuntu < 20.04, install the following libraries:
|
||||
|
||||
```sh
|
||||
$ sudo apt-get install build-essential clang libdbus-1-dev libgtk-3-dev \
|
||||
libnotify-dev libgnome-keyring-dev \
|
||||
libasound2-dev libcap-dev libcups2-dev libxtst-dev \
|
||||
libxss1 libnss3-dev gcc-multilib g++-multilib curl \
|
||||
gperf bison python-dbusmock openjdk-8-jre
|
||||
```
|
||||
|
||||
On RHEL / CentOS, install the following libraries:
|
||||
|
||||
```sh
|
||||
$ sudo yum install clang dbus-devel gtk3-devel libnotify-devel \
|
||||
libgnome-keyring-devel xorg-x11-server-utils libcap-devel \
|
||||
cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \
|
||||
nss-devel python-dbusmock openjdk-8-jre
|
||||
```
|
||||
|
||||
On Fedora, install the following libraries:
|
||||
|
||||
```sh
|
||||
$ sudo dnf install clang dbus-devel gperf gtk3-devel \
|
||||
libnotify-devel libgnome-keyring-devel libcap-devel \
|
||||
cups-devel libXtst-devel alsa-lib-devel libXrandr-devel \
|
||||
nss-devel python-dbusmock
|
||||
```
|
||||
|
||||
On Arch Linux / Manjaro, install the following libraries:
|
||||
|
||||
```sh
|
||||
$ sudo pacman -Syu base-devel clang libdbus gtk2 libnotify \
|
||||
libgnome-keyring alsa-lib libcap libcups libxtst \
|
||||
libxss nss gcc-multilib curl gperf bison \
|
||||
python2 python-dbusmock jdk8-openjdk
|
||||
```
|
||||
|
||||
Other distributions may offer similar packages for installation via package
|
||||
managers such as pacman. Or one can compile from source code.
|
||||
Additionally, Electron's [Linux dependency installer](https://github.com/electron/build-images/blob/main/tools/install-deps.sh) can be referenced to get the current dependencies that Electron requires in addition to what Chromium installs via [build/install-deps.sh](https://chromium.googlesource.com/chromium/src/+/HEAD/build/install-build-deps.sh).
|
||||
|
||||
### Cross compilation
|
||||
|
||||
If you want to build for an `arm` target you should also install the following
|
||||
dependencies:
|
||||
If you want to build for an `arm` target, you can use Electron's [Linux dependency installer](https://github.com/electron/build-images/blob/main/tools/install-deps.sh) to install the additional dependencies by passing the `--arm argument`:
|
||||
|
||||
```sh
|
||||
$ sudo apt-get install libc6-dev-armhf-cross linux-libc-dev-armhf-cross \
|
||||
g++-arm-linux-gnueabihf
|
||||
```
|
||||
|
||||
Similarly for `arm64`, install the following:
|
||||
|
||||
```sh
|
||||
$ sudo apt-get install libc6-dev-arm64-cross linux-libc-dev-arm64-cross \
|
||||
g++-aarch64-linux-gnu
|
||||
$ sudo install-deps.sh --arm
|
||||
```
|
||||
|
||||
And to cross-compile for `arm` or targets, you should pass the
|
||||
|
||||
@@ -222,8 +222,10 @@ auto_filenames = {
|
||||
browser_bundle_deps = [
|
||||
"lib/browser/api/app.ts",
|
||||
"lib/browser/api/auto-updater.ts",
|
||||
"lib/browser/api/auto-updater/auto-updater-msix.ts",
|
||||
"lib/browser/api/auto-updater/auto-updater-native.ts",
|
||||
"lib/browser/api/auto-updater/auto-updater-win.ts",
|
||||
"lib/browser/api/auto-updater/msix-update-win.ts",
|
||||
"lib/browser/api/auto-updater/squirrel-update-win.ts",
|
||||
"lib/browser/api/base-window.ts",
|
||||
"lib/browser/api/browser-view.ts",
|
||||
|
||||
@@ -587,6 +587,7 @@ filenames = {
|
||||
"shell/common/electron_command_line.cc",
|
||||
"shell/common/electron_command_line.h",
|
||||
"shell/common/electron_constants.h",
|
||||
"shell/common/electron_paths.cc",
|
||||
"shell/common/electron_paths.h",
|
||||
"shell/common/gin_converters/accelerator_converter.cc",
|
||||
"shell/common/gin_converters/accelerator_converter.h",
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
if (process.platform === 'win32') {
|
||||
module.exports = require('./auto-updater/auto-updater-win');
|
||||
// windowsStore indicates whether the app is running as a packaged app (MSIX), even outside of the store
|
||||
if (process.windowsStore) {
|
||||
module.exports = require('./auto-updater/auto-updater-msix');
|
||||
} else {
|
||||
module.exports = require('./auto-updater/auto-updater-win');
|
||||
}
|
||||
} else {
|
||||
module.exports = require('./auto-updater/auto-updater-native');
|
||||
}
|
||||
|
||||
449
lib/browser/api/auto-updater/auto-updater-msix.ts
Normal file
449
lib/browser/api/auto-updater/auto-updater-msix.ts
Normal file
@@ -0,0 +1,449 @@
|
||||
import * as msixUpdate from '@electron/internal/browser/api/auto-updater/msix-update-win';
|
||||
|
||||
import { app, net } from 'electron/main';
|
||||
|
||||
import { EventEmitter } from 'events';
|
||||
|
||||
interface UpdateInfo {
|
||||
ok: boolean; // False if error encountered
|
||||
available?: boolean; // True if the update is available, false if not
|
||||
updateUrl?: string; // The URL of the update
|
||||
releaseNotes?: string; // The release notes of the update
|
||||
releaseName?: string; // The release name of the update
|
||||
releaseDate?: Date; // The release date of the update
|
||||
}
|
||||
|
||||
interface MSIXPackageInfo {
|
||||
id: string;
|
||||
familyName: string;
|
||||
developmentMode: boolean;
|
||||
version: string;
|
||||
signatureKind: 'developer' | 'enterprise' | 'none' | 'store' | 'system';
|
||||
appInstallerUri?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for updating an MSIX package.
|
||||
* Used with `updateMsix()` to control how the package update behaves.
|
||||
*
|
||||
* These options correspond to the Windows.Management.Deployment.AddPackageOptions class properties.
|
||||
*
|
||||
* @see https://learn.microsoft.com/en-us/uwp/api/windows.management.deployment.addpackageoptions?view=winrt-26100
|
||||
*/
|
||||
export interface UpdateMsixOptions {
|
||||
/**
|
||||
* Gets or sets a value that indicates whether to delay registration of the main package
|
||||
* or dependency packages if the packages are currently in use.
|
||||
*
|
||||
* Corresponds to `AddPackageOptions.DeferRegistrationWhenPackagesAreInUse`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
deferRegistration?: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets a value that indicates whether the app is installed in developer mode.
|
||||
* When set, the app is installed in development mode which allows for a more rapid
|
||||
* development cycle. The BlockMap.xml, [Content_Types].xml, and digital signature
|
||||
* files are not required for app installation.
|
||||
*
|
||||
* Corresponds to `AddPackageOptions.DeveloperMode`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
developerMode?: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets a value that indicates whether the processes associated with the package
|
||||
* will be shut down forcibly so that registration can continue if the package, or any
|
||||
* package that depends on the package, is currently in use.
|
||||
*
|
||||
* Corresponds to `AddPackageOptions.ForceAppShutdown`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceShutdown?: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets a value that indicates whether the processes associated with the package
|
||||
* will be shut down forcibly so that registration can continue if the package is
|
||||
* currently in use.
|
||||
*
|
||||
* Corresponds to `AddPackageOptions.ForceTargetAppShutdown`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceTargetShutdown?: boolean;
|
||||
|
||||
/**
|
||||
* Gets or sets a value that indicates whether to force a specific version of a package
|
||||
* to be added, regardless of if a higher version is already added.
|
||||
*
|
||||
* Corresponds to `AddPackageOptions.ForceUpdateFromAnyVersion`
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceUpdateFromAnyVersion?: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Options for registering an MSIX package.
|
||||
* Used with `registerPackage()` to control how the package registration behaves.
|
||||
*
|
||||
* These options correspond to the Windows.Management.Deployment.DeploymentOptions enum.
|
||||
*
|
||||
* @see https://learn.microsoft.com/en-us/uwp/api/windows.management.deployment.deploymentoptions?view=winrt-26100
|
||||
*/
|
||||
interface RegisterPackageOptions {
|
||||
/**
|
||||
* Force shutdown of the application if it's currently running.
|
||||
* If this package, or any package that depends on this package, is currently in use,
|
||||
* the processes associated with the package are shut down forcibly so that registration can continue.
|
||||
*
|
||||
* Corresponds to `DeploymentOptions.ForceApplicationShutdown` (value: 1)
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceShutdown?: boolean;
|
||||
|
||||
/**
|
||||
* Force shutdown of the target application if it's currently running.
|
||||
* If this package is currently in use, the processes associated with the package
|
||||
* are shut down forcibly so that registration can continue.
|
||||
*
|
||||
* Corresponds to `DeploymentOptions.ForceTargetApplicationShutdown` (value: 64)
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceTargetShutdown?: boolean;
|
||||
|
||||
/**
|
||||
* Force a specific version of a package to be staged/registered, regardless of if
|
||||
* a higher version is already staged/registered.
|
||||
*
|
||||
* Corresponds to `DeploymentOptions.ForceUpdateFromAnyVersion` (value: 262144)
|
||||
*
|
||||
* @default false
|
||||
*/
|
||||
forceUpdateFromAnyVersion?: boolean;
|
||||
}
|
||||
|
||||
class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
updateAvailable: boolean = false;
|
||||
updateURL: string | null = null;
|
||||
updateHeaders: Record<string, string> | null = null;
|
||||
allowAnyVersion: boolean = false;
|
||||
|
||||
// Private: Validate that the URL points to an MSIX file (following redirects)
|
||||
private async validateMsixUrl (url: string): Promise<void> {
|
||||
try {
|
||||
// Make a HEAD request to follow redirects and get the final URL
|
||||
const response = await net.fetch(url, {
|
||||
method: 'HEAD',
|
||||
headers: this.updateHeaders ? new Headers(this.updateHeaders) : undefined,
|
||||
redirect: 'follow' // Follow redirects to get the final URL
|
||||
});
|
||||
|
||||
// Get the final URL after redirects (response.url contains the final URL)
|
||||
const finalUrl = response.url || url;
|
||||
const urlObj = new URL(finalUrl);
|
||||
const pathname = urlObj.pathname.toLowerCase();
|
||||
|
||||
// Check if final URL ends with .msix or .msixbundle extension
|
||||
const hasMsixExtension = pathname.endsWith('.msix') || pathname.endsWith('.msixbundle');
|
||||
|
||||
if (!hasMsixExtension) {
|
||||
throw new Error(`Update URL does not point to an MSIX file. Expected .msix or .msixbundle extension, got final URL: ${finalUrl}`);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error instanceof TypeError) {
|
||||
throw new Error(`Invalid MSIX URL: ${url}`);
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Check if URL is a direct MSIX file (following redirects)
|
||||
private async isDirectMsixUrl (url: string, emitError: boolean = false): Promise<boolean> {
|
||||
try {
|
||||
await this.validateMsixUrl(url);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if (emitError) {
|
||||
this.emitError(error as Error);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Supports both versioning (x.y.z) and Windows version format (x.y.z.a)
|
||||
// Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if v1 === v2
|
||||
private compareVersions (v1: string, v2: string): number {
|
||||
const parts1 = v1.split('.').map(part => {
|
||||
const parsed = parseInt(part, 10);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
});
|
||||
const parts2 = v2.split('.').map(part => {
|
||||
const parsed = parseInt(part, 10);
|
||||
return isNaN(parsed) ? 0 : parsed;
|
||||
});
|
||||
|
||||
const maxLength = Math.max(parts1.length, parts2.length);
|
||||
|
||||
for (let i = 0; i < maxLength; i++) {
|
||||
const part1 = parts1[i] ?? 0;
|
||||
const part2 = parts2[i] ?? 0;
|
||||
|
||||
if (part1 > part2) return 1;
|
||||
if (part1 < part2) return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Private: Parse the static releases array format
|
||||
// This is a static JSON file containing all releases
|
||||
private parseStaticReleasFile (json: any, currentVersion: string): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
||||
if (!Array.isArray(json.releases) || !json.currentRelease || typeof json.currentRelease !== 'string') {
|
||||
this.emitError(new Error('Invalid releases format. Expected \'releases\' array and \'currentRelease\' string.'));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
|
||||
// Use currentRelease property to determine if update is available
|
||||
const currentReleaseVersion = json.currentRelease;
|
||||
|
||||
// Compare current version with currentRelease
|
||||
const versionComparison = this.compareVersions(currentReleaseVersion, currentVersion);
|
||||
|
||||
// If versions match, we're up to date
|
||||
if (versionComparison === 0) {
|
||||
return { ok: true, available: false };
|
||||
}
|
||||
|
||||
// If currentRelease is older than current version, check allowAnyVersion
|
||||
if (versionComparison < 0) {
|
||||
// If allowAnyVersion is true, allow downgrades
|
||||
if (this.allowAnyVersion) {
|
||||
// Continue to find the release entry for downgrade
|
||||
} else {
|
||||
return { ok: true, available: false };
|
||||
}
|
||||
}
|
||||
|
||||
// currentRelease is newer, find the release entry
|
||||
const releaseEntry = json.releases.find((r: any) => r.version === currentReleaseVersion);
|
||||
|
||||
if (!releaseEntry || !releaseEntry.updateTo) {
|
||||
this.emitError(new Error(`Release entry for version '${currentReleaseVersion}' not found or missing 'updateTo' property.`));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
|
||||
const updateTo = releaseEntry.updateTo;
|
||||
|
||||
if (!updateTo.url) {
|
||||
this.emitError(new Error(`Invalid release entry. 'updateTo.url' is missing for version ${currentReleaseVersion}.`));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
|
||||
return {
|
||||
ok: true,
|
||||
available: true,
|
||||
url: updateTo.url,
|
||||
name: updateTo.name,
|
||||
notes: updateTo.notes,
|
||||
pub_date: updateTo.pub_date
|
||||
};
|
||||
}
|
||||
|
||||
private parseDynamicReleasFile (json: any): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
||||
if (!json.url) {
|
||||
this.emitError(new Error('Invalid releases format. Expected \'url\' string property.'));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
return { ok: true, available: true, url: json.url, name: json.name, notes: json.notes, pub_date: json.pub_date };
|
||||
}
|
||||
|
||||
private async fetchSquirrelJson (url: string) {
|
||||
const headers: Record<string, string> = {
|
||||
...this.updateHeaders,
|
||||
Accept: 'application/json' // Always set Accept header, overriding any user-provided Accept
|
||||
};
|
||||
const response = await net.fetch(url, {
|
||||
headers
|
||||
});
|
||||
|
||||
if (response.status === 204) {
|
||||
return { ok: true, available: false };
|
||||
} else if (response.status === 200) {
|
||||
const updateJson = await response.json();
|
||||
|
||||
// Check if this is the static releases array format
|
||||
if (Array.isArray(updateJson.releases)) {
|
||||
// Get current package version
|
||||
const packageInfo = msixUpdate.getPackageInfo();
|
||||
const currentVersion = packageInfo.version;
|
||||
|
||||
if (!currentVersion) {
|
||||
this.emitError(new Error('Cannot determine current package version.'));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
|
||||
return this.parseStaticReleasFile(updateJson, currentVersion);
|
||||
} else {
|
||||
// Dynamic format: server returns JSON with update info for current version
|
||||
return this.parseDynamicReleasFile(updateJson);
|
||||
}
|
||||
} else {
|
||||
this.emitError(new Error(`Unexpected response status: ${response.status}`));
|
||||
return { ok: false, available: false };
|
||||
}
|
||||
}
|
||||
|
||||
private async getUpdateInfo (url: string): Promise<UpdateInfo> {
|
||||
if (url && await this.isDirectMsixUrl(url)) {
|
||||
return { ok: true, available: true, updateUrl: url, releaseDate: new Date() };
|
||||
} else {
|
||||
const updateJson = await this.fetchSquirrelJson(url);
|
||||
if (!updateJson.ok) {
|
||||
return { ok: false };
|
||||
} else if (updateJson.ok && !updateJson.available) {
|
||||
return { ok: true, available: false };
|
||||
} else {
|
||||
// updateJson.ok && updateJson.available must be true here
|
||||
// Parse the publication date if present (ISO 8601 format)
|
||||
let releaseDate: Date | null = null;
|
||||
if (updateJson.pub_date) {
|
||||
releaseDate = new Date(updateJson.pub_date);
|
||||
}
|
||||
|
||||
const updateUrl = updateJson.url ?? '';
|
||||
const releaseNotes = updateJson.notes ?? '';
|
||||
const releaseName = updateJson.name ?? '';
|
||||
releaseDate = releaseDate ?? new Date();
|
||||
|
||||
if (!await this.isDirectMsixUrl(updateUrl, true)) {
|
||||
return { ok: false };
|
||||
} else {
|
||||
return {
|
||||
ok: true,
|
||||
available: true,
|
||||
updateUrl,
|
||||
releaseNotes,
|
||||
releaseName,
|
||||
releaseDate
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getFeedURL () {
|
||||
return this.updateURL ?? '';
|
||||
}
|
||||
|
||||
setFeedURL (options: { url: string; headers?: Record<string, string>; allowAnyVersion?: boolean } | string) {
|
||||
let updateURL: string;
|
||||
let headers: Record<string, string> | undefined;
|
||||
let allowAnyVersion: boolean | undefined;
|
||||
if (typeof options === 'object') {
|
||||
if (typeof options.url === 'string') {
|
||||
updateURL = options.url;
|
||||
headers = options.headers;
|
||||
allowAnyVersion = options.allowAnyVersion;
|
||||
} else {
|
||||
throw new TypeError('Expected options object to contain a \'url\' string property in setFeedUrl call');
|
||||
}
|
||||
} else if (typeof options === 'string') {
|
||||
updateURL = options;
|
||||
} else {
|
||||
throw new TypeError('Expected an options object with a \'url\' property to be provided');
|
||||
}
|
||||
this.updateURL = updateURL;
|
||||
this.updateHeaders = headers ?? null;
|
||||
this.allowAnyVersion = allowAnyVersion ?? false;
|
||||
}
|
||||
|
||||
getPackageInfo (): MSIXPackageInfo {
|
||||
return msixUpdate.getPackageInfo() as MSIXPackageInfo;
|
||||
}
|
||||
|
||||
async checkForUpdates () {
|
||||
const url = this.updateURL;
|
||||
if (!url) {
|
||||
return this.emitError(new Error('Update URL is not set'));
|
||||
}
|
||||
|
||||
// Check if running in MSIX package
|
||||
const packageInfo = msixUpdate.getPackageInfo();
|
||||
if (!packageInfo.familyName) {
|
||||
return this.emitError(new Error('MSIX updates are not supported'));
|
||||
}
|
||||
|
||||
// If appInstallerUri is set, Windows App Installer manages updates automatically
|
||||
// Prevent updates here to avoid conflicts
|
||||
if (packageInfo.appInstallerUri) {
|
||||
return this.emitError(new Error('Auto-updates are managed by Windows App Installer. Updates are not allowed when installed via Application Manifest.'));
|
||||
}
|
||||
|
||||
this.emit('checking-for-update');
|
||||
try {
|
||||
const msixUrlInfo = await this.getUpdateInfo(url);
|
||||
if (!msixUrlInfo.ok) {
|
||||
return this.emitError(new Error('Invalid update or MSIX URL. See previous errors.'));
|
||||
}
|
||||
|
||||
if (!msixUrlInfo.available) {
|
||||
this.emit('update-not-available');
|
||||
} else {
|
||||
this.updateAvailable = true;
|
||||
this.emit('update-available');
|
||||
await msixUpdate.updateMsix(msixUrlInfo.updateUrl, {
|
||||
deferRegistration: true,
|
||||
developerMode: false,
|
||||
forceShutdown: false,
|
||||
forceTargetShutdown: false,
|
||||
forceUpdateFromAnyVersion: this.allowAnyVersion
|
||||
} as UpdateMsixOptions);
|
||||
|
||||
this.emit('update-downloaded', {}, msixUrlInfo.releaseNotes, msixUrlInfo.releaseName, msixUrlInfo.releaseDate, msixUrlInfo.updateUrl, () => {
|
||||
this.quitAndInstall();
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
this.emitError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async quitAndInstall () {
|
||||
if (!this.updateAvailable) {
|
||||
this.emitError(new Error('No update available, can\'t quit and install'));
|
||||
app.quit();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Get package info to get family name
|
||||
const packageInfo = msixUpdate.getPackageInfo();
|
||||
if (!packageInfo.familyName) {
|
||||
return this.emitError(new Error('MSIX updates are not supported'));
|
||||
}
|
||||
|
||||
msixUpdate.registerRestartOnUpdate('');
|
||||
this.emit('before-quit-for-update');
|
||||
// force shutdown of the application and register the package to be installed on restart
|
||||
await msixUpdate.registerPackage(packageInfo.familyName, {
|
||||
forceShutdown: true
|
||||
} as RegisterPackageOptions);
|
||||
} catch (error) {
|
||||
this.emitError(error as Error);
|
||||
}
|
||||
}
|
||||
|
||||
// Private: Emit both error object and message, this is to keep compatibility
|
||||
// with Old APIs.
|
||||
emitError (error: Error) {
|
||||
this.emit('error', error, error.message);
|
||||
}
|
||||
}
|
||||
|
||||
export default new AutoUpdater();
|
||||
@@ -20,6 +20,11 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
||||
return this.updateURL ?? '';
|
||||
}
|
||||
|
||||
getPackageInfo () {
|
||||
// Squirrel-based Windows apps don't have MSIX package information
|
||||
return undefined;
|
||||
}
|
||||
|
||||
setFeedURL (options: { url: string } | string) {
|
||||
let updateURL: string;
|
||||
if (typeof options === 'object') {
|
||||
|
||||
4
lib/browser/api/auto-updater/msix-update-win.ts
Normal file
4
lib/browser/api/auto-updater/msix-update-win.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } =
|
||||
process._linkedBinding('electron_browser_msix_updater');
|
||||
|
||||
export { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo };
|
||||
@@ -119,7 +119,10 @@ export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypa
|
||||
p.reject(err);
|
||||
});
|
||||
|
||||
if (!req.body?.pipeTo(Writable.toWeb(r as unknown as Writable)).then(() => r.end())) { r.end(); }
|
||||
// pipeTo expects a WritableStream<Uint8Array>. Node.js' Writable.toWeb returns WritableStream<any>,
|
||||
// which causes a TS structural mismatch.
|
||||
const writable = Writable.toWeb(r as unknown as Writable) as unknown as WritableStream<Uint8Array>;
|
||||
if (!req.body?.pipeTo(writable).then(() => r.end())) { r.end(); }
|
||||
|
||||
return p.promise;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,8 @@ import { createReadStream } from 'fs';
|
||||
import { Readable } from 'stream';
|
||||
import { ReadableStream } from 'stream/web';
|
||||
|
||||
import type { ReadableStreamDefaultReader } from 'stream/web';
|
||||
|
||||
// Global protocol APIs.
|
||||
const { registerSchemesAsPrivileged, getStandardSchemes, Protocol } = process._linkedBinding('electron_browser_protocol');
|
||||
|
||||
@@ -12,7 +14,7 @@ const ERR_UNEXPECTED = -9;
|
||||
|
||||
const isBuiltInScheme = (scheme: string) => ['http', 'https', 'file'].includes(scheme);
|
||||
|
||||
function makeStreamFromPipe (pipe: any): ReadableStream {
|
||||
function makeStreamFromPipe (pipe: any): ReadableStream<Uint8Array> {
|
||||
const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
|
||||
return new ReadableStream({
|
||||
async pull (controller) {
|
||||
@@ -38,21 +40,26 @@ function makeStreamFromFileInfo ({
|
||||
filePath: string;
|
||||
offset?: number;
|
||||
length?: number;
|
||||
}): ReadableStream {
|
||||
}): ReadableStream<Uint8Array> {
|
||||
// Node's Readable.toWeb produces a WHATWG ReadableStream whose chunks are Uint8Array.
|
||||
return Readable.toWeb(createReadStream(filePath, {
|
||||
start: offset,
|
||||
end: length >= 0 ? offset + length : undefined
|
||||
}));
|
||||
})) as ReadableStream<Uint8Array>;
|
||||
}
|
||||
|
||||
function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): RequestInit['body'] {
|
||||
if (!uploadData) return null;
|
||||
// Optimization: skip creating a stream if the request is just a single buffer.
|
||||
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') return uploadData[0].bytes;
|
||||
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') {
|
||||
return uploadData[0].bytes as any;
|
||||
}
|
||||
|
||||
const chunks = [...uploadData] as any[]; // TODO: types are wrong
|
||||
let current: ReadableStreamDefaultReader | null = null;
|
||||
return new ReadableStream({
|
||||
const chunks = [...uploadData] as any[]; // TODO: refine ProtocolRequest types
|
||||
// Use Node's web stream types explicitly to avoid DOM lib vs Node lib structural mismatches.
|
||||
// Generic <Uint8Array> ensures reader.read() returns value?: Uint8Array consistent with enqueue.
|
||||
let current: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
||||
return new ReadableStream<Uint8Array>({
|
||||
async pull (controller) {
|
||||
if (current) {
|
||||
const { done, value } = await current.read();
|
||||
@@ -67,7 +74,7 @@ function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): Reque
|
||||
if (!chunks.length) { return controller.close(); }
|
||||
const chunk = chunks.shift()!;
|
||||
if (chunk.type === 'rawData') {
|
||||
controller.enqueue(chunk.bytes);
|
||||
controller.enqueue(chunk.bytes as Uint8Array);
|
||||
} else if (chunk.type === 'file') {
|
||||
current = makeStreamFromFileInfo(chunk).getReader();
|
||||
return this.pull!(controller);
|
||||
|
||||
@@ -40,7 +40,7 @@ process.on('uncaughtException', function (error) {
|
||||
// Emit 'exit' event on quit.
|
||||
const { app } = require('electron');
|
||||
|
||||
app.on('quit', (_event, exitCode) => {
|
||||
app.on('quit', (_event: any, exitCode: number) => {
|
||||
process.emit('exit', exitCode);
|
||||
});
|
||||
|
||||
|
||||
@@ -11,12 +11,14 @@ const { contextIsolationEnabled } = internalContextBridge;
|
||||
* 1) Use menu API to show context menu.
|
||||
*/
|
||||
window.onload = function () {
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
|
||||
'InspectorFrontendHost', 'showContextMenuAtPoint'
|
||||
], createMenu);
|
||||
} else {
|
||||
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
|
||||
if (window.InspectorFrontendHost) {
|
||||
if (contextIsolationEnabled) {
|
||||
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
|
||||
'InspectorFrontendHost', 'showContextMenuAtPoint'
|
||||
], createMenu);
|
||||
} else {
|
||||
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
23
package.json
23
package.json
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"name": "electron",
|
||||
"name": "@electron-ci/dev-root",
|
||||
"version": "0.0.0-development",
|
||||
"repository": "https://github.com/electron/electron",
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
"@azure/storage-blob": "^12.28.0",
|
||||
"@datadog/datadog-ci": "^4.1.2",
|
||||
"@electron/asar": "^3.2.13",
|
||||
"@electron/docs-parser": "^2.0.0",
|
||||
"@electron/fiddle-core": "^1.3.4",
|
||||
"@electron/github-app-auth": "^2.2.1",
|
||||
"@electron/lint-roller": "^3.1.2",
|
||||
"@electron/github-app-auth": "^3.2.0",
|
||||
"@electron/lint-roller": "^3.2.0",
|
||||
"@electron/typescript-definitions": "^9.1.2",
|
||||
"@octokit/rest": "^20.1.2",
|
||||
"@primer/octicons": "^10.0.0",
|
||||
@@ -24,7 +25,6 @@
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
"check-for-leaks": "^1.2.1",
|
||||
"dugite": "^2.7.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.32.0",
|
||||
@@ -40,6 +40,7 @@
|
||||
"lint-staged": "^16.1.0",
|
||||
"markdownlint-cli2": "^0.18.0",
|
||||
"minimist": "^1.2.8",
|
||||
"node-gyp": "^11.4.2",
|
||||
"null-loader": "^4.0.1",
|
||||
"pre-flight": "^2.0.0",
|
||||
"process": "^0.11.10",
|
||||
@@ -134,6 +135,18 @@
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
"nan": "nodejs/nan#e14bdcd1f72d62bca1d541b66da43130384ec213"
|
||||
"dbus-native/xml2js": "0.5.0",
|
||||
"abstract-socket": "github:deepak1556/node-abstractsocket#928cc591decd12aff7dad96449da8afc29832c19",
|
||||
"minimist@npm:~0.0.1": "0.2.4"
|
||||
},
|
||||
"packageManager": "yarn@4.12.0",
|
||||
"workspaces": [
|
||||
"spec",
|
||||
"spec/fixtures/native-addon/*"
|
||||
],
|
||||
"dependenciesMeta": {
|
||||
"abstract-socket": {
|
||||
"built": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -143,3 +143,8 @@ allow_electron_to_depend_on_components_os_crypt_sync.patch
|
||||
expose_referrerscriptinfo_hostdefinedoptionsindex.patch
|
||||
chore_disable_protocol_handler_dcheck.patch
|
||||
fix_release_mouse_buttons_on_focus_loss_on_wayland.patch
|
||||
viz_fix_visual_artifacts_due_to_resizing_root_render_pass_with_dcomp.patch
|
||||
viz_do_not_overallocate_surface_on_initial_render.patch
|
||||
viz_create_isbufferqueuesupportedandenabled.patch
|
||||
viz_fix_visual_artifacts_while_resizing_window_with_dcomp.patch
|
||||
graphite_handle_out_of_order_recording_errors.patch
|
||||
|
||||
@@ -46,7 +46,7 @@ index 7280ef29b85c1b16b11dd9e4d628a9eb579bb4dd..ab56e29d402a400a8c91aa97b6e82ad7
|
||||
# than here in :chrome_dll.
|
||||
deps += [ "//chrome:packed_resources_integrity_header" ]
|
||||
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
|
||||
index 15cfa64348e80a3507fc83c3819e9abdb238bffb..4650080f45da4e321d4b0f0dabd98f2bf097c7c2 100644
|
||||
index d8978eb678048850cc0e629d0c978f23417b5be9..219c9ae3dde4f2239b5c1331b99c8f2e9a170628 100644
|
||||
--- a/chrome/test/BUILD.gn
|
||||
+++ b/chrome/test/BUILD.gn
|
||||
@@ -7571,9 +7571,12 @@ test("unit_tests") {
|
||||
|
||||
@@ -312,7 +312,7 @@ index 7e3d46902fbf736b4240eb3fcb89975a7b222197..57fdc89fc265ad70cb0bff8443cc1026
|
||||
|
||||
auto DrawAsSinglePath = [&]() {
|
||||
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
|
||||
index a25e99fac5f5a171becff833464cdb3dfe7522c2..123a7c1fc980b1b3bcbe0e3e00afa594632d720b 100644
|
||||
index 2e464ef7bbfd7e0d84df348d012e8020d4758726..b59eceacbd4143ef183bb67c39f9ccccc7582adb 100644
|
||||
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
|
||||
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
|
||||
@@ -214,6 +214,10 @@
|
||||
|
||||
@@ -0,0 +1,348 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
Date: Fri, 30 Jan 2026 12:51:05 -0800
|
||||
Subject: [graphite] Handle out of order recording errors
|
||||
|
||||
Explicitly handle out of order recording errors to crash just like async
|
||||
shader compile failed errors. Also, emit the insert status to an UMA
|
||||
histogram at 1% subsampling and log all error statuses at runtime.
|
||||
|
||||
Bug: 458722690
|
||||
Change-Id: Id94657be6ae870dcf8adba71aff216386a6a6964
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7533855
|
||||
Commit-Queue: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
Auto-Submit: Sunny Sachanandani <sunnyps@chromium.org>
|
||||
Reviewed-by: Jonathan Ross <jonross@chromium.org>
|
||||
Reviewed-by: Michael Ludwig <michaelludwig@google.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1577487}
|
||||
|
||||
diff --git a/gpu/command_buffer/service/graphite_shared_context.cc b/gpu/command_buffer/service/graphite_shared_context.cc
|
||||
index f9a47ac16bddc2352d81f43629d8ce8c2d2b3b99..9856d68f893e4329fda5d002835613161b346bc1 100644
|
||||
--- a/gpu/command_buffer/service/graphite_shared_context.cc
|
||||
+++ b/gpu/command_buffer/service/graphite_shared_context.cc
|
||||
@@ -4,7 +4,10 @@
|
||||
|
||||
#include "gpu/command_buffer/service/graphite_shared_context.h"
|
||||
|
||||
+#include "base/logging.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
+#include "base/metrics/histogram_macros.h"
|
||||
+#include "base/rand_util.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "gpu/command_buffer/common/shm_count.h"
|
||||
#include "third_party/skia/include/core/SkColorSpace.h"
|
||||
@@ -14,6 +17,44 @@
|
||||
namespace gpu {
|
||||
|
||||
namespace {
|
||||
+// This is emitted to UMA - values should not be reordered, only appended!
|
||||
+// LINT.IfChange(InsertRecordingStatusUma)
|
||||
+enum class InsertRecordingStatusUma {
|
||||
+ kSuccess,
|
||||
+ kInvalidRecording,
|
||||
+ kPromiseImageInstantiationFailed,
|
||||
+ kAddCommandsFailed,
|
||||
+ kAsyncShaderCompilesFailed,
|
||||
+ kOutOfOrderRecording,
|
||||
+ kMaxValue = kOutOfOrderRecording
|
||||
+};
|
||||
+// LINT.ThenChange(//tools/metrics/histograms/metadata/gpu/enums.xml:GraphiteInsertRecordingStatus)
|
||||
+
|
||||
+InsertRecordingStatusUma InsertRecordingStatusUma(
|
||||
+ skgpu::graphite::InsertStatus insert_status) {
|
||||
+ // InsertStatus almost behaves like an enum class, but not quite since it can
|
||||
+ // convert to both bool and integer types and can't be used in a switch.
|
||||
+ if (insert_status == skgpu::graphite::InsertStatus::kSuccess) {
|
||||
+ return InsertRecordingStatusUma::kSuccess;
|
||||
+ } else if (insert_status ==
|
||||
+ skgpu::graphite::InsertStatus::kInvalidRecording) {
|
||||
+ return InsertRecordingStatusUma::kInvalidRecording;
|
||||
+ } else if (insert_status ==
|
||||
+ skgpu::graphite::InsertStatus::kPromiseImageInstantiationFailed) {
|
||||
+ return InsertRecordingStatusUma::kPromiseImageInstantiationFailed;
|
||||
+ } else if (insert_status ==
|
||||
+ skgpu::graphite::InsertStatus::kAddCommandsFailed) {
|
||||
+ return InsertRecordingStatusUma::kAddCommandsFailed;
|
||||
+ } else if (insert_status ==
|
||||
+ skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed) {
|
||||
+ return InsertRecordingStatusUma::kAsyncShaderCompilesFailed;
|
||||
+ } else if (insert_status ==
|
||||
+ skgpu::graphite::InsertStatus::kOutOfOrderRecording) {
|
||||
+ return InsertRecordingStatusUma::kOutOfOrderRecording;
|
||||
+ }
|
||||
+ NOTREACHED();
|
||||
+}
|
||||
+
|
||||
struct RecordingContext {
|
||||
skgpu::graphite::GpuFinishedProc old_finished_proc;
|
||||
skgpu::graphite::GpuFinishedContext old_context;
|
||||
@@ -216,20 +257,38 @@ bool GraphiteSharedContext::InsertRecordingImpl(
|
||||
|
||||
auto insert_status = graphite_context_->insertRecording(*info_ptr);
|
||||
|
||||
- // TODO(433845560): Check the kAddCommandsFailed failures.
|
||||
- // Crash only if we're not simulating a failure for testing.
|
||||
const bool simulating_insert_failure =
|
||||
info_ptr->fSimulatedStatus != skgpu::graphite::InsertStatus::kSuccess;
|
||||
|
||||
- // InsertStatus::kAsyncShaderCompilesFailed is also an unrecoverable error for
|
||||
- // which we should also clear the disk shader cache in case the error was due
|
||||
- // to a corrupted cached shader blob.
|
||||
+ // Crash, log, or emit UMA only if we're not simulating a failure for testing.
|
||||
+ if (!simulating_insert_failure) {
|
||||
+ if (base::ShouldRecordSubsampledMetric(0.01)) {
|
||||
+ UMA_HISTOGRAM_ENUMERATION("GPU.Graphite.InsertRecordingStatus",
|
||||
+ InsertRecordingStatusUma(insert_status));
|
||||
+ }
|
||||
+ if (insert_status != skgpu::graphite::InsertStatus::kSuccess) {
|
||||
+ // skgpu::graphite::InsertStatus almost behaves like an enum class, but
|
||||
+ // not quite - it can't be static_cast to an int.
|
||||
+ LOG(ERROR) << "Graphite insertRecording failed with status "
|
||||
+ << static_cast<int>(InsertRecordingStatusUma(insert_status));
|
||||
+ }
|
||||
+ }
|
||||
+
|
||||
+ // kAsyncShaderCompilesFailed and kOutOfOrderRecording are unrecoverable
|
||||
+ // failures because they cause future recordings to be rendered incorrectly.
|
||||
+ // TODO(433845560): Check the kAddCommandsFailed failures.
|
||||
+ const bool is_unrecoverable_failure =
|
||||
+ insert_status ==
|
||||
+ skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed ||
|
||||
+ insert_status == skgpu::graphite::InsertStatus::kOutOfOrderRecording;
|
||||
+ // For kAsyncShaderCompilesFailed, we should also clear the disk shader
|
||||
+ // cache in case the error was due to a corrupted cached shader blob.
|
||||
+ std::optional<GpuProcessShmCount::ScopedIncrement> use_shader_cache;
|
||||
if (insert_status ==
|
||||
skgpu::graphite::InsertStatus::kAsyncShaderCompilesFailed) {
|
||||
- GpuProcessShmCount::ScopedIncrement use_shader_cache(
|
||||
- use_shader_cache_shm_count_);
|
||||
- CHECK(simulating_insert_failure);
|
||||
+ use_shader_cache.emplace(use_shader_cache_shm_count_);
|
||||
}
|
||||
+ CHECK(simulating_insert_failure || !is_unrecoverable_failure);
|
||||
|
||||
// All other failure modes are recoverable in the sense that future recordings
|
||||
// will be rendered correctly, so merely return a boolean here so that callers
|
||||
diff --git a/gpu/command_buffer/service/graphite_shared_context_unittest.cc b/gpu/command_buffer/service/graphite_shared_context_unittest.cc
|
||||
index 0de107a9d86ff8217a4c537598f198f313ecf9df..852343ab6804efed962b707b748ddf1920c20ea7 100644
|
||||
--- a/gpu/command_buffer/service/graphite_shared_context_unittest.cc
|
||||
+++ b/gpu/command_buffer/service/graphite_shared_context_unittest.cc
|
||||
@@ -6,6 +6,7 @@
|
||||
|
||||
#include "base/threading/thread.h"
|
||||
#include "gpu/command_buffer/common/shm_count.h"
|
||||
+#include "gpu/command_buffer/service/skia_utils.h"
|
||||
#include "skia/buildflags.h"
|
||||
#include "testing/gmock/include/gmock/gmock.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
@@ -86,8 +87,7 @@ class GraphiteSharedContextTest : public testing::TestWithParam<bool> {
|
||||
|
||||
wgpu::DeviceDescriptor device_desc = {};
|
||||
|
||||
- wgpu::Device device =
|
||||
- wgpu::Adapter(adapters[0].Get()).CreateDevice(&device_desc);
|
||||
+ auto device = wgpu::Adapter(adapters[0].Get()).CreateDevice(&device_desc);
|
||||
CHECK(device);
|
||||
|
||||
skgpu::graphite::DawnBackendContext backend_context = {};
|
||||
@@ -95,7 +95,11 @@ class GraphiteSharedContextTest : public testing::TestWithParam<bool> {
|
||||
backend_context.fDevice = device;
|
||||
backend_context.fQueue = device.GetQueue();
|
||||
|
||||
- skgpu::graphite::ContextOptions context_options = {};
|
||||
+ // Use the default Graphite context options that Chromium uses e.g. disallow
|
||||
+ // things like out of order recordings.
|
||||
+ gpu::GpuDriverBugWorkarounds workarounds;
|
||||
+ auto context_options = GetDefaultGraphiteContextOptions(workarounds);
|
||||
+
|
||||
graphite_shared_context_ = std::make_unique<GraphiteSharedContext>(
|
||||
skgpu::graphite::ContextFactory::MakeDawn(backend_context,
|
||||
context_options),
|
||||
@@ -133,14 +137,12 @@ TEST_P(GraphiteSharedContextTest, ConcurrentAccess) {
|
||||
auto run_graphite_functions =
|
||||
[](GraphiteSharedContext* graphite_shared_context) {
|
||||
// Call a method that acquires the lock
|
||||
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
|
||||
- graphite_shared_context->makeRecorder();
|
||||
+ auto recorder = graphite_shared_context->makeRecorder();
|
||||
EXPECT_TRUE(recorder);
|
||||
|
||||
for (int i = 0; i < 2; ++i) {
|
||||
for (int j = 0; j < 10; ++j) {
|
||||
- std::unique_ptr<skgpu::graphite::Recording> recording =
|
||||
- recorder->snap();
|
||||
+ auto recording = recorder->snap();
|
||||
skgpu::graphite::InsertRecordingInfo info = {};
|
||||
info.fRecording = recording.get();
|
||||
EXPECT_TRUE(recording);
|
||||
@@ -172,18 +174,17 @@ TEST_P(GraphiteSharedContextTest, ConcurrentAccess) {
|
||||
}
|
||||
|
||||
TEST_P(GraphiteSharedContextTest, AsyncShaderCompilesFailed) {
|
||||
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
|
||||
- graphite_shared_context_->makeRecorder();
|
||||
+ auto recorder = graphite_shared_context_->makeRecorder();
|
||||
EXPECT_TRUE(recorder);
|
||||
|
||||
auto ii = SkImageInfo::Make(64, 64, kN32_SkColorType, kPremul_SkAlphaType);
|
||||
- sk_sp<SkSurface> surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
+ auto surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
surface1->getCanvas()->clear(SK_ColorRED);
|
||||
|
||||
- sk_sp<SkSurface> surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
+ auto surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
surface2->getCanvas()->drawImage(surface1->makeTemporaryImage(), 0, 0);
|
||||
|
||||
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
|
||||
+ auto recording = recorder->snap();
|
||||
EXPECT_TRUE(recording);
|
||||
|
||||
skgpu::graphite::InsertRecordingInfo info = {};
|
||||
@@ -197,19 +198,43 @@ TEST_P(GraphiteSharedContextTest, AsyncShaderCompilesFailed) {
|
||||
EXPECT_FALSE(graphite_shared_context_->insertRecording(info));
|
||||
}
|
||||
|
||||
+TEST_P(GraphiteSharedContextTest, OutOfOrderRecording) {
|
||||
+ auto recorder = graphite_shared_context_->makeRecorder();
|
||||
+ EXPECT_TRUE(recorder);
|
||||
+
|
||||
+ auto ii = SkImageInfo::Make(64, 64, kN32_SkColorType, kPremul_SkAlphaType);
|
||||
+ auto surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
+ surface1->getCanvas()->clear(SK_ColorRED);
|
||||
+ auto recording1 = recorder->snap();
|
||||
+ EXPECT_TRUE(recording1);
|
||||
+
|
||||
+ auto surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
+ surface2->getCanvas()->drawImage(surface1->makeTemporaryImage(), 0, 0);
|
||||
+ auto recording2 = recorder->snap();
|
||||
+ EXPECT_TRUE(recording2);
|
||||
+
|
||||
+ skgpu::graphite::InsertRecordingInfo info = {};
|
||||
+
|
||||
+ info.fRecording = recording2.get();
|
||||
+ graphite_shared_context_->insertRecording(info);
|
||||
+
|
||||
+ info.fRecording = recording1.get();
|
||||
+ EXPECT_DEATH_IF_SUPPORTED(graphite_shared_context_->insertRecording(info),
|
||||
+ "");
|
||||
+}
|
||||
+
|
||||
TEST_P(GraphiteSharedContextTest, AddCommandsFailed) {
|
||||
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
|
||||
- graphite_shared_context_->makeRecorder();
|
||||
+ auto recorder = graphite_shared_context_->makeRecorder();
|
||||
EXPECT_TRUE(recorder);
|
||||
|
||||
auto ii = SkImageInfo::Make(64, 64, kN32_SkColorType, kPremul_SkAlphaType);
|
||||
- sk_sp<SkSurface> surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
+ auto surface1 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
surface1->getCanvas()->clear(SK_ColorRED);
|
||||
|
||||
- sk_sp<SkSurface> surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
+ auto surface2 = SkSurfaces::RenderTarget(recorder.get(), ii);
|
||||
surface2->getCanvas()->drawImage(surface1->makeTemporaryImage(), 0, 0);
|
||||
|
||||
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
|
||||
+ auto recording = recorder->snap();
|
||||
EXPECT_TRUE(recording);
|
||||
|
||||
skgpu::graphite::InsertRecordingInfo info = {};
|
||||
@@ -223,39 +248,37 @@ TEST_P(GraphiteSharedContextTest, AddCommandsFailed) {
|
||||
}
|
||||
|
||||
TEST_P(GraphiteSharedContextTest, LowPendingRecordings) {
|
||||
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
|
||||
- graphite_shared_context_->makeRecorder();
|
||||
+ auto recorder = graphite_shared_context_->makeRecorder();
|
||||
EXPECT_TRUE(recorder);
|
||||
|
||||
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
|
||||
- EXPECT_TRUE(recording);
|
||||
-
|
||||
- skgpu::graphite::InsertRecordingInfo info = {};
|
||||
- info.fRecording = recording.get();
|
||||
-
|
||||
// No flush is expected if the number of pending recordings is low.
|
||||
EXPECT_CALL(backend_flush_callback_, Flush()).Times(0);
|
||||
|
||||
for (size_t i = 0; i < kMaxPendingRecordings - 1; ++i) {
|
||||
+ auto recording = recorder->snap();
|
||||
+ EXPECT_TRUE(recording);
|
||||
+
|
||||
+ skgpu::graphite::InsertRecordingInfo info = {};
|
||||
+ info.fRecording = recording.get();
|
||||
+
|
||||
EXPECT_TRUE(graphite_shared_context_->insertRecording(info));
|
||||
}
|
||||
}
|
||||
|
||||
TEST_P(GraphiteSharedContextTest, MaxPendingRecordings) {
|
||||
- std::unique_ptr<skgpu::graphite::Recorder> recorder =
|
||||
- graphite_shared_context_->makeRecorder();
|
||||
+ auto recorder = graphite_shared_context_->makeRecorder();
|
||||
EXPECT_TRUE(recorder);
|
||||
|
||||
- std::unique_ptr<skgpu::graphite::Recording> recording = recorder->snap();
|
||||
- EXPECT_TRUE(recording);
|
||||
-
|
||||
- skgpu::graphite::InsertRecordingInfo info = {};
|
||||
- info.fRecording = recording.get();
|
||||
-
|
||||
// Expect a flush when the number of pending recordings reaches the max.
|
||||
EXPECT_CALL(backend_flush_callback_, Flush()).Times(1);
|
||||
|
||||
for (size_t i = 0; i < kMaxPendingRecordings; ++i) {
|
||||
+ auto recording = recorder->snap();
|
||||
+ EXPECT_TRUE(recording);
|
||||
+
|
||||
+ skgpu::graphite::InsertRecordingInfo info = {};
|
||||
+ info.fRecording = recording.get();
|
||||
+
|
||||
EXPECT_TRUE(graphite_shared_context_->insertRecording(info));
|
||||
}
|
||||
}
|
||||
diff --git a/tools/metrics/histograms/metadata/gpu/enums.xml b/tools/metrics/histograms/metadata/gpu/enums.xml
|
||||
index 58471a1d69c65cdf559f98ec2e11550bc16520d2..b62d8b78d34bfec491bcd5e6c65ff63eaedf00dd 100644
|
||||
--- a/tools/metrics/histograms/metadata/gpu/enums.xml
|
||||
+++ b/tools/metrics/histograms/metadata/gpu/enums.xml
|
||||
@@ -1104,6 +1104,19 @@ Called by update_gpu_driver_bug_workaround_entries.py.-->
|
||||
<int value="10" label="NoKillForGpuProgressInCrashDump"/>
|
||||
</enum>
|
||||
|
||||
+<!-- LINT.IfChange(GraphiteInsertRecordingStatus) -->
|
||||
+
|
||||
+<enum name="GraphiteInsertRecordingStatus">
|
||||
+ <int value="0" label="Success"/>
|
||||
+ <int value="1" label="InvalidRecording"/>
|
||||
+ <int value="2" label="PromiseImageInstantiationFailed"/>
|
||||
+ <int value="3" label="AddCommandsFailed"/>
|
||||
+ <int value="4" label="AsyncShaderCompilesFailed"/>
|
||||
+ <int value="5" label="OutOfOrderRecording"/>
|
||||
+</enum>
|
||||
+
|
||||
+<!-- LINT.ThenChange(//gpu/command_buffer/service/graphite_shared_context.cc:InsertRecordingStatusUma) -->
|
||||
+
|
||||
<enum name="HasDiscreteGpu">
|
||||
<int value="0" label="No"/>
|
||||
<int value="1" label="Yes"/>
|
||||
diff --git a/tools/metrics/histograms/metadata/gpu/histograms.xml b/tools/metrics/histograms/metadata/gpu/histograms.xml
|
||||
index ea8b129a2f4aba4d8afd9c3338f21574a12185dd..80bf969cbf66eac7f970b7c096da83c69424c38a 100644
|
||||
--- a/tools/metrics/histograms/metadata/gpu/histograms.xml
|
||||
+++ b/tools/metrics/histograms/metadata/gpu/histograms.xml
|
||||
@@ -1004,6 +1004,16 @@ chromium-metrics-reviews@google.com.
|
||||
</summary>
|
||||
</histogram>
|
||||
|
||||
+<histogram name="Gpu.Graphite.InsertRecordingStatus"
|
||||
+ enum="GraphiteInsertRecordingStatus" expires_after="2026-04-30">
|
||||
+ <owner>sunnyps@chromium.org</owner>
|
||||
+ <owner>michaelludwig@google.com</owner>
|
||||
+ <owner>chrome-gpu-metric-alerts@chromium.org</owner>
|
||||
+ <summary>
|
||||
+ The return value for each Graphite insertRecording call made by Chromium.
|
||||
+ </summary>
|
||||
+</histogram>
|
||||
+
|
||||
<histogram name="GPU.GraphValidation.Duration" units="ms"
|
||||
expires_after="2025-11-15">
|
||||
<owner>vasilyt@chromium.org</owner>
|
||||
@@ -0,0 +1,93 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Niklas Wenzel <dev@nikwen.de>
|
||||
Date: Wed, 3 Dec 2025 17:10:02 +0100
|
||||
Subject: viz: Create IsBufferQueueSupportedAndEnabled()
|
||||
|
||||
Manual backport of crrev.com/c/7210913
|
||||
|
||||
Bug: 457463689
|
||||
Change-Id: I31bbaa6b5d79697c6bb5e1fc6738f6ea5a937b4f
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7210913
|
||||
Reviewed-by: Michael Tang <tangm@microsoft.com>
|
||||
Commit-Queue: David Sanders <dsanders11@ucsbalum.com>
|
||||
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1553190}
|
||||
|
||||
diff --git a/components/viz/service/display/output_surface.cc b/components/viz/service/display/output_surface.cc
|
||||
index ff795eb057ac64f40aa842fec8e053d12f57f538..71287614c627d39f8d019889498205ab3eff2a69 100644
|
||||
--- a/components/viz/service/display/output_surface.cc
|
||||
+++ b/components/viz/service/display/output_surface.cc
|
||||
@@ -21,6 +21,16 @@
|
||||
|
||||
namespace viz {
|
||||
|
||||
+namespace {
|
||||
+
|
||||
+#if BUILDFLAG(IS_WIN)
|
||||
+// Use BufferQueue for the primary plane instead of a DXGI swap chain or DComp
|
||||
+// surface.
|
||||
+BASE_FEATURE(kBufferQueue, base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
+#endif
|
||||
+
|
||||
+} // namespace
|
||||
+
|
||||
OutputSurface::Capabilities::Capabilities() = default;
|
||||
OutputSurface::Capabilities::~Capabilities() = default;
|
||||
OutputSurface::Capabilities::Capabilities(const Capabilities& capabilities) =
|
||||
@@ -94,6 +104,12 @@ bool IsDelegatedCompositingSupportedAndEnabled(
|
||||
// Ensure we check the feature flag iff the feature is supported.
|
||||
return features::IsDelegatedCompositingEnabled();
|
||||
}
|
||||
+
|
||||
+bool IsBufferQueueSupportedAndEnabled(
|
||||
+ OutputSurface::DCSupportLevel support_level) {
|
||||
+ return support_level >= OutputSurface::DCSupportLevel::kDCompDynamicTexture &&
|
||||
+ base::FeatureList::IsEnabled(kBufferQueue);
|
||||
+}
|
||||
#endif
|
||||
|
||||
} // namespace viz
|
||||
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
|
||||
index 25306ab6e18a266efdc329e4ddd81f5303033f4c..589f4c10dad9c807c9e3ce7baba63795b629435b 100644
|
||||
--- a/components/viz/service/display/output_surface.h
|
||||
+++ b/components/viz/service/display/output_surface.h
|
||||
@@ -307,6 +307,9 @@ class VIZ_SERVICE_EXPORT OutputSurface {
|
||||
// `features::IsDelegatedCompositingEnabled()`.
|
||||
bool IsDelegatedCompositingSupportedAndEnabled(
|
||||
OutputSurface::DCSupportLevel support_level);
|
||||
+
|
||||
+bool IsBufferQueueSupportedAndEnabled(
|
||||
+ OutputSurface::DCSupportLevel support_level);
|
||||
#endif
|
||||
|
||||
} // namespace viz
|
||||
diff --git a/components/viz/service/display/skia_renderer.cc b/components/viz/service/display/skia_renderer.cc
|
||||
index e34e15dda13a183568fc3e186d3b89da1e828ad4..9b9a4c02c975799fe42b04f0d5b680274d28b09e 100644
|
||||
--- a/components/viz/service/display/skia_renderer.cc
|
||||
+++ b/components/viz/service/display/skia_renderer.cc
|
||||
@@ -121,12 +121,6 @@ namespace {
|
||||
BASE_FEATURE(kDumpWithoutCrashingOnMissingRenderPassBacking,
|
||||
base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
|
||||
-#if BUILDFLAG(IS_WIN)
|
||||
-// Use BufferQueue for the primary plane instead of a DXGI swap chain or DComp
|
||||
-// surface.
|
||||
-BASE_FEATURE(kBufferQueue, base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
-#endif
|
||||
-
|
||||
// Smallest unit that impacts anti-aliasing output. We use this to determine
|
||||
// when an exterior edge (with AA) has been clipped (no AA). The specific value
|
||||
// was chosen to match that used by gl_renderer.
|
||||
@@ -992,10 +986,8 @@ SkiaRenderer::SkiaRenderer(const RendererSettings* settings,
|
||||
|
||||
// It's possible to use BufferQueue with DComp textures, so we can optionally
|
||||
// enable it behind a feature flag.
|
||||
- const bool want_buffer_queue =
|
||||
- output_surface_->capabilities().dc_support_level >=
|
||||
- OutputSurface::DCSupportLevel::kDCompDynamicTexture &&
|
||||
- base::FeatureList::IsEnabled(kBufferQueue);
|
||||
+ const bool want_buffer_queue = IsBufferQueueSupportedAndEnabled(
|
||||
+ output_surface_->capabilities().dc_support_level);
|
||||
#else
|
||||
const bool want_buffer_queue = true;
|
||||
#endif
|
||||
@@ -0,0 +1,45 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Niklas Wenzel <dev@nikwen.de>
|
||||
Date: Wed, 3 Dec 2025 17:08:55 +0100
|
||||
Subject: viz: Do not overallocate surface on initial render
|
||||
|
||||
Manual backport of crrev.com/c/7129658
|
||||
|
||||
Change-Id: I0baa5865dbe66efc7b0f3f793c8e89cdc6aaa0b6
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7129658
|
||||
Commit-Queue: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Reviewed-by: Michael Tang <tangm@microsoft.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1544293}
|
||||
|
||||
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
|
||||
index c03a4b3db61371021a94128f698126b5fef2f577..01abf5d36e2e117acf6f9cdc91307c9ac5616453 100644
|
||||
--- a/components/viz/service/display/direct_renderer.cc
|
||||
+++ b/components/viz/service/display/direct_renderer.cc
|
||||
@@ -1081,10 +1081,13 @@ gfx::Size DirectRenderer::CalculateTextureSizeForRenderPass(
|
||||
// buffer area and number of reallocations to quantify the trade-off.
|
||||
gfx::Size DirectRenderer::CalculateSizeForOutputSurface(
|
||||
const gfx::Size& requested_viewport_size) {
|
||||
+ const gfx::Size surface_size = surface_size_for_swap_buffers();
|
||||
+
|
||||
// We're not able to clip back buffers if output surface does not support
|
||||
- // clipping.
|
||||
- if (requested_viewport_size == surface_size_for_swap_buffers() ||
|
||||
+ // clipping. We don't round on the initial frame when a window is first shown.
|
||||
+ if (requested_viewport_size == surface_size ||
|
||||
!output_surface_->capabilities().supports_viewporter ||
|
||||
+ surface_size.IsZero() ||
|
||||
settings_->dont_round_texture_sizes_for_pixel_tests) {
|
||||
device_viewport_size_ = requested_viewport_size;
|
||||
return requested_viewport_size;
|
||||
@@ -1102,8 +1105,8 @@ gfx::Size DirectRenderer::CalculateSizeForOutputSurface(
|
||||
// allows backings to be more easily reused during a resize operation.
|
||||
const int request_width = requested_viewport_size.width();
|
||||
const int request_height = requested_viewport_size.height();
|
||||
- int surface_width = surface_size_for_swap_buffers().width();
|
||||
- int surface_height = surface_size_for_swap_buffers().height();
|
||||
+ int surface_width = surface_size.width();
|
||||
+ int surface_height = surface_size.height();
|
||||
constexpr int multiple = 256;
|
||||
|
||||
// If |request_width| or |request_height| is already a multiple of |multiple|,
|
||||
@@ -0,0 +1,43 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Niklas Wenzel <dev@nikwen.de>
|
||||
Date: Thu, 20 Nov 2025 11:51:44 -0800
|
||||
Subject: viz: Fix visual artifacts due to resizing root render pass with DComp
|
||||
|
||||
Backport of crrev.com/c/7156576
|
||||
|
||||
Refs https://github.com/electron/electron/issues/36280#issuecomment-3560964534
|
||||
|
||||
Bug: 457463689
|
||||
Change-Id: I7e00eadb2a1c3b44d64939d1a870d89befee158b
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7156576
|
||||
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Reviewed-by: Jonathan Ross <jonross@chromium.org>
|
||||
Commit-Queue: Jonathan Ross <jonross@chromium.org>
|
||||
Reviewed-by: Michael Tang <tangm@microsoft.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1547987}
|
||||
|
||||
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
|
||||
index 81649eb59eb71ac950779af0f521a1f2a02e3add..7ac9ea1cdc4416a7af8dc2a75404cbc15be6cfad 100644
|
||||
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc
|
||||
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
|
||||
@@ -39,6 +39,11 @@
|
||||
namespace viz {
|
||||
|
||||
namespace {
|
||||
+// With DirectComposition, resize surface based on root render pass size to
|
||||
+// avoid gutter which shows stale pixels.
|
||||
+BASE_FEATURE(kDirectCompositionResizeBasedOnRootSurface,
|
||||
+ base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
+
|
||||
base::TimeTicks g_last_reshape_failure = base::TimeTicks();
|
||||
|
||||
NOINLINE void CheckForLoopFailures() {
|
||||
@@ -155,6 +160,8 @@ SkiaOutputDeviceDComp::SkiaOutputDeviceDComp(
|
||||
capabilities_.renderer_allocates_images = true;
|
||||
capabilities_.supports_viewporter = presenter_->SupportsViewporter();
|
||||
capabilities_.supports_non_backed_solid_color_overlays = true;
|
||||
+ capabilities_.resize_based_on_root_surface =
|
||||
+ base::FeatureList::IsEnabled(kDirectCompositionResizeBasedOnRootSurface);
|
||||
|
||||
DCHECK(context_state_);
|
||||
DCHECK(context_state_->gr_context() ||
|
||||
@@ -0,0 +1,122 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Niklas Wenzel <dev@nikwen.de>
|
||||
Date: Wed, 3 Dec 2025 17:42:58 +0100
|
||||
Subject: viz: Fix visual artifacts while resizing window with DComp
|
||||
|
||||
Manual backport of crrev.com/c/7115438
|
||||
|
||||
Bug: 457463689
|
||||
Change-Id: I9c684effe15e0b112ae533faa243e5a035e9c875
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7115438
|
||||
Commit-Queue: David Sanders <dsanders11@ucsbalum.com>
|
||||
Reviewed-by: Michael Tang <tangm@microsoft.com>
|
||||
Reviewed-by: Vasiliy Telezhnikov <vasilyt@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1553192}
|
||||
|
||||
diff --git a/components/viz/service/display/direct_renderer.cc b/components/viz/service/display/direct_renderer.cc
|
||||
index 01abf5d36e2e117acf6f9cdc91307c9ac5616453..7e45d9ea46d0d4095169daf748e20c98db21969d 100644
|
||||
--- a/components/viz/service/display/direct_renderer.cc
|
||||
+++ b/components/viz/service/display/direct_renderer.cc
|
||||
@@ -243,6 +243,31 @@ void DirectRenderer::DrawFrame(
|
||||
current_frame()->device_viewport_size = device_viewport_size;
|
||||
current_frame()->display_color_spaces = display_color_spaces;
|
||||
|
||||
+ gfx::Size surface_resource_size =
|
||||
+ CalculateSizeForOutputSurface(device_viewport_size);
|
||||
+
|
||||
+#if BUILDFLAG(IS_WIN)
|
||||
+ if (output_surface_->capabilities().clear_drawn_areas_outside_viewport &&
|
||||
+ device_viewport_size != surface_resource_size) {
|
||||
+ // On Windows with DirectComposition, we cannot synchronize the swap chain
|
||||
+ // |Present| and the DComp |Commit| calls to take effect at the same time.
|
||||
+ // (Both take effect asynchronously.) Hence, presenting a frame and changing
|
||||
+ // the DComp layer clip rect can happen at different times. This can lead to
|
||||
+ // ugly visual artifacts while resizing the window because it can reveal
|
||||
+ // areas of the surface that are outside the viewport (crbug.com/457463689).
|
||||
+ // To prevent those artifacts, we clear areas outside of the viewport with a
|
||||
+ // transparent color. Transparency is expensive, so we use it only while
|
||||
+ // resizing.
|
||||
+ // This line gives us a transparent image format and triggers the background
|
||||
+ // to be cleared in |SkiaRenderer::ClearFramebuffer|.
|
||||
+ root_render_pass->has_transparent_background = true;
|
||||
+ // Redraw and swap the whole surface.
|
||||
+ root_render_pass->output_rect = gfx::Rect(surface_resource_size);
|
||||
+ current_frame()->root_damage_rect = gfx::Rect(surface_resource_size);
|
||||
+ current_frame()->device_viewport_size = surface_resource_size;
|
||||
+ }
|
||||
+#endif
|
||||
+
|
||||
output_surface_->SetNeedsMeasureNextDrawLatency();
|
||||
BeginDrawingFrame();
|
||||
|
||||
@@ -274,8 +299,6 @@ void DirectRenderer::DrawFrame(
|
||||
current_frame()->display_color_spaces.GetOutputBufferFormat(
|
||||
current_frame()->root_render_pass->content_color_usage,
|
||||
frame_has_alpha));
|
||||
- gfx::Size surface_resource_size =
|
||||
- CalculateSizeForOutputSurface(device_viewport_size);
|
||||
if (overlay_processor_) {
|
||||
// Display transform and viewport size are needed for overlay validator on
|
||||
// Android SurfaceControl, and viewport size is need on Windows. These need
|
||||
@@ -397,8 +420,10 @@ void DirectRenderer::DrawFrame(
|
||||
|
||||
// If we need to redraw the frame, the whole output should be considered
|
||||
// damaged.
|
||||
- if (needs_full_frame_redraw)
|
||||
- current_frame()->root_damage_rect = gfx::Rect(device_viewport_size);
|
||||
+ if (needs_full_frame_redraw) {
|
||||
+ current_frame()->root_damage_rect =
|
||||
+ gfx::Rect(current_frame()->device_viewport_size);
|
||||
+ }
|
||||
|
||||
if (!skip_drawing_root_render_pass) {
|
||||
DrawRenderPassAndExecuteCopyRequests(root_render_pass);
|
||||
diff --git a/components/viz/service/display/output_surface.h b/components/viz/service/display/output_surface.h
|
||||
index 589f4c10dad9c807c9e3ce7baba63795b629435b..641bbfc732c88141ddd929a4c334360462259ee4 100644
|
||||
--- a/components/viz/service/display/output_surface.h
|
||||
+++ b/components/viz/service/display/output_surface.h
|
||||
@@ -106,6 +106,11 @@ class VIZ_SERVICE_EXPORT OutputSurface {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Whether this OutputSurface supports direct composition layers.
|
||||
DCSupportLevel dc_support_level = DCSupportLevel::kNone;
|
||||
+ // Whether to 1) clear all drawn areas outside the viewport with a
|
||||
+ // transparent background color when drawing a frame and 2) swap them. This
|
||||
+ // is necessary if the surface clip rect can get out of sync with the
|
||||
+ // viewport size (e.g., due to a race condition).
|
||||
+ bool clear_drawn_areas_outside_viewport = false;
|
||||
#endif
|
||||
// Whether this OutputSurface should skip DrawAndSwap(). This is true for
|
||||
// the unified display on Chrome OS. All drawing is handled by the physical
|
||||
diff --git a/components/viz/service/display_embedder/skia_output_device_dcomp.cc b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
|
||||
index 7ac9ea1cdc4416a7af8dc2a75404cbc15be6cfad..8e027d1382c5d639c1e114b8e25b6cea3af3445d 100644
|
||||
--- a/components/viz/service/display_embedder/skia_output_device_dcomp.cc
|
||||
+++ b/components/viz/service/display_embedder/skia_output_device_dcomp.cc
|
||||
@@ -39,10 +39,8 @@
|
||||
namespace viz {
|
||||
|
||||
namespace {
|
||||
-// With DirectComposition, resize surface based on root render pass size to
|
||||
-// avoid gutter which shows stale pixels.
|
||||
-BASE_FEATURE(kDirectCompositionResizeBasedOnRootSurface,
|
||||
- base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
+// Apply fixes for crbug.com/457463689.
|
||||
+BASE_FEATURE(kDirectCompositionResizeFixes, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
|
||||
base::TimeTicks g_last_reshape_failure = base::TimeTicks();
|
||||
|
||||
@@ -161,7 +159,14 @@ SkiaOutputDeviceDComp::SkiaOutputDeviceDComp(
|
||||
capabilities_.supports_viewporter = presenter_->SupportsViewporter();
|
||||
capabilities_.supports_non_backed_solid_color_overlays = true;
|
||||
capabilities_.resize_based_on_root_surface =
|
||||
- base::FeatureList::IsEnabled(kDirectCompositionResizeBasedOnRootSurface);
|
||||
+ base::FeatureList::IsEnabled(kDirectCompositionResizeFixes);
|
||||
+ // With delegated compositing or a buffer queue, |Present| and |Commit| are
|
||||
+ // synchronized and the clear is not needed.
|
||||
+ capabilities_.clear_drawn_areas_outside_viewport =
|
||||
+ base::FeatureList::IsEnabled(kDirectCompositionResizeFixes) &&
|
||||
+ !IsDelegatedCompositingSupportedAndEnabled(
|
||||
+ capabilities_.dc_support_level) &&
|
||||
+ !IsBufferQueueSupportedAndEnabled(capabilities_.dc_support_level);
|
||||
|
||||
DCHECK(context_state_);
|
||||
DCHECK(context_state_->gr_context() ||
|
||||
@@ -12,5 +12,6 @@
|
||||
{ "patch_dir": "src/electron/patches/ReactiveObjC", "repo": "src/third_party/squirrel.mac/vendor/ReactiveObjC" },
|
||||
{ "patch_dir": "src/electron/patches/webrtc", "repo": "src/third_party/webrtc" },
|
||||
{ "patch_dir": "src/electron/patches/reclient-configs", "repo": "src/third_party/engflow-reclient-configs" },
|
||||
{ "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" }
|
||||
{ "patch_dir": "src/electron/patches/sqlite", "repo": "src/third_party/sqlite/src" },
|
||||
{ "patch_dir": "src/electron/patches/skia", "repo": "src/third_party/skia" }
|
||||
]
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
chore_expose_ui_to_allow_electron_to_set_dock_side.patch
|
||||
fix_element_tree_flickering_with_node_inspection.patch
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Alex Rudenko <alexrudenko@chromium.org>
|
||||
Date: Thu, 20 Nov 2025 11:18:30 +0100
|
||||
Subject: Fix element tree flickering with node inspection
|
||||
|
||||
Regressed in https://crrev.com/c/6888910. The highlighting in the tree
|
||||
outline emits the INSPECT_MODE_WILL_BE_TOGGLED on the overlay model
|
||||
causing the tree to clear the highlight immediately. This CL
|
||||
recovers the logic missed in the https://crrev.com/c/6888910
|
||||
to prevent re-entrancy during highlighting.
|
||||
|
||||
Fixed: 462120622
|
||||
Change-Id: I08af098f7a142085c2fb16511031855623e07c4b
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/7170551
|
||||
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
|
||||
Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
|
||||
Auto-Submit: Alex Rudenko <alexrudenko@chromium.org>
|
||||
|
||||
diff --git a/front_end/panels/elements/DOMTreeWidget.test.ts b/front_end/panels/elements/DOMTreeWidget.test.ts
|
||||
index 43ae133087a61c47c84349c7550485bd52b23847..d83fddd7afc1871b8064631d63c1b6b1e5357b70 100644
|
||||
--- a/front_end/panels/elements/DOMTreeWidget.test.ts
|
||||
+++ b/front_end/panels/elements/DOMTreeWidget.test.ts
|
||||
@@ -24,6 +24,7 @@ describeWithMockConnection('DOMTreeWidget', () => {
|
||||
elementsTreeOutline,
|
||||
alreadyExpandedParentTreeElement: null,
|
||||
highlightedTreeElement: null,
|
||||
+ isUpdatingHighlights: false,
|
||||
});
|
||||
const domTree = new Elements.ElementsTreeOutline.DOMTreeWidget(undefined, view);
|
||||
domTree.performUpdate();
|
||||
diff --git a/front_end/panels/elements/ElementsTreeOutline.ts b/front_end/panels/elements/ElementsTreeOutline.ts
|
||||
index bd8093e1c0961648e782234ed5826273d6ace6d9..c6c8d9d29a84f872126ca0b7c847dffe08c285ad 100644
|
||||
--- a/front_end/panels/elements/ElementsTreeOutline.ts
|
||||
+++ b/front_end/panels/elements/ElementsTreeOutline.ts
|
||||
@@ -105,6 +105,7 @@ interface ViewInput {
|
||||
interface ViewOutput {
|
||||
elementsTreeOutline?: ElementsTreeOutline;
|
||||
highlightedTreeElement: ElementsTreeElement|null;
|
||||
+ isUpdatingHighlights: boolean;
|
||||
alreadyExpandedParentTreeElement: ElementsTreeElement|null;
|
||||
}
|
||||
|
||||
@@ -136,6 +137,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
||||
// Node highlighting logic. FIXME: express as a lit template.
|
||||
const previousHighlightedNode = output.highlightedTreeElement?.node() ?? null;
|
||||
if (previousHighlightedNode !== input.currentHighlightedNode) {
|
||||
+ output.isUpdatingHighlights = true;
|
||||
let treeElement: ElementsTreeElement|null = null;
|
||||
|
||||
if (output.highlightedTreeElement) {
|
||||
@@ -173,6 +175,7 @@ export const DEFAULT_VIEW = (input: ViewInput, output: ViewOutput, target: HTMLE
|
||||
output.highlightedTreeElement = treeElement;
|
||||
output.elementsTreeOutline.setHoverEffect(treeElement);
|
||||
treeElement?.reveal(true);
|
||||
+ output.isUpdatingHighlights = false;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -225,6 +228,7 @@ export class DOMTreeWidget extends UI.Widget.Widget {
|
||||
#viewOutput: ViewOutput = {
|
||||
highlightedTreeElement: null,
|
||||
alreadyExpandedParentTreeElement: null,
|
||||
+ isUpdatingHighlights: false,
|
||||
};
|
||||
#highlightThrottler = new Common.Throttler.Throttler(100);
|
||||
|
||||
@@ -239,8 +243,8 @@ export class DOMTreeWidget extends UI.Widget.Widget {
|
||||
SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.HIGHLIGHT_NODE_REQUESTED, this.#highlightNode, this,
|
||||
{scoped: true});
|
||||
SDK.TargetManager.TargetManager.instance().addModelListener(
|
||||
- SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.INSPECT_MODE_WILL_BE_TOGGLED, this.#clearState, this,
|
||||
- {scoped: true});
|
||||
+ SDK.OverlayModel.OverlayModel, SDK.OverlayModel.Events.INSPECT_MODE_WILL_BE_TOGGLED,
|
||||
+ this.#clearHighlightedNode, this, {scoped: true});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -251,7 +255,13 @@ export class DOMTreeWidget extends UI.Widget.Widget {
|
||||
});
|
||||
}
|
||||
|
||||
- #clearState(): void {
|
||||
+ #clearHighlightedNode(): void {
|
||||
+ // Highlighting an element via tree outline will emit the
|
||||
+ // INSPECT_MODE_WILL_BE_TOGGLED event, therefore, we skip it if the view
|
||||
+ // informed us that it is updating the element.
|
||||
+ if (this.#viewOutput.isUpdatingHighlights) {
|
||||
+ return;
|
||||
+ }
|
||||
this.#currentHighlightedNode = null;
|
||||
this.requestUpdate();
|
||||
}
|
||||
@@ -305,11 +315,11 @@ export class DOMTreeWidget extends UI.Widget.Widget {
|
||||
currentHighlightedNode: this.#currentHighlightedNode,
|
||||
onElementsTreeUpdated: this.onElementsTreeUpdated.bind(this),
|
||||
onSelectedNodeChanged: event => {
|
||||
- this.#clearState();
|
||||
+ this.#clearHighlightedNode();
|
||||
this.onSelectedNodeChanged(event);
|
||||
},
|
||||
- onElementCollapsed: this.#clearState.bind(this),
|
||||
- onElementExpanded: this.#clearState.bind(this),
|
||||
+ onElementCollapsed: this.#clearHighlightedNode.bind(this),
|
||||
+ onElementExpanded: this.#clearHighlightedNode.bind(this),
|
||||
},
|
||||
this.#viewOutput, this.contentElement);
|
||||
}
|
||||
@@ -7,3 +7,4 @@ fix_support_new_variant_of_namedpropertyhandlerconfiguration_and.patch
|
||||
fix_correct_usages_of_v8_returnvalue_void_set_nonempty_for_new.patch
|
||||
chore_remove_deprecated_functioncallbackinfo_holder.patch
|
||||
fix_replace_deprecated_get_setprototype.patch
|
||||
chore_add_yarnrc_yml_and_yarn_lock_file_to_use_yarn_v4.patch
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,3 +53,7 @@ fix_replace_deprecated_setprototype.patch
|
||||
fix_redefined_macos_sdk_header_symbols.patch
|
||||
src_use_cp_utf8_for_wide_file_names_on_win32.patch
|
||||
fix_ensure_traverseparent_bails_on_resource_path_exit.patch
|
||||
src_handle_der_decoding_errors_from_system_certificates.patch
|
||||
remove_obsolete_noarraybufferzerofillscope.patch
|
||||
src_prepare_for_v8_sandboxing.patch
|
||||
test_correct_conditional_secure_heap_flags_test.patch
|
||||
|
||||
@@ -6,10 +6,10 @@ Subject: Remove deprecated `GetIsolate`
|
||||
https://chromium-review.googlesource.com/c/v8/v8/+/6905244
|
||||
|
||||
diff --git a/src/api/environment.cc b/src/api/environment.cc
|
||||
index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7c630fddf 100644
|
||||
index ceac508418f489a8077c1bc85a2feaf85bf60480..33827edce63c9fe08b52aea59571391a83853443 100644
|
||||
--- a/src/api/environment.cc
|
||||
+++ b/src/api/environment.cc
|
||||
@@ -654,7 +654,7 @@ std::unique_ptr<MultiIsolatePlatform> MultiIsolatePlatform::Create(
|
||||
@@ -646,7 +646,7 @@ std::unique_ptr<MultiIsolatePlatform> MultiIsolatePlatform::Create(
|
||||
|
||||
MaybeLocal<Object> GetPerContextExports(Local<Context> context,
|
||||
IsolateData* isolate_data) {
|
||||
@@ -18,7 +18,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
|
||||
EscapableHandleScope handle_scope(isolate);
|
||||
|
||||
Local<Object> global = context->Global();
|
||||
@@ -700,7 +700,7 @@ void ProtoThrower(const FunctionCallbackInfo<Value>& info) {
|
||||
@@ -692,7 +692,7 @@ void ProtoThrower(const FunctionCallbackInfo<Value>& info) {
|
||||
// This runs at runtime, regardless of whether the context
|
||||
// is created from a snapshot.
|
||||
Maybe<void> InitializeContextRuntime(Local<Context> context) {
|
||||
@@ -27,7 +27,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
// When `IsCodeGenerationFromStringsAllowed` is true, V8 takes the fast path
|
||||
@@ -779,7 +779,7 @@ Maybe<void> InitializeContextRuntime(Local<Context> context) {
|
||||
@@ -771,7 +771,7 @@ Maybe<void> InitializeContextRuntime(Local<Context> context) {
|
||||
}
|
||||
|
||||
Maybe<void> InitializeBaseContextForSnapshot(Local<Context> context) {
|
||||
@@ -36,7 +36,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
// Delete `Intl.v8BreakIterator`
|
||||
@@ -804,7 +804,7 @@ Maybe<void> InitializeBaseContextForSnapshot(Local<Context> context) {
|
||||
@@ -796,7 +796,7 @@ Maybe<void> InitializeBaseContextForSnapshot(Local<Context> context) {
|
||||
}
|
||||
|
||||
Maybe<void> InitializeMainContextForSnapshot(Local<Context> context) {
|
||||
@@ -45,7 +45,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
|
||||
HandleScope handle_scope(isolate);
|
||||
|
||||
// Initialize the default values.
|
||||
@@ -822,7 +822,7 @@ Maybe<void> InitializeMainContextForSnapshot(Local<Context> context) {
|
||||
@@ -814,7 +814,7 @@ Maybe<void> InitializeMainContextForSnapshot(Local<Context> context) {
|
||||
MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
|
||||
IsolateData* isolate_data) {
|
||||
CHECK(isolate_data);
|
||||
@@ -54,7 +54,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
|
||||
EscapableHandleScope scope(isolate);
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
@@ -846,7 +846,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
|
||||
@@ -838,7 +838,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
|
||||
MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
|
||||
IsolateData* isolate_data) {
|
||||
CHECK(isolate_data);
|
||||
@@ -63,7 +63,7 @@ index 8e227ddd1be50c046a8cf2895a31d607eb7d31de..82f53bba29613de212f64be440ca20d7
|
||||
EscapableHandleScope scope(isolate);
|
||||
Context::Scope context_scope(context);
|
||||
|
||||
@@ -872,7 +872,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
|
||||
@@ -864,7 +864,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
|
||||
Maybe<void> InitializePrimordials(Local<Context> context,
|
||||
IsolateData* isolate_data) {
|
||||
// Run per-context JS files.
|
||||
@@ -508,10 +508,10 @@ index 492d5f455f45a5c8a957ecdabed38709a633f640..48f9917113555c7ed87e37750c45d152
|
||||
Local<Array> keys;
|
||||
if (!entries->GetOwnPropertyNames(context).ToLocal(&keys))
|
||||
diff --git a/src/node_errors.cc b/src/node_errors.cc
|
||||
index 4386a1bc5678e351ce084cd2c47202561619b164..8d51201ad24999ed8f54e16c7878432d41841cf2 100644
|
||||
index 36a21b9523351fe2f225ffe7fca184d737640b62..c6e9e2c00064348c435ae10871a4013cb49376e6 100644
|
||||
--- a/src/node_errors.cc
|
||||
+++ b/src/node_errors.cc
|
||||
@@ -633,7 +633,7 @@ v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings(
|
||||
@@ -631,7 +631,7 @@ v8::ModifyCodeGenerationFromStringsResult ModifyCodeGenerationFromStrings(
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Value> source,
|
||||
bool is_code_like) {
|
||||
@@ -520,7 +520,7 @@ index 4386a1bc5678e351ce084cd2c47202561619b164..8d51201ad24999ed8f54e16c7878432d
|
||||
|
||||
if (context->GetNumberOfEmbedderDataFields() <=
|
||||
ContextEmbedderIndex::kAllowCodeGenerationFromStrings) {
|
||||
@@ -1000,7 +1000,7 @@ const char* errno_string(int errorno) {
|
||||
@@ -1037,7 +1037,7 @@ const char* errno_string(int errorno) {
|
||||
}
|
||||
|
||||
void PerIsolateMessageListener(Local<Message> message, Local<Value> error) {
|
||||
@@ -529,7 +529,7 @@ index 4386a1bc5678e351ce084cd2c47202561619b164..8d51201ad24999ed8f54e16c7878432d
|
||||
switch (message->ErrorLevel()) {
|
||||
case Isolate::MessageErrorLevel::kMessageWarning: {
|
||||
Environment* env = Environment::GetCurrent(isolate);
|
||||
@@ -1161,7 +1161,7 @@ void Initialize(Local<Object> target,
|
||||
@@ -1198,7 +1198,7 @@ void Initialize(Local<Object> target,
|
||||
SetMethod(
|
||||
context, target, "getErrorSourcePositions", GetErrorSourcePositions);
|
||||
|
||||
@@ -640,7 +640,7 @@ index 66a8ee48fc68b22eaf6c9d9209cc5cb2439e55ff..b31e244f8af7d37c35319853a478776c
|
||||
env->AssignToContext(context, this, ContextInfo(""));
|
||||
}
|
||||
diff --git a/src/node_report.cc b/src/node_report.cc
|
||||
index 8ff711f12e19f73e50daac2b9c0fd26773d32758..6966710e8f0be542364850776ddf76f9223a02e7 100644
|
||||
index e1f4e30417d17f85d2294f3a01e6d90f0a443948..12a59307b452b0d67ebe909cae0458b8a0ca085b 100644
|
||||
--- a/src/node_report.cc
|
||||
+++ b/src/node_report.cc
|
||||
@@ -399,7 +399,7 @@ static void PrintJavaScriptErrorProperties(JSONWriter* writer,
|
||||
|
||||
@@ -8,10 +8,10 @@ they use themselves as the entry point. We should try to upstream some form
|
||||
of this.
|
||||
|
||||
diff --git a/lib/internal/process/pre_execution.js b/lib/internal/process/pre_execution.js
|
||||
index d12d21905c4823f45cffeea4423e99e81b1008f5..4987cd3f6c9eefb440bca3f58113df6cd5b410ac 100644
|
||||
index 15443a710ccf53fae333da3b1fbb52a970c658d5..464b34829c1a566836bfca6bbc2b87fcf5e50016 100644
|
||||
--- a/lib/internal/process/pre_execution.js
|
||||
+++ b/lib/internal/process/pre_execution.js
|
||||
@@ -267,12 +267,14 @@ function patchProcessObject(expandArgv1) {
|
||||
@@ -265,12 +265,14 @@ function patchProcessObject(expandArgv1) {
|
||||
// the entry point.
|
||||
if (expandArgv1 && process.argv[1] && process.argv[1][0] !== '-') {
|
||||
// Expand process.argv[1] into a full path.
|
||||
|
||||
@@ -86,10 +86,10 @@ index 0ca643aa74d13f278685d2330b791182b55c15b4..cbcecfba33070b820aca0e2814982160
|
||||
NODE_DEFINE_CONSTANT(target, ETIMEDOUT);
|
||||
#endif
|
||||
diff --git a/src/node_errors.cc b/src/node_errors.cc
|
||||
index ae8553ee2022d60fea4572976b14ba9cd253aa45..4386a1bc5678e351ce084cd2c47202561619b164 100644
|
||||
index 238942d45a136facec55ca5a2534e2dc407137e9..36a21b9523351fe2f225ffe7fca184d737640b62 100644
|
||||
--- a/src/node_errors.cc
|
||||
+++ b/src/node_errors.cc
|
||||
@@ -862,10 +862,6 @@ const char* errno_string(int errorno) {
|
||||
@@ -899,10 +899,6 @@ const char* errno_string(int errorno) {
|
||||
ERRNO_CASE(ENOBUFS);
|
||||
#endif
|
||||
|
||||
@@ -100,7 +100,7 @@ index ae8553ee2022d60fea4572976b14ba9cd253aa45..4386a1bc5678e351ce084cd2c4720256
|
||||
#ifdef ENODEV
|
||||
ERRNO_CASE(ENODEV);
|
||||
#endif
|
||||
@@ -904,14 +900,6 @@ const char* errno_string(int errorno) {
|
||||
@@ -941,14 +937,6 @@ const char* errno_string(int errorno) {
|
||||
ERRNO_CASE(ENOSPC);
|
||||
#endif
|
||||
|
||||
@@ -115,7 +115,7 @@ index ae8553ee2022d60fea4572976b14ba9cd253aa45..4386a1bc5678e351ce084cd2c4720256
|
||||
#ifdef ENOSYS
|
||||
ERRNO_CASE(ENOSYS);
|
||||
#endif
|
||||
@@ -994,10 +982,6 @@ const char* errno_string(int errorno) {
|
||||
@@ -1031,10 +1019,6 @@ const char* errno_string(int errorno) {
|
||||
ERRNO_CASE(ESTALE);
|
||||
#endif
|
||||
|
||||
|
||||
@@ -48,7 +48,7 @@ index fe669d40c31a29334b047b9cfee3067f64ef0a7b..9e5de7bbe574add017cd12ee091304d0
|
||||
|
||||
static CFunction fast_timing_safe_equal(CFunction::Make(FastTimingSafeEqual));
|
||||
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
|
||||
index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344e563ad72 100644
|
||||
index b9f0c97938203b4652780a7d707c5e83319330b0..8a5b6b57321c2843a965a7e51b2ebed991a1e424 100644
|
||||
--- a/src/node_buffer.cc
|
||||
+++ b/src/node_buffer.cc
|
||||
@@ -44,6 +44,14 @@
|
||||
@@ -74,7 +74,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::Global;
|
||||
using v8::HandleScope;
|
||||
@@ -584,19 +591,24 @@ void SlowCopy(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -583,19 +590,24 @@ void SlowCopy(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
// Assume caller has properly validated args.
|
||||
uint32_t FastCopy(Local<Value> receiver,
|
||||
@@ -107,7 +107,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
|
||||
return to_copy;
|
||||
}
|
||||
|
||||
@@ -865,19 +877,17 @@ void Compare(const FunctionCallbackInfo<Value> &args) {
|
||||
@@ -864,19 +876,17 @@ void Compare(const FunctionCallbackInfo<Value> &args) {
|
||||
}
|
||||
|
||||
int32_t FastCompare(v8::Local<v8::Value>,
|
||||
@@ -135,7 +135,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
|
||||
}
|
||||
|
||||
static v8::CFunction fast_compare(v8::CFunction::Make(FastCompare));
|
||||
@@ -1149,14 +1159,13 @@ void SlowIndexOfNumber(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -1148,14 +1158,13 @@ void SlowIndexOfNumber(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
|
||||
int32_t FastIndexOfNumber(v8::Local<v8::Value>,
|
||||
@@ -153,7 +153,7 @@ index e39852c8e0392e0a9ae5d4ea58be115416e19233..c94b14741c827a81d69a6f036426a344
|
||||
}
|
||||
|
||||
static v8::CFunction fast_index_of_number(
|
||||
@@ -1496,21 +1505,31 @@ void SlowWriteString(const FunctionCallbackInfo<Value>& args) {
|
||||
@@ -1510,21 +1519,31 @@ void SlowWriteString(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
template <encoding encoding>
|
||||
uint32_t FastWriteString(Local<Value> receiver,
|
||||
|
||||
@@ -9,10 +9,10 @@ This is already applied in newer versions of Node so we can drop
|
||||
this patch once we upgrade to v23.
|
||||
|
||||
diff --git a/src/api/environment.cc b/src/api/environment.cc
|
||||
index 0a358735c331767e8eb563a80e9aaccfb544c27b..d7d18d5fcbf008ac131db189a141315af4f6410b 100644
|
||||
index 79327877b0f34cbafb3efcc21617027d4011f806..8a12475857135bd3e904610b7eb887c397c8e73c 100644
|
||||
--- a/src/api/environment.cc
|
||||
+++ b/src/api/environment.cc
|
||||
@@ -835,7 +835,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
|
||||
@@ -827,7 +827,7 @@ MaybeLocal<Object> InitializePrivateSymbols(Local<Context> context,
|
||||
|
||||
Local<Object> private_symbols_object;
|
||||
if (!private_symbols->NewInstance(context).ToLocal(&private_symbols_object) ||
|
||||
@@ -21,7 +21,7 @@ index 0a358735c331767e8eb563a80e9aaccfb544c27b..d7d18d5fcbf008ac131db189a141315a
|
||||
.IsNothing()) {
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
@@ -861,7 +861,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
|
||||
@@ -853,7 +853,7 @@ MaybeLocal<Object> InitializePerIsolateSymbols(Local<Context> context,
|
||||
Local<Object> per_isolate_symbols_object;
|
||||
if (!per_isolate_symbols->NewInstance(context).ToLocal(
|
||||
&per_isolate_symbols_object) ||
|
||||
|
||||
@@ -18,10 +18,10 @@ This can be removed when Node.js upgrades to a version of V8 containing CLs
|
||||
from the above issue.
|
||||
|
||||
diff --git a/src/api/environment.cc b/src/api/environment.cc
|
||||
index cb37fa080fc8e8d524cfa2758c4a8c2c5652324d..8e227ddd1be50c046a8cf2895a31d607eb7d31de 100644
|
||||
index fd71ceac65ccef1d2832b45b0b5612877cee22c1..ceac508418f489a8077c1bc85a2feaf85bf60480 100644
|
||||
--- a/src/api/environment.cc
|
||||
+++ b/src/api/environment.cc
|
||||
@@ -316,6 +316,10 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
|
||||
@@ -308,6 +308,10 @@ Isolate* NewIsolate(Isolate::CreateParams* params,
|
||||
MultiIsolatePlatform* platform,
|
||||
const SnapshotData* snapshot_data,
|
||||
const IsolateSettings& settings) {
|
||||
@@ -32,7 +32,7 @@ index cb37fa080fc8e8d524cfa2758c4a8c2c5652324d..8e227ddd1be50c046a8cf2895a31d607
|
||||
Isolate* isolate = Isolate::Allocate();
|
||||
if (isolate == nullptr) return nullptr;
|
||||
|
||||
@@ -359,9 +363,12 @@ Isolate* NewIsolate(ArrayBufferAllocator* allocator,
|
||||
@@ -351,9 +355,12 @@ Isolate* NewIsolate(ArrayBufferAllocator* allocator,
|
||||
uv_loop_t* event_loop,
|
||||
MultiIsolatePlatform* platform,
|
||||
const EmbedderSnapshotData* snapshot_data,
|
||||
|
||||
653
patches/node/remove_obsolete_noarraybufferzerofillscope.patch
Normal file
653
patches/node/remove_obsolete_noarraybufferzerofillscope.patch
Normal file
@@ -0,0 +1,653 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Robo <hop2deep@gmail.com>
|
||||
Date: Wed, 21 Jan 2026 09:53:15 +0000
|
||||
Subject: remove obsolete NoArrayBufferZeroFillScope
|
||||
|
||||
Replace the scope in favor of the V8 api added in
|
||||
https://chromium-review.googlesource.com/c/v8/v8/+/5679067
|
||||
|
||||
Ports changes from
|
||||
1) https://github.com/nodejs/node/commit/869ea331f3a8215229290e2e6038956874c382a6
|
||||
2) https://github.com/nodejs/node/commit/ef9dc0857a73610f5de5dc9f37afd0a927c4c17f
|
||||
3) partially from https://github.com/nodejs/node/commit/e0a71517fef4ca83f2d40d2d1600022bc82a7f9f
|
||||
|
||||
This is needed to remove dependency on the zero_fill_field_
|
||||
that is exposed to JS
|
||||
Refs https://github.com/nodejs/node/commit/3cdb1cd437f63dd256ae2ab3b7e9016257326cb4
|
||||
|
||||
diff --git a/src/api/environment.cc b/src/api/environment.cc
|
||||
index 8a12475857135bd3e904610b7eb887c397c8e73c..3702672f4e2c3b1bff9f9f5c66ca251af447826a 100644
|
||||
--- a/src/api/environment.cc
|
||||
+++ b/src/api/environment.cc
|
||||
@@ -107,11 +107,7 @@ MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
|
||||
}
|
||||
|
||||
void* NodeArrayBufferAllocator::Allocate(size_t size) {
|
||||
- void* ret;
|
||||
- if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
|
||||
- ret = allocator_->Allocate(size);
|
||||
- else
|
||||
- ret = allocator_->AllocateUninitialized(size);
|
||||
+ void* ret = allocator_->Allocate(size);
|
||||
if (ret != nullptr) [[likely]] {
|
||||
total_mem_usage_.fetch_add(size, std::memory_order_relaxed);
|
||||
}
|
||||
diff --git a/src/crypto/crypto_cipher.cc b/src/crypto/crypto_cipher.cc
|
||||
index c00d3616e08b00b1e0a3a29b2dbb5278e1e14fcc..8939c5e5085d00b098f66074b9ee033f5be55d08 100644
|
||||
--- a/src/crypto/crypto_cipher.cc
|
||||
+++ b/src/crypto/crypto_cipher.cc
|
||||
@@ -20,6 +20,7 @@ using ncrypto::SSLPointer;
|
||||
using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Context;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::FunctionTemplate;
|
||||
@@ -774,10 +775,10 @@ CipherBase::UpdateResult CipherBase::Update(
|
||||
return kErrorState;
|
||||
}
|
||||
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
|
||||
- *out = ArrayBuffer::NewBackingStore(env()->isolate(), buf_len);
|
||||
- }
|
||||
+ *out = ArrayBuffer::NewBackingStore(
|
||||
+ env()->isolate(),
|
||||
+ buf_len,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
buffer = {
|
||||
.data = reinterpret_cast<const unsigned char*>(data),
|
||||
@@ -852,11 +853,10 @@ bool CipherBase::Final(std::unique_ptr<BackingStore>* out) {
|
||||
|
||||
const int mode = ctx_.getMode();
|
||||
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
|
||||
- *out = ArrayBuffer::NewBackingStore(
|
||||
- env()->isolate(), static_cast<size_t>(ctx_.getBlockSize()));
|
||||
- }
|
||||
+ *out = ArrayBuffer::NewBackingStore(
|
||||
+ env()->isolate(),
|
||||
+ static_cast<size_t>(ctx_.getBlockSize()),
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
if (kind_ == kDecipher &&
|
||||
Cipher::FromCtx(ctx_).isSupportedAuthenticatedMode()) {
|
||||
@@ -972,10 +972,10 @@ bool PublicKeyCipher::Cipher(
|
||||
return false;
|
||||
}
|
||||
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- *out = ArrayBuffer::NewBackingStore(env->isolate(), out_len);
|
||||
- }
|
||||
+ *out = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
+ out_len,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
if (EVP_PKEY_cipher(
|
||||
ctx.get(),
|
||||
diff --git a/src/crypto/crypto_common.cc b/src/crypto/crypto_common.cc
|
||||
index b81b9005365272217c77e2b9289bd9f877c0e77c..185b1d8fe657b5db64dc92497ca742d69f7a2764 100644
|
||||
--- a/src/crypto/crypto_common.cc
|
||||
+++ b/src/crypto/crypto_common.cc
|
||||
@@ -37,6 +37,7 @@ using ncrypto::X509Pointer;
|
||||
using ncrypto::X509View;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Context;
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::Integer;
|
||||
@@ -307,11 +308,10 @@ MaybeLocal<Object> ECPointToBuffer(Environment* env,
|
||||
return MaybeLocal<Object>();
|
||||
}
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), len);
|
||||
- }
|
||||
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
+ len,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
len = EC_POINT_point2oct(group,
|
||||
point,
|
||||
diff --git a/src/crypto/crypto_ec.cc b/src/crypto/crypto_ec.cc
|
||||
index 2a3107dbbf5c0dfe70c2e105338d186e5620ddbf..f36c84a77313bd57d0a7a902d1a2529636ca1422 100644
|
||||
--- a/src/crypto/crypto_ec.cc
|
||||
+++ b/src/crypto/crypto_ec.cc
|
||||
@@ -29,6 +29,7 @@ using ncrypto::MarkPopErrorOnReturn;
|
||||
using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Context;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::FunctionTemplate;
|
||||
@@ -201,14 +202,10 @@ void ECDH::ComputeSecret(const FunctionCallbackInfo<Value>& args) {
|
||||
return;
|
||||
}
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- // NOTE: field_size is in bits
|
||||
- int field_size = EC_GROUP_get_degree(ecdh->group_);
|
||||
- size_t out_len = (field_size + 7) / 8;
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), out_len);
|
||||
- }
|
||||
+ int field_size = EC_GROUP_get_degree(ecdh->group_);
|
||||
+ size_t out_len = (field_size + 7) / 8;
|
||||
+ auto bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), out_len, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
if (!ECDH_compute_key(
|
||||
bs->Data(), bs->ByteLength(), pub, ecdh->key_.get(), nullptr))
|
||||
@@ -257,12 +254,10 @@ void ECDH::GetPrivateKey(const FunctionCallbackInfo<Value>& args) {
|
||||
return THROW_ERR_CRYPTO_OPERATION_FAILED(env,
|
||||
"Failed to get ECDH private key");
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(),
|
||||
- BignumPointer::GetByteCount(b));
|
||||
- }
|
||||
+ auto bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
+ BignumPointer::GetByteCount(b),
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
CHECK_EQ(bs->ByteLength(),
|
||||
BignumPointer::EncodePaddedInto(
|
||||
b, static_cast<unsigned char*>(bs->Data()), bs->ByteLength()));
|
||||
diff --git a/src/crypto/crypto_rsa.cc b/src/crypto/crypto_rsa.cc
|
||||
index 1f2fccce6ed8f14525557644e0bdd130eedf3337..1099a8f89bb53083f01942ee14fff453d8cdc0af 100644
|
||||
--- a/src/crypto/crypto_rsa.cc
|
||||
+++ b/src/crypto/crypto_rsa.cc
|
||||
@@ -20,6 +20,7 @@ using ncrypto::EVPKeyPointer;
|
||||
using ncrypto::RSAPointer;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::Int32;
|
||||
using v8::JustVoid;
|
||||
@@ -535,12 +536,10 @@ Maybe<void> GetRsaKeyDetail(Environment* env,
|
||||
return Nothing<void>();
|
||||
}
|
||||
|
||||
- std::unique_ptr<BackingStore> public_exponent;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- public_exponent = ArrayBuffer::NewBackingStore(
|
||||
- env->isolate(), BignumPointer::GetByteCount(e));
|
||||
- }
|
||||
+ auto public_exponent = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
+ BignumPointer::GetByteCount(e),
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
CHECK_EQ(BignumPointer::EncodePaddedInto(
|
||||
e,
|
||||
static_cast<unsigned char*>(public_exponent->Data()),
|
||||
diff --git a/src/crypto/crypto_sig.cc b/src/crypto/crypto_sig.cc
|
||||
index 2f6e683e3497d4315259773d09443e5215bff28f..c33e93c34ef32c18e6de6bc03698ed6b851b4aa3 100644
|
||||
--- a/src/crypto/crypto_sig.cc
|
||||
+++ b/src/crypto/crypto_sig.cc
|
||||
@@ -21,6 +21,7 @@ using ncrypto::EVPKeyPointer;
|
||||
using ncrypto::EVPMDCtxPointer;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Boolean;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::FunctionTemplate;
|
||||
@@ -92,11 +93,8 @@ std::unique_ptr<BackingStore> Node_SignFinal(Environment* env,
|
||||
return nullptr;
|
||||
|
||||
size_t sig_len = pkey.size();
|
||||
- std::unique_ptr<BackingStore> sig;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- sig = ArrayBuffer::NewBackingStore(env->isolate(), sig_len);
|
||||
- }
|
||||
+ auto sig = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), sig_len, BackingStoreInitializationMode::kUninitialized);
|
||||
EVPKeyCtxPointer pkctx = pkey.newCtx();
|
||||
if (pkctx && EVP_PKEY_sign_init(pkctx.get()) > 0 &&
|
||||
ApplyRSAOptions(pkey, pkctx.get(), padding, pss_salt_len) &&
|
||||
@@ -168,11 +166,9 @@ std::unique_ptr<BackingStore> ConvertSignatureToP1363(
|
||||
if (n == kNoDsaSignature)
|
||||
return std::move(signature);
|
||||
|
||||
- std::unique_ptr<BackingStore> buf;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- buf = ArrayBuffer::NewBackingStore(env->isolate(), 2 * n);
|
||||
- }
|
||||
+ auto buf = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), 2 * n, BackingStoreInitializationMode::kUninitialized);
|
||||
+
|
||||
if (!ExtractP1363(static_cast<unsigned char*>(signature->Data()),
|
||||
static_cast<unsigned char*>(buf->Data()),
|
||||
signature->ByteLength(), n))
|
||||
diff --git a/src/crypto/crypto_tls.cc b/src/crypto/crypto_tls.cc
|
||||
index a80685790bd29102d99929ff81f866e0aee370f1..24336b86f6f25533a7b668e9f9806a5635e3a398 100644
|
||||
--- a/src/crypto/crypto_tls.cc
|
||||
+++ b/src/crypto/crypto_tls.cc
|
||||
@@ -46,6 +46,7 @@ using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::ArrayBufferView;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
using v8::DontDelete;
|
||||
@@ -1087,10 +1088,10 @@ int TLSWrap::DoWrite(WriteWrap* w,
|
||||
// and copying it when it could just be used.
|
||||
|
||||
if (nonempty_count != 1) {
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env()->isolate(), length);
|
||||
- }
|
||||
+ bs = ArrayBuffer::NewBackingStore(
|
||||
+ env()->isolate(),
|
||||
+ length,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
size_t offset = 0;
|
||||
for (i = 0; i < count; i++) {
|
||||
memcpy(static_cast<char*>(bs->Data()) + offset,
|
||||
@@ -1107,8 +1108,10 @@ int TLSWrap::DoWrite(WriteWrap* w,
|
||||
written = SSL_write(ssl_.get(), buf->base, buf->len);
|
||||
|
||||
if (written == -1) {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env()->isolate(), length);
|
||||
+ bs = ArrayBuffer::NewBackingStore(
|
||||
+ env()->isolate(),
|
||||
+ length,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
memcpy(bs->Data(), buf->base, buf->len);
|
||||
}
|
||||
}
|
||||
@@ -1746,11 +1749,8 @@ void TLSWrap::GetFinished(const FunctionCallbackInfo<Value>& args) {
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), len);
|
||||
- }
|
||||
+ auto bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), len, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
CHECK_EQ(bs->ByteLength(),
|
||||
SSL_get_finished(w->ssl_.get(), bs->Data(), bs->ByteLength()));
|
||||
@@ -1777,11 +1777,8 @@ void TLSWrap::GetPeerFinished(const FunctionCallbackInfo<Value>& args) {
|
||||
if (len == 0)
|
||||
return;
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), len);
|
||||
- }
|
||||
+ auto bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), len, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
CHECK_EQ(bs->ByteLength(),
|
||||
SSL_get_peer_finished(w->ssl_.get(), bs->Data(), bs->ByteLength()));
|
||||
@@ -1806,11 +1803,8 @@ void TLSWrap::GetSession(const FunctionCallbackInfo<Value>& args) {
|
||||
if (slen <= 0)
|
||||
return; // Invalid or malformed session.
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), slen);
|
||||
- }
|
||||
+ auto bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), slen, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
unsigned char* p = static_cast<unsigned char*>(bs->Data());
|
||||
CHECK_LT(0, i2d_SSL_SESSION(sess, &p));
|
||||
@@ -1993,11 +1987,8 @@ void TLSWrap::ExportKeyingMaterial(const FunctionCallbackInfo<Value>& args) {
|
||||
uint32_t olen = args[0].As<Uint32>()->Value();
|
||||
Utf8Value label(env->isolate(), args[1]);
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), olen);
|
||||
- }
|
||||
+ auto bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), olen, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
ByteSource context;
|
||||
bool use_context = !args[2]->IsUndefined();
|
||||
diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc
|
||||
index fd29d17de195017970856ce30d7a9c5785b0b8ee..7fe3d09851d4476fa3f77ea0a0b49e8af13fae4f 100644
|
||||
--- a/src/crypto/crypto_x509.cc
|
||||
+++ b/src/crypto/crypto_x509.cc
|
||||
@@ -28,6 +28,7 @@ using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::ArrayBufferView;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
using v8::Date;
|
||||
@@ -683,11 +684,8 @@ MaybeLocal<Object> GetPubKey(Environment* env, OSSL3_CONST RSA* rsa) {
|
||||
int size = i2d_RSA_PUBKEY(rsa, nullptr);
|
||||
CHECK_GE(size, 0);
|
||||
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), size);
|
||||
- }
|
||||
+ auto bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(), size, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
unsigned char* serialized = reinterpret_cast<unsigned char*>(bs->Data());
|
||||
CHECK_GE(i2d_RSA_PUBKEY(rsa, &serialized), 0);
|
||||
diff --git a/src/encoding_binding.cc b/src/encoding_binding.cc
|
||||
index 5ace688bb7ffc86eedf5aff11ab0ab487ad9440e..31058543a164bb45ba989e8c433994e0857a98b9 100644
|
||||
--- a/src/encoding_binding.cc
|
||||
+++ b/src/encoding_binding.cc
|
||||
@@ -15,6 +15,7 @@ namespace encoding_binding {
|
||||
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Context;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::Isolate;
|
||||
@@ -123,9 +124,8 @@ void BindingData::EncodeUtf8String(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
Local<ArrayBuffer> ab;
|
||||
{
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- std::unique_ptr<BackingStore> bs =
|
||||
- ArrayBuffer::NewBackingStore(isolate, length);
|
||||
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
|
||||
+ isolate, length, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
CHECK(bs);
|
||||
|
||||
diff --git a/src/env-inl.h b/src/env-inl.h
|
||||
index 98e1e1e75bae94038bba0049447ab48b0acfb8cc..fe395bf89f9c1e5bb2dabc8fceda7b9b2b877415 100644
|
||||
--- a/src/env-inl.h
|
||||
+++ b/src/env-inl.h
|
||||
@@ -44,16 +44,6 @@
|
||||
|
||||
namespace node {
|
||||
|
||||
-NoArrayBufferZeroFillScope::NoArrayBufferZeroFillScope(
|
||||
- IsolateData* isolate_data)
|
||||
- : node_allocator_(isolate_data->node_allocator()) {
|
||||
- if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 0;
|
||||
-}
|
||||
-
|
||||
-NoArrayBufferZeroFillScope::~NoArrayBufferZeroFillScope() {
|
||||
- if (node_allocator_ != nullptr) node_allocator_->zero_fill_field()[0] = 1;
|
||||
-}
|
||||
-
|
||||
inline v8::Isolate* IsolateData::isolate() const {
|
||||
return isolate_;
|
||||
}
|
||||
diff --git a/src/env.cc b/src/env.cc
|
||||
index 161d577e0ea6a251c83ba1903b1ec9a582a5317c..52713421465c23c959afac1955168294b2048341 100644
|
||||
--- a/src/env.cc
|
||||
+++ b/src/env.cc
|
||||
@@ -39,6 +39,9 @@ namespace node {
|
||||
|
||||
using errors::TryCatchScope;
|
||||
using v8::Array;
|
||||
+using v8::ArrayBuffer;
|
||||
+using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
using v8::CppHeap;
|
||||
@@ -724,9 +727,10 @@ void Environment::add_refs(int64_t diff) {
|
||||
}
|
||||
|
||||
uv_buf_t Environment::allocate_managed_buffer(const size_t suggested_size) {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(isolate_data());
|
||||
- std::unique_ptr<v8::BackingStore> bs =
|
||||
- v8::ArrayBuffer::NewBackingStore(isolate(), suggested_size);
|
||||
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
|
||||
+ isolate(),
|
||||
+ suggested_size,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
uv_buf_t buf = uv_buf_init(static_cast<char*>(bs->Data()), bs->ByteLength());
|
||||
released_allocated_buffers_.emplace(buf.base, std::move(bs));
|
||||
return buf;
|
||||
diff --git a/src/env.h b/src/env.h
|
||||
index c346e3a9c827993036438685d758a734f9ce8c05..28c8df87c8e2f06e2ed8c554260bfdedb860bb4a 100644
|
||||
--- a/src/env.h
|
||||
+++ b/src/env.h
|
||||
@@ -114,19 +114,6 @@ class ModuleWrap;
|
||||
class Environment;
|
||||
class Realm;
|
||||
|
||||
-// Disables zero-filling for ArrayBuffer allocations in this scope. This is
|
||||
-// similar to how we implement Buffer.allocUnsafe() in JS land.
|
||||
-class NoArrayBufferZeroFillScope {
|
||||
- public:
|
||||
- inline explicit NoArrayBufferZeroFillScope(IsolateData* isolate_data);
|
||||
- inline ~NoArrayBufferZeroFillScope();
|
||||
-
|
||||
- private:
|
||||
- NodeArrayBufferAllocator* node_allocator_;
|
||||
-
|
||||
- friend class Environment;
|
||||
-};
|
||||
-
|
||||
struct IsolateDataSerializeInfo {
|
||||
std::vector<SnapshotIndex> primitive_values;
|
||||
std::vector<PropInfo> template_values;
|
||||
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
|
||||
index e844fe6cb33acefd075516e675075421ad5c3cff..06c84eb6ec097e3cb39502116135a7802aed13ce 100644
|
||||
--- a/src/node_buffer.cc
|
||||
+++ b/src/node_buffer.cc
|
||||
@@ -66,6 +66,7 @@ namespace Buffer {
|
||||
using v8::ArrayBuffer;
|
||||
using v8::ArrayBufferView;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Context;
|
||||
using v8::EscapableHandleScope;
|
||||
using v8::FunctionCallbackInfo;
|
||||
@@ -376,9 +377,8 @@ MaybeLocal<Object> New(Environment* env, size_t length) {
|
||||
|
||||
Local<ArrayBuffer> ab;
|
||||
{
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- std::unique_ptr<BackingStore> bs =
|
||||
- ArrayBuffer::NewBackingStore(isolate, length);
|
||||
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
|
||||
+ isolate, length, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
CHECK(bs);
|
||||
|
||||
@@ -417,18 +417,14 @@ MaybeLocal<Object> Copy(Environment* env, const char* data, size_t length) {
|
||||
return Local<Object>();
|
||||
}
|
||||
|
||||
- Local<ArrayBuffer> ab;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- std::unique_ptr<BackingStore> bs =
|
||||
- ArrayBuffer::NewBackingStore(isolate, length);
|
||||
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
|
||||
+ isolate, length, BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
- CHECK(bs);
|
||||
+ CHECK(bs);
|
||||
|
||||
- memcpy(bs->Data(), data, length);
|
||||
+ memcpy(bs->Data(), data, length);
|
||||
|
||||
- ab = ArrayBuffer::New(isolate, std::move(bs));
|
||||
- }
|
||||
+ Local<ArrayBuffer> ab = ArrayBuffer::New(isolate, std::move(bs));
|
||||
|
||||
MaybeLocal<Object> obj =
|
||||
New(env, ab, 0, ab->ByteLength())
|
||||
@@ -1440,14 +1436,16 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
Local<ArrayBuffer> buf;
|
||||
|
||||
- NodeArrayBufferAllocator* allocator = env->isolate_data()->node_allocator();
|
||||
// 0-length, or zero-fill flag is set, or building snapshot
|
||||
if (size == 0 || per_process::cli_options->zero_fill_all_buffers ||
|
||||
- allocator == nullptr) {
|
||||
+ env->isolate_data()->is_building_snapshot()) {
|
||||
buf = ArrayBuffer::New(isolate, size);
|
||||
} else {
|
||||
- std::unique_ptr<BackingStore> store =
|
||||
- ArrayBuffer::NewBackingStore(isolate, size);
|
||||
+ std::unique_ptr<BackingStore> store = ArrayBuffer::NewBackingStore(
|
||||
+ isolate,
|
||||
+ size,
|
||||
+ BackingStoreInitializationMode::kUninitialized,
|
||||
+ v8::BackingStoreOnFailureMode::kReturnNull);
|
||||
if (!store) {
|
||||
return env->ThrowRangeError("Array buffer allocation failed");
|
||||
}
|
||||
diff --git a/src/node_http2.cc b/src/node_http2.cc
|
||||
index 8237c9b7d325dd925ae8798d7795fcd94eeb13d0..a22cf6c4e33e5cf2d3168ce03dc35af8a9584af7 100644
|
||||
--- a/src/node_http2.cc
|
||||
+++ b/src/node_http2.cc
|
||||
@@ -27,6 +27,7 @@ using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::ArrayBufferView;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
using v8::EscapableHandleScope;
|
||||
@@ -298,11 +299,10 @@ Local<Value> Http2Settings::Pack(
|
||||
size_t count,
|
||||
const nghttp2_settings_entry* entries) {
|
||||
EscapableHandleScope scope(env->isolate());
|
||||
- std::unique_ptr<BackingStore> bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(env->isolate(), count * 6);
|
||||
- }
|
||||
+ std::unique_ptr<BackingStore> bs = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
+ count * 6,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
if (nghttp2_pack_settings_payload(static_cast<uint8_t*>(bs->Data()),
|
||||
bs->ByteLength(),
|
||||
entries,
|
||||
@@ -468,13 +468,11 @@ Origins::Origins(
|
||||
return;
|
||||
}
|
||||
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs_ = ArrayBuffer::NewBackingStore(env->isolate(),
|
||||
- alignof(nghttp2_origin_entry) - 1 +
|
||||
- count_ * sizeof(nghttp2_origin_entry) +
|
||||
- origin_string_len);
|
||||
- }
|
||||
+ bs_ = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
+ alignof(nghttp2_origin_entry) - 1 +
|
||||
+ count_ * sizeof(nghttp2_origin_entry) + origin_string_len,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
|
||||
// Make sure the start address is aligned appropriately for an nghttp2_nv*.
|
||||
char* start = nbytes::AlignUp(static_cast<char*>(bs_->Data()),
|
||||
@@ -2120,12 +2118,10 @@ void Http2Session::OnStreamRead(ssize_t nread, const uv_buf_t& buf_) {
|
||||
// happen, we concatenate the data we received with the already-stored
|
||||
// pending input data, slicing off the already processed part.
|
||||
size_t pending_len = stream_buf_.len - stream_buf_offset_;
|
||||
- std::unique_ptr<BackingStore> new_bs;
|
||||
- {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env()->isolate_data());
|
||||
- new_bs = ArrayBuffer::NewBackingStore(env()->isolate(),
|
||||
- pending_len + nread);
|
||||
- }
|
||||
+ std::unique_ptr<BackingStore> new_bs = ArrayBuffer::NewBackingStore(
|
||||
+ env()->isolate(),
|
||||
+ pending_len + nread,
|
||||
+ BackingStoreInitializationMode::kUninitialized);
|
||||
memcpy(static_cast<char*>(new_bs->Data()),
|
||||
stream_buf_.base + stream_buf_offset_,
|
||||
pending_len);
|
||||
diff --git a/src/node_internals.h b/src/node_internals.h
|
||||
index 12ea72b61b0a5e194207bb369dfed4b8667107cb..18844e18a32d6b07e62481138fa2342765643484 100644
|
||||
--- a/src/node_internals.h
|
||||
+++ b/src/node_internals.h
|
||||
@@ -121,8 +121,6 @@ v8::MaybeLocal<v8::Object> InitializePrivateSymbols(
|
||||
|
||||
class NodeArrayBufferAllocator : public ArrayBufferAllocator {
|
||||
public:
|
||||
- inline uint32_t* zero_fill_field() { return &zero_fill_field_; }
|
||||
-
|
||||
void* Allocate(size_t size) override; // Defined in src/node.cc
|
||||
void* AllocateUninitialized(size_t size) override;
|
||||
void Free(void* data, size_t size) override;
|
||||
@@ -139,7 +137,6 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator {
|
||||
}
|
||||
|
||||
private:
|
||||
- uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land.
|
||||
std::atomic<size_t> total_mem_usage_ {0};
|
||||
|
||||
// Delegate to V8's allocator for compatibility with the V8 memory cage.
|
||||
diff --git a/src/stream_base.cc b/src/stream_base.cc
|
||||
index fc81108120f0066f2d5dabedc74e22cb6c84d8e4..0bf2642599ee91e2d2aa20d6d066c80b3026ecc5 100644
|
||||
--- a/src/stream_base.cc
|
||||
+++ b/src/stream_base.cc
|
||||
@@ -19,6 +19,7 @@ namespace node {
|
||||
using v8::Array;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
using v8::ConstructorBehavior;
|
||||
using v8::Context;
|
||||
using v8::DontDelete;
|
||||
@@ -243,8 +244,8 @@ int StreamBase::Writev(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
std::unique_ptr<BackingStore> bs;
|
||||
if (storage_size > 0) {
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(isolate, storage_size);
|
||||
+ bs = ArrayBuffer::NewBackingStore(
|
||||
+ isolate, storage_size, BackingStoreInitializationMode::kUninitialized);
|
||||
}
|
||||
|
||||
offset = 0;
|
||||
@@ -398,14 +399,14 @@ int StreamBase::WriteString(const FunctionCallbackInfo<Value>& args) {
|
||||
|
||||
if (try_write) {
|
||||
// Copy partial data
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(isolate, buf.len);
|
||||
+ bs = ArrayBuffer::NewBackingStore(
|
||||
+ isolate, buf.len, BackingStoreInitializationMode::kUninitialized);
|
||||
memcpy(bs->Data(), buf.base, buf.len);
|
||||
data_size = buf.len;
|
||||
} else {
|
||||
// Write it
|
||||
- NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
- bs = ArrayBuffer::NewBackingStore(isolate, storage_size);
|
||||
+ bs = ArrayBuffer::NewBackingStore(
|
||||
+ isolate, storage_size, BackingStoreInitializationMode::kUninitialized);
|
||||
data_size = StringBytes::Write(isolate,
|
||||
static_cast<char*>(bs->Data()),
|
||||
storage_size,
|
||||
@@ -0,0 +1,74 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Joyee Cheung <joyeec9h3@gmail.com>
|
||||
Date: Thu, 20 Nov 2025 13:50:28 +0900
|
||||
Subject: src: handle DER decoding errors from system certificates
|
||||
|
||||
When decoding certificates from the system store, it's not actually
|
||||
guaranteed to succeed. In case the system returns a certificate
|
||||
that cannot be decoded (might be related to SSL implementation issues),
|
||||
skip them.
|
||||
|
||||
diff --git a/src/crypto/crypto_context.cc b/src/crypto/crypto_context.cc
|
||||
index 0c6b12f8e17b4a7e86ebc836a4e1cc77333f211a..dacf10c3c2e663b03a251c86d69276d0be0dff9d 100644
|
||||
--- a/src/crypto/crypto_context.cc
|
||||
+++ b/src/crypto/crypto_context.cc
|
||||
@@ -505,7 +505,11 @@ void ReadMacOSKeychainCertificates(
|
||||
CFRelease(search);
|
||||
|
||||
if (ortn) {
|
||||
- fprintf(stderr, "ERROR: SecItemCopyMatching failed %d\n", ortn);
|
||||
+ per_process::Debug(DebugCategory::CRYPTO,
|
||||
+ "Cannot read certificates from system because "
|
||||
+ "SecItemCopyMatching failed %d\n",
|
||||
+ ortn);
|
||||
+ return;
|
||||
}
|
||||
|
||||
CFIndex count = CFArrayGetCount(curr_anchors);
|
||||
@@ -516,7 +520,9 @@ void ReadMacOSKeychainCertificates(
|
||||
|
||||
CFDataRef der_data = SecCertificateCopyData(cert_ref);
|
||||
if (!der_data) {
|
||||
- fprintf(stderr, "ERROR: SecCertificateCopyData failed\n");
|
||||
+ per_process::Debug(DebugCategory::CRYPTO,
|
||||
+ "Skipping read of a system certificate "
|
||||
+ "because SecCertificateCopyData failed\n");
|
||||
continue;
|
||||
}
|
||||
auto data_buffer_pointer = CFDataGetBytePtr(der_data);
|
||||
@@ -524,9 +530,19 @@ void ReadMacOSKeychainCertificates(
|
||||
X509* cert =
|
||||
d2i_X509(nullptr, &data_buffer_pointer, CFDataGetLength(der_data));
|
||||
CFRelease(der_data);
|
||||
+
|
||||
+ if (cert == nullptr) {
|
||||
+ per_process::Debug(DebugCategory::CRYPTO,
|
||||
+ "Skipping read of a system certificate "
|
||||
+ "because decoding failed\n");
|
||||
+ continue;
|
||||
+ }
|
||||
+
|
||||
bool is_valid = IsCertificateTrustedForPolicy(cert, cert_ref);
|
||||
if (is_valid) {
|
||||
system_root_certificates_X509->emplace_back(cert);
|
||||
+ } else {
|
||||
+ X509_free(cert);
|
||||
}
|
||||
}
|
||||
CFRelease(curr_anchors);
|
||||
@@ -636,7 +652,14 @@ void GatherCertsForLocation(std::vector<X509*>* vector,
|
||||
reinterpret_cast<const unsigned char*>(cert_from_store->pbCertEncoded);
|
||||
const size_t cert_size = cert_from_store->cbCertEncoded;
|
||||
|
||||
- vector->emplace_back(d2i_X509(nullptr, &cert_data, cert_size));
|
||||
+ X509* x509 = d2i_X509(nullptr, &cert_data, cert_size);
|
||||
+ if (x509 == nullptr) {
|
||||
+ per_process::Debug(DebugCategory::CRYPTO,
|
||||
+ "Skipping read of a system certificate "
|
||||
+ "because decoding failed\n");
|
||||
+ } else {
|
||||
+ vector->emplace_back(x509);
|
||||
+ }
|
||||
}
|
||||
}
|
||||
|
||||
276
patches/node/src_prepare_for_v8_sandboxing.patch
Normal file
276
patches/node/src_prepare_for_v8_sandboxing.patch
Normal file
@@ -0,0 +1,276 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: James M Snell <jasnell@gmail.com>
|
||||
Date: Sun, 18 May 2025 10:46:30 -0700
|
||||
Subject: src: prepare for v8 sandboxing
|
||||
|
||||
PR-URL: https://github.com/nodejs/node/pull/58376
|
||||
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
|
||||
Reviewed-By: Chengzhong Wu <legendecas@gmail.com>
|
||||
|
||||
diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc
|
||||
index f23cedf4f2449d8edc9a8de1b70332e75d693cdd..5ac2b1a83688fe99b13c37bf375ca6e22497dc18 100644
|
||||
--- a/src/crypto/crypto_dh.cc
|
||||
+++ b/src/crypto/crypto_dh.cc
|
||||
@@ -22,6 +22,8 @@ using ncrypto::DHPointer;
|
||||
using ncrypto::EVPKeyCtxPointer;
|
||||
using ncrypto::EVPKeyPointer;
|
||||
using v8::ArrayBuffer;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
+using v8::BackingStoreOnFailureMode;
|
||||
using v8::ConstructorBehavior;
|
||||
using v8::Context;
|
||||
using v8::DontDelete;
|
||||
@@ -55,12 +57,27 @@ void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const {
|
||||
|
||||
namespace {
|
||||
MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
|
||||
+#ifdef V8_ENABLE_SANDBOX
|
||||
+ auto backing = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
+ data.size(),
|
||||
+ BackingStoreInitializationMode::kUninitialized,
|
||||
+ BackingStoreOnFailureMode::kReturnNull);
|
||||
+ if (!backing) {
|
||||
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
|
||||
+ return MaybeLocal<Value>();
|
||||
+ }
|
||||
+ if (data.size() > 0) {
|
||||
+ memcpy(backing->Data(), data.get(), data.size());
|
||||
+ }
|
||||
+#else
|
||||
auto backing = ArrayBuffer::NewBackingStore(
|
||||
data.get(),
|
||||
data.size(),
|
||||
[](void* data, size_t len, void* ptr) { DataPointer free_me(data, len); },
|
||||
nullptr);
|
||||
data.release();
|
||||
+#endif // V8_ENABLE_SANDBOX
|
||||
|
||||
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
|
||||
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>());
|
||||
diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc
|
||||
index eab18ab9888e2f7c0757fefab80505d8c99dc742..7ecf810ea0f4106c7c44593dae1b0a3cf0673380 100644
|
||||
--- a/src/crypto/crypto_util.cc
|
||||
+++ b/src/crypto/crypto_util.cc
|
||||
@@ -37,6 +37,8 @@ using ncrypto::SSLCtxPointer;
|
||||
using ncrypto::SSLPointer;
|
||||
using v8::ArrayBuffer;
|
||||
using v8::BackingStore;
|
||||
+using v8::BackingStoreInitializationMode;
|
||||
+using v8::BackingStoreOnFailureMode;
|
||||
using v8::BigInt;
|
||||
using v8::Context;
|
||||
using v8::Exception;
|
||||
@@ -359,34 +361,29 @@ ByteSource& ByteSource::operator=(ByteSource&& other) noexcept {
|
||||
return *this;
|
||||
}
|
||||
|
||||
-std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(Environment* env) {
|
||||
+std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(
|
||||
+ Environment* env) {
|
||||
// It's ok for allocated_data_ to be nullptr but
|
||||
// only if size_ is zero.
|
||||
CHECK_IMPLIES(size_ > 0, allocated_data_ != nullptr);
|
||||
-#if defined(V8_ENABLE_SANDBOX)
|
||||
- // When V8 sandboxed pointers are enabled, we have to copy into the memory
|
||||
- // cage. We still want to ensure we erase the data on free though, so
|
||||
- // provide a custom deleter that calls OPENSSL_cleanse.
|
||||
- if (!size())
|
||||
- return ArrayBuffer::NewBackingStore(env->isolate(), 0);
|
||||
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
|
||||
- void* v8_data = allocator->Allocate(size());
|
||||
- CHECK(v8_data);
|
||||
- memcpy(v8_data, allocated_data_, size());
|
||||
- OPENSSL_clear_free(allocated_data_, size());
|
||||
+#ifdef V8_ENABLE_SANDBOX
|
||||
+ // If the v8 sandbox is enabled, then all array buffers must be allocated
|
||||
+ // via the isolate. External buffers are not allowed. So, instead of wrapping
|
||||
+ // the allocated data we'll copy it instead.
|
||||
+
|
||||
+ // TODO(@jasnell): It would be nice to use an abstracted utility to do this
|
||||
+ // branch instead of duplicating the V8_ENABLE_SANDBOX check each time.
|
||||
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
|
||||
- v8_data,
|
||||
+ env->isolate(),
|
||||
size(),
|
||||
- [](void* data, size_t length, void*) {
|
||||
- OPENSSL_cleanse(data, length);
|
||||
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
|
||||
- allocator->Free(data, length);
|
||||
- }, nullptr);
|
||||
- CHECK(ptr);
|
||||
- allocated_data_ = nullptr;
|
||||
- data_ = nullptr;
|
||||
- size_ = 0;
|
||||
- return ptr;
|
||||
+ BackingStoreInitializationMode::kUninitialized,
|
||||
+ BackingStoreOnFailureMode::kReturnNull);
|
||||
+ if (!ptr) {
|
||||
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
|
||||
+ return nullptr;
|
||||
+ }
|
||||
+ memcpy(ptr->Data(), allocated_data_, size());
|
||||
+ OPENSSL_clear_free(allocated_data_, size_);
|
||||
#else
|
||||
std::unique_ptr<BackingStore> ptr = ArrayBuffer::NewBackingStore(
|
||||
allocated_data_,
|
||||
@@ -394,12 +391,12 @@ std::unique_ptr<BackingStore> ByteSource::ReleaseToBackingStore(Environment* env
|
||||
[](void* data, size_t length, void* deleter_data) {
|
||||
OPENSSL_clear_free(deleter_data, length);
|
||||
}, allocated_data_);
|
||||
+#endif // V8_ENABLE_SANDBOX
|
||||
CHECK(ptr);
|
||||
allocated_data_ = nullptr;
|
||||
data_ = nullptr;
|
||||
size_ = 0;
|
||||
return ptr;
|
||||
-#endif // defined(V8_ENABLE_SANDBOX)
|
||||
}
|
||||
|
||||
Local<ArrayBuffer> ByteSource::ToArrayBuffer(Environment* env) {
|
||||
@@ -711,8 +708,19 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
|
||||
}
|
||||
#else
|
||||
void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
|
||||
- CHECK(args[0]->IsUint32());
|
||||
Environment* env = Environment::GetCurrent(args);
|
||||
+#ifdef V8_ENABLE_SANDBOX
|
||||
+ // The v8 sandbox is enabled, so we cannot use the secure heap because
|
||||
+ // the sandbox requires that all array buffers be allocated via the isolate.
|
||||
+ // That is fundamentally incompatible with the secure heap which allocates
|
||||
+ // in openssl's secure heap area. Instead we'll just throw an error here.
|
||||
+ //
|
||||
+ // That said, we really shouldn't get here in the first place since the
|
||||
+ // option to enable the secure heap is only available when the sandbox
|
||||
+ // is disabled.
|
||||
+ UNREACHABLE();
|
||||
+#else
|
||||
+ CHECK(args[0]->IsUint32());
|
||||
uint32_t len = args[0].As<Uint32>()->Value();
|
||||
void* data = OPENSSL_malloc(len);
|
||||
if (data == nullptr) {
|
||||
@@ -730,6 +738,7 @@ void SecureBuffer(const FunctionCallbackInfo<Value>& args) {
|
||||
data);
|
||||
Local<ArrayBuffer> buffer = ArrayBuffer::New(env->isolate(), store);
|
||||
args.GetReturnValue().Set(Uint8Array::New(buffer, 0, len));
|
||||
+#endif // V8_ENABLE_SANDBOX
|
||||
}
|
||||
#endif // defined(V8_ENABLE_SANDBOX)
|
||||
|
||||
diff --git a/src/crypto/crypto_x509.cc b/src/crypto/crypto_x509.cc
|
||||
index 7fe3d09851d4476fa3f77ea0a0b49e8af13fae4f..b60ad6b1cdb0b923ba2de1b7205ed5ce07612b81 100644
|
||||
--- a/src/crypto/crypto_x509.cc
|
||||
+++ b/src/crypto/crypto_x509.cc
|
||||
@@ -29,6 +29,7 @@ using v8::ArrayBuffer;
|
||||
using v8::ArrayBufferView;
|
||||
using v8::BackingStore;
|
||||
using v8::BackingStoreInitializationMode;
|
||||
+using v8::BackingStoreOnFailureMode;
|
||||
using v8::Boolean;
|
||||
using v8::Context;
|
||||
using v8::Date;
|
||||
@@ -168,18 +169,20 @@ MaybeLocal<Value> ToV8Value(Local<Context> context, const BIOPointer& bio) {
|
||||
MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
|
||||
if (bio == nullptr || !*bio) return {};
|
||||
BUF_MEM* mem = *bio;
|
||||
-#if defined(V8_ENABLE_SANDBOX)
|
||||
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
|
||||
- void* v8_data = allocator->Allocate(mem->length);
|
||||
- CHECK(v8_data);
|
||||
- memcpy(v8_data, mem->data, mem->length);
|
||||
- std::unique_ptr<v8::BackingStore> backing = ArrayBuffer::NewBackingStore(
|
||||
- v8_data,
|
||||
+#ifdef V8_ENABLE_SANDBOX
|
||||
+ // If the v8 sandbox is enabled, then all array buffers must be allocated
|
||||
+ // via the isolate. External buffers are not allowed. So, instead of wrapping
|
||||
+ // the BIOPointer we'll copy it instead.
|
||||
+ auto backing = ArrayBuffer::NewBackingStore(
|
||||
+ env->isolate(),
|
||||
mem->length,
|
||||
- [](void* data, size_t length, void*) {
|
||||
- std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
|
||||
- allocator->Free(data, length);
|
||||
- }, nullptr);
|
||||
+ BackingStoreInitializationMode::kUninitialized,
|
||||
+ BackingStoreOnFailureMode::kReturnNull);
|
||||
+ if (!backing) {
|
||||
+ THROW_ERR_MEMORY_ALLOCATION_FAILED(env);
|
||||
+ return MaybeLocal<Value>();
|
||||
+ }
|
||||
+ memcpy(backing->Data(), mem->data, mem->length);
|
||||
#else
|
||||
auto backing = ArrayBuffer::NewBackingStore(
|
||||
mem->data,
|
||||
@@ -188,8 +191,7 @@ MaybeLocal<Value> ToBuffer(Environment* env, BIOPointer* bio) {
|
||||
BIOPointer free_me(static_cast<BIO*>(data));
|
||||
},
|
||||
bio->release());
|
||||
-#endif
|
||||
-
|
||||
+#endif // V8_ENABLE_SANDBOX
|
||||
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
|
||||
Local<Value> ret;
|
||||
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {};
|
||||
diff --git a/src/js_native_api_v8.cc b/src/js_native_api_v8.cc
|
||||
index 413db3ed9b88d7b7fb2ac6dd1153dade9ff830fd..6da93b8569a34841e846c320ec0a6ca7f1ea0da6 100644
|
||||
--- a/src/js_native_api_v8.cc
|
||||
+++ b/src/js_native_api_v8.cc
|
||||
@@ -114,7 +114,7 @@ napi_status NewExternalString(napi_env env,
|
||||
CHECK_NEW_STRING_ARGS(env, str, length, result);
|
||||
|
||||
napi_status status;
|
||||
-#if defined(V8_ENABLE_SANDBOX)
|
||||
+#ifdef V8_ENABLE_SANDBOX
|
||||
status = create_api(env, str, length, result);
|
||||
if (status == napi_ok) {
|
||||
if (copied != nullptr) {
|
||||
diff --git a/src/node_api.cc b/src/node_api.cc
|
||||
index 2769997f0ede0e921fcb8826942609e497e5f9cb..d9b17780f6143f1c3f8488a20144376963e43fbc 100644
|
||||
--- a/src/node_api.cc
|
||||
+++ b/src/node_api.cc
|
||||
@@ -1056,7 +1056,7 @@ napi_create_external_buffer(napi_env env,
|
||||
NAPI_PREAMBLE(env);
|
||||
CHECK_ARG(env, result);
|
||||
|
||||
-#if defined(V8_ENABLE_SANDBOX)
|
||||
+#ifdef V8_ENABLE_SANDBOX
|
||||
return napi_set_last_error(env, napi_no_external_buffers_allowed);
|
||||
#else
|
||||
v8::Isolate* isolate = env->isolate;
|
||||
diff --git a/src/node_options.cc b/src/node_options.cc
|
||||
index e93e8684e518b30a2514768a269be6d32d1f5b94..547cda780376f578b0f78eb9158dc14a3faf874d 100644
|
||||
--- a/src/node_options.cc
|
||||
+++ b/src/node_options.cc
|
||||
@@ -83,6 +83,8 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors,
|
||||
}
|
||||
|
||||
// Any value less than 2 disables use of the secure heap.
|
||||
+#ifndef V8_ENABLE_SANDBOX
|
||||
+ // The secure heap is not supported when V8_ENABLE_SANDBOX is enabled.
|
||||
if (secure_heap >= 2) {
|
||||
if ((secure_heap & (secure_heap - 1)) != 0)
|
||||
errors->push_back("--secure-heap must be a power of 2");
|
||||
@@ -95,6 +97,7 @@ void PerProcessOptions::CheckOptions(std::vector<std::string>* errors,
|
||||
if ((secure_heap_min & (secure_heap_min - 1)) != 0)
|
||||
errors->push_back("--secure-heap-min must be a power of 2");
|
||||
}
|
||||
+#endif // V8_ENABLE_SANDBOX
|
||||
#endif // HAVE_OPENSSL
|
||||
|
||||
if (use_largepages != "off" &&
|
||||
@@ -1243,6 +1246,7 @@ PerProcessOptionsParser::PerProcessOptionsParser(
|
||||
"force FIPS crypto (cannot be disabled)",
|
||||
&PerProcessOptions::force_fips_crypto,
|
||||
kAllowedInEnvvar);
|
||||
+#ifndef V8_ENABLE_SANDBOX
|
||||
AddOption("--secure-heap",
|
||||
"total size of the OpenSSL secure heap",
|
||||
&PerProcessOptions::secure_heap,
|
||||
@@ -1251,6 +1255,7 @@ PerProcessOptionsParser::PerProcessOptionsParser(
|
||||
"minimum allocation size from the OpenSSL secure heap",
|
||||
&PerProcessOptions::secure_heap_min,
|
||||
kAllowedInEnvvar);
|
||||
+#endif // V8_ENABLE_SANDBOX
|
||||
#endif // HAVE_OPENSSL
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
AddOption("--openssl-legacy-provider",
|
||||
@@ -10,10 +10,10 @@ Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
|
||||
Reviewed-By: James M Snell <jasnell@gmail.com>
|
||||
|
||||
diff --git a/src/api/environment.cc b/src/api/environment.cc
|
||||
index 82f53bba29613de212f64be440ca20d7c630fddf..0a358735c331767e8eb563a80e9aaccfb544c27b 100644
|
||||
index 33827edce63c9fe08b52aea59571391a83853443..79327877b0f34cbafb3efcc21617027d4011f806 100644
|
||||
--- a/src/api/environment.cc
|
||||
+++ b/src/api/environment.cc
|
||||
@@ -886,7 +886,7 @@ Maybe<void> InitializePrimordials(Local<Context> context,
|
||||
@@ -878,7 +878,7 @@ Maybe<void> InitializePrimordials(Local<Context> context,
|
||||
CHECK(!exports->Has(context, primordials_string).FromJust());
|
||||
|
||||
Local<Object> primordials = Object::New(isolate);
|
||||
@@ -51,10 +51,10 @@ index 7b2efa49468c0bed2f5935552addd3ab37d0a50b..413db3ed9b88d7b7fb2ac6dd1153dade
|
||||
return GET_RETURN_STATUS(env);
|
||||
}
|
||||
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
|
||||
index c94b14741c827a81d69a6f036426a344e563ad72..15129e4455fdc8792f21511a04d534ba3a4ebb5f 100644
|
||||
index 8a5b6b57321c2843a965a7e51b2ebed991a1e424..e844fe6cb33acefd075516e675075421ad5c3cff 100644
|
||||
--- a/src/node_buffer.cc
|
||||
+++ b/src/node_buffer.cc
|
||||
@@ -284,8 +284,9 @@ MaybeLocal<Uint8Array> New(Environment* env,
|
||||
@@ -283,8 +283,9 @@ MaybeLocal<Uint8Array> New(Environment* env,
|
||||
size_t length) {
|
||||
CHECK(!env->buffer_prototype_object().IsEmpty());
|
||||
Local<Uint8Array> ui = Uint8Array::New(ab, byte_offset, length);
|
||||
|
||||
@@ -6,63 +6,6 @@ Subject: support V8 sandboxed pointers
|
||||
This refactors several allocators to allocate within the V8 memory cage,
|
||||
allowing them to be compatible with the V8_SANDBOXED_POINTERS feature.
|
||||
|
||||
diff --git a/src/api/environment.cc b/src/api/environment.cc
|
||||
index fd71ceac65ccef1d2832b45b0b5612877cee22c1..cb37fa080fc8e8d524cfa2758c4a8c2c5652324d 100644
|
||||
--- a/src/api/environment.cc
|
||||
+++ b/src/api/environment.cc
|
||||
@@ -106,6 +106,14 @@ MaybeLocal<Value> PrepareStackTraceCallback(Local<Context> context,
|
||||
return result;
|
||||
}
|
||||
|
||||
+NodeArrayBufferAllocator::NodeArrayBufferAllocator() {
|
||||
+ zero_fill_field_ = static_cast<uint32_t*>(allocator_->Allocate(sizeof(*zero_fill_field_)));
|
||||
+}
|
||||
+
|
||||
+NodeArrayBufferAllocator::~NodeArrayBufferAllocator() {
|
||||
+ allocator_->Free(zero_fill_field_, sizeof(*zero_fill_field_));
|
||||
+}
|
||||
+
|
||||
void* NodeArrayBufferAllocator::Allocate(size_t size) {
|
||||
void* ret;
|
||||
if (zero_fill_field_ || per_process::cli_options->zero_fill_all_buffers)
|
||||
diff --git a/src/crypto/crypto_dh.cc b/src/crypto/crypto_dh.cc
|
||||
index f23cedf4f2449d8edc9a8de1b70332e75d693cdd..976653dd1e9363e046788fc3419a9b649ceb2ea4 100644
|
||||
--- a/src/crypto/crypto_dh.cc
|
||||
+++ b/src/crypto/crypto_dh.cc
|
||||
@@ -55,13 +55,32 @@ void DiffieHellman::MemoryInfo(MemoryTracker* tracker) const {
|
||||
|
||||
namespace {
|
||||
MaybeLocal<Value> DataPointerToBuffer(Environment* env, DataPointer&& data) {
|
||||
+#if defined(V8_ENABLE_SANDBOX)
|
||||
+ std::unique_ptr<v8::BackingStore> backing;
|
||||
+ if (data.size() > 0) {
|
||||
+ std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
|
||||
+ void* v8_data = allocator->Allocate(data.size());
|
||||
+ CHECK(v8_data);
|
||||
+ memcpy(v8_data, data.get(), data.size());
|
||||
+ backing = ArrayBuffer::NewBackingStore(
|
||||
+ v8_data,
|
||||
+ data.size(),
|
||||
+ [](void* data, size_t length, void*) {
|
||||
+ std::unique_ptr<ArrayBuffer::Allocator> allocator(ArrayBuffer::Allocator::NewDefaultAllocator());
|
||||
+ allocator->Free(data, length);
|
||||
+ }, nullptr);
|
||||
+ } else {
|
||||
+ NoArrayBufferZeroFillScope no_zero_fill_scope(env->isolate_data());
|
||||
+ backing = v8::ArrayBuffer::NewBackingStore(env->isolate(), data.size());
|
||||
+ }
|
||||
+#else
|
||||
auto backing = ArrayBuffer::NewBackingStore(
|
||||
data.get(),
|
||||
data.size(),
|
||||
[](void* data, size_t len, void* ptr) { DataPointer free_me(data, len); },
|
||||
nullptr);
|
||||
data.release();
|
||||
-
|
||||
+#endif
|
||||
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
|
||||
return Buffer::New(env, ab, 0, ab->ByteLength()).FromMaybe(Local<Value>());
|
||||
}
|
||||
diff --git a/src/crypto/crypto_util.cc b/src/crypto/crypto_util.cc
|
||||
index 4505786745c54a529f904d5e7813a86204e0a78b..eab18ab9888e2f7c0757fefab80505d8c99dc742 100644
|
||||
--- a/src/crypto/crypto_util.cc
|
||||
@@ -188,6 +131,28 @@ index f616223cfb0f6e10f7cf57ada9704316bde2797e..eb6dad44a49d997097c8fb5009eeb60a
|
||||
auto ab = ArrayBuffer::New(env->isolate(), std::move(backing));
|
||||
Local<Value> ret;
|
||||
if (!Buffer::New(env, ab, 0, ab->ByteLength()).ToLocal(&ret)) return {};
|
||||
diff --git a/src/node_buffer.cc b/src/node_buffer.cc
|
||||
index 357dc5f6d1c1c2d3756a94c1326b0502403e1eaf..b9f0c97938203b4652780a7d707c5e83319330b0 100644
|
||||
--- a/src/node_buffer.cc
|
||||
+++ b/src/node_buffer.cc
|
||||
@@ -1412,7 +1412,7 @@ inline size_t CheckNumberToSize(Local<Value> number) {
|
||||
CHECK(value >= 0 && value < maxSize);
|
||||
size_t size = static_cast<size_t>(value);
|
||||
#ifdef V8_ENABLE_SANDBOX
|
||||
- CHECK_LE(size, kMaxSafeBufferSizeForSandbox);
|
||||
+ CHECK_LE(size, v8::internal::kMaxSafeBufferSizeForSandbox);
|
||||
#endif
|
||||
return size;
|
||||
}
|
||||
@@ -1437,7 +1437,7 @@ void CreateUnsafeArrayBuffer(const FunctionCallbackInfo<Value>& args) {
|
||||
buf = ArrayBuffer::New(isolate, size);
|
||||
} else {
|
||||
std::unique_ptr<BackingStore> store =
|
||||
- ArrayBuffer::NewBackingStoreForNodeLTS(isolate, size);
|
||||
+ ArrayBuffer::NewBackingStore(isolate, size);
|
||||
if (!store) {
|
||||
return env->ThrowRangeError("Array buffer allocation failed");
|
||||
}
|
||||
diff --git a/src/node_i18n.cc b/src/node_i18n.cc
|
||||
index 6be3920632b25db450025ebab6a2636e4811cdbe..b49916d2b5fc5e58cf3fb67329430fd3df8fb813 100644
|
||||
--- a/src/node_i18n.cc
|
||||
@@ -228,30 +193,6 @@ index 6be3920632b25db450025ebab6a2636e4811cdbe..b49916d2b5fc5e58cf3fb67329430fd3
|
||||
}
|
||||
|
||||
constexpr const char* EncodingName(const enum encoding encoding) {
|
||||
diff --git a/src/node_internals.h b/src/node_internals.h
|
||||
index 12ea72b61b0a5e194207bb369dfed4b8667107cb..64442215714a98f648971e517ddd9c77e38fe3f2 100644
|
||||
--- a/src/node_internals.h
|
||||
+++ b/src/node_internals.h
|
||||
@@ -121,7 +121,9 @@ v8::MaybeLocal<v8::Object> InitializePrivateSymbols(
|
||||
|
||||
class NodeArrayBufferAllocator : public ArrayBufferAllocator {
|
||||
public:
|
||||
- inline uint32_t* zero_fill_field() { return &zero_fill_field_; }
|
||||
+ NodeArrayBufferAllocator();
|
||||
+ ~NodeArrayBufferAllocator() override;
|
||||
+ inline uint32_t* zero_fill_field() { return zero_fill_field_; }
|
||||
|
||||
void* Allocate(size_t size) override; // Defined in src/node.cc
|
||||
void* AllocateUninitialized(size_t size) override;
|
||||
@@ -139,7 +141,7 @@ class NodeArrayBufferAllocator : public ArrayBufferAllocator {
|
||||
}
|
||||
|
||||
private:
|
||||
- uint32_t zero_fill_field_ = 1; // Boolean but exposed as uint32 to JS land.
|
||||
+ uint32_t* zero_fill_field_ = nullptr; // Boolean but exposed as uint32 to JS land.
|
||||
std::atomic<size_t> total_mem_usage_ {0};
|
||||
|
||||
// Delegate to V8's allocator for compatibility with the V8 memory cage.
|
||||
diff --git a/src/node_serdes.cc b/src/node_serdes.cc
|
||||
index c55a2e28066147ae5ca5def10ec76ccc03c634b4..c54183c72944989219b6437c9e571a3f7f3f8dd5 100644
|
||||
--- a/src/node_serdes.cc
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Tue, 4 Nov 2025 21:20:26 +0100
|
||||
Subject: test: correct conditional secure heap flags test
|
||||
|
||||
PR-URL: https://github.com/nodejs/node/pull/60385
|
||||
Reviewed-By: Colin Ihrig <cjihrig@gmail.com>
|
||||
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
|
||||
(cherry picked from commit 53c4a39fec941e04150554fdd3e654b48f2e1b31)
|
||||
|
||||
diff --git a/test/parallel/test-process-env-allowed-flags-are-documented.js b/test/parallel/test-process-env-allowed-flags-are-documented.js
|
||||
index afd43cfffe638f4f084f1c36949068e7239eadc3..c70e073bab888c349e8f5c691f5679a3796c896c 100644
|
||||
--- a/test/parallel/test-process-env-allowed-flags-are-documented.js
|
||||
+++ b/test/parallel/test-process-env-allowed-flags-are-documented.js
|
||||
@@ -49,6 +49,8 @@ if (!hasOpenSSL3) {
|
||||
documented.delete('--openssl-shared-config');
|
||||
}
|
||||
|
||||
+const isV8Sandboxed = process.config.variables.v8_enable_sandbox;
|
||||
+
|
||||
// Filter out options that are conditionally present.
|
||||
const conditionalOpts = [
|
||||
{
|
||||
@@ -74,6 +76,9 @@ const conditionalOpts = [
|
||||
}, {
|
||||
include: process.features.inspector,
|
||||
filter: (opt) => opt.startsWith('--inspect') || opt === '--debug-port'
|
||||
+ }, {
|
||||
+ include: !isV8Sandboxed,
|
||||
+ filter: (opt) => ['--secure-heap', '--secure-heap-min'].includes(opt)
|
||||
},
|
||||
];
|
||||
documented.forEach((opt) => {
|
||||
1
patches/skia/.patches
Normal file
1
patches/skia/.patches
Normal file
@@ -0,0 +1 @@
|
||||
graphite_add_insertstatus_koutoforderrecording.patch
|
||||
@@ -0,0 +1,54 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Michael Ludwig <michaelludwig@google.com>
|
||||
Date: Fri, 16 Jan 2026 15:28:27 -0500
|
||||
Subject: [graphite] Add InsertStatus::kOutOfOrderRecording
|
||||
|
||||
Bug: b/458722690
|
||||
Change-Id: I1c3661b9c765f46f039f0044ae3557a0e7f619cf
|
||||
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/1143598
|
||||
Auto-Submit: Michael Ludwig <michaelludwig@google.com>
|
||||
Reviewed-by: Thomas Smith <thomsmit@google.com>
|
||||
Commit-Queue: Nicolette Prevost <nicolettep@google.com>
|
||||
Reviewed-by: Nicolette Prevost <nicolettep@google.com>
|
||||
|
||||
diff --git a/include/gpu/graphite/GraphiteTypes.h b/include/gpu/graphite/GraphiteTypes.h
|
||||
index a638da8988b2e8963255537c36f98d0233c917e8..a8e40871531ce0c2a2b98a4c8ba1c44e9b374c2d 100644
|
||||
--- a/include/gpu/graphite/GraphiteTypes.h
|
||||
+++ b/include/gpu/graphite/GraphiteTypes.h
|
||||
@@ -52,7 +52,12 @@ public:
|
||||
kAddCommandsFailed,
|
||||
// Internal failure, shader pipeline compilation failed (driver issue, or disk corruption),
|
||||
// state unrecoverable.
|
||||
- kAsyncShaderCompilesFailed
|
||||
+ kAsyncShaderCompilesFailed,
|
||||
+ // The inserted Recording is out of order from what the Context expects (when
|
||||
+ // `[Context|Recorder]Options::fRequireOrderedRecordings` is true), which can either
|
||||
+ // represent a client synchronization error or an internal failure when a prior dependent
|
||||
+ // Recording failed for some reason, no CB changes but state likely unrecoverable.
|
||||
+ kOutOfOrderRecording,
|
||||
};
|
||||
|
||||
constexpr InsertStatus() : fValue(kSuccess) {}
|
||||
diff --git a/relnotes/out_of_order_status.md b/relnotes/out_of_order_status.md
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..74f7c889338925d132a2d80cdba40d321807c614
|
||||
--- /dev/null
|
||||
+++ b/relnotes/out_of_order_status.md
|
||||
@@ -0,0 +1,4 @@
|
||||
+* Graphite's InsertStatus now has an additional kOutOfOrderRecording to differentiate this
|
||||
+ unrecoverable error from programming errors that would lead to kInvalidRecording. Out of order
|
||||
+ recordings can currently arise "naturally" if prior dependent recordings failed due to resource
|
||||
+ creation or update errors from the GPU driver.
|
||||
diff --git a/src/gpu/graphite/QueueManager.cpp b/src/gpu/graphite/QueueManager.cpp
|
||||
index 2948e071390095f3a1a1d7fdb6c7878adeaf9c53..f5f97f23e4957b819f9d92e8f03f49cd6fad1ab3 100644
|
||||
--- a/src/gpu/graphite/QueueManager.cpp
|
||||
+++ b/src/gpu/graphite/QueueManager.cpp
|
||||
@@ -122,7 +122,7 @@ InsertStatus QueueManager::addRecording(const InsertRecordingInfo& info, Context
|
||||
if (recorderID != SK_InvalidGenID) {
|
||||
uint32_t* recordingID = fLastAddedRecordingIDs.find(recorderID);
|
||||
RETURN_FAIL_IF(recordingID && info.fRecording->priv().uniqueID() != *recordingID + 1,
|
||||
- InsertStatus::kInvalidRecording,
|
||||
+ InsertStatus::kOutOfOrderRecording,
|
||||
"Recordings are expected to be replayed in order");
|
||||
|
||||
// Note the new Recording ID.
|
||||
@@ -1,2 +1,5 @@
|
||||
chore_allow_customizing_microtask_policy_per_context.patch
|
||||
turboshaft_avoid_introducing_too_many_variables.patch
|
||||
runtime_setprototypeproperties_handling_of.patch
|
||||
runtime_correcting_setprototypeproperties.patch
|
||||
reduce_stack_memory_consumption_in_bytecodegenerator.patch
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Jakob Kummerow <jkummerow@chromium.org>
|
||||
Date: Fri, 21 Nov 2025 11:52:15 +0100
|
||||
Subject: Reduce stack memory consumption in BytecodeGenerator
|
||||
|
||||
Using a SmallVector with 64 stack-allocated entries is not great
|
||||
for functions that can be used in deep recursions, so this patch
|
||||
replaces that with a ZoneVector.
|
||||
By allocating any backing store only when it's actually needed,
|
||||
and presizing that initial backing store when that happens, this
|
||||
should have similar performance while using a lot less stack space,
|
||||
allowing us to compile more deeply nested expressions.
|
||||
|
||||
For the highly artificial example of a function full of nested
|
||||
empty blocks `function f() {{{{{...}}}}}`, this increases the max
|
||||
nesting level from around 670 to around 2600. With more aggressive
|
||||
inlining in official builds, it can have similar effects on patterns
|
||||
that don't even call `VisitStatements()` recursively.
|
||||
|
||||
Bug: 429332174
|
||||
Change-Id: Id11e15ba0fd9bc39efa68bf83e1181fe0e7274a7
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7180480
|
||||
Reviewed-by: Raphael Herouart <rherouart@chromium.org>
|
||||
Commit-Queue: Jakob Kummerow <jkummerow@chromium.org>
|
||||
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#103870}
|
||||
|
||||
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
|
||||
index 63400abcc7ca0bd5369d566dd37e2ae93d7c3efe..a1cf142edc4c0224731ebab988bcbf9272acd57e 100644
|
||||
--- a/src/interpreter/bytecode-generator.cc
|
||||
+++ b/src/interpreter/bytecode-generator.cc
|
||||
@@ -2306,10 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
|
||||
// The subsequent ones must be about the same <var> to return true.
|
||||
|
||||
bool BytecodeGenerator::IsPrototypeAssignment(
|
||||
- Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
|
||||
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
- std::unordered_set<const AstRawString*>& duplicates) {
|
||||
+ Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments) {
|
||||
// The expression Statement is an assignment
|
||||
// ========================================
|
||||
ExpressionStatement* expr_stmt = stmt->AsExpressionStatement();
|
||||
@@ -2381,41 +2378,46 @@ bool BytecodeGenerator::IsPrototypeAssignment(
|
||||
return false;
|
||||
}
|
||||
|
||||
- if (!duplicates.insert(prop_str).second) {
|
||||
- return false;
|
||||
- }
|
||||
-
|
||||
- if (*var == nullptr) {
|
||||
- // This is the first proto assignment in the sequence
|
||||
- *var = tmp_var;
|
||||
- *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
|
||||
- } else if (*var != tmp_var) {
|
||||
- // This prototype assignment is about another var
|
||||
- return false;
|
||||
+ if (!*assignments) {
|
||||
+ // This is the first prototype assignment in the sequence.
|
||||
+ *assignments = std::make_unique<PrototypeAssignments>(
|
||||
+ tmp_var, proto_prop->obj()->AsVariableProxy()->hole_check_mode(),
|
||||
+ ZoneVector<PrototypeAssignment>(zone()));
|
||||
+ (*assignments)->properties.reserve(8);
|
||||
+ (*assignments)->duplicates.insert(prop_str);
|
||||
+ } else {
|
||||
+ if (!(*assignments)->duplicates.insert(prop_str).second) return false;
|
||||
+ if ((*assignments)->var != tmp_var) {
|
||||
+ // This prototype assignment is about another var.
|
||||
+ return false;
|
||||
+ }
|
||||
+ DCHECK_EQ((*assignments)->hole_check_mode,
|
||||
+ proto_prop->obj()->AsVariableProxy()->hole_check_mode());
|
||||
}
|
||||
|
||||
- // Success
|
||||
- properties.push_back(std::make_pair(
|
||||
- prop_str,
|
||||
- value)); // This will be reused as part of an ObjectLiteral
|
||||
+ // Success!
|
||||
+ (*assignments)
|
||||
+ ->properties.push_back(std::make_pair(
|
||||
+ prop_str,
|
||||
+ value)); // This will be reused as part of an ObjectLiteral.
|
||||
|
||||
- DCHECK_EQ(*hole_check_mode,
|
||||
- proto_prop->obj()->AsVariableProxy()->hole_check_mode());
|
||||
return true;
|
||||
}
|
||||
|
||||
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
|
||||
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
- Variable* var, HoleCheckMode hole_check_mode) {
|
||||
+ std::unique_ptr<PrototypeAssignments> assignments) {
|
||||
// Create a boiler plate object in the constant pool to be merged into the
|
||||
- // proto
|
||||
+ // proto.
|
||||
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
|
||||
- proto_assign_seq_.push_back(std::make_pair(
|
||||
- zone()->New<ProtoAssignmentSeqBuilder>(properties), entry));
|
||||
+ proto_assign_seq_.push_back(
|
||||
+ std::make_pair(zone()->New<ProtoAssignmentSeqBuilder>(
|
||||
+ std::move(assignments->properties)),
|
||||
+ entry));
|
||||
+ const ZoneVector<PrototypeAssignment>* props =
|
||||
+ proto_assign_seq_.back().first->properties();
|
||||
|
||||
int first_idx = -1;
|
||||
- for (auto& p : properties) {
|
||||
+ for (auto& p : *props) {
|
||||
auto func = p.second->AsFunctionLiteral();
|
||||
if (func) {
|
||||
int idx = GetNewClosureSlot(func);
|
||||
@@ -2426,13 +2428,13 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
|
||||
}
|
||||
}
|
||||
|
||||
- // We need it to be valid, even if unused
|
||||
+ // We need {first_idx} to be valid, even if it's unused.
|
||||
if (first_idx == -1) {
|
||||
first_idx = 0;
|
||||
}
|
||||
- // Load the variable whose prototype is to be set into the Accumulator
|
||||
- BuildVariableLoad(var, hole_check_mode);
|
||||
- // Merge in-place proto-def boilerplate object into Accumulator
|
||||
+ // Load the variable whose prototype is to be set into the Accumulator.
|
||||
+ BuildVariableLoad(assignments->var, assignments->hole_check_mode);
|
||||
+ // Merge in-place proto-def boilerplate object into the Accumulator.
|
||||
builder()->SetPrototypeProperties(entry, first_idx);
|
||||
}
|
||||
|
||||
@@ -2440,25 +2442,23 @@ void BytecodeGenerator::VisitStatements(
|
||||
const ZonePtrList<Statement>* statements, int start) {
|
||||
for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
|
||||
if (v8_flags.proto_assign_seq_opt) {
|
||||
- Variable* var = nullptr;
|
||||
- HoleCheckMode hole_check_mode;
|
||||
-
|
||||
int proto_assign_idx = stmt_idx;
|
||||
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>
|
||||
- properties(zone());
|
||||
- std::unordered_set<const AstRawString*> duplicates;
|
||||
+ // {VisitStatements} can be used for deep recursions, so this is a
|
||||
+ // stack-friendly design: statically we only need one {unique_ptr}, and
|
||||
+ // the actual storage is heap-allocated when it is needed.
|
||||
+ std::unique_ptr<PrototypeAssignments> assignments;
|
||||
while (proto_assign_idx < statements->length() &&
|
||||
- IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
|
||||
- &hole_check_mode, properties, duplicates)) {
|
||||
+ IsPrototypeAssignment(statements->at(proto_assign_idx),
|
||||
+ &assignments)) {
|
||||
++proto_assign_idx;
|
||||
}
|
||||
|
||||
if (proto_assign_idx - stmt_idx > 1) {
|
||||
- DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
|
||||
- VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
|
||||
- stmt_idx = proto_assign_idx - 1; // the outer loop should now ignore
|
||||
- // these statements
|
||||
+ DCHECK_EQ(static_cast<size_t>(proto_assign_idx - stmt_idx),
|
||||
+ assignments->properties.size());
|
||||
+ VisitConsecutivePrototypeAssignments(std::move(assignments));
|
||||
+ stmt_idx = proto_assign_idx - 1; // The outer loop should now ignore
|
||||
+ // these statements.
|
||||
DCHECK(!builder()->RemainderOfBlockIsDead());
|
||||
continue;
|
||||
}
|
||||
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
|
||||
index 5dd136a01848aaf4a182dd31aa05a6ebe845fae8..b2e4cf6d40c3cd66042f5f52533b4883ff159418 100644
|
||||
--- a/src/interpreter/bytecode-generator.h
|
||||
+++ b/src/interpreter/bytecode-generator.h
|
||||
@@ -30,6 +30,13 @@ class LoopBuilder;
|
||||
class BlockCoverageBuilder;
|
||||
class BytecodeJumpTable;
|
||||
|
||||
+struct PrototypeAssignments {
|
||||
+ Variable* var;
|
||||
+ HoleCheckMode hole_check_mode;
|
||||
+ ZoneVector<PrototypeAssignment> properties;
|
||||
+ std::unordered_set<const AstRawString*> duplicates;
|
||||
+};
|
||||
+
|
||||
class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
public:
|
||||
enum TypeHint : uint8_t {
|
||||
@@ -69,18 +76,12 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
#define DECLARE_VISIT(type) void Visit##type(type* node);
|
||||
AST_NODE_LIST(DECLARE_VISIT)
|
||||
#undef DECLARE_VISIT
|
||||
- static constexpr int kInitialPropertyCount = 64;
|
||||
|
||||
bool IsPrototypeAssignment(
|
||||
- Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
|
||||
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
- std::unordered_set<const AstRawString*>& duplicate);
|
||||
-
|
||||
- void VisitConsecutivePrototypeAssignments(
|
||||
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
- Variable* var, HoleCheckMode hole_check_mode);
|
||||
+ Statement* stmt, std::unique_ptr<PrototypeAssignments>* assignments);
|
||||
+ V8_NOINLINE void VisitConsecutivePrototypeAssignments(
|
||||
+ std::unique_ptr<PrototypeAssignments> assignments);
|
||||
+
|
||||
// Visiting function for declarations list and statements are overridden.
|
||||
void VisitModuleDeclarations(Declaration::List* declarations);
|
||||
void VisitGlobalDeclarations(Declaration::List* declarations);
|
||||
diff --git a/src/interpreter/prototype-assignment-sequence-builder.cc b/src/interpreter/prototype-assignment-sequence-builder.cc
|
||||
index a7c05b6e8555fde41ba6a81523437fd03b16c3e6..fb427c825c2753d3e12f557279a5230985a1ec44 100644
|
||||
--- a/src/interpreter/prototype-assignment-sequence-builder.cc
|
||||
+++ b/src/interpreter/prototype-assignment-sequence-builder.cc
|
||||
@@ -25,7 +25,7 @@ void ProtoAssignmentSeqBuilder::BuildBoilerplateDescription(
|
||||
|
||||
int position = 0;
|
||||
for (size_t i = 0; i < properties_.size(); i++) {
|
||||
- auto pair = properties_.at(i);
|
||||
+ PrototypeAssignment pair = properties_.at(i);
|
||||
const AstRawString* key_str = pair.first;
|
||||
|
||||
DirectHandle<Object> key = Cast<Object>(key_str->string());
|
||||
diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
|
||||
index 0c29f4f4bf0843877b2bd568866d8e290d4f3b51..6583022aa43a30df707325e6b7a14891f97a4ef1 100644
|
||||
--- a/src/interpreter/prototype-assignment-sequence-builder.h
|
||||
+++ b/src/interpreter/prototype-assignment-sequence-builder.h
|
||||
@@ -7,17 +7,17 @@
|
||||
|
||||
#include "src/ast/ast.h"
|
||||
#include "src/objects/literal-objects.h"
|
||||
+#include "src/zone/zone-containers.h"
|
||||
|
||||
namespace v8 {
|
||||
namespace internal {
|
||||
|
||||
+using PrototypeAssignment = std::pair<const AstRawString*, Expression*>;
|
||||
+
|
||||
class ProtoAssignmentSeqBuilder final : public ZoneObject {
|
||||
public:
|
||||
- static constexpr int kInitialPropertyCount = 64;
|
||||
-
|
||||
explicit ProtoAssignmentSeqBuilder(
|
||||
- const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties)
|
||||
+ ZoneVector<PrototypeAssignment>&& properties)
|
||||
: properties_(std::move(properties)) {}
|
||||
|
||||
~ProtoAssignmentSeqBuilder() = default;
|
||||
@@ -53,10 +53,10 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
|
||||
IsolateT* isolate,
|
||||
Handle<Script> script);
|
||||
|
||||
+ const ZoneVector<PrototypeAssignment>* properties() { return &properties_; }
|
||||
+
|
||||
private:
|
||||
- SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>
|
||||
- properties_;
|
||||
+ ZoneVector<PrototypeAssignment> properties_;
|
||||
IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
|
||||
};
|
||||
|
||||
148
patches/v8/runtime_correcting_setprototypeproperties.patch
Normal file
148
patches/v8/runtime_correcting_setprototypeproperties.patch
Normal file
@@ -0,0 +1,148 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Raphael Herouart <rherouart@chromium.org>
|
||||
Date: Tue, 18 Nov 2025 11:00:26 +0000
|
||||
Subject: [runtime] Correcting SetPrototypeProperties:
|
||||
|
||||
- Off by One Error in code generation
|
||||
- Attributes should be preserved when setting properties
|
||||
- SmallVector Memory Leak
|
||||
|
||||
Bug: 429332174
|
||||
Change-Id: I8a669a98f44777ad54ea7e7bb06fc4f8c552c865
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7160576
|
||||
Reviewed-by: Toon Verwaest <verwaest@chromium.org>
|
||||
Commit-Queue: Raphael Herouart <rherouart@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#103784}
|
||||
|
||||
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
|
||||
index bf874d94ec34056d499fea9cbd034f959d506b58..63400abcc7ca0bd5369d566dd37e2ae93d7c3efe 100644
|
||||
--- a/src/interpreter/bytecode-generator.cc
|
||||
+++ b/src/interpreter/bytecode-generator.cc
|
||||
@@ -2307,8 +2307,8 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
|
||||
|
||||
bool BytecodeGenerator::IsPrototypeAssignment(
|
||||
Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
|
||||
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
+ kInitialPropertyCount>& properties,
|
||||
std::unordered_set<const AstRawString*>& duplicates) {
|
||||
// The expression Statement is an assignment
|
||||
// ========================================
|
||||
@@ -2405,8 +2405,8 @@ bool BytecodeGenerator::IsPrototypeAssignment(
|
||||
}
|
||||
|
||||
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
|
||||
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
+ kInitialPropertyCount>& properties,
|
||||
Variable* var, HoleCheckMode hole_check_mode) {
|
||||
// Create a boiler plate object in the constant pool to be merged into the
|
||||
// proto
|
||||
@@ -2444,9 +2444,9 @@ void BytecodeGenerator::VisitStatements(
|
||||
HoleCheckMode hole_check_mode;
|
||||
|
||||
int proto_assign_idx = stmt_idx;
|
||||
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>
|
||||
- properties;
|
||||
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
+ kInitialPropertyCount>
|
||||
+ properties(zone());
|
||||
std::unordered_set<const AstRawString*> duplicates;
|
||||
while (proto_assign_idx < statements->length() &&
|
||||
IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
|
||||
@@ -2457,10 +2457,10 @@ void BytecodeGenerator::VisitStatements(
|
||||
if (proto_assign_idx - stmt_idx > 1) {
|
||||
DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
|
||||
VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
|
||||
- stmt_idx = proto_assign_idx; // the outer loop should now ignore these
|
||||
- // statements
|
||||
+ stmt_idx = proto_assign_idx - 1; // the outer loop should now ignore
|
||||
+ // these statements
|
||||
DCHECK(!builder()->RemainderOfBlockIsDead());
|
||||
- if (stmt_idx == statements->length()) break;
|
||||
+ continue;
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
|
||||
index 41c5cda1ca6dd4017b329e41bad8af3bcc4ee242..5dd136a01848aaf4a182dd31aa05a6ebe845fae8 100644
|
||||
--- a/src/interpreter/bytecode-generator.h
|
||||
+++ b/src/interpreter/bytecode-generator.h
|
||||
@@ -73,13 +73,13 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
|
||||
bool IsPrototypeAssignment(
|
||||
Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
|
||||
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
+ kInitialPropertyCount>& properties,
|
||||
std::unordered_set<const AstRawString*>& duplicate);
|
||||
|
||||
void VisitConsecutivePrototypeAssignments(
|
||||
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties,
|
||||
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
+ kInitialPropertyCount>& properties,
|
||||
Variable* var, HoleCheckMode hole_check_mode);
|
||||
// Visiting function for declarations list and statements are overridden.
|
||||
void VisitModuleDeclarations(Declaration::List* declarations);
|
||||
diff --git a/src/interpreter/prototype-assignment-sequence-builder.h b/src/interpreter/prototype-assignment-sequence-builder.h
|
||||
index 880ed4dcec8399e9b52e6d1924b8773bff1db063..0c29f4f4bf0843877b2bd568866d8e290d4f3b51 100644
|
||||
--- a/src/interpreter/prototype-assignment-sequence-builder.h
|
||||
+++ b/src/interpreter/prototype-assignment-sequence-builder.h
|
||||
@@ -16,8 +16,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
|
||||
static constexpr int kInitialPropertyCount = 64;
|
||||
|
||||
explicit ProtoAssignmentSeqBuilder(
|
||||
- const base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>& properties)
|
||||
+ const SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
+ kInitialPropertyCount>& properties)
|
||||
: properties_(std::move(properties)) {}
|
||||
|
||||
~ProtoAssignmentSeqBuilder() = default;
|
||||
@@ -54,8 +54,8 @@ class ProtoAssignmentSeqBuilder final : public ZoneObject {
|
||||
Handle<Script> script);
|
||||
|
||||
private:
|
||||
- base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
- kInitialPropertyCount>
|
||||
+ SmallZoneVector<std::pair<const AstRawString*, Expression*>,
|
||||
+ kInitialPropertyCount>
|
||||
properties_;
|
||||
IndirectHandle<ObjectBoilerplateDescription> boilerplate_description_;
|
||||
};
|
||||
diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
|
||||
index ded3b466625c99da6f27936ae4e5e0c879baec1b..961b9ee5b7d329bb018300fe2f18bea60e1cd883 100644
|
||||
--- a/src/runtime/runtime-literals.cc
|
||||
+++ b/src/runtime/runtime-literals.cc
|
||||
@@ -730,6 +730,13 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
|
||||
feedback_cell_array, current_slot));
|
||||
}
|
||||
|
||||
+ if (IsSpecialReceiverMap(js_proto->map())) {
|
||||
+ RETURN_RESULT_OR_FAILURE(
|
||||
+ isolate, SetPrototypePropertiesSlow(isolate, context, obj,
|
||||
+ object_boilerplate_description,
|
||||
+ feedback_cell_array, current_slot));
|
||||
+ }
|
||||
+
|
||||
bool is_default_func_prototype =
|
||||
IsDefaultFunctionPrototype(js_proto, isolate);
|
||||
|
||||
@@ -782,7 +789,11 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
|
||||
value = InstantiateIfSharedFunctionInfo(
|
||||
context, isolate, value, feedback_cell_array, current_slot);
|
||||
DirectHandle<String> name = Cast<String>(key);
|
||||
- JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, NONE)
|
||||
+ Maybe<PropertyAttributes> maybe = JSReceiver::GetPropertyAttributes(&it);
|
||||
+ PropertyAttributes attr = maybe.ToChecked();
|
||||
+ if (attr == ABSENT) attr = NONE;
|
||||
+
|
||||
+ JSObject::SetOwnPropertyIgnoreAttributes(js_proto, name, value, attr)
|
||||
.Check();
|
||||
|
||||
result = value;
|
||||
153
patches/v8/runtime_setprototypeproperties_handling_of.patch
Normal file
153
patches/v8/runtime_setprototypeproperties_handling_of.patch
Normal file
@@ -0,0 +1,153 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Raphael Herouart <rherouart@chromium.org>
|
||||
Date: Tue, 21 Oct 2025 14:49:06 +0000
|
||||
Subject: [runtime] SetPrototypeProperties handling of
|
||||
Hole/Undefined/Non-Object Values
|
||||
|
||||
Bug: 451750214
|
||||
Change-Id: Ida29afe0b92ed4a6acff97214005ed3589093294
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/v8/v8/+/7062734
|
||||
Commit-Queue: Raphael Herouart <rherouart@chromium.org>
|
||||
Reviewed-by: Leszek Swirski <leszeks@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#103261}
|
||||
|
||||
diff --git a/src/interpreter/bytecode-generator.cc b/src/interpreter/bytecode-generator.cc
|
||||
index 6d444622bacbb71c4754bc055da5cae39ed213b7..bf874d94ec34056d499fea9cbd034f959d506b58 100644
|
||||
--- a/src/interpreter/bytecode-generator.cc
|
||||
+++ b/src/interpreter/bytecode-generator.cc
|
||||
@@ -2306,7 +2306,7 @@ void BytecodeGenerator::VisitDeclarations(Declaration::List* declarations) {
|
||||
// The subsequent ones must be about the same <var> to return true.
|
||||
|
||||
bool BytecodeGenerator::IsPrototypeAssignment(
|
||||
- Statement* stmt, Variable** var,
|
||||
+ Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
|
||||
base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
kInitialPropertyCount>& properties,
|
||||
std::unordered_set<const AstRawString*>& duplicates) {
|
||||
@@ -2388,6 +2388,7 @@ bool BytecodeGenerator::IsPrototypeAssignment(
|
||||
if (*var == nullptr) {
|
||||
// This is the first proto assignment in the sequence
|
||||
*var = tmp_var;
|
||||
+ *hole_check_mode = proto_prop->obj()->AsVariableProxy()->hole_check_mode();
|
||||
} else if (*var != tmp_var) {
|
||||
// This prototype assignment is about another var
|
||||
return false;
|
||||
@@ -2398,13 +2399,15 @@ bool BytecodeGenerator::IsPrototypeAssignment(
|
||||
prop_str,
|
||||
value)); // This will be reused as part of an ObjectLiteral
|
||||
|
||||
+ DCHECK_EQ(*hole_check_mode,
|
||||
+ proto_prop->obj()->AsVariableProxy()->hole_check_mode());
|
||||
return true;
|
||||
}
|
||||
|
||||
void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
|
||||
const base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
kInitialPropertyCount>& properties,
|
||||
- Variable* var) {
|
||||
+ Variable* var, HoleCheckMode hole_check_mode) {
|
||||
// Create a boiler plate object in the constant pool to be merged into the
|
||||
// proto
|
||||
size_t entry = builder()->AllocateDeferredConstantPoolEntry();
|
||||
@@ -2428,7 +2431,7 @@ void BytecodeGenerator::VisitConsecutivePrototypeAssignments(
|
||||
first_idx = 0;
|
||||
}
|
||||
// Load the variable whose prototype is to be set into the Accumulator
|
||||
- BuildVariableLoad(var, HoleCheckMode::kElided);
|
||||
+ BuildVariableLoad(var, hole_check_mode);
|
||||
// Merge in-place proto-def boilerplate object into Accumulator
|
||||
builder()->SetPrototypeProperties(entry, first_idx);
|
||||
}
|
||||
@@ -2438,6 +2441,8 @@ void BytecodeGenerator::VisitStatements(
|
||||
for (int stmt_idx = start; stmt_idx < statements->length(); stmt_idx++) {
|
||||
if (v8_flags.proto_assign_seq_opt) {
|
||||
Variable* var = nullptr;
|
||||
+ HoleCheckMode hole_check_mode;
|
||||
+
|
||||
int proto_assign_idx = stmt_idx;
|
||||
base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
kInitialPropertyCount>
|
||||
@@ -2445,13 +2450,13 @@ void BytecodeGenerator::VisitStatements(
|
||||
std::unordered_set<const AstRawString*> duplicates;
|
||||
while (proto_assign_idx < statements->length() &&
|
||||
IsPrototypeAssignment(statements->at(proto_assign_idx), &var,
|
||||
- properties, duplicates)) {
|
||||
+ &hole_check_mode, properties, duplicates)) {
|
||||
++proto_assign_idx;
|
||||
}
|
||||
|
||||
if (proto_assign_idx - stmt_idx > 1) {
|
||||
DCHECK_EQ((size_t)(proto_assign_idx - stmt_idx), properties.size());
|
||||
- VisitConsecutivePrototypeAssignments(properties, var);
|
||||
+ VisitConsecutivePrototypeAssignments(properties, var, hole_check_mode);
|
||||
stmt_idx = proto_assign_idx; // the outer loop should now ignore these
|
||||
// statements
|
||||
DCHECK(!builder()->RemainderOfBlockIsDead());
|
||||
diff --git a/src/interpreter/bytecode-generator.h b/src/interpreter/bytecode-generator.h
|
||||
index b6421819c353febe283069852645a6b85e4c855a..41c5cda1ca6dd4017b329e41bad8af3bcc4ee242 100644
|
||||
--- a/src/interpreter/bytecode-generator.h
|
||||
+++ b/src/interpreter/bytecode-generator.h
|
||||
@@ -72,7 +72,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
static constexpr int kInitialPropertyCount = 64;
|
||||
|
||||
bool IsPrototypeAssignment(
|
||||
- Statement* stmt, Variable** var,
|
||||
+ Statement* stmt, Variable** var, HoleCheckMode* hole_check_mode,
|
||||
base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
kInitialPropertyCount>& properties,
|
||||
std::unordered_set<const AstRawString*>& duplicate);
|
||||
@@ -80,7 +80,7 @@ class BytecodeGenerator final : public AstVisitor<BytecodeGenerator> {
|
||||
void VisitConsecutivePrototypeAssignments(
|
||||
const base::SmallVector<std::pair<const AstRawString*, Expression*>,
|
||||
kInitialPropertyCount>& properties,
|
||||
- Variable* var);
|
||||
+ Variable* var, HoleCheckMode hole_check_mode);
|
||||
// Visiting function for declarations list and statements are overridden.
|
||||
void VisitModuleDeclarations(Declaration::List* declarations);
|
||||
void VisitGlobalDeclarations(Declaration::List* declarations);
|
||||
diff --git a/src/runtime/runtime-literals.cc b/src/runtime/runtime-literals.cc
|
||||
index 83074253314c438eed3006332c63c6bf7dfe4421..ded3b466625c99da6f27936ae4e5e0c879baec1b 100644
|
||||
--- a/src/runtime/runtime-literals.cc
|
||||
+++ b/src/runtime/runtime-literals.cc
|
||||
@@ -613,7 +613,7 @@ static DirectHandle<Object> InstantiateIfSharedFunctionInfo(
|
||||
}
|
||||
|
||||
static MaybeDirectHandle<Object> SetPrototypePropertiesSlow(
|
||||
- Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSObject> obj,
|
||||
+ Isolate* isolate, DirectHandle<Context> context, DirectHandle<JSAny> obj,
|
||||
Handle<ObjectBoilerplateDescription> object_boilerplate_description,
|
||||
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array,
|
||||
int& current_slot, int start_index = 0) {
|
||||
@@ -684,7 +684,7 @@ RUNTIME_FUNCTION(Runtime_SetPrototypeProperties) {
|
||||
HandleScope scope(isolate);
|
||||
DCHECK_EQ(4, args.length());
|
||||
DirectHandle<Context> context(isolate->context(), isolate);
|
||||
- DirectHandle<JSObject> obj = args.at<JSObject>(0); // acc JS Object
|
||||
+ DirectHandle<JSAny> obj = args.at<JSAny>(0); // acc JS Object
|
||||
Handle<ObjectBoilerplateDescription> object_boilerplate_description =
|
||||
args.at<ObjectBoilerplateDescription>(1);
|
||||
DirectHandle<ClosureFeedbackCellArray> feedback_cell_array =
|
||||
diff --git a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
|
||||
index 84ebbb755f8a5432c2e8da3d26ceb648bee2cf2d..bb9d697caec6e79810fa757abec4b71ad4cf3f2f 100644
|
||||
--- a/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
|
||||
+++ b/test/unittests/interpreter/bytecode_expectations/SetPrototypePropertiesOptimization.golden
|
||||
@@ -50,15 +50,17 @@ snippet: "
|
||||
"
|
||||
frame size: 0
|
||||
parameter count: 1
|
||||
-bytecode array length: 7
|
||||
+bytecode array length: 9
|
||||
bytecodes: [
|
||||
B(LdaImmutableCurrentContextSlot), U8(2),
|
||||
- /* 109 E> */ B(SetPrototypeProperties), U8(0), U8(0),
|
||||
+ /* 109 E> */ B(ThrowReferenceErrorIfHole), U8(1),
|
||||
+ B(SetPrototypeProperties), U8(0), U8(0),
|
||||
B(LdaUndefined),
|
||||
/* 245 S> */ B(Return),
|
||||
]
|
||||
constant pool: [
|
||||
OBJECT_BOILERPLATE_DESCRIPTION_TYPE,
|
||||
+ INTERNALIZED_ONE_BYTE_STRING_TYPE ["MyClass"],
|
||||
]
|
||||
handlers: [
|
||||
]
|
||||
@@ -40,6 +40,7 @@ DevToolsSecurity -enable
|
||||
# security import "$dir"/public.key -k $KEY_CHAIN
|
||||
|
||||
# Generate Trust Settings
|
||||
# TODO: Remove NPX
|
||||
npm_config_yes=true npx ts-node "$(dirname $0)"/gen-trust.ts "$dir"/certificate.cer "$dir"/trust.xml
|
||||
|
||||
# Import Trust Settings
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
const chalk = require('chalk');
|
||||
const { GitProcess } = require('dugite');
|
||||
|
||||
const childProcess = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
@@ -61,13 +61,16 @@ function getAbsoluteElectronExec () {
|
||||
return path.resolve(SRC_DIR, getElectronExec());
|
||||
}
|
||||
|
||||
async function handleGitCall (args, gitDir) {
|
||||
const details = await GitProcess.exec(args, gitDir);
|
||||
if (details.exitCode === 0) {
|
||||
return details.stdout.replace(/^\*|\s+|\s+$/, '');
|
||||
function handleGitCall (args, gitDir) {
|
||||
const result = childProcess.spawnSync('git', args, {
|
||||
cwd: gitDir,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
if (result.status === 0) {
|
||||
return result.stdout.replace(/^\*|\s+|\s+$/, '');
|
||||
} else {
|
||||
const error = GitProcess.parseError(details.stderr);
|
||||
console.log(`${fail} couldn't parse git process call: `, error);
|
||||
console.log(`${fail} couldn't parse git process call: `, result.stderr);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { GitProcess } = require('dugite');
|
||||
const { ESLint } = require('eslint');
|
||||
const minimist = require('minimist');
|
||||
|
||||
@@ -153,7 +152,7 @@ const LINTERS = [{
|
||||
}, {
|
||||
key: 'javascript',
|
||||
roots: ['build', 'default_app', 'lib', 'npm', 'script', 'spec'],
|
||||
ignoreRoots: ['spec/node_modules'],
|
||||
ignoreRoots: ['spec/node_modules', 'spec/fixtures/native-addon'],
|
||||
test: filename => filename.endsWith('.js') || filename.endsWith('.ts') || filename.endsWith('.mjs'),
|
||||
run: async (opts, filenames) => {
|
||||
const eslint = new ESLint({
|
||||
@@ -282,7 +281,7 @@ const LINTERS = [{
|
||||
}, {
|
||||
key: 'md',
|
||||
roots: ['.'],
|
||||
ignoreRoots: ['.git', 'node_modules', 'spec/node_modules'],
|
||||
ignoreRoots: ['.git', 'node_modules', 'spec/node_modules', 'spec/fixtures/native-addon'],
|
||||
test: filename => filename.endsWith('.md'),
|
||||
run: async (opts, filenames) => {
|
||||
const { getCodeBlocks } = await import('@electron/lint-roller/dist/lib/markdown.js');
|
||||
@@ -431,9 +430,13 @@ function populateLinterWithArgs (linter, opts) {
|
||||
}
|
||||
|
||||
async function findChangedFiles (top) {
|
||||
const result = await GitProcess.exec(['diff', '--name-only', '--cached'], top);
|
||||
if (result.exitCode !== 0) {
|
||||
console.log('Failed to find changed files', GitProcess.parseError(result.stderr));
|
||||
const result = childProcess.spawnSync('git', ['diff', '--name-only', '--cached'], {
|
||||
cwd: top,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
console.log('Failed to find changed files', result.stderr);
|
||||
process.exit(1);
|
||||
}
|
||||
const relativePaths = result.stdout.split(/\r\n|\r|\n/g);
|
||||
|
||||
@@ -9,7 +9,7 @@ const NAN_DIR = path.resolve(BASE, 'third_party', 'nan');
|
||||
const NPX_CMD = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
|
||||
const utils = require('./lib/utils');
|
||||
const { YARN_VERSION } = require('./yarn');
|
||||
const { YARN_SCRIPT_PATH } = require('./yarn');
|
||||
|
||||
if (!require.main) {
|
||||
throw new Error('Must call the nan spec runner directly');
|
||||
@@ -106,13 +106,12 @@ async function main () {
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
|
||||
if (buildStatus !== 0 || signal != null) {
|
||||
console.error('Failed to build nan test modules');
|
||||
return process.exit(buildStatus !== 0 ? buildStatus : signal);
|
||||
}
|
||||
|
||||
const { status: installStatus, signal: installSignal } = cp.spawnSync(NPX_CMD, [`yarn@${YARN_VERSION}`, 'install'], {
|
||||
const { status: installStatus, signal: installSignal } = cp.spawnSync(process.execPath, [YARN_SCRIPT_PATH, 'install'], {
|
||||
env,
|
||||
cwd: NAN_DIR,
|
||||
stdio: 'inherit',
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"parallel/test-tls-passphrase",
|
||||
"parallel/test-tls-peer-certificate",
|
||||
"parallel/test-tls-pfx-authorizationerror",
|
||||
"parallel/test-tls-psk-alpn-callback-exception-handling",
|
||||
"parallel/test-tls-psk-circuit",
|
||||
"parallel/test-tls-reduced-SECLEVEL-in-cipher",
|
||||
"parallel/test-tls-root-certificates",
|
||||
|
||||
@@ -13,6 +13,7 @@ import { createGitHubTokenStrategy } from '../github-token';
|
||||
import { ELECTRON_ORG, ELECTRON_REPO, ElectronReleaseRepo, NIGHTLY_REPO } from '../types';
|
||||
|
||||
const rootPackageJson = JSON.parse(fs.readFileSync(path.resolve(__dirname, '../../../package.json'), 'utf-8'));
|
||||
rootPackageJson.name = 'electron';
|
||||
|
||||
if (!process.env.ELECTRON_NPM_OTP) {
|
||||
console.error('Please set ELECTRON_NPM_OTP');
|
||||
@@ -211,6 +212,7 @@ new Promise<string>((resolve, reject) => {
|
||||
});
|
||||
})
|
||||
.then((tarballPath) => {
|
||||
// TODO: Remove NPX
|
||||
const existingVersionJSON = childProcess.execSync(`npx npm@7 view ${rootPackageJson.name}@${currentElectronVersion} --json`).toString('utf-8');
|
||||
// It's possible this is a re-run and we already have published the package, if not we just publish like normal
|
||||
if (!existingVersionJSON) {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { GitProcess } from 'dugite';
|
||||
import { valid, compare, gte, lte } from 'semver';
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { basename } from 'node:path';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
@@ -20,8 +20,12 @@ const semverify = (version: string) => version.replace(/^origin\//, '').replace(
|
||||
|
||||
const runGit = async (args: string[]) => {
|
||||
console.info(`Running: git ${args.join(' ')}`);
|
||||
const response = await GitProcess.exec(args, ELECTRON_DIR);
|
||||
if (response.exitCode !== 0) {
|
||||
const response = spawnSync('git', args, {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
if (response.status !== 0) {
|
||||
throw new Error(response.stderr.trim());
|
||||
}
|
||||
return response.stdout.trim();
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import { GitProcess } from 'dugite';
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
||||
import { resolve as _resolve } from 'node:path';
|
||||
|
||||
@@ -105,8 +105,13 @@ class Pool {
|
||||
**/
|
||||
|
||||
const runGit = async (dir: string, args: string[]) => {
|
||||
const response = await GitProcess.exec(args, dir);
|
||||
if (response.exitCode !== 0) {
|
||||
const response = spawnSync('git', args, {
|
||||
cwd: dir,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe'],
|
||||
maxBuffer: 100 * 1024 * 1024 // 100MB buffer to handle large git outputs
|
||||
});
|
||||
if (response.status !== 0) {
|
||||
throw new Error(response.stderr.trim());
|
||||
}
|
||||
return response.stdout.trim();
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
import { Octokit } from '@octokit/rest';
|
||||
import * as chalk from 'chalk';
|
||||
import { GitProcess } from 'dugite';
|
||||
|
||||
import { execSync } from 'node:child_process';
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
import { join } from 'node:path';
|
||||
|
||||
import { createGitHubTokenStrategy } from './github-token';
|
||||
@@ -166,11 +165,12 @@ async function createRelease (
|
||||
}
|
||||
|
||||
async function pushRelease (branch: string) {
|
||||
const pushDetails = await GitProcess.exec(
|
||||
['push', 'origin', `HEAD:${branch}`, '--follow-tags'],
|
||||
ELECTRON_DIR
|
||||
);
|
||||
if (pushDetails.exitCode === 0) {
|
||||
const pushDetails = spawnSync('git', ['push', 'origin', `HEAD:${branch}`, '--follow-tags'], {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
if (pushDetails.status === 0) {
|
||||
console.log(
|
||||
`${pass} Successfully pushed the release. Wait for ` +
|
||||
'release builds to finish before running "npm run release".'
|
||||
@@ -191,11 +191,12 @@ async function runReleaseBuilds (branch: string, newVersion: string) {
|
||||
|
||||
async function tagRelease (version: string) {
|
||||
console.log(`Tagging release ${version}.`);
|
||||
const checkoutDetails = await GitProcess.exec(
|
||||
['tag', '-a', '-m', version, version],
|
||||
ELECTRON_DIR
|
||||
);
|
||||
if (checkoutDetails.exitCode === 0) {
|
||||
const checkoutDetails = spawnSync('git', ['tag', '-a', '-m', version, version], {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
if (checkoutDetails.status === 0) {
|
||||
console.log(`${pass} Successfully tagged ${version}.`);
|
||||
} else {
|
||||
console.log(
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { GitProcess } from 'dugite';
|
||||
import * as semver from 'semver';
|
||||
|
||||
import { spawnSync } from 'node:child_process';
|
||||
|
||||
import { ELECTRON_DIR } from '../lib/utils';
|
||||
|
||||
export enum PreType {
|
||||
@@ -27,7 +28,11 @@ export const isStable = (v: string) => {
|
||||
|
||||
export async function nextAlpha (v: string) {
|
||||
const next = semver.coerce(semver.clean(v));
|
||||
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-alpha.*`], ELECTRON_DIR);
|
||||
const tagBlob = spawnSync('git', ['tag', '--list', '-l', `v${next}-alpha.*`], {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
|
||||
tags.sort((t1, t2) => {
|
||||
const a = parseInt(t1.split('.').pop()!, 10);
|
||||
@@ -41,7 +46,11 @@ export async function nextAlpha (v: string) {
|
||||
|
||||
export async function nextBeta (v: string) {
|
||||
const next = semver.coerce(semver.clean(v));
|
||||
const tagBlob = await GitProcess.exec(['tag', '--list', '-l', `v${next}-beta.*`], ELECTRON_DIR);
|
||||
const tagBlob = spawnSync('git', ['tag', '--list', '-l', `v${next}-beta.*`], {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
const tags = tagBlob.stdout.split('\n').filter(e => e !== '');
|
||||
tags.sort((t1, t2) => {
|
||||
const a = parseInt(t1.split('.').pop()!, 10);
|
||||
@@ -57,7 +66,11 @@ export async function nextNightly (v: string) {
|
||||
let next = semver.valid(semver.coerce(v));
|
||||
const pre = `nightly.${getCurrentDate()}`;
|
||||
|
||||
const branch = (await GitProcess.exec(['rev-parse', '--abbrev-ref', 'HEAD'], ELECTRON_DIR)).stdout.trim();
|
||||
const branch = spawnSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
}).stdout.trim();
|
||||
if (branch === 'main') {
|
||||
next = semver.inc(await getLastMajorForMain(), 'major');
|
||||
} else if (isStable(v)) {
|
||||
@@ -69,8 +82,12 @@ export async function nextNightly (v: string) {
|
||||
|
||||
async function getLastMajorForMain () {
|
||||
let branchNames;
|
||||
const result = await GitProcess.exec(['branch', '-a', '--remote', '--list', 'origin/[0-9]*-x-y'], ELECTRON_DIR);
|
||||
if (result.exitCode === 0) {
|
||||
const result = spawnSync('git', ['branch', '-a', '--remote', '--list', 'origin/[0-9]*-x-y'], {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8',
|
||||
stdio: ['inherit', 'pipe', 'pipe']
|
||||
});
|
||||
if (result.status === 0) {
|
||||
branchNames = result.stdout.trim().split('\n');
|
||||
const filtered = branchNames.map(b => b.replace('origin/', ''));
|
||||
return getNextReleaseBranch(filtered);
|
||||
|
||||
@@ -114,11 +114,14 @@ async function runClangTidy (
|
||||
outDir: string,
|
||||
filenames: string[],
|
||||
checks: string = '',
|
||||
jobs: number = 1
|
||||
jobs: number = 1,
|
||||
fix: boolean = false
|
||||
): Promise<boolean> {
|
||||
const cmd = path.resolve(LLVM_BIN, 'clang-tidy');
|
||||
const args = [`-p=${outDir}`, '--use-color'];
|
||||
const args = [`-p=${outDir}`];
|
||||
|
||||
if (!process.env.CI) args.push('--use-color');
|
||||
if (fix) args.push('--fix');
|
||||
if (checks) args.push(`--checks=${checks}`);
|
||||
|
||||
// Remove any files that aren't in the compilation database to prevent
|
||||
@@ -209,7 +212,7 @@ function parseCommandLine () {
|
||||
if (!arg || arg.startsWith('-')) {
|
||||
console.log(
|
||||
'Usage: script/run-clang-tidy.ts [-h|--help] [--jobs|-j] ' +
|
||||
'[--checks] --out-dir OUTDIR [file1 file2]'
|
||||
'[--fix] [--checks] --out-dir OUTDIR [file1 file2]'
|
||||
);
|
||||
process.exit(0);
|
||||
}
|
||||
@@ -218,7 +221,7 @@ function parseCommandLine () {
|
||||
};
|
||||
|
||||
const opts = minimist(process.argv.slice(2), {
|
||||
boolean: ['help'],
|
||||
boolean: ['fix', 'help'],
|
||||
string: ['checks', 'out-dir'],
|
||||
default: { jobs: 1 },
|
||||
alias: { help: 'h', jobs: 'j' },
|
||||
@@ -270,17 +273,23 @@ async function main (): Promise<boolean> {
|
||||
const filenames = [];
|
||||
|
||||
if (opts._.length > 0) {
|
||||
if (opts._.some((filename) => filename.endsWith('.h'))) {
|
||||
throw new ErrorWithExitCode(
|
||||
'Filenames must be for translation units, not headers', 3
|
||||
);
|
||||
}
|
||||
|
||||
filenames.push(...opts._.map((filename) => path.resolve(filename)));
|
||||
} else {
|
||||
filenames.push(
|
||||
...(await findMatchingFiles(
|
||||
path.resolve(SOURCE_ROOT, 'shell'),
|
||||
(filename: string) => /.*\.(?:cc|h|mm)$/.test(filename)
|
||||
(filename: string) => /.*\.(?:cc|mm)$/.test(filename)
|
||||
))
|
||||
);
|
||||
}
|
||||
|
||||
return runClangTidy(outDir, filenames, opts.checks, opts.jobs);
|
||||
return runClangTidy(outDir, filenames, opts.checks, opts.jobs, opts.fix);
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
|
||||
@@ -21,6 +21,7 @@ const fail = chalk.red('✗');
|
||||
const FAILURE_STATUS_KEY = 'Electron_Spec_Runner_Failures';
|
||||
|
||||
const args = minimist(process.argv, {
|
||||
boolean: ['skipYarnInstall'],
|
||||
string: ['runners', 'target', 'electronVersion'],
|
||||
number: ['enableRerun'],
|
||||
unknown: arg => unknownFlags.push(arg)
|
||||
@@ -36,10 +37,9 @@ for (const flag of unknownFlags) {
|
||||
}
|
||||
|
||||
const utils = require('./lib/utils');
|
||||
const { YARN_VERSION } = require('./yarn');
|
||||
const { YARN_SCRIPT_PATH } = require('./yarn');
|
||||
|
||||
const BASE = path.resolve(__dirname, '../..');
|
||||
const NPX_CMD = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
|
||||
const runners = new Map([
|
||||
['main', { description: 'Main process specs', run: runMainProcessElectronTests }]
|
||||
@@ -96,7 +96,7 @@ async function main () {
|
||||
const somethingChanged = (currentSpecHash !== lastSpecHash) ||
|
||||
(lastSpecInstallHash !== currentSpecInstallHash);
|
||||
|
||||
if (somethingChanged) {
|
||||
if (somethingChanged && !args.skipYarnInstall) {
|
||||
await installSpecModules(path.resolve(__dirname, '..', 'spec'));
|
||||
await getSpecHash().then(saveSpecHash);
|
||||
}
|
||||
@@ -375,7 +375,7 @@ async function runTestUsingElectron (specDir, testName, shouldRerun, additionalA
|
||||
if (shouldRerun) {
|
||||
await rerunFailedTests(specDir, testName);
|
||||
} else {
|
||||
return false;
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
console.log(`${pass} Electron ${testName} process tests passed.`);
|
||||
@@ -419,7 +419,8 @@ async function installSpecModules (dir) {
|
||||
if (fs.existsSync(path.resolve(dir, 'node_modules'))) {
|
||||
await fs.promises.rm(path.resolve(dir, 'node_modules'), { force: true, recursive: true });
|
||||
}
|
||||
const { status } = childProcess.spawnSync(NPX_CMD, [`yarn@${YARN_VERSION}`, 'install', '--frozen-lockfile'], {
|
||||
const yarnArgs = [YARN_SCRIPT_PATH, 'install', '--immutable'];
|
||||
const { status } = childProcess.spawnSync(process.execPath, yarnArgs, {
|
||||
env,
|
||||
cwd: dir,
|
||||
stdio: 'inherit',
|
||||
@@ -429,14 +430,27 @@ async function installSpecModules (dir) {
|
||||
console.log(`${fail} Failed to yarn install in '${dir}'`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (process.platform === 'linux') {
|
||||
const { status: rebuildStatus } = childProcess.spawnSync('npm', ['rebuild', 'abstract-socket'], {
|
||||
env,
|
||||
cwd: dir,
|
||||
stdio: 'inherit',
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
if (rebuildStatus !== 0) {
|
||||
console.log(`${fail} Failed to rebuild abstract-socket native module`);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function getSpecHash () {
|
||||
return Promise.all([
|
||||
(async () => {
|
||||
const hasher = crypto.createHash('SHA256');
|
||||
hasher.update(fs.readFileSync(path.resolve(__dirname, '../yarn.lock')));
|
||||
hasher.update(fs.readFileSync(path.resolve(__dirname, '../spec/package.json')));
|
||||
hasher.update(fs.readFileSync(path.resolve(__dirname, '../spec/yarn.lock')));
|
||||
hasher.update(fs.readFileSync(path.resolve(__dirname, '../script/spec-runner.js')));
|
||||
return hasher.digest('hex');
|
||||
})(),
|
||||
|
||||
18
script/yarn.js
Normal file → Executable file
18
script/yarn.js
Normal file → Executable file
@@ -1,21 +1,7 @@
|
||||
const cp = require('node:child_process');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const YARN_VERSION = /'yarn_version': '(.+?)'/.exec(fs.readFileSync(path.resolve(__dirname, '../DEPS'), 'utf8'))[1];
|
||||
const NPX_CMD = process.platform === 'win32' ? 'npx.cmd' : 'npx';
|
||||
exports.YARN_SCRIPT_PATH = path.resolve(__dirname, '..', '.yarn/releases/yarn-4.12.0.cjs');
|
||||
|
||||
if (require.main === module) {
|
||||
const child = cp.spawn(NPX_CMD, [`yarn@${YARN_VERSION}`, ...process.argv.slice(2)], {
|
||||
stdio: 'inherit',
|
||||
env: {
|
||||
...process.env,
|
||||
npm_config_yes: 'true'
|
||||
},
|
||||
shell: process.platform === 'win32'
|
||||
});
|
||||
|
||||
child.on('exit', code => process.exit(code));
|
||||
require(exports.YARN_SCRIPT_PATH);
|
||||
}
|
||||
|
||||
exports.YARN_VERSION = YARN_VERSION;
|
||||
|
||||
@@ -43,13 +43,10 @@
|
||||
#include "shell/browser/electron_gpu_client.h"
|
||||
#include "shell/browser/feature_list.h"
|
||||
#include "shell/browser/relauncher.h"
|
||||
#include "shell/common/application_info.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "shell/common/logging.h"
|
||||
#include "shell/common/options_switches.h"
|
||||
#include "shell/common/platform_util.h"
|
||||
#include "shell/common/process_util.h"
|
||||
#include "shell/common/thread_restrictions.h"
|
||||
#include "shell/renderer/electron_renderer_client.h"
|
||||
#include "shell/renderer/electron_sandboxed_renderer_client.h"
|
||||
#include "shell/utility/electron_content_utility_client.h"
|
||||
@@ -121,100 +118,6 @@ void InvalidParameterHandler(const wchar_t*,
|
||||
}
|
||||
#endif
|
||||
|
||||
// TODO(nornagon): move path provider overriding to its own file in
|
||||
// shell/common
|
||||
bool ElectronPathProvider(int key, base::FilePath* result) {
|
||||
bool create_dir = false;
|
||||
base::FilePath cur;
|
||||
switch (key) {
|
||||
case chrome::DIR_USER_DATA:
|
||||
if (!base::PathService::Get(DIR_APP_DATA, &cur))
|
||||
return false;
|
||||
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
|
||||
GetPossiblyOverriddenApplicationName()));
|
||||
create_dir = true;
|
||||
break;
|
||||
case DIR_CRASH_DUMPS:
|
||||
if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur))
|
||||
return false;
|
||||
cur = cur.Append(FILE_PATH_LITERAL("Crashpad"));
|
||||
create_dir = true;
|
||||
break;
|
||||
case chrome::DIR_APP_DICTIONARIES:
|
||||
// TODO(nornagon): can we just default to using Chrome's logic here?
|
||||
if (!base::PathService::Get(DIR_SESSION_DATA, &cur))
|
||||
return false;
|
||||
cur = cur.Append(base::FilePath::FromUTF8Unsafe("Dictionaries"));
|
||||
create_dir = true;
|
||||
break;
|
||||
case DIR_SESSION_DATA:
|
||||
// By default and for backward, equivalent to DIR_USER_DATA.
|
||||
return base::PathService::Get(chrome::DIR_USER_DATA, result);
|
||||
case DIR_USER_CACHE: {
|
||||
#if BUILDFLAG(IS_POSIX)
|
||||
int parent_key = base::DIR_CACHE;
|
||||
#else
|
||||
// On Windows, there's no OS-level centralized location for caches, so
|
||||
// store the cache in the app data directory.
|
||||
int parent_key = base::DIR_ROAMING_APP_DATA;
|
||||
#endif
|
||||
if (!base::PathService::Get(parent_key, &cur))
|
||||
return false;
|
||||
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
|
||||
GetPossiblyOverriddenApplicationName()));
|
||||
create_dir = true;
|
||||
break;
|
||||
}
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
case DIR_APP_DATA: {
|
||||
auto env = base::Environment::Create();
|
||||
cur = base::nix::GetXDGDirectory(
|
||||
env.get(), base::nix::kXdgConfigHomeEnvVar, base::nix::kDotConfigDir);
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
case DIR_RECENT:
|
||||
if (!platform_util::GetFolderPath(DIR_RECENT, &cur))
|
||||
return false;
|
||||
create_dir = true;
|
||||
break;
|
||||
#endif
|
||||
case DIR_APP_LOGS:
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
if (!base::PathService::Get(base::DIR_HOME, &cur))
|
||||
return false;
|
||||
cur = cur.Append(FILE_PATH_LITERAL("Library"));
|
||||
cur = cur.Append(FILE_PATH_LITERAL("Logs"));
|
||||
cur = cur.Append(base::FilePath::FromUTF8Unsafe(
|
||||
GetPossiblyOverriddenApplicationName()));
|
||||
#else
|
||||
if (!base::PathService::Get(chrome::DIR_USER_DATA, &cur))
|
||||
return false;
|
||||
cur = cur.Append(base::FilePath::FromUTF8Unsafe("logs"));
|
||||
#endif
|
||||
create_dir = true;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO(bauerb): http://crbug.com/259796
|
||||
ScopedAllowBlockingForElectron allow_blocking;
|
||||
if (create_dir && !base::PathExists(cur) && !base::CreateDirectory(cur)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
*result = cur;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void RegisterPathProvider() {
|
||||
base::PathService::RegisterProvider(ElectronPathProvider, PATH_START,
|
||||
PATH_END);
|
||||
}
|
||||
|
||||
void ValidateV8Snapshot(v8::StartupData* data) {
|
||||
if (data->data &&
|
||||
electron::fuses::IsEmbeddedAsarIntegrityValidationEnabled()) {
|
||||
|
||||
@@ -155,7 +155,8 @@ void MenuMac::PopupOnUI(const base::WeakPtr<NativeWindow>& native_window,
|
||||
if (rightmostMenuPoint > screenRight)
|
||||
position.x = position.x - [menu size].width;
|
||||
|
||||
[popup_controllers_[window_id] setCloseCallback:std::move(close_callback)];
|
||||
[popup_controllers_[window_id]
|
||||
setPopupCloseCallback:std::move(close_callback)];
|
||||
|
||||
if (frame && frame->render_frame_host()) {
|
||||
auto* rfh = frame->render_frame_host()->GetOutermostMainFrameOrEmbedder();
|
||||
|
||||
528
shell/browser/api/electron_api_msix_updater.cc
Normal file
528
shell/browser/api/electron_api_msix_updater.cc
Normal file
@@ -0,0 +1,528 @@
|
||||
// Copyright (c) 2025 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/api/electron_api_msix_updater.h"
|
||||
|
||||
#include <sstream>
|
||||
#include <string_view>
|
||||
#include "base/environment.h"
|
||||
#include "base/functional/bind.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "base/task/task_traits.h"
|
||||
#include "base/task/thread_pool.h"
|
||||
#include "content/public/browser/browser_task_traits.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/browser/window_list.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/error_thrower.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include <appmodel.h>
|
||||
#include <roapi.h>
|
||||
#include <windows.applicationmodel.h>
|
||||
#include <windows.foundation.collections.h>
|
||||
#include <windows.foundation.h>
|
||||
#include <windows.foundation.metadata.h>
|
||||
#include <windows.h>
|
||||
#include <windows.management.deployment.h>
|
||||
// Use pre-generated C++/WinRT headers from //third_party/nearby instead of the
|
||||
// SDK's cppwinrt headers, which are missing implementation files.
|
||||
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.ApplicationModel.h"
|
||||
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.Collections.h"
|
||||
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.Metadata.h"
|
||||
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Foundation.h"
|
||||
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/Windows.Management.Deployment.h"
|
||||
#include "third_party/nearby/src/internal/platform/implementation/windows/generated/winrt/base.h"
|
||||
|
||||
#include "base/win/scoped_com_initializer.h"
|
||||
#endif
|
||||
|
||||
namespace electron {
|
||||
|
||||
const bool debug_msix_updater =
|
||||
base::Environment::Create()->HasVar("ELECTRON_DEBUG_MSIX_UPDATER");
|
||||
|
||||
} // namespace electron
|
||||
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Helper function for debug logging
|
||||
void DebugLog(std::string_view log_msg) {
|
||||
if (electron::debug_msix_updater)
|
||||
LOG(INFO) << std::string(log_msg);
|
||||
}
|
||||
|
||||
// Check if the process has a package identity
|
||||
bool HasPackageIdentity() {
|
||||
UINT32 length = 0;
|
||||
LONG rc = GetCurrentPackageFullName(&length, NULL);
|
||||
return rc != APPMODEL_ERROR_NO_PACKAGE;
|
||||
}
|
||||
|
||||
// POD struct to hold MSIX update options
|
||||
struct UpdateMsixOptions {
|
||||
bool defer_registration = false;
|
||||
bool developer_mode = false;
|
||||
bool force_shutdown = false;
|
||||
bool force_target_shutdown = false;
|
||||
bool force_update_from_any_version = false;
|
||||
};
|
||||
|
||||
// POD struct to hold package registration options
|
||||
struct RegisterPackageOptions {
|
||||
bool force_shutdown = false;
|
||||
bool force_target_shutdown = false;
|
||||
bool force_update_from_any_version = false;
|
||||
};
|
||||
|
||||
// Performs MSIX update on IO thread
|
||||
void DoUpdateMsix(const std::string& package_uri,
|
||||
UpdateMsixOptions opts,
|
||||
scoped_refptr<base::SingleThreadTaskRunner> reply_runner,
|
||||
gin_helper::Promise<void> promise) {
|
||||
DebugLog("DoUpdateMsix: Starting");
|
||||
|
||||
using winrt::Windows::Foundation::AsyncStatus;
|
||||
using winrt::Windows::Foundation::Uri;
|
||||
using winrt::Windows::Management::Deployment::AddPackageOptions;
|
||||
using winrt::Windows::Management::Deployment::DeploymentResult;
|
||||
using winrt::Windows::Management::Deployment::PackageManager;
|
||||
|
||||
std::string error;
|
||||
std::wstring packageUriString =
|
||||
std::wstring(package_uri.begin(), package_uri.end());
|
||||
Uri uri{packageUriString};
|
||||
PackageManager packageManager;
|
||||
AddPackageOptions packageOptions;
|
||||
|
||||
// Use the pre-parsed options
|
||||
packageOptions.DeferRegistrationWhenPackagesAreInUse(opts.defer_registration);
|
||||
packageOptions.DeveloperMode(opts.developer_mode);
|
||||
packageOptions.ForceAppShutdown(opts.force_shutdown);
|
||||
packageOptions.ForceTargetAppShutdown(opts.force_target_shutdown);
|
||||
packageOptions.ForceUpdateFromAnyVersion(opts.force_update_from_any_version);
|
||||
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Calling AddPackageByUriAsync... URI: " << package_uri;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Update options - deferRegistration: " << opts.defer_registration
|
||||
<< ", developerMode: " << opts.developer_mode
|
||||
<< ", forceShutdown: " << opts.force_shutdown
|
||||
<< ", forceTargetShutdown: " << opts.force_target_shutdown
|
||||
<< ", forceUpdateFromAnyVersion: "
|
||||
<< opts.force_update_from_any_version;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
|
||||
auto deploymentOperation =
|
||||
packageManager.AddPackageByUriAsync(uri, packageOptions);
|
||||
|
||||
if (!deploymentOperation) {
|
||||
DebugLog("Deployment operation is null");
|
||||
error =
|
||||
"Deployment is NULL. See "
|
||||
"http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing.";
|
||||
} else {
|
||||
if (!opts.force_shutdown && !opts.force_target_shutdown) {
|
||||
DebugLog("Waiting for deployment...");
|
||||
deploymentOperation.get();
|
||||
DebugLog("Deployment finished.");
|
||||
|
||||
if (deploymentOperation.Status() == AsyncStatus::Error) {
|
||||
auto deploymentResult{deploymentOperation.GetResults()};
|
||||
std::string errorText = winrt::to_string(deploymentResult.ErrorText());
|
||||
std::string errorCode =
|
||||
std::to_string(static_cast<int>(deploymentOperation.ErrorCode()));
|
||||
error = errorText + " (" + errorCode + ")";
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Deployment failed: " << error;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
} else if (deploymentOperation.Status() == AsyncStatus::Canceled) {
|
||||
DebugLog("Deployment canceled");
|
||||
error = "Deployment canceled";
|
||||
} else if (deploymentOperation.Status() == AsyncStatus::Completed) {
|
||||
DebugLog("MSIX Deployment completed.");
|
||||
} else {
|
||||
error = "Deployment status unknown";
|
||||
DebugLog("Deployment status unknown");
|
||||
}
|
||||
} else {
|
||||
// At this point, we can not await the deployment because we require a
|
||||
// shutdown of the app to continue, so we do a fire and forget. When the
|
||||
// deployment process tries ot shutdown the app, the process waits for us
|
||||
// to finish here. But to finish we need to shutdow. That leads to a 30s
|
||||
// dealock, till we forcefully get shutdown by the OS.
|
||||
DebugLog(
|
||||
"Deployment initiated. Force shutdown or target shutdown requested. "
|
||||
"Good bye!");
|
||||
}
|
||||
}
|
||||
|
||||
// Post result back
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
if (error.empty()) {
|
||||
promise.Resolve();
|
||||
} else {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
}
|
||||
},
|
||||
std::move(promise), error));
|
||||
}
|
||||
|
||||
// Performs package registration on IO thread
|
||||
void DoRegisterPackage(const std::string& family_name,
|
||||
RegisterPackageOptions opts,
|
||||
scoped_refptr<base::SingleThreadTaskRunner> reply_runner,
|
||||
gin_helper::Promise<void> promise) {
|
||||
DebugLog("DoRegisterPackage: Starting");
|
||||
|
||||
using winrt::Windows::Foundation::AsyncStatus;
|
||||
using winrt::Windows::Foundation::Collections::IIterable;
|
||||
using winrt::Windows::Management::Deployment::DeploymentOptions;
|
||||
using winrt::Windows::Management::Deployment::PackageManager;
|
||||
|
||||
std::string error;
|
||||
auto familyNameH = winrt::to_hstring(family_name);
|
||||
PackageManager packageManager;
|
||||
DeploymentOptions deploymentOptions = DeploymentOptions::None;
|
||||
|
||||
// Use the pre-parsed options (no V8 access needed)
|
||||
if (opts.force_shutdown) {
|
||||
deploymentOptions |= DeploymentOptions::ForceApplicationShutdown;
|
||||
}
|
||||
if (opts.force_target_shutdown) {
|
||||
deploymentOptions |= DeploymentOptions::ForceTargetApplicationShutdown;
|
||||
}
|
||||
if (opts.force_update_from_any_version) {
|
||||
deploymentOptions |= DeploymentOptions::ForceUpdateFromAnyVersion;
|
||||
}
|
||||
|
||||
// Create empty collections for dependency and optional packages
|
||||
IIterable<winrt::hstring> emptyDependencies{nullptr};
|
||||
IIterable<winrt::hstring> emptyOptional{nullptr};
|
||||
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Calling RegisterPackageByFamilyNameAsync... FamilyName: "
|
||||
<< family_name;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Registration options - forceShutdown: " << opts.force_shutdown
|
||||
<< ", forceTargetShutdown: " << opts.force_target_shutdown
|
||||
<< ", forceUpdateFromAnyVersion: "
|
||||
<< opts.force_update_from_any_version;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
|
||||
auto deploymentOperation = packageManager.RegisterPackageByFamilyNameAsync(
|
||||
familyNameH, emptyDependencies, deploymentOptions, nullptr,
|
||||
emptyOptional);
|
||||
|
||||
if (!deploymentOperation) {
|
||||
error =
|
||||
"Deployment is NULL. See "
|
||||
"http://go.microsoft.com/fwlink/?LinkId=235160 for diagnosing.";
|
||||
} else {
|
||||
if (!opts.force_shutdown && !opts.force_target_shutdown) {
|
||||
DebugLog("Waiting for registration...");
|
||||
deploymentOperation.get();
|
||||
DebugLog("Registration finished.");
|
||||
|
||||
if (deploymentOperation.Status() == AsyncStatus::Error) {
|
||||
auto deploymentResult{deploymentOperation.GetResults()};
|
||||
std::string errorText = winrt::to_string(deploymentResult.ErrorText());
|
||||
std::string errorCode =
|
||||
std::to_string(static_cast<int>(deploymentOperation.ErrorCode()));
|
||||
error = errorText + " (" + errorCode + ")";
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "Registration failed: " << error;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
} else if (deploymentOperation.Status() == AsyncStatus::Canceled) {
|
||||
DebugLog("Registration canceled");
|
||||
error = "Registration canceled";
|
||||
} else if (deploymentOperation.Status() == AsyncStatus::Completed) {
|
||||
DebugLog("MSIX Registration completed.");
|
||||
} else {
|
||||
error = "Registration status unknown";
|
||||
DebugLog("Registration status unknown");
|
||||
}
|
||||
} else {
|
||||
// At this point, we can not await the registration because we require a
|
||||
// shutdown of the app to continue, so we do a fire and forget. When the
|
||||
// registration process tries ot shutdown the app, the process waits for
|
||||
// us to finish here. But to finish we need to shutdown. That leads to a
|
||||
// 30s dealock, till we forcefully get shutdown by the OS.
|
||||
DebugLog(
|
||||
"Registration initiated. Force shutdown or target shutdown "
|
||||
"requested. Good bye!");
|
||||
}
|
||||
}
|
||||
|
||||
// Post result back to UI thread
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
if (error.empty()) {
|
||||
promise.Resolve();
|
||||
} else {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
}
|
||||
},
|
||||
std::move(promise), error));
|
||||
}
|
||||
#endif
|
||||
|
||||
// Update MSIX package
|
||||
v8::Local<v8::Promise> UpdateMsix(const std::string& package_uri,
|
||||
gin_helper::Dictionary options) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
gin_helper::Promise<void> promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (!HasPackageIdentity()) {
|
||||
DebugLog("UpdateMsix: The process has no package identity");
|
||||
promise.RejectWithErrorMessage("The process has no package identity.");
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Parse options on UI thread (where V8 is available)
|
||||
UpdateMsixOptions opts;
|
||||
options.Get("deferRegistration", &opts.defer_registration);
|
||||
options.Get("developerMode", &opts.developer_mode);
|
||||
options.Get("forceShutdown", &opts.force_shutdown);
|
||||
options.Get("forceTargetShutdown", &opts.force_target_shutdown);
|
||||
options.Get("forceUpdateFromAnyVersion", &opts.force_update_from_any_version);
|
||||
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "UpdateMsix called with URI: " << package_uri;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
|
||||
// Post to IO thread
|
||||
content::GetIOThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&DoUpdateMsix, package_uri, opts,
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault(),
|
||||
std::move(promise)));
|
||||
#else
|
||||
promise.RejectWithErrorMessage(
|
||||
"MSIX updates are only supported on Windows with identity.");
|
||||
#endif
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Register MSIX package
|
||||
v8::Local<v8::Promise> RegisterPackage(const std::string& family_name,
|
||||
gin_helper::Dictionary options) {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
gin_helper::Promise<void> promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (!HasPackageIdentity()) {
|
||||
DebugLog("RegisterPackage: The process has no package identity");
|
||||
promise.RejectWithErrorMessage("The process has no package identity.");
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Parse options on UI thread (where V8 is available)
|
||||
RegisterPackageOptions opts;
|
||||
options.Get("forceShutdown", &opts.force_shutdown);
|
||||
options.Get("forceTargetShutdown", &opts.force_target_shutdown);
|
||||
options.Get("forceUpdateFromAnyVersion", &opts.force_update_from_any_version);
|
||||
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "RegisterPackage called with family name: " << family_name;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
|
||||
// Post to IO thread with POD options (no V8 objects)
|
||||
content::GetIOThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&DoRegisterPackage, family_name, opts,
|
||||
base::SingleThreadTaskRunner::GetCurrentDefault(),
|
||||
std::move(promise)));
|
||||
#else
|
||||
promise.RejectWithErrorMessage(
|
||||
"MSIX package registration is only supported on Windows.");
|
||||
#endif
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Register application restart
|
||||
// Only registers for update restarts (not crashes, hangs, or reboots)
|
||||
bool RegisterRestartOnUpdate(const std::string& command_line) {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (!HasPackageIdentity()) {
|
||||
DebugLog("Cannot restart: no package identity");
|
||||
return false;
|
||||
}
|
||||
|
||||
const wchar_t* commandLine = nullptr;
|
||||
// Flags: RESTART_NO_CRASH | RESTART_NO_HANG | RESTART_NO_REBOOT
|
||||
// This means: only restart on updates (RESTART_NO_PATCH is NOT set)
|
||||
const DWORD dwFlags = 1 | 2 | 8; // 11
|
||||
|
||||
if (!command_line.empty()) {
|
||||
std::wstring commandLineW =
|
||||
std::wstring(command_line.begin(), command_line.end());
|
||||
commandLine = commandLineW.c_str();
|
||||
}
|
||||
|
||||
HRESULT hr = RegisterApplicationRestart(commandLine, dwFlags);
|
||||
if (FAILED(hr)) {
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "RegisterApplicationRestart failed with error code: " << hr;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "RegisterApplicationRestart succeeded"
|
||||
<< (command_line.empty() ? "" : " with command line");
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Get package information
|
||||
v8::Local<v8::Value> GetPackageInfo() {
|
||||
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Check if running in a package
|
||||
if (!HasPackageIdentity()) {
|
||||
DebugLog("GetPackageInfo: The process has no package identity");
|
||||
gin_helper::ErrorThrower thrower(isolate);
|
||||
thrower.ThrowTypeError("The process has no package identity.");
|
||||
return v8::Null(isolate);
|
||||
}
|
||||
|
||||
DebugLog("GetPackageInfo: Retrieving package information");
|
||||
|
||||
gin_helper::Dictionary result(isolate, v8::Object::New(isolate));
|
||||
|
||||
// Check API contract version (Windows 10 version 1703 or later)
|
||||
if (winrt::Windows::Foundation::Metadata::ApiInformation::
|
||||
IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7)) {
|
||||
using winrt::Windows::ApplicationModel::Package;
|
||||
using winrt::Windows::ApplicationModel::PackageSignatureKind;
|
||||
Package package = Package::Current();
|
||||
|
||||
// Get package ID and family name
|
||||
std::string packageId = winrt::to_string(package.Id().FullName());
|
||||
std::string familyName = winrt::to_string(package.Id().FamilyName());
|
||||
|
||||
result.Set("id", packageId);
|
||||
result.Set("familyName", familyName);
|
||||
result.Set("developmentMode", package.IsDevelopmentMode());
|
||||
|
||||
// Get package version
|
||||
auto packageVersion = package.Id().Version();
|
||||
std::string version = std::to_string(packageVersion.Major) + "." +
|
||||
std::to_string(packageVersion.Minor) + "." +
|
||||
std::to_string(packageVersion.Build) + "." +
|
||||
std::to_string(packageVersion.Revision);
|
||||
result.Set("version", version);
|
||||
|
||||
// Convert signature kind to string
|
||||
std::string signatureKind;
|
||||
switch (package.SignatureKind()) {
|
||||
case PackageSignatureKind::Developer:
|
||||
signatureKind = "developer";
|
||||
break;
|
||||
case PackageSignatureKind::Enterprise:
|
||||
signatureKind = "enterprise";
|
||||
break;
|
||||
case PackageSignatureKind::None:
|
||||
signatureKind = "none";
|
||||
break;
|
||||
case PackageSignatureKind::Store:
|
||||
signatureKind = "store";
|
||||
break;
|
||||
case PackageSignatureKind::System:
|
||||
signatureKind = "system";
|
||||
break;
|
||||
default:
|
||||
signatureKind = "none";
|
||||
break;
|
||||
}
|
||||
result.Set("signatureKind", signatureKind);
|
||||
|
||||
// Get app installer info if available
|
||||
auto appInstallerInfo = package.GetAppInstallerInfo();
|
||||
if (appInstallerInfo != nullptr) {
|
||||
std::string uriStr = winrt::to_string(appInstallerInfo.Uri().ToString());
|
||||
result.Set("appInstallerUri", uriStr);
|
||||
}
|
||||
} else {
|
||||
// Windows version doesn't meet minimum API requirements
|
||||
result.Set("familyName", "");
|
||||
result.Set("id", "");
|
||||
result.Set("developmentMode", false);
|
||||
result.Set("signatureKind", "none");
|
||||
result.Set("version", "");
|
||||
}
|
||||
|
||||
return result.GetHandle();
|
||||
#else
|
||||
// Non-Windows platforms
|
||||
gin_helper::Dictionary result(isolate, v8::Object::New(isolate));
|
||||
result.Set("familyName", "");
|
||||
result.Set("id", "");
|
||||
result.Set("developmentMode", false);
|
||||
result.Set("signatureKind", "none");
|
||||
result.Set("version", "");
|
||||
return result.GetHandle();
|
||||
#endif
|
||||
}
|
||||
|
||||
void Initialize(v8::Local<v8::Object> exports,
|
||||
v8::Local<v8::Value> unused,
|
||||
v8::Local<v8::Context> context,
|
||||
void* priv) {
|
||||
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
|
||||
gin_helper::Dictionary dict(isolate, exports);
|
||||
|
||||
dict.SetMethod("updateMsix", base::BindRepeating(&UpdateMsix));
|
||||
dict.SetMethod("registerPackage", base::BindRepeating(&RegisterPackage));
|
||||
dict.SetMethod("registerRestartOnUpdate",
|
||||
base::BindRepeating(&RegisterRestartOnUpdate));
|
||||
dict.SetMethod("getPackageInfo",
|
||||
base::BindRepeating([]() { return GetPackageInfo(); }));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_msix_updater, Initialize)
|
||||
14
shell/browser/api/electron_api_msix_updater.h
Normal file
14
shell/browser/api/electron_api_msix_updater.h
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright (c) 2013 GitHub, Inc.
|
||||
// Use of this source code is governed by the MIT license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_
|
||||
#define ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_
|
||||
|
||||
namespace electron {
|
||||
|
||||
extern const bool debug_msix_updater;
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_API_ELECTRON_API_MSIX_UPDATER_H_
|
||||
@@ -1037,6 +1037,9 @@ void WebContents::InitWithWebContents(
|
||||
}
|
||||
|
||||
WebContents::~WebContents() {
|
||||
if (inspectable_web_contents_)
|
||||
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
|
||||
|
||||
if (owner_window_) {
|
||||
owner_window_->RemoveBackgroundThrottlingSource(this);
|
||||
}
|
||||
@@ -1051,8 +1054,6 @@ WebContents::~WebContents() {
|
||||
return;
|
||||
}
|
||||
|
||||
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
|
||||
|
||||
// This event is only for internal use, which is emitted when WebContents is
|
||||
// being destroyed.
|
||||
Emit("will-destroy");
|
||||
@@ -2189,8 +2190,8 @@ void WebContents::DevToolsOpened() {
|
||||
// Inherit owner window in devtools when it doesn't have one.
|
||||
auto* devtools = inspectable_web_contents_->GetDevToolsWebContents();
|
||||
bool has_window = devtools->GetUserData(NativeWindowRelay::UserDataKey());
|
||||
if (owner_window_ && !has_window) {
|
||||
DCHECK(!owner_window_.WasInvalidated());
|
||||
if (owner_window() && !has_window) {
|
||||
CHECK(!owner_window_.WasInvalidated());
|
||||
DCHECK_EQ(handle->owner_window(), nullptr);
|
||||
handle->SetOwnerWindow(devtools, owner_window());
|
||||
}
|
||||
@@ -2395,6 +2396,9 @@ void WebContents::LoadURL(const GURL& url,
|
||||
return;
|
||||
}
|
||||
|
||||
if (web_contents()->NeedToFireBeforeUnloadOrUnloadEvents())
|
||||
pending_unload_url_ = url;
|
||||
|
||||
// Discard non-committed entries to ensure we don't re-use a pending entry.
|
||||
web_contents()->GetController().DiscardNonCommittedEntries();
|
||||
web_contents()->GetController().LoadURLWithParams(params);
|
||||
@@ -3897,8 +3901,15 @@ void WebContents::RunBeforeUnloadDialog(content::WebContents* web_contents,
|
||||
content::RenderFrameHost* rfh,
|
||||
bool is_reload,
|
||||
DialogClosedCallback callback) {
|
||||
// TODO: asyncify?
|
||||
bool default_prevented = Emit("will-prevent-unload");
|
||||
|
||||
if (pending_unload_url_.has_value() && !default_prevented) {
|
||||
Emit("did-fail-load", static_cast<int>(net::ERR_ABORTED),
|
||||
net::ErrorToShortString(net::ERR_ABORTED),
|
||||
pending_unload_url_.value().possibly_invalid_spec(), true);
|
||||
pending_unload_url_.reset();
|
||||
}
|
||||
|
||||
std::move(callback).Run(default_prevented, std::u16string());
|
||||
}
|
||||
|
||||
|
||||
@@ -844,6 +844,8 @@ class WebContents final : public ExclusiveAccessContext,
|
||||
// that field to ensure the dtor destroys them in the right order.
|
||||
raw_ptr<WebContentsZoomController> zoom_controller_ = nullptr;
|
||||
|
||||
std::optional<GURL> pending_unload_url_ = std::nullopt;
|
||||
|
||||
// Maps url to file path, used by the file requests sent from devtools.
|
||||
typedef std::map<std::string, base::FilePath> PathsMap;
|
||||
PathsMap saved_files_;
|
||||
|
||||
@@ -5,12 +5,14 @@
|
||||
#include "shell/browser/api/electron_api_web_request.h"
|
||||
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <utility>
|
||||
|
||||
#include "base/containers/fixed_flat_map.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
#include "base/task/sequenced_task_runner.h"
|
||||
#include "base/values.h"
|
||||
#include "content/public/browser/web_contents.h"
|
||||
@@ -25,6 +27,7 @@
|
||||
#include "shell/browser/api/electron_api_web_frame_main.h"
|
||||
#include "shell/browser/electron_browser_context.h"
|
||||
#include "shell/browser/javascript_environment.h"
|
||||
#include "shell/browser/login_handler.h"
|
||||
#include "shell/common/gin_converters/callback_converter.h"
|
||||
#include "shell/common/gin_converters/frame_converter.h"
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
@@ -108,7 +111,7 @@ v8::Local<v8::Value> HttpResponseHeadersToV8(
|
||||
|
||||
// Overloaded by multiple types to fill the |details| object.
|
||||
void ToDictionary(gin_helper::Dictionary* details,
|
||||
extensions::WebRequestInfo* info) {
|
||||
const extensions::WebRequestInfo* info) {
|
||||
details->Set("id", info->id);
|
||||
details->Set("url", info->url);
|
||||
details->Set("method", info->method);
|
||||
@@ -255,7 +258,7 @@ bool WebRequest::RequestFilter::MatchesType(
|
||||
}
|
||||
|
||||
bool WebRequest::RequestFilter::MatchesRequest(
|
||||
extensions::WebRequestInfo* info) const {
|
||||
const extensions::WebRequestInfo* info) const {
|
||||
// Matches URL and type, and does not match exclude URL.
|
||||
return MatchesURL(info->url, include_url_patterns_) &&
|
||||
!MatchesURL(info->url, exclude_url_patterns_) &&
|
||||
@@ -287,6 +290,10 @@ struct WebRequest::BlockedRequest {
|
||||
net::CompletionOnceCallback callback;
|
||||
// Only used for onBeforeSendHeaders.
|
||||
BeforeSendHeadersCallback before_send_headers_callback;
|
||||
// The callback to invoke for auth. If |auth_callback.is_null()| is false,
|
||||
// |callback| must be NULL.
|
||||
// Only valid for OnAuthRequired.
|
||||
AuthCallback auth_callback;
|
||||
// Only used for onBeforeSendHeaders.
|
||||
raw_ptr<net::HttpRequestHeaders> request_headers = nullptr;
|
||||
// Only used for onHeadersReceived.
|
||||
@@ -297,6 +304,8 @@ struct WebRequest::BlockedRequest {
|
||||
std::string status_line;
|
||||
// Only used for onBeforeRequest.
|
||||
raw_ptr<GURL> new_url = nullptr;
|
||||
// Owns the LoginHandler while waiting for auth credentials.
|
||||
std::unique_ptr<LoginHandler> login_handler;
|
||||
};
|
||||
|
||||
WebRequest::SimpleListenerInfo::SimpleListenerInfo(RequestFilter filter_,
|
||||
@@ -603,6 +612,36 @@ void WebRequest::OnSendHeaders(extensions::WebRequestInfo* info,
|
||||
HandleSimpleEvent(SimpleEvent::kOnSendHeaders, info, request, headers);
|
||||
}
|
||||
|
||||
WebRequest::AuthRequiredResponse WebRequest::OnAuthRequired(
|
||||
const extensions::WebRequestInfo* request_info,
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
WebRequest::AuthCallback callback,
|
||||
net::AuthCredentials* credentials) {
|
||||
content::RenderFrameHost* rfh = content::RenderFrameHost::FromID(
|
||||
request_info->render_process_id, request_info->frame_routing_id);
|
||||
content::WebContents* web_contents = nullptr;
|
||||
if (rfh)
|
||||
web_contents = content::WebContents::FromRenderFrameHost(rfh);
|
||||
|
||||
BlockedRequest blocked_request;
|
||||
blocked_request.auth_callback = std::move(callback);
|
||||
blocked_requests_[request_info->id] = std::move(blocked_request);
|
||||
|
||||
auto login_callback =
|
||||
base::BindOnce(&WebRequest::OnLoginAuthResult, base::Unretained(this),
|
||||
request_info->id, credentials);
|
||||
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers =
|
||||
request_info->response_headers;
|
||||
blocked_requests_[request_info->id].login_handler =
|
||||
std::make_unique<LoginHandler>(
|
||||
auth_info, web_contents,
|
||||
static_cast<base::ProcessId>(request_info->render_process_id),
|
||||
request_info->url, response_headers, std::move(login_callback));
|
||||
|
||||
return AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_IO_PENDING;
|
||||
}
|
||||
|
||||
void WebRequest::OnBeforeRedirect(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const GURL& new_location) {
|
||||
@@ -732,6 +771,26 @@ void WebRequest::HandleSimpleEvent(SimpleEvent event,
|
||||
info.listener.Run(gin::ConvertToV8(isolate, details));
|
||||
}
|
||||
|
||||
void WebRequest::OnLoginAuthResult(
|
||||
uint64_t id,
|
||||
net::AuthCredentials* credentials,
|
||||
const std::optional<net::AuthCredentials>& maybe_creds) {
|
||||
auto iter = blocked_requests_.find(id);
|
||||
if (iter == blocked_requests_.end())
|
||||
NOTREACHED();
|
||||
|
||||
AuthRequiredResponse action =
|
||||
AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_NO_ACTION;
|
||||
if (maybe_creds.has_value()) {
|
||||
*credentials = maybe_creds.value();
|
||||
action = AuthRequiredResponse::AUTH_REQUIRED_RESPONSE_SET_AUTH;
|
||||
}
|
||||
|
||||
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
|
||||
FROM_HERE, base::BindOnce(std::move(iter->second.auth_callback), action));
|
||||
blocked_requests_.erase(iter);
|
||||
}
|
||||
|
||||
// static
|
||||
gin_helper::Handle<WebRequest> WebRequest::FromOrCreate(
|
||||
v8::Isolate* isolate,
|
||||
|
||||
@@ -82,6 +82,11 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
|
||||
void OnSendHeaders(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const net::HttpRequestHeaders& headers) override;
|
||||
AuthRequiredResponse OnAuthRequired(
|
||||
const extensions::WebRequestInfo* info,
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
AuthCallback callback,
|
||||
net::AuthCredentials* credentials) override;
|
||||
void OnBeforeRedirect(extensions::WebRequestInfo* info,
|
||||
const network::ResourceRequest& request,
|
||||
const GURL& new_location) override;
|
||||
@@ -157,6 +162,12 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
|
||||
v8::Local<v8::Value> response);
|
||||
void OnHeadersReceivedListenerResult(uint64_t id,
|
||||
v8::Local<v8::Value> response);
|
||||
// Callback invoked by LoginHandler when auth credentials are supplied via
|
||||
// the unified 'login' event. Bridges back into WebRequest's AuthCallback.
|
||||
void OnLoginAuthResult(
|
||||
uint64_t id,
|
||||
net::AuthCredentials* credentials,
|
||||
const std::optional<net::AuthCredentials>& maybe_creds);
|
||||
|
||||
class RequestFilter {
|
||||
public:
|
||||
@@ -174,7 +185,7 @@ class WebRequest final : public gin_helper::DeprecatedWrappable<WebRequest>,
|
||||
bool is_match_pattern = true);
|
||||
void AddType(extensions::WebRequestResourceType type);
|
||||
|
||||
bool MatchesRequest(extensions::WebRequestInfo* info) const;
|
||||
bool MatchesRequest(const extensions::WebRequestInfo* info) const;
|
||||
|
||||
private:
|
||||
bool MatchesURL(const GURL& url,
|
||||
|
||||
@@ -42,6 +42,23 @@ LoginHandler::LoginHandler(
|
||||
response_headers, first_auth_attempt));
|
||||
}
|
||||
|
||||
LoginHandler::LoginHandler(
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
content::WebContents* web_contents,
|
||||
base::ProcessId process_id,
|
||||
const GURL& url,
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback)
|
||||
: LoginHandler(auth_info,
|
||||
web_contents,
|
||||
/*is_request_for_primary_main_frame=*/false,
|
||||
/*is_request_for_navigation=*/false,
|
||||
process_id,
|
||||
url,
|
||||
std::move(response_headers),
|
||||
/*first_auth_attempt=*/false,
|
||||
std::move(auth_required_callback)) {}
|
||||
|
||||
void LoginHandler::EmitEvent(
|
||||
net::AuthChallengeInfo auth_info,
|
||||
content::WebContents* web_contents,
|
||||
|
||||
@@ -32,6 +32,13 @@ class LoginHandler : public content::LoginDelegate {
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
bool first_auth_attempt,
|
||||
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
|
||||
LoginHandler(
|
||||
const net::AuthChallengeInfo& auth_info,
|
||||
content::WebContents* web_contents,
|
||||
base::ProcessId process_id,
|
||||
const GURL& url,
|
||||
scoped_refptr<net::HttpResponseHeaders> response_headers,
|
||||
content::LoginDelegate::LoginAuthRequiredCallback auth_required_callback);
|
||||
~LoginHandler() override;
|
||||
|
||||
// disable copy
|
||||
|
||||
@@ -56,11 +56,6 @@ static NSDictionary* UNNotificationResponseToNSDictionary(
|
||||
}
|
||||
|
||||
- (void)applicationWillFinishLaunching:(NSNotification*)notify {
|
||||
// Don't add the "Enter Full Screen" menu item automatically.
|
||||
[[NSUserDefaults standardUserDefaults]
|
||||
setBool:NO
|
||||
forKey:@"NSFullScreenMenuItemEverywhere"];
|
||||
|
||||
[[[NSWorkspace sharedWorkspace] notificationCenter]
|
||||
addObserver:self
|
||||
selector:@selector(willPowerOff:)
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user