mirror of
https://github.com/electron/electron.git
synced 2026-02-26 03:01:17 -05:00
Compare commits
19 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a229dbf7a5 | ||
|
|
cad033849b | ||
|
|
16fc71f561 | ||
|
|
f521b01eb8 | ||
|
|
1a9c08914b | ||
|
|
0aba4a6ab8 | ||
|
|
c49899af4c | ||
|
|
92ef86b64a | ||
|
|
30d8e1834c | ||
|
|
b35f4eeaf0 | ||
|
|
63f7692da5 | ||
|
|
a4af2354dc | ||
|
|
bd49864f1d | ||
|
|
abc5d1280d | ||
|
|
712bafde02 | ||
|
|
013768c429 | ||
|
|
681a2c1aba | ||
|
|
98521d22ec | ||
|
|
faa40332ad |
6
.github/actions/build-electron/action.yml
vendored
6
.github/actions/build-electron/action.yml
vendored
@@ -188,6 +188,7 @@ runs:
|
||||
- name: Publish Electron Dist ${{ inputs.step-suffix }}
|
||||
if: ${{ inputs.is-release == 'true' }}
|
||||
shell: bash
|
||||
id: github-upload
|
||||
run: |
|
||||
rm -rf src/out/Default/obj
|
||||
cd src/electron
|
||||
@@ -198,6 +199,11 @@ runs:
|
||||
echo 'Uploading Electron release distribution to GitHub releases'
|
||||
script/release/uploaders/upload.py --verbose
|
||||
fi
|
||||
- name: Generate artifact attestation
|
||||
if: ${{ inputs.is-release == 'true' }}
|
||||
uses: actions/attest-build-provenance@96278af6caaf10aea03fd8d33a09a777ca52d62f # v3.2.0
|
||||
with:
|
||||
subject-path: ${{ steps.github-upload.outputs.UPLOADED_PATHS }}
|
||||
- name: Generate siso report
|
||||
if: ${{ inputs.target-platform != 'win' && !cancelled() }}
|
||||
shell: bash
|
||||
|
||||
16
.github/problem-matchers/markdownlint.json
vendored
Normal file
16
.github/problem-matchers/markdownlint.json
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "markdownlint",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.+):(\\d+):(\\d+)\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"message": 4
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
11
.github/workflows/apply-patches.yml
vendored
11
.github/workflows/apply-patches.yml
vendored
@@ -56,16 +56,17 @@ jobs:
|
||||
path: src/electron
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Rebase onto Base Branch
|
||||
ref: ${{ github.event.pull_request.base.ref }}
|
||||
- name: Merge PR HEAD
|
||||
working-directory: src/electron
|
||||
env:
|
||||
BASE_REF: ${{ github.event.pull_request.base.ref }}
|
||||
PR_NUMBER: ${{ github.event.pull_request.number }}
|
||||
run: |
|
||||
git config user.email "electron@github.com"
|
||||
git config user.name "Electron Bot"
|
||||
git fetch origin ${BASE_REF}
|
||||
git rebase origin/${BASE_REF}
|
||||
git fetch origin refs/pull/${PR_NUMBER}/head
|
||||
git merge --squash FETCH_HEAD
|
||||
git commit -n -m "Squashed commits"
|
||||
- name: Checkout & Sync & Save
|
||||
uses: ./src/electron/.github/actions/checkout
|
||||
with:
|
||||
|
||||
15
.github/workflows/linux-publish.yml
vendored
15
.github/workflows/linux-publish.yml
vendored
@@ -43,9 +43,12 @@ jobs:
|
||||
uses: ./src/electron/.github/actions/checkout
|
||||
|
||||
publish-x64:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-linux
|
||||
with:
|
||||
environment: production-release
|
||||
@@ -60,9 +63,12 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
publish-arm:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-linux
|
||||
with:
|
||||
environment: production-release
|
||||
@@ -77,9 +83,12 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
publish-arm64:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-linux
|
||||
with:
|
||||
environment: production-release
|
||||
|
||||
20
.github/workflows/macos-publish.yml
vendored
20
.github/workflows/macos-publish.yml
vendored
@@ -47,9 +47,12 @@ jobs:
|
||||
target-platform: macos
|
||||
|
||||
publish-x64-darwin:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-macos
|
||||
with:
|
||||
environment: production-release
|
||||
@@ -64,9 +67,12 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
publish-x64-mas:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-macos
|
||||
with:
|
||||
environment: production-release
|
||||
@@ -81,9 +87,12 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
publish-arm64-darwin:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-macos
|
||||
with:
|
||||
environment: production-release
|
||||
@@ -98,9 +107,12 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
publish-arm64-mas:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-macos
|
||||
with:
|
||||
environment: production-release
|
||||
|
||||
12
.github/workflows/pipeline-electron-lint.yml
vendored
12
.github/workflows/pipeline-electron-lint.yml
vendored
@@ -65,9 +65,11 @@ jobs:
|
||||
curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/buildtools/DEPS?format=TEXT" | base64 -d > src/buildtools/DEPS
|
||||
|
||||
gclient sync --spec="solutions=[{'name':'src/buildtools','url':None,'deps_file':'DEPS','custom_vars':{'process_deps':True},'managed':False}]"
|
||||
- name: Add ESLint problem matcher
|
||||
- name: Add problem matchers
|
||||
shell: bash
|
||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/eslint-stylish.json"
|
||||
run: |
|
||||
echo "::add-matcher::src/electron/.github/problem-matchers/eslint-stylish.json"
|
||||
echo "::add-matcher::src/electron/.github/problem-matchers/markdownlint.json"
|
||||
- name: Run Lint
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -85,4 +87,8 @@ jobs:
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/yarn.js tsc -p tsconfig.script.json
|
||||
|
||||
- name: Check GHA Workflows
|
||||
shell: bash
|
||||
run: |
|
||||
cd src/electron
|
||||
node script/copy-pipeline-segment-publish.js --check
|
||||
|
||||
237
.github/workflows/pipeline-segment-electron-publish.yml
vendored
Normal file
237
.github/workflows/pipeline-segment-electron-publish.yml
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
# AUTOGENERATED FILE - DO NOT EDIT MANUALLY
|
||||
# ONLY EDIT .github/workflows/pipeline-segment-electron-build.yml
|
||||
|
||||
name: Pipeline Segment - Electron Build
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
environment:
|
||||
description: using the production or testing environment
|
||||
required: false
|
||||
type: string
|
||||
target-platform:
|
||||
type: string
|
||||
description: Platform to run on, can be macos, win or linux
|
||||
required: true
|
||||
target-arch:
|
||||
type: string
|
||||
description: Arch to build for, can be x64, arm64, ia32 or arm
|
||||
required: true
|
||||
target-variant:
|
||||
type: string
|
||||
description: Variant to build for, no effect on non-macOS target platforms. Can
|
||||
be darwin, mas or all.
|
||||
default: all
|
||||
build-runs-on:
|
||||
type: string
|
||||
description: What host to run the build
|
||||
required: true
|
||||
build-container:
|
||||
type: string
|
||||
description: JSON container information for aks runs-on
|
||||
required: false
|
||||
default: '{"image":null}'
|
||||
is-release:
|
||||
description: Whether this build job is a release job
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
gn-build-type:
|
||||
description: The gn build type - testing or release
|
||||
required: true
|
||||
type: string
|
||||
default: testing
|
||||
generate-symbols:
|
||||
description: Whether or not to generate symbols
|
||||
required: true
|
||||
type: boolean
|
||||
default: false
|
||||
upload-to-storage:
|
||||
description: Whether or not to upload build artifacts to external storage
|
||||
required: true
|
||||
type: string
|
||||
default: "0"
|
||||
is-asan:
|
||||
description: Building the Address Sanitizer (ASan) Linux build
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
enable-ssh:
|
||||
description: Enable SSH debugging
|
||||
required: false
|
||||
type: boolean
|
||||
default: false
|
||||
permissions: {}
|
||||
concurrency:
|
||||
group: electron-build-${{ inputs.target-platform }}-${{ inputs.target-arch
|
||||
}}-${{ inputs.target-variant }}-${{ inputs.is-asan }}-${{
|
||||
github.ref_protected == true && github.run_id || github.ref }}
|
||||
cancel-in-progress: ${{ github.ref_protected != true }}
|
||||
env:
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
CHROMIUM_GIT_COOKIE_WINDOWS_STRING: ${{ secrets.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }}
|
||||
DD_API_KEY: ${{ secrets.DD_API_KEY }}
|
||||
ELECTRON_ARTIFACTS_BLOB_STORAGE: ${{ secrets.ELECTRON_ARTIFACTS_BLOB_STORAGE }}
|
||||
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
|
||||
SUDOWOODO_EXCHANGE_URL: ${{ secrets.SUDOWOODO_EXCHANGE_URL }}
|
||||
SUDOWOODO_EXCHANGE_TOKEN: ${{ secrets.SUDOWOODO_EXCHANGE_TOKEN }}
|
||||
GCLIENT_EXTRA_ARGS: ${{ inputs.target-platform == 'macos' &&
|
||||
'--custom-var=checkout_mac=True --custom-var=host_os=mac' ||
|
||||
inputs.target-platform == 'win' && '--custom-var=checkout_win=True' ||
|
||||
'--custom-var=checkout_arm=True --custom-var=checkout_arm64=True' }}
|
||||
ELECTRON_OUT_DIR: Default
|
||||
ACTIONS_STEP_DEBUG: ${{ secrets.ACTIONS_STEP_DEBUG }}
|
||||
jobs:
|
||||
build:
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
runs-on: ${{ inputs.build-runs-on }}
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
container: ${{ fromJSON(inputs.build-container) }}
|
||||
environment: ${{ inputs.environment }}
|
||||
env:
|
||||
TARGET_ARCH: ${{ inputs.target-arch }}
|
||||
TARGET_PLATFORM: ${{ inputs.target-platform }}
|
||||
steps:
|
||||
- name: Create src dir
|
||||
run: |
|
||||
mkdir src
|
||||
- name: Checkout Electron
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
path: src/electron
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Setup SSH Debugging
|
||||
if: ${{ inputs.target-platform == 'macos' && (inputs.enable-ssh ||
|
||||
env.ACTIONS_STEP_DEBUG == 'true') }}
|
||||
uses: ./src/electron/.github/actions/ssh-debug
|
||||
with:
|
||||
tunnel: "true"
|
||||
env:
|
||||
CLOUDFLARE_TUNNEL_CERT: ${{ secrets.CLOUDFLARE_TUNNEL_CERT }}
|
||||
CLOUDFLARE_TUNNEL_HOSTNAME: ${{ vars.CLOUDFLARE_TUNNEL_HOSTNAME }}
|
||||
CLOUDFLARE_USER_CA_CERT: ${{ secrets.CLOUDFLARE_USER_CA_CERT }}
|
||||
AUTHORIZED_USERS: ${{ secrets.SSH_DEBUG_AUTHORIZED_USERS }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
- name: Free up space (macOS)
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
uses: ./src/electron/.github/actions/free-space-macos
|
||||
- name: Check disk space after freeing up space
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
run: df -h
|
||||
- name: Setup Node.js/npm
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
|
||||
with:
|
||||
node-version: 20.19.x
|
||||
cache: yarn
|
||||
cache-dependency-path: src/electron/yarn.lock
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Install AZCopy
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
run: brew install azcopy
|
||||
- name: Set GN_EXTRA_ARGS for Linux
|
||||
if: ${{ inputs.target-platform == 'linux' }}
|
||||
run: >
|
||||
if [ "${{ inputs.target-arch }}" = "arm" ]; then
|
||||
if [ "${{ inputs.is-release }}" = true ]; then
|
||||
GN_EXTRA_ARGS='target_cpu="arm" build_tflite_with_xnnpack=false symbol_level=1'
|
||||
else
|
||||
GN_EXTRA_ARGS='target_cpu="arm" build_tflite_with_xnnpack=false'
|
||||
fi
|
||||
elif [ "${{ inputs.target-arch }}" = "arm64" ]; then
|
||||
GN_EXTRA_ARGS='target_cpu="arm64" fatal_linker_warnings=false enable_linux_installer=false'
|
||||
elif [ "${{ inputs.is-asan }}" = true ]; then
|
||||
GN_EXTRA_ARGS='is_asan=true'
|
||||
fi
|
||||
|
||||
echo "GN_EXTRA_ARGS=$GN_EXTRA_ARGS" >> $GITHUB_ENV
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Install Build Tools
|
||||
uses: ./src/electron/.github/actions/install-build-tools
|
||||
- name: Generate DEPS Hash
|
||||
run: |
|
||||
node src/electron/script/generate-deps-hash.js
|
||||
DEPSHASH=v1-src-cache-$(cat src/electron/.depshash)
|
||||
echo "DEPSHASH=$DEPSHASH" >> $GITHUB_ENV
|
||||
echo "CACHE_PATH=$DEPSHASH.tar" >> $GITHUB_ENV
|
||||
- name: Restore src cache via AZCopy
|
||||
if: ${{ inputs.target-platform != 'linux' }}
|
||||
uses: ./src/electron/.github/actions/restore-cache-azcopy
|
||||
with:
|
||||
target-platform: ${{ inputs.target-platform }}
|
||||
- name: Restore src cache via AKS
|
||||
if: ${{ inputs.target-platform == 'linux' }}
|
||||
uses: ./src/electron/.github/actions/restore-cache-aks
|
||||
- name: Checkout Electron
|
||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||
with:
|
||||
path: src/electron
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Fix Sync
|
||||
if: ${{ inputs.target-platform != 'linux' }}
|
||||
uses: ./src/electron/.github/actions/fix-sync
|
||||
with:
|
||||
target-platform: ${{ inputs.target-platform }}
|
||||
env:
|
||||
ELECTRON_DEPOT_TOOLS_DISABLE_LOG: true
|
||||
- name: Init Build Tools
|
||||
run: >
|
||||
e init -f --root=$(pwd) --out=Default ${{ inputs.gn-build-type }}
|
||||
--import ${{ inputs.gn-build-type }} --target-cpu ${{
|
||||
inputs.target-arch }} --remote-build siso
|
||||
- name: Run Electron Only Hooks
|
||||
run: |
|
||||
e d gclient runhooks --spec="solutions=[{'name':'src/electron','url':None,'deps_file':'DEPS','custom_vars':{'process_deps':False},'managed':False}]"
|
||||
- name: Regenerate DEPS Hash
|
||||
run: >
|
||||
(cd src/electron && git checkout .) && node
|
||||
src/electron/script/generate-deps-hash.js
|
||||
|
||||
echo "DEPSHASH=$(cat src/electron/.depshash)" >> $GITHUB_ENV
|
||||
- name: Add CHROMIUM_BUILDTOOLS_PATH to env
|
||||
run: echo "CHROMIUM_BUILDTOOLS_PATH=$(pwd)/src/buildtools" >> $GITHUB_ENV
|
||||
- name: Free up space (macOS)
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
uses: ./src/electron/.github/actions/free-space-macos
|
||||
- name: Build Electron
|
||||
if: ${{ inputs.target-platform != 'macos' || (inputs.target-variant == 'all' ||
|
||||
inputs.target-variant == 'darwin') }}
|
||||
uses: ./src/electron/.github/actions/build-electron
|
||||
with:
|
||||
target-arch: ${{ inputs.target-arch }}
|
||||
target-platform: ${{ inputs.target-platform }}
|
||||
artifact-platform: ${{ inputs.target-platform == 'macos' && 'darwin' ||
|
||||
inputs.target-platform }}
|
||||
is-release: ${{ inputs.is-release }}
|
||||
generate-symbols: ${{ inputs.generate-symbols }}
|
||||
upload-to-storage: ${{ inputs.upload-to-storage }}
|
||||
is-asan: ${{ inputs.is-asan }}
|
||||
- name: Set GN_EXTRA_ARGS for MAS Build
|
||||
if: ${{ inputs.target-platform == 'macos' && (inputs.target-variant == 'all' ||
|
||||
inputs.target-variant == 'mas') }}
|
||||
run: |
|
||||
echo "MAS_BUILD=true" >> $GITHUB_ENV
|
||||
GN_EXTRA_ARGS='is_mas_build=true'
|
||||
echo "GN_EXTRA_ARGS=$GN_EXTRA_ARGS" >> $GITHUB_ENV
|
||||
- name: Build Electron (MAS)
|
||||
if: ${{ inputs.target-platform == 'macos' && (inputs.target-variant == 'all' ||
|
||||
inputs.target-variant == 'mas') }}
|
||||
uses: ./src/electron/.github/actions/build-electron
|
||||
with:
|
||||
target-arch: ${{ inputs.target-arch }}
|
||||
target-platform: ${{ inputs.target-platform }}
|
||||
artifact-platform: mas
|
||||
is-release: ${{ inputs.is-release }}
|
||||
generate-symbols: ${{ inputs.generate-symbols }}
|
||||
upload-to-storage: ${{ inputs.upload-to-storage }}
|
||||
step-suffix: (mas)
|
||||
8
.github/workflows/rerun-apply-patches.yml
vendored
8
.github/workflows/rerun-apply-patches.yml
vendored
@@ -35,20 +35,20 @@ jobs:
|
||||
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')
|
||||
CHECK=$(gh pr view "$PR_NUMBER" --json statusCheckRollup --jq '[.statusCheckRollup[] | select(.workflowName == "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
|
||||
CONCLUSION=$(echo "$CHECK" | jq -r '.conclusion')
|
||||
if [ "$CONCLUSION" = "SKIPPED" ]; then
|
||||
echo " apply-patches job was skipped for PR #${PR_NUMBER} (no patches)"
|
||||
continue
|
||||
fi
|
||||
|
||||
LINK=$(echo "$CHECK" | jq -r '.link')
|
||||
LINK=$(echo "$CHECK" | jq -r '.detailsUrl')
|
||||
|
||||
# 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)
|
||||
|
||||
15
.github/workflows/windows-publish.yml
vendored
15
.github/workflows/windows-publish.yml
vendored
@@ -51,9 +51,12 @@ jobs:
|
||||
target-platform: win
|
||||
|
||||
publish-x64-win:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-windows
|
||||
with:
|
||||
environment: production-release
|
||||
@@ -67,9 +70,12 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
publish-arm64-win:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-windows
|
||||
with:
|
||||
environment: production-release
|
||||
@@ -83,9 +89,12 @@ jobs:
|
||||
secrets: inherit
|
||||
|
||||
publish-x86-win:
|
||||
uses: ./.github/workflows/pipeline-segment-electron-build.yml
|
||||
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
|
||||
permissions:
|
||||
artifact-metadata: write
|
||||
attestations: write
|
||||
contents: read
|
||||
id-token: write
|
||||
needs: checkout-windows
|
||||
with:
|
||||
environment: production-release
|
||||
|
||||
32
BUILD.gn
32
BUILD.gn
@@ -420,37 +420,6 @@ 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",
|
||||
@@ -466,7 +435,6 @@ source_set("electron_lib") {
|
||||
":electron_fuses",
|
||||
":electron_generate_node_defines",
|
||||
":electron_js2c",
|
||||
":electron_msix_updater",
|
||||
":electron_version_header",
|
||||
":resources",
|
||||
"buildflags",
|
||||
|
||||
@@ -756,6 +756,9 @@ Returns [`Rectangle`](structures/rectangle.md) - The `bounds` of the window as `
|
||||
> [!NOTE]
|
||||
> On macOS, the y-coordinate value returned will be at minimum the [Tray](tray.md) height. For example, calling `win.setBounds({ x: 25, y: 20, width: 800, height: 600 })` with a tray height of 38 means that `win.getBounds()` will return `{ x: 25, y: 38, width: 800, height: 600 }`.
|
||||
|
||||
> [!NOTE]
|
||||
> On Wayland, this method will return `{ x: 0, y: 0, ... }` as introspecting or programmatically changing the global window coordinates is prohibited.
|
||||
|
||||
#### `win.getBackgroundColor()`
|
||||
|
||||
Returns `string` - Gets the background color of the window in Hex (`#RRGGBB`) format.
|
||||
@@ -969,6 +972,9 @@ Moves window to `x` and `y`.
|
||||
|
||||
Returns `Integer[]` - Contains the window's current position.
|
||||
|
||||
> [!NOTE]
|
||||
> On Wayland, this method will return `[0, 0]` as introspecting or programmatically changing the global window coordinates is prohibited.
|
||||
|
||||
#### `win.setTitle(title)`
|
||||
|
||||
* `title` string
|
||||
|
||||
@@ -862,6 +862,9 @@ Returns [`Rectangle`](structures/rectangle.md) - The `bounds` of the window as `
|
||||
> [!NOTE]
|
||||
> On macOS, the y-coordinate value returned will be at minimum the [Tray](tray.md) height. For example, calling `win.setBounds({ x: 25, y: 20, width: 800, height: 600 })` with a tray height of 38 means that `win.getBounds()` will return `{ x: 25, y: 38, width: 800, height: 600 }`.
|
||||
|
||||
> [!NOTE]
|
||||
> On Wayland, this method will return `{ x: 0, y: 0, ... }` as introspecting or programmatically changing the global window coordinates is prohibited.
|
||||
|
||||
#### `win.getBackgroundColor()`
|
||||
|
||||
Returns `string` - Gets the background color of the window in Hex (`#RRGGBB`) format.
|
||||
@@ -1087,6 +1090,9 @@ Not supported on Wayland (Linux).
|
||||
|
||||
Returns `Integer[]` - Contains the window's current position.
|
||||
|
||||
> [!NOTE]
|
||||
> On Wayland, this method will return `[0, 0]` as introspecting or programmatically changing the global window coordinates is prohibited.
|
||||
|
||||
#### `win.setTitle(title)`
|
||||
|
||||
* `title` string
|
||||
|
||||
@@ -94,18 +94,45 @@ The `desktopCapturer` module has the following methods:
|
||||
Returns `Promise<DesktopCapturerSource[]>` - Resolves with an array of [`DesktopCapturerSource`](structures/desktop-capturer-source.md) objects, each `DesktopCapturerSource` represents a screen or an individual window that can be captured.
|
||||
|
||||
> [!NOTE]
|
||||
> Capturing the screen contents requires user consent on macOS 10.15 Catalina or higher,
|
||||
> which can detected by [`systemPreferences.getMediaAccessStatus`][].
|
||||
|
||||
> * Capturing audio requires `NSAudioCaptureUsageDescription` Info.plist key on macOS 14.2 Sonoma and higher - [read more](#macos-versions-142-or-higher).
|
||||
> * Capturing the screen contents requires user consent on macOS 10.15 Catalina or higher, which can detected by [`systemPreferences.getMediaAccessStatus`][].
|
||||
|
||||
[`navigator.mediaDevices.getUserMedia`]: https://developer.mozilla.org/en/docs/Web/API/MediaDevices/getUserMedia
|
||||
[`systemPreferences.getMediaAccessStatus`]: system-preferences.md#systempreferencesgetmediaaccessstatusmediatype-windows-macos
|
||||
|
||||
## Caveats
|
||||
|
||||
### Linux
|
||||
|
||||
`desktopCapturer.getSources(options)` only returns a single source on Linux when using Pipewire.
|
||||
|
||||
PipeWire supports a single capture for both screens and windows. If you request the window and screen type, the selected source will be returned as a window capture.
|
||||
|
||||
`navigator.mediaDevices.getUserMedia` does not work on macOS for audio capture due to a fundamental limitation whereby apps that want to access the system's audio require a [signed kernel extension](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html). Chromium, and by extension Electron, does not provide this.
|
||||
---
|
||||
|
||||
It is possible to circumvent this limitation by capturing system audio with another macOS app like Soundflower and passing it through a virtual audio input device. This virtual device can then be queried with `navigator.mediaDevices.getUserMedia`.
|
||||
### MacOS versions 14.2 or higher
|
||||
|
||||
`NSAudioCaptureUsageDescription` Info.plist key must be added in-order for audio to be captured by `desktopCapturer`. If instead you are running electron from another program like a terminal or IDE then that parent program must contain the Info.plist key.
|
||||
|
||||
This is in order to facillitate use of Apple's new [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps#Configure-the-sample-code-project) by Chromium.
|
||||
|
||||
> [!WARNING]
|
||||
> Failure of `desktopCapturer` to start an audio stream due to `NSAudioCaptureUsageDescription` permission not present will still create a dead audio stream however no warnings or errors are displayed.
|
||||
|
||||
As of electron `v39.0.0-beta.4` Chromium [made Apple's new `CoreAudio Tap API` the default](https://source.chromium.org/chromium/chromium/src/+/ad17e8f8b93d5f34891b06085d373a668918255e) for desktop audio capture. There is no fallback to the older `Screen & System Audio Recording` permissions system even if [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps) stream creation fails.
|
||||
|
||||
If you need to continue using `Screen & System Audio Recording` permissions for `desktopCapturer` on macOS versions 14.2 and later, you can apply a chromium feature flag to force use of that older permissions system:
|
||||
|
||||
```js
|
||||
// main.js (right beneath your require/import statments)
|
||||
app.commandLine.appendSwitch('disable-features', 'MacCatapLoopbackAudioForScreenShare')
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### MacOS versions 12.7.6 or lower
|
||||
|
||||
`navigator.mediaDevices.getUserMedia` does not work on macOS versions 12.7.6 and prior for audio capture due to a fundamental limitation whereby apps that want to access the system's audio require a [signed kernel extension](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html). Chromium, and by extension Electron, does not provide this. Only in macOS 13 and onwards does Apple provide APIs to capture desktop audio without the need for a signed kernel extension.
|
||||
|
||||
It is possible to circumvent this limitation by capturing system audio with another macOS app like [BlackHole](https://existential.audio/blackhole/) or [Soundflower](https://rogueamoeba.com/freebies/soundflower/) and passing it through a virtual audio input device. This virtual device can then be queried with `navigator.mediaDevices.getUserMedia`.
|
||||
|
||||
@@ -107,7 +107,7 @@ A `string` (optional) indicating the item's role, if set. Can be `undo`, `redo`,
|
||||
|
||||
#### `menuItem.accelerator`
|
||||
|
||||
An `Accelerator` (optional) indicating the item's accelerator, if set.
|
||||
An `Accelerator | null` indicating the item's accelerator, if set.
|
||||
|
||||
#### `menuItem.userAccelerator` _Readonly_ _macOS_
|
||||
|
||||
|
||||
@@ -36,6 +36,12 @@ Process: [Main](../glossary.md#main-process)<br />
|
||||
`com.apple.security.cs.allow-unsigned-executable-memory` entitlements. This will allow the utility process
|
||||
to load unsigned libraries. Unless you specifically need this capability, it is best to leave this disabled.
|
||||
Default is `false`.
|
||||
* `disclaim` boolean (optional) _macOS_ - With this flag, the utility process will disclaim
|
||||
responsibility for the child process. This causes the operating system to consider the child
|
||||
process as a separate entity for purposes of security policies like Transparency, Consent, and
|
||||
Control (TCC). When responsibility is disclaimed, the parent process will not be attributed
|
||||
for any TCC requests initiated by the child process. This is useful when launching processes
|
||||
that run third-party or otherwise untrusted code. Default is `false`.
|
||||
* `respondToAuthRequestsFromMainProcess` boolean (optional) - With this flag, all HTTP 401 and 407 network
|
||||
requests created via the [net module](net.md) will allow responding to them via the
|
||||
[`app#login`](app.md#event-login) event in the main process instead of the default
|
||||
|
||||
@@ -37,6 +37,22 @@ webContents.setWindowOpenHandler((details) => {
|
||||
})
|
||||
```
|
||||
|
||||
### Behavior Changed: `NSAudioCaptureUsageDescription` should be included in your app's Info.plist file to use `desktopCapturer` (🍏 macOS ≥14.2)
|
||||
|
||||
Per [Chromium update](https://source.chromium.org/chromium/chromium/src/+/ad17e8f8b93d5f34891b06085d373a668918255e) which enables Apple's newer [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps#Configure-the-sample-code-project) by default, you now must have `NSAudioCaptureUsageDescription` defined in your `Info.plist` to use `desktopCapturer`.
|
||||
|
||||
Electron's `desktopCapturer` will create a dead audio stream if the new permission is absent however no errors or warnings will occur. This is partially a side-effect of Chromium not falling back to the older `Screen & System Audio Recording` permissions system if the new system fails.
|
||||
|
||||
To restore previous behavior:
|
||||
|
||||
```js
|
||||
// main.js (right beneath your require/import statments)
|
||||
app.commandLine.appendSwitch(
|
||||
'disable-features',
|
||||
'MacCatapLoopbackAudioForScreenShare'
|
||||
)
|
||||
```
|
||||
|
||||
### Behavior Changed: shared texture OSR `paint` event data structure
|
||||
|
||||
When using shared texture offscreen rendering feature, the `paint` event now emits a more structured object.
|
||||
|
||||
@@ -200,7 +200,7 @@ macOS has a number of platform-specific menu roles available. Many of these map
|
||||
|
||||
* `recentDocuments` - The submenu is an "Open Recent" menu.
|
||||
* `clearRecentDocuments` - Map to the [`clearRecentDocuments`](https://developer.apple.com/documentation/appkit/nsdocumentcontroller/clearrecentdocuments(_:)) action.
|
||||
* `shareMenu` - The submenu is [share menu][ShareMenu]. The `sharingItem` property must also be set to indicate the item to share.
|
||||
* `shareMenu` - The submenu is [share menu](../api/share-menu.md). The `sharingItem` property must also be set to indicate the item to share.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> When specifying a `role` on macOS, `label` and `accelerator` are the only
|
||||
|
||||
@@ -277,6 +277,8 @@ filenames = {
|
||||
"shell/browser/api/electron_api_in_app_purchase.h",
|
||||
"shell/browser/api/electron_api_menu.cc",
|
||||
"shell/browser/api/electron_api_menu.h",
|
||||
"shell/browser/api/electron_api_msix_updater.cc",
|
||||
"shell/browser/api/electron_api_msix_updater.h",
|
||||
"shell/browser/api/electron_api_native_theme.cc",
|
||||
"shell/browser/api/electron_api_native_theme.h",
|
||||
"shell/browser/api/electron_api_net_log.cc",
|
||||
|
||||
@@ -353,6 +353,7 @@ export function shouldOverrideCheckStatus (role: RoleId) {
|
||||
|
||||
export function getDefaultAccelerator (role: RoleId) {
|
||||
if (hasRole(role)) return roleList[role].accelerator;
|
||||
return undefined;
|
||||
}
|
||||
|
||||
export function shouldRegisterAccelerator (role: RoleId) {
|
||||
|
||||
@@ -25,7 +25,7 @@ const MenuItem = function (this: any, options: any) {
|
||||
|
||||
this.overrideReadOnlyProperty('type', roles.getDefaultType(this.role));
|
||||
this.overrideReadOnlyProperty('role');
|
||||
this.overrideReadOnlyProperty('accelerator');
|
||||
this.overrideReadOnlyProperty('accelerator', roles.getDefaultAccelerator(this.role));
|
||||
this.overrideReadOnlyProperty('icon');
|
||||
this.overrideReadOnlyProperty('submenu');
|
||||
|
||||
|
||||
@@ -57,7 +57,8 @@
|
||||
"url": "^0.11.4",
|
||||
"webpack": "^5.95.0",
|
||||
"webpack-cli": "^5.1.4",
|
||||
"wrapper-webpack-plugin": "^2.2.0"
|
||||
"wrapper-webpack-plugin": "^2.2.0",
|
||||
"yaml": "^2.8.1"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
@@ -132,6 +133,10 @@
|
||||
"DEPS": [
|
||||
"node script/gen-hunspell-filenames.js",
|
||||
"node script/gen-libc++-filenames.js"
|
||||
],
|
||||
".github/workflows/pipeline-segment-electron-build.yml": [
|
||||
"node script/copy-pipeline-segment-publish.js",
|
||||
"git add .github/workflows/pipeline-segment-electron-publish.yml"
|
||||
]
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -147,3 +147,6 @@ 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
|
||||
ozone_wayland_treat_dnd_drop_performed_with_none_action_as_a.patch
|
||||
cherry-pick-e045399a1ecb.patch
|
||||
|
||||
132
patches/chromium/cherry-pick-e045399a1ecb.patch
Normal file
132
patches/chromium/cherry-pick-e045399a1ecb.patch
Normal file
@@ -0,0 +1,132 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: =?UTF-8?q?Dominik=20R=C3=B6ttsches?= <drott@chromium.org>
|
||||
Date: Thu, 12 Feb 2026 06:35:36 -0800
|
||||
Subject: Avoid stale iteration in CSSFontFeatureValuesMap
|
||||
MIME-Version: 1.0
|
||||
Content-Type: text/plain; charset=UTF-8
|
||||
Content-Transfer-Encoding: 8bit
|
||||
|
||||
To avoid invalid iterator state, take a snapshot of the
|
||||
map when creating the iteration source. This addresses
|
||||
the immediate problem of iterating while modifying.
|
||||
|
||||
Remaining work tracked in https://crbug.com/483936078
|
||||
|
||||
Fixed: 483569511
|
||||
Change-Id: Ie29cfdf7ed94bbe189b44c842a5efce571bb2cee
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7566570
|
||||
Commit-Queue: Dominik Röttsches <drott@chromium.org>
|
||||
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
|
||||
Cr-Commit-Position: refs/heads/main@{#1583927}
|
||||
|
||||
diff --git a/third_party/blink/renderer/core/css/css_font_feature_values_map.cc b/third_party/blink/renderer/core/css/css_font_feature_values_map.cc
|
||||
index 0c5990799fbfdff5f1d1e04a9038a471217ad0d2..2ea27901e3ba503e7e1acc5dacf90dc60d52ac1a 100644
|
||||
--- a/third_party/blink/renderer/core/css/css_font_feature_values_map.cc
|
||||
+++ b/third_party/blink/renderer/core/css/css_font_feature_values_map.cc
|
||||
@@ -13,16 +13,15 @@ class FontFeatureValuesMapIterationSource final
|
||||
: public PairSyncIterable<CSSFontFeatureValuesMap>::IterationSource {
|
||||
public:
|
||||
FontFeatureValuesMapIterationSource(const CSSFontFeatureValuesMap& map,
|
||||
- const FontFeatureAliases* aliases)
|
||||
- : map_(map), aliases_(aliases), iterator_(aliases->begin()) {}
|
||||
+ const FontFeatureAliases aliases)
|
||||
+ : map_(map),
|
||||
+ aliases_(std::move(aliases)),
|
||||
+ iterator_(aliases_.begin()) {}
|
||||
|
||||
bool FetchNextItem(ScriptState* script_state,
|
||||
String& map_key,
|
||||
Vector<uint32_t>& map_value) override {
|
||||
- if (!aliases_) {
|
||||
- return false;
|
||||
- }
|
||||
- if (iterator_ == aliases_->end()) {
|
||||
+ if (iterator_ == aliases_.end()) {
|
||||
return false;
|
||||
}
|
||||
map_key = iterator_->key;
|
||||
@@ -37,9 +36,13 @@ class FontFeatureValuesMapIterationSource final
|
||||
}
|
||||
|
||||
private:
|
||||
- // Needs to be kept alive while we're iterating over it.
|
||||
const Member<const CSSFontFeatureValuesMap> map_;
|
||||
- const FontFeatureAliases* aliases_;
|
||||
+ // Create a copy to keep the iterator from becoming invalid if there are
|
||||
+ // modifications to the aliases HashMap while iterating.
|
||||
+ // TODO(https://crbug.com/483936078): Implement live/stable iteration over
|
||||
+ // FontFeatureAliases by changing its storage type, avoiding taking a copy
|
||||
+ // here.
|
||||
+ const FontFeatureAliases aliases_;
|
||||
FontFeatureAliases::const_iterator iterator_;
|
||||
};
|
||||
|
||||
@@ -49,8 +52,8 @@ uint32_t CSSFontFeatureValuesMap::size() const {
|
||||
|
||||
PairSyncIterable<CSSFontFeatureValuesMap>::IterationSource*
|
||||
CSSFontFeatureValuesMap::CreateIterationSource(ScriptState*) {
|
||||
- return MakeGarbageCollected<FontFeatureValuesMapIterationSource>(*this,
|
||||
- aliases_);
|
||||
+ return MakeGarbageCollected<FontFeatureValuesMapIterationSource>(
|
||||
+ *this, aliases_ ? *aliases_ : FontFeatureAliases());
|
||||
}
|
||||
|
||||
bool CSSFontFeatureValuesMap::GetMapEntry(ScriptState*,
|
||||
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font_feature_values_map_iteration.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font_feature_values_map_iteration.html
|
||||
new file mode 100644
|
||||
index 0000000000000000000000000000000000000000..eac7198b0b4a58007cbcc77ad3e9357a1009117c
|
||||
--- /dev/null
|
||||
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font_feature_values_map_iteration.html
|
||||
@@ -0,0 +1,52 @@
|
||||
+<!DOCTYPE html>
|
||||
+<html>
|
||||
+ <head>
|
||||
+ <title>CSSFontFeatureValuesMap Iteration and Modification</title>
|
||||
+ <link
|
||||
+ rel="help"
|
||||
+ href="https://drafts.csswg.org/css-fonts-4/#om-fontfeaturevalues"
|
||||
+ />
|
||||
+ <meta
|
||||
+ name="assert"
|
||||
+ content="Iteration while modifying CSSFontFeatureValuesMap does not crash."
|
||||
+ />
|
||||
+ <script type="text/javascript" src="/resources/testharness.js"></script>
|
||||
+ <script
|
||||
+ type="text/javascript"
|
||||
+ src="/resources/testharnessreport.js"
|
||||
+ ></script>
|
||||
+ </head>
|
||||
+ <body>
|
||||
+ <style>
|
||||
+ @font-feature-values TestFont {
|
||||
+ @styleset {
|
||||
+ a: 1;
|
||||
+ b: 2;
|
||||
+ c: 3;
|
||||
+ }
|
||||
+ }
|
||||
+ </style>
|
||||
+ <script>
|
||||
+ test(() => {
|
||||
+ const rule = document.styleSheets[0].cssRules[0];
|
||||
+ const map = rule.styleset;
|
||||
+ const iterator = map.entries();
|
||||
+ let count = 0;
|
||||
+
|
||||
+ while (count < 10) {
|
||||
+ const { value: entry, done } = iterator.next();
|
||||
+ if (done) break;
|
||||
+
|
||||
+ const [key, value] = entry;
|
||||
+
|
||||
+ map.delete(key);
|
||||
+ for (let i = 0; i < 100; i++) {
|
||||
+ map.set(`newkey_${count}_${i}`, i);
|
||||
+ }
|
||||
+
|
||||
+ count++;
|
||||
+ }
|
||||
+ }, "Iteration of the CSSFontFeatureValuesMap does not crash.");
|
||||
+ </script>
|
||||
+ </body>
|
||||
+</html>
|
||||
@@ -9,6 +9,8 @@ Subject: feat: configure launch options for service process
|
||||
Allows configuring base::LaunchOptions::handles_to_inherit, base::LaunchOptions::stdout_handle,
|
||||
base::LaunchOptions::stderr_handle and base::LaunchOptions::feedback_cursor_off when launching
|
||||
the child process.
|
||||
- Mac:
|
||||
Allows configuring base::LaunchOptions::disclaim_responsibility when launching the child process.
|
||||
- All:
|
||||
Allows configuring base::LauncOptions::current_directory, base::LaunchOptions::enviroment
|
||||
and base::LaunchOptions::clear_environment.
|
||||
@@ -165,10 +167,10 @@ index 0791b5317fc6846389f65f93734ae5e816d04623..48948b409d6da58ade72c60ed848df49
|
||||
FinishStartSandboxedProcessOnLauncherThread,
|
||||
this));
|
||||
diff --git a/content/browser/service_host/service_process_host_impl.cc b/content/browser/service_host/service_process_host_impl.cc
|
||||
index d9c14f91747bde0e76056d7f2f2ada166e67f994..53be16879777a3b9bef58ead5f7e420c1bf6acbe 100644
|
||||
index d9c14f91747bde0e76056d7f2f2ada166e67f994..09335acac17f526fb8d8e42e4b2d993b11045786 100644
|
||||
--- a/content/browser/service_host/service_process_host_impl.cc
|
||||
+++ b/content/browser/service_host/service_process_host_impl.cc
|
||||
@@ -69,6 +69,17 @@ void LaunchServiceProcess(mojo::GenericPendingReceiver receiver,
|
||||
@@ -69,6 +69,21 @@ void LaunchServiceProcess(mojo::GenericPendingReceiver receiver,
|
||||
utility_options.WithGpuClientAllowed();
|
||||
}
|
||||
|
||||
@@ -179,6 +181,10 @@ index d9c14f91747bde0e76056d7f2f2ada166e67f994..53be16879777a3b9bef58ead5f7e420c
|
||||
+#elif BUILDFLAG(IS_POSIX)
|
||||
+ utility_options.WithAdditionalFds(std::move(service_options.fds_to_remap));
|
||||
+#endif
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ utility_options.WithDisclaimResponsibility(
|
||||
+ service_options.disclaim_responsibility);
|
||||
+#endif
|
||||
+ utility_options.WithCurrentDirectory(service_options.current_directory);
|
||||
+ utility_options.WithEnvironment(service_options.environment,
|
||||
+ service_options.clear_environment);
|
||||
@@ -187,7 +193,7 @@ index d9c14f91747bde0e76056d7f2f2ada166e67f994..53be16879777a3b9bef58ead5f7e420c
|
||||
|
||||
UtilityProcessHost::Start(std::move(utility_options),
|
||||
diff --git a/content/browser/service_host/utility_process_host.cc b/content/browser/service_host/utility_process_host.cc
|
||||
index ab48c8b88a7cf311205b221169308d96e95f239f..d714d2022f685052e1def3b0d242d2ea596d790b 100644
|
||||
index ab48c8b88a7cf311205b221169308d96e95f239f..54883f202c1769227c823d46454173b53692a7dd 100644
|
||||
--- a/content/browser/service_host/utility_process_host.cc
|
||||
+++ b/content/browser/service_host/utility_process_host.cc
|
||||
@@ -245,13 +245,13 @@ UtilityProcessHost::Options& UtilityProcessHost::Options::WithFileToPreload(
|
||||
@@ -207,7 +213,7 @@ index ab48c8b88a7cf311205b221169308d96e95f239f..d714d2022f685052e1def3b0d242d2ea
|
||||
|
||||
#if BUILDFLAG(USE_ZYGOTE)
|
||||
UtilityProcessHost::Options& UtilityProcessHost::Options::WithZygoteForTesting(
|
||||
@@ -261,6 +261,36 @@ UtilityProcessHost::Options& UtilityProcessHost::Options::WithZygoteForTesting(
|
||||
@@ -261,6 +261,45 @@ UtilityProcessHost::Options& UtilityProcessHost::Options::WithZygoteForTesting(
|
||||
}
|
||||
#endif // BUILDFLAG(USE_ZYGOTE)
|
||||
|
||||
@@ -240,11 +246,20 @@ index ab48c8b88a7cf311205b221169308d96e95f239f..d714d2022f685052e1def3b0d242d2ea
|
||||
+ return *this;
|
||||
+}
|
||||
+#endif // BUILDFLAG(IS_WIN)
|
||||
+
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+UtilityProcessHost::Options&
|
||||
+UtilityProcessHost::Options::WithDisclaimResponsibility(
|
||||
+ bool disclaim_responsibility) {
|
||||
+ disclaim_responsibility_ = disclaim_responsibility;
|
||||
+ return *this;
|
||||
+}
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
+
|
||||
UtilityProcessHost::Options&
|
||||
UtilityProcessHost::Options::WithBoundReceiverOnChildProcessForTesting(
|
||||
mojo::GenericPendingReceiver receiver) {
|
||||
@@ -525,9 +555,26 @@ bool UtilityProcessHost::StartProcess() {
|
||||
@@ -525,9 +564,30 @@ bool UtilityProcessHost::StartProcess() {
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_GPU_CHANNEL_MEDIA_CAPTURE) && !BUILDFLAG(IS_WIN)
|
||||
|
||||
@@ -269,11 +284,15 @@ index ab48c8b88a7cf311205b221169308d96e95f239f..d714d2022f685052e1def3b0d242d2ea
|
||||
+#if BUILDFLAG(IS_WIN)
|
||||
+ delegate->SetFeedbackCursorOff(options_.feedback_cursor_off_);
|
||||
+#endif // BUILDFLAG(IS_WIN)
|
||||
+
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ delegate->SetDisclaimResponsibility(options_.disclaim_responsibility_);
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (!options_.preload_libraries_.empty()) {
|
||||
diff --git a/content/browser/service_host/utility_process_host.h b/content/browser/service_host/utility_process_host.h
|
||||
index 580fa663e729a43bef44a10de8983c4aecc312fb..f39af3df87786a472f987309ac0dea699b9f0d9f 100644
|
||||
index 580fa663e729a43bef44a10de8983c4aecc312fb..c339f2b8504beee6ed2e5746a8e6b961a7aa9746 100644
|
||||
--- a/content/browser/service_host/utility_process_host.h
|
||||
+++ b/content/browser/service_host/utility_process_host.h
|
||||
@@ -31,6 +31,7 @@
|
||||
@@ -284,7 +303,7 @@ index 580fa663e729a43bef44a10de8983c4aecc312fb..f39af3df87786a472f987309ac0dea69
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
namespace base {
|
||||
@@ -134,14 +135,31 @@ class CONTENT_EXPORT UtilityProcessHost final
|
||||
@@ -134,14 +135,36 @@ class CONTENT_EXPORT UtilityProcessHost final
|
||||
std::variant<base::FilePath, base::ScopedFD> file);
|
||||
#endif
|
||||
|
||||
@@ -315,11 +334,16 @@ index 580fa663e729a43bef44a10de8983c4aecc312fb..f39af3df87786a472f987309ac0dea69
|
||||
+ // Specifies if the process should trigger mouse cursor feedback.
|
||||
+ Options& WithFeedbackCursorOff(bool feedback_cursor_off);
|
||||
+#endif // BUILDFLAG(IS_WIN)
|
||||
+
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ // Specifies if the process should disclaim TCC responsibility.
|
||||
+ Options& WithDisclaimResponsibility(bool disclaim_responsibility);
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
+
|
||||
// Requests that the process bind a receiving pipe targeting the interface
|
||||
// named by `receiver`. Calls to this method generally end up in
|
||||
// `ChildThreadImpl::OnBindReceiver()` and the option is used for testing
|
||||
@@ -185,6 +203,27 @@ class CONTENT_EXPORT UtilityProcessHost final
|
||||
@@ -185,6 +208,32 @@ class CONTENT_EXPORT UtilityProcessHost final
|
||||
std::optional<raw_ptr<ZygoteCommunication>> zygote_for_testing_;
|
||||
#endif // BUILDFLAG(USE_ZYGOTE)
|
||||
|
||||
@@ -343,12 +367,17 @@ index 580fa663e729a43bef44a10de8983c4aecc312fb..f39af3df87786a472f987309ac0dea69
|
||||
+ // Specifies if the process should trigger mouse cursor feedback.
|
||||
+ bool feedback_cursor_off_ = false;
|
||||
+#endif // BUILDFLAG(IS_WIN)
|
||||
+
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ // Specifies if the process should disclaim TCC responsibility.
|
||||
+ bool disclaim_responsibility_ = false;
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
+
|
||||
#if BUILDFLAG(ENABLE_GPU_CHANNEL_MEDIA_CAPTURE)
|
||||
// Whether or not to bind viz::mojom::Gpu to the utility process.
|
||||
bool allowed_gpu_;
|
||||
diff --git a/content/browser/service_host/utility_sandbox_delegate.cc b/content/browser/service_host/utility_sandbox_delegate.cc
|
||||
index 8f89c28144e1ecf3d7dbf9a3b43031cbad12a8ea..faa49bb63bd2e9080da441286bdbf427f22cd26f 100644
|
||||
index 8f89c28144e1ecf3d7dbf9a3b43031cbad12a8ea..0c6e724f8ad0926076274f696b77b17b77f2aaa9 100644
|
||||
--- a/content/browser/service_host/utility_sandbox_delegate.cc
|
||||
+++ b/content/browser/service_host/utility_sandbox_delegate.cc
|
||||
@@ -43,17 +43,19 @@ UtilitySandboxedProcessLauncherDelegate::
|
||||
@@ -406,8 +435,24 @@ index 8f89c28144e1ecf3d7dbf9a3b43031cbad12a8ea..faa49bb63bd2e9080da441286bdbf427
|
||||
|
||||
#if BUILDFLAG(USE_ZYGOTE)
|
||||
ZygoteCommunication* UtilitySandboxedProcessLauncherDelegate::GetZygote() {
|
||||
@@ -203,6 +222,15 @@ UtilitySandboxedProcessLauncherDelegate::GetProcessRequirement() {
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
+
|
||||
+void UtilitySandboxedProcessLauncherDelegate::SetDisclaimResponsibility(
|
||||
+ bool disclaim_responsibility) {
|
||||
+ disclaim_responsibility_ = disclaim_responsibility;
|
||||
+}
|
||||
+
|
||||
+bool UtilitySandboxedProcessLauncherDelegate::DisclaimResponsibility() {
|
||||
+ return disclaim_responsibility_;
|
||||
+}
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
|
||||
} // namespace content
|
||||
diff --git a/content/browser/service_host/utility_sandbox_delegate.h b/content/browser/service_host/utility_sandbox_delegate.h
|
||||
index f2e8c1d62c1cb1677f618b584ed401685b03034b..7c47ec471e4676365cf3e023808983cb3e7a18d0 100644
|
||||
index f2e8c1d62c1cb1677f618b584ed401685b03034b..00a20885092bd299e817685dd18c3971b25f721b 100644
|
||||
--- a/content/browser/service_host/utility_sandbox_delegate.h
|
||||
+++ b/content/browser/service_host/utility_sandbox_delegate.h
|
||||
@@ -36,7 +36,9 @@ class CONTENT_EXPORT UtilitySandboxedProcessLauncherDelegate
|
||||
@@ -438,7 +483,12 @@ index f2e8c1d62c1cb1677f618b584ed401685b03034b..7c47ec471e4676365cf3e023808983cb
|
||||
|
||||
#if BUILDFLAG(USE_ZYGOTE)
|
||||
void SetZygote(ZygoteCommunication* handle);
|
||||
@@ -77,9 +84,7 @@ class CONTENT_EXPORT UtilitySandboxedProcessLauncherDelegate
|
||||
@@ -74,12 +81,12 @@ class CONTENT_EXPORT UtilitySandboxedProcessLauncherDelegate
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
std::optional<base::mac::ProcessRequirement> GetProcessRequirement() override;
|
||||
+ void SetDisclaimResponsibility(bool disclaim_responsibility);
|
||||
+ bool DisclaimResponsibility() override;
|
||||
#endif // BUILDFLAG(IS_MAC)
|
||||
|
||||
private:
|
||||
@@ -448,7 +498,7 @@ index f2e8c1d62c1cb1677f618b584ed401685b03034b..7c47ec471e4676365cf3e023808983cb
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Adds preload-libraries to the delegate blob for utility_main() to access
|
||||
@@ -95,12 +100,17 @@ class CONTENT_EXPORT UtilitySandboxedProcessLauncherDelegate
|
||||
@@ -95,12 +102,20 @@ class CONTENT_EXPORT UtilitySandboxedProcessLauncherDelegate
|
||||
std::optional<raw_ptr<ZygoteCommunication>> zygote_;
|
||||
#endif // BUILDFLAG(USE_ZYGOTE)
|
||||
|
||||
@@ -463,6 +513,9 @@ index f2e8c1d62c1cb1677f618b584ed401685b03034b..7c47ec471e4676365cf3e023808983cb
|
||||
+#if BUILDFLAG(IS_WIN)
|
||||
+ bool feedback_cursor_off_ = false;
|
||||
+#endif // BUILDFLAG(IS_WIN)
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ bool disclaim_responsibility_ = false;
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
};
|
||||
} // namespace content
|
||||
|
||||
@@ -489,10 +542,10 @@ index 39c96d4423b24695eee86353057cfeed19318b57..31b343d97b7672294644041c9bb1a4cd
|
||||
}
|
||||
|
||||
diff --git a/content/public/browser/service_process_host.cc b/content/public/browser/service_process_host.cc
|
||||
index d1bc550a891979e2d41d8d5b18a2f9287468e460..5fcac7a8493e5065f80303067a04f59e7c4509ef 100644
|
||||
index d1bc550a891979e2d41d8d5b18a2f9287468e460..5d255f628788bc8b40d8df0039b08c06ffec8730 100644
|
||||
--- a/content/public/browser/service_process_host.cc
|
||||
+++ b/content/public/browser/service_process_host.cc
|
||||
@@ -53,12 +53,53 @@ ServiceProcessHost::Options::WithExtraCommandLineSwitches(
|
||||
@@ -53,12 +53,62 @@ ServiceProcessHost::Options::WithExtraCommandLineSwitches(
|
||||
return *this;
|
||||
}
|
||||
|
||||
@@ -542,12 +595,21 @@ index d1bc550a891979e2d41d8d5b18a2f9287468e460..5fcac7a8493e5065f80303067a04f59e
|
||||
+ return *this;
|
||||
+}
|
||||
+#endif // #if BUILDFLAG(IS_WIN)
|
||||
+
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ServiceProcessHost::Options&
|
||||
+ServiceProcessHost::Options::WithDisclaimResponsibility(
|
||||
+ bool should_disclaim_responsibility) {
|
||||
+ disclaim_responsibility = should_disclaim_responsibility;
|
||||
+ return *this;
|
||||
+}
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
+
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
ServiceProcessHost::Options&
|
||||
ServiceProcessHost::Options::WithPreloadedLibraries(
|
||||
diff --git a/content/public/browser/service_process_host.h b/content/public/browser/service_process_host.h
|
||||
index 0062d2cb6634b8b29977a0312516b1b13936b40a..611a52e908f4cb70fbe5628e220a082e45320b70 100644
|
||||
index 0062d2cb6634b8b29977a0312516b1b13936b40a..888ff36d70c83010f1f45e9eeb2dd6b573158db5 100644
|
||||
--- a/content/public/browser/service_process_host.h
|
||||
+++ b/content/public/browser/service_process_host.h
|
||||
@@ -14,6 +14,7 @@
|
||||
@@ -569,7 +631,7 @@ index 0062d2cb6634b8b29977a0312516b1b13936b40a..611a52e908f4cb70fbe5628e220a082e
|
||||
namespace base {
|
||||
class Process;
|
||||
} // namespace base
|
||||
@@ -94,11 +99,35 @@ class CONTENT_EXPORT ServiceProcessHost {
|
||||
@@ -94,11 +99,40 @@ class CONTENT_EXPORT ServiceProcessHost {
|
||||
// Specifies extra command line switches to append before launch.
|
||||
Options& WithExtraCommandLineSwitches(std::vector<std::string> switches);
|
||||
|
||||
@@ -601,11 +663,16 @@ index 0062d2cb6634b8b29977a0312516b1b13936b40a..611a52e908f4cb70fbe5628e220a082e
|
||||
+ // Specifies if the process should trigger mouse cursor feedback.
|
||||
+ Options& WithFeedbackCursorOff(bool feedback_cursor_off);
|
||||
+#endif // #if BUILDFLAG(IS_WIN)
|
||||
+
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ // Specifies if the process should disclaim TCC responsibility.
|
||||
+ Options& WithDisclaimResponsibility(bool disclaim_responsibility);
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
+
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// Specifies libraries to preload before the sandbox is locked down. Paths
|
||||
// should be absolute paths. Libraries will be preloaded before sandbox
|
||||
@@ -127,11 +156,23 @@ class CONTENT_EXPORT ServiceProcessHost {
|
||||
@@ -127,11 +161,26 @@ class CONTENT_EXPORT ServiceProcessHost {
|
||||
std::optional<GURL> site;
|
||||
std::optional<int> child_flags;
|
||||
std::vector<std::string> extra_switches;
|
||||
@@ -626,6 +693,9 @@ index 0062d2cb6634b8b29977a0312516b1b13936b40a..611a52e908f4cb70fbe5628e220a082e
|
||||
+#if BUILDFLAG(IS_WIN)
|
||||
+ bool feedback_cursor_off = false;
|
||||
+#endif // BUILDFLAG(IS_WIN)
|
||||
+#if BUILDFLAG(IS_MAC)
|
||||
+ bool disclaim_responsibility = false;
|
||||
+#endif // BUILDFLAG(IS_MAC)
|
||||
};
|
||||
|
||||
// An interface which can be implemented and registered/unregistered with
|
||||
|
||||
@@ -84,10 +84,10 @@ index 2648adb1cf38ab557b66ffd0e3034b26b04d76d6..98eab587f343f6ca472efc3d4e7b31b2
|
||||
private:
|
||||
const std::string service_interface_name_;
|
||||
diff --git a/content/browser/service_host/utility_process_host.cc b/content/browser/service_host/utility_process_host.cc
|
||||
index d714d2022f685052e1def3b0d242d2ea596d790b..fb4cee1416836c4d0b807a862507a52347f24dd2 100644
|
||||
index 54883f202c1769227c823d46454173b53692a7dd..b2a2f64435f566c67531bac9d0782215739d8fe6 100644
|
||||
--- a/content/browser/service_host/utility_process_host.cc
|
||||
+++ b/content/browser/service_host/utility_process_host.cc
|
||||
@@ -629,7 +629,7 @@ void UtilityProcessHost::OnProcessCrashed(int exit_code) {
|
||||
@@ -642,7 +642,7 @@ void UtilityProcessHost::OnProcessCrashed(int exit_code) {
|
||||
: Client::CrashType::kPreIpcInitialization;
|
||||
}
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
@@ -97,7 +97,7 @@ index d714d2022f685052e1def3b0d242d2ea596d790b..fb4cee1416836c4d0b807a862507a523
|
||||
|
||||
std::optional<std::string> UtilityProcessHost::GetServiceName() {
|
||||
diff --git a/content/browser/service_host/utility_process_host.h b/content/browser/service_host/utility_process_host.h
|
||||
index f39af3df87786a472f987309ac0dea699b9f0d9f..6f470d19fa5fa0bd4d2bdb1be4aa0cb3a70d3936 100644
|
||||
index c339f2b8504beee6ed2e5746a8e6b961a7aa9746..e75b67e08755c90670d44b1e06a685aa2b35539d 100644
|
||||
--- a/content/browser/service_host/utility_process_host.h
|
||||
+++ b/content/browser/service_host/utility_process_host.h
|
||||
@@ -88,7 +88,7 @@ class CONTENT_EXPORT UtilityProcessHost final
|
||||
|
||||
@@ -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>
|
||||
@@ -81,7 +81,7 @@ index 4bf9a3c27e05c6635b2beb8e880b5b43dbed61b5..f328fbb49c45991f44a9c75325491d08
|
||||
|
||||
} // namespace base
|
||||
diff --git a/base/process/launch_mac.cc b/base/process/launch_mac.cc
|
||||
index b63d58da9837ba4d1e4aff8f24f2cd977c5ed02d..8387fd7d2bcf8951b6cc024829c16d970799190c 100644
|
||||
index b63d58da9837ba4d1e4aff8f24f2cd977c5ed02d..49b4c0b69731386ef5a4b7dfb782aa8f4ae09cdd 100644
|
||||
--- a/base/process/launch_mac.cc
|
||||
+++ b/base/process/launch_mac.cc
|
||||
@@ -84,6 +84,10 @@ int posix_spawnattr_set_csm_np(const posix_spawnattr_t*, uint32_t)
|
||||
@@ -135,15 +135,26 @@ index b63d58da9837ba4d1e4aff8f24f2cd977c5ed02d..8387fd7d2bcf8951b6cc024829c16d97
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -301,7 +321,7 @@ Process LaunchProcess(const std::vector<std::string>& argv,
|
||||
@@ -301,16 +321,16 @@ Process LaunchProcess(const std::vector<std::string>& argv,
|
||||
file_actions.Inherit(STDERR_FILENO);
|
||||
}
|
||||
|
||||
-#if BUILDFLAG(IS_MAC)
|
||||
+#if 0
|
||||
+#if !IS_MAS_BUILD()
|
||||
if (options.disclaim_responsibility) {
|
||||
DPSXCHECK(responsibility_spawnattrs_setdisclaim(attr.get(), 1));
|
||||
}
|
||||
+#endif
|
||||
|
||||
EnvironmentMap new_environment_map = options.environment;
|
||||
+#if !IS_MAS_BUILD()
|
||||
MachPortRendezvousServerMac::AddFeatureStateToEnvironment(
|
||||
new_environment_map);
|
||||
-#else
|
||||
- const EnvironmentMap& new_environment_map = options.environment;
|
||||
#endif
|
||||
|
||||
std::vector<char*> argv_cstr;
|
||||
diff --git a/base/process/process_info_mac.mm b/base/process/process_info_mac.mm
|
||||
index e12c1d078147d956a1d9b1bc498c1b1d6fe7b974..233362259dc4e728ed37435e650417647b45a6af 100644
|
||||
--- a/base/process/process_info_mac.mm
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: AbdAlRahman Gad <agad@igalia.com>
|
||||
Date: Wed, 15 Oct 2025 09:34:12 -0700
|
||||
Subject: Ozone/Wayland: Treat DND drop performed with NONE action as a
|
||||
cancellation
|
||||
|
||||
According to the Wayland protocol, a "drop performed" event can be
|
||||
followed with a `cancelled` event. This is the behavior of compositors
|
||||
like KWin.
|
||||
|
||||
We were always treating "drop performed" as a completed drop. This
|
||||
change corrects the logic to pass `DragResult::kCancelled` in this
|
||||
scenario.
|
||||
|
||||
Bug: 447037092
|
||||
Change-Id: I0f3805365355bb364e15a9ab6d5a6954698cce1f
|
||||
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7002773
|
||||
Reviewed-by: Nick Yamane <nickdiego@igalia.com>
|
||||
Reviewed-by: Max Ihlenfeldt <max@igalia.com>
|
||||
Commit-Queue: AbdAlRahman Gad <agad@igalia.com>
|
||||
Cr-Commit-Position: refs/heads/main@{#1530269}
|
||||
|
||||
diff --git a/ui/ozone/platform/wayland/host/wayland_data_device.h b/ui/ozone/platform/wayland/host/wayland_data_device.h
|
||||
index 128baea3fe9a03f9d76afa234d6c21e9a4583cf7..d9824490e402308e64b5f54cbf9b46646f6021e8 100644
|
||||
--- a/ui/ozone/platform/wayland/host/wayland_data_device.h
|
||||
+++ b/ui/ozone/platform/wayland/host/wayland_data_device.h
|
||||
@@ -90,6 +90,8 @@ class WaylandDataDevice : public WaylandDataDeviceBase {
|
||||
FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest, StartDrag);
|
||||
FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest, ReceiveDrag);
|
||||
FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest, CancelIncomingDrag);
|
||||
+ FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest,
|
||||
+ DndDropPerformedWithNoneActionThenCancelled);
|
||||
FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest,
|
||||
DestroyWindowWhileFetchingForeignData);
|
||||
FRIEND_TEST_ALL_PREFIXES(WaylandDataDragControllerTest,
|
||||
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
|
||||
index 893bf03a8f4aa473faf3a1232ea1902d226d8ca6..b557e4654dbe2a5d6f94bf31466c78bf83744c32 100644
|
||||
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
|
||||
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller.cc
|
||||
@@ -573,7 +573,13 @@ void WaylandDataDragController::OnDataSourceDropPerformed(
|
||||
<< " origin=" << !!origin_window_
|
||||
<< " nested_dispatcher=" << !!nested_dispatcher_;
|
||||
|
||||
- HandleDragEnd(DragResult::kCompleted, timestamp);
|
||||
+ // Treat a "drop performed" event with a `dnd_action` of NONE (0) as a
|
||||
+ // cancellation (passing `kCancelled`). Per the protocol, `cancelled` event
|
||||
+ // can be sent after "drop performed", that is what `KWin` does, for example.
|
||||
+ // See crbug.com/447037092.
|
||||
+ HandleDragEnd(data_source_->dnd_action() ? DragResult::kCompleted
|
||||
+ : DragResult::kCancelled,
|
||||
+ timestamp);
|
||||
}
|
||||
|
||||
void WaylandDataDragController::OnDataSourceSend(WaylandDataSource* source,
|
||||
diff --git a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
|
||||
index c70a03d082f518b48dda58be893fc9181507e1ab..baf42205a786d42a341a6dc9265c8d929c6a7454 100644
|
||||
--- a/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
|
||||
+++ b/ui/ozone/platform/wayland/host/wayland_data_drag_controller_unittest.cc
|
||||
@@ -497,6 +497,36 @@ MATCHER_P(PointFNear, n, "") {
|
||||
return arg.IsWithinDistance(n, 0.01f);
|
||||
}
|
||||
|
||||
+// Tests that if the compositor sends wl_data_source.dnd_drop_performed with
|
||||
+// DND_ACTION_NONE, the drag controller treats it as a cancelled operation by
|
||||
+// calling OnDragLeave, and can still handle a subsequent
|
||||
+// wl_data_source.cancelled event gracefully. Regression test for
|
||||
+// https://crbug.com/447037092.
|
||||
+TEST_P(WaylandDataDragControllerTest,
|
||||
+ DndDropPerformedWithNoneActionThenCancelled) {
|
||||
+ FocusAndPressLeftPointerButton(window_.get(), &delegate_);
|
||||
+
|
||||
+ // Post test task to be performed asynchronously once the dnd-related protocol
|
||||
+ // objects are ready.
|
||||
+ ScheduleTestTask(base::BindLambdaForTesting([&]() {
|
||||
+ // Now the server can read the data and give it to our callback.
|
||||
+ ReadAndCheckData(kMimeTypeUtf8PlainText, kSampleTextForDragAndDrop);
|
||||
+
|
||||
+ EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(1);
|
||||
+ SendDndDropPerformed();
|
||||
+
|
||||
+ // Emulate server sending an wl_data_source::cancelled event so the drag
|
||||
+ // loop is finished.
|
||||
+ EXPECT_CALL(*drop_handler_, OnDragLeave()).Times(0);
|
||||
+ SendDndCancelled();
|
||||
+ }));
|
||||
+
|
||||
+ RunMouseDragWithSampleData(window_.get(), DragDropTypes::DRAG_NONE);
|
||||
+
|
||||
+ // Ensure drag delegate it properly reset when the drag loop quits.
|
||||
+ EXPECT_FALSE(data_device()->drag_delegate_);
|
||||
+}
|
||||
+
|
||||
TEST_P(WaylandDataDragControllerTest, ReceiveDrag) {
|
||||
const uint32_t surface_id = window_->root_surface()->get_surface_id();
|
||||
|
||||
diff --git a/ui/ozone/platform/wayland/host/wayland_data_source.cc b/ui/ozone/platform/wayland/host/wayland_data_source.cc
|
||||
index 761f6a456456e11ad0e8c4dbe408c360de50f02d..f1b74febf52ecadef0da6574d50dfe3709ee48d7 100644
|
||||
--- a/ui/ozone/platform/wayland/host/wayland_data_source.cc
|
||||
+++ b/ui/ozone/platform/wayland/host/wayland_data_source.cc
|
||||
@@ -30,12 +30,12 @@ DataSource<T>::DataSource(T* data_source,
|
||||
DCHECK(delegate_);
|
||||
|
||||
Initialize();
|
||||
- VLOG(1) << "DataSoure created:" << this;
|
||||
+ VLOG(1) << "DataSource created:" << this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
DataSource<T>::~DataSource() {
|
||||
- VLOG(1) << "DataSoure deleted:" << this;
|
||||
+ VLOG(1) << "DataSource deleted:" << this;
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
@@ -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
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.
|
||||
@@ -9,3 +9,4 @@ refactor_use_non-deprecated_nskeyedarchiver_apis.patch
|
||||
chore_turn_off_launchapplicationaturl_deprecation_errors_in_squirrel.patch
|
||||
fix_crash_when_process_to_extract_zip_cannot_be_launched.patch
|
||||
use_uttype_class_instead_of_deprecated_uttypeconformsto.patch
|
||||
fix_clean_up_old_staged_updates_before_downloading_new_update.patch
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Andy Locascio <loc@anthropic.com>
|
||||
Date: Tue, 6 Jan 2026 08:23:03 -0800
|
||||
Subject: fix: clean up old staged updates before downloading new update
|
||||
|
||||
When checkForUpdates() is called while an update is already staged,
|
||||
Squirrel creates a new temporary directory for the download without
|
||||
cleaning up the old one. This can lead to significant disk usage if
|
||||
the app keeps checking for updates without restarting.
|
||||
|
||||
This change adds a force parameter to pruneUpdateDirectories that
|
||||
bypasses the AwaitingRelaunch state check. This is called before
|
||||
creating a new temp directory, ensuring old staged updates are
|
||||
cleaned up when a new download starts.
|
||||
|
||||
diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m
|
||||
index d156616e81e6f25a3bded30e6216b8fc311f31bc..6cd4346bf43b191147aff819cb93387e71275a46 100644
|
||||
--- a/Squirrel/SQRLUpdater.m
|
||||
+++ b/Squirrel/SQRLUpdater.m
|
||||
@@ -543,11 +543,17 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL
|
||||
#pragma mark File Management
|
||||
|
||||
- (RACSignal *)uniqueTemporaryDirectoryForUpdate {
|
||||
- return [[[RACSignal
|
||||
+ // Clean up any old staged update directories before creating a new one.
|
||||
+ // This prevents disk usage from growing when checkForUpdates() is called
|
||||
+ // multiple times without the app restarting.
|
||||
+ return [[[[[self
|
||||
+ pruneUpdateDirectoriesWithForce:YES]
|
||||
+ ignoreValues]
|
||||
+ concat:[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
- }]
|
||||
+ }]]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
|
||||
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
|
||||
@@ -643,7 +649,7 @@ - (BOOL)isRunningOnReadOnlyVolume {
|
||||
|
||||
- (RACSignal *)performHousekeeping {
|
||||
return [[RACSignal
|
||||
- merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]]
|
||||
+ merge:@[ [self pruneUpdateDirectoriesWithForce:NO], [self truncateLogs] ]]
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error doing housekeeping: %@", error);
|
||||
return [RACSignal empty];
|
||||
@@ -658,11 +664,12 @@ - (RACSignal *)performHousekeeping {
|
||||
///
|
||||
/// Sends each removed directory then completes, or errors, on an unspecified
|
||||
/// thread.
|
||||
-- (RACSignal *)pruneUpdateDirectories {
|
||||
+- (RACSignal *)pruneUpdateDirectoriesWithForce:(BOOL)force {
|
||||
return [[[RACSignal
|
||||
defer:^{
|
||||
- // If we already have updates downloaded we don't wanna prune them.
|
||||
- if (self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
|
||||
+ // If we already have updates downloaded we don't wanna prune them,
|
||||
+ // unless force is YES (used when starting a new download).
|
||||
+ if (!force && self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
|
||||
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
32
script/copy-pipeline-segment-publish.js
Normal file
32
script/copy-pipeline-segment-publish.js
Normal file
@@ -0,0 +1,32 @@
|
||||
const yaml = require('yaml');
|
||||
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
const PREFIX = '# AUTOGENERATED FILE - DO NOT EDIT MANUALLY\n# ONLY EDIT .github/workflows/pipeline-segment-electron-build.yml\n\n';
|
||||
|
||||
const base = path.resolve(__dirname, '../.github/workflows/pipeline-segment-electron-build.yml');
|
||||
const target = path.resolve(__dirname, '../.github/workflows/pipeline-segment-electron-publish.yml');
|
||||
|
||||
const baseContents = fs.readFileSync(base, 'utf-8');
|
||||
|
||||
const parsedBase = yaml.parse(baseContents);
|
||||
parsedBase.jobs.build.permissions = {
|
||||
'artifact-metadata': 'write',
|
||||
attestations: 'write',
|
||||
contents: 'read',
|
||||
'id-token': 'write'
|
||||
};
|
||||
|
||||
if (process.argv.includes('--check')) {
|
||||
if (fs.readFileSync(target, 'utf-8') !== PREFIX + yaml.stringify(parsedBase)) {
|
||||
console.error(`${target} is out of date`);
|
||||
console.error('Please run "copy-pipeline-segment-publish.js" to update it');
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
fs.writeFileSync(
|
||||
target,
|
||||
PREFIX + yaml.stringify(parsedBase)
|
||||
);
|
||||
}
|
||||
@@ -368,6 +368,14 @@ def upload_io_to_github(release, filename, filepath, version):
|
||||
sys.stdout.buffer.write(c)
|
||||
sys.stdout.flush()
|
||||
|
||||
if "GITHUB_OUTPUT" in os.environ:
|
||||
output_path = os.environ["GITHUB_OUTPUT"]
|
||||
with open(output_path, "r+", encoding='utf-8') as github_output:
|
||||
if len(github_output.readlines()) > 0:
|
||||
github_output.write(",")
|
||||
else:
|
||||
github_output.write('UPLOADED_PATHS=')
|
||||
github_output.write(filepath)
|
||||
|
||||
def upload_sha256_checksum(version, file_path, key_prefix=None):
|
||||
checksum_path = f'{file_path}.sha256sum'
|
||||
|
||||
@@ -11,14 +11,9 @@
|
||||
#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"
|
||||
@@ -33,16 +28,10 @@
|
||||
#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 <wrl.h>
|
||||
|
||||
#include "base/win/scoped_com_initializer.h"
|
||||
#include "base/win/core_winrt_util.h"
|
||||
#include "base/win/scoped_hstring.h"
|
||||
#endif
|
||||
|
||||
namespace electron {
|
||||
@@ -55,6 +44,53 @@ const bool debug_msix_updater =
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
|
||||
// Type aliases for cleaner code
|
||||
using ABI::Windows::ApplicationModel::IAppInstallerInfo;
|
||||
using ABI::Windows::ApplicationModel::IPackage;
|
||||
using ABI::Windows::ApplicationModel::IPackage2;
|
||||
using ABI::Windows::ApplicationModel::IPackage4;
|
||||
using ABI::Windows::ApplicationModel::IPackage6;
|
||||
using ABI::Windows::ApplicationModel::IPackageId;
|
||||
using ABI::Windows::ApplicationModel::IPackageStatics;
|
||||
using ABI::Windows::ApplicationModel::PackageSignatureKind;
|
||||
using ABI::Windows::ApplicationModel::PackageSignatureKind_Developer;
|
||||
using ABI::Windows::ApplicationModel::PackageSignatureKind_Enterprise;
|
||||
using ABI::Windows::ApplicationModel::PackageSignatureKind_None;
|
||||
using ABI::Windows::ApplicationModel::PackageSignatureKind_Store;
|
||||
using ABI::Windows::ApplicationModel::PackageSignatureKind_System;
|
||||
using ABI::Windows::Foundation::AsyncStatus;
|
||||
using ABI::Windows::Foundation::IAsyncInfo;
|
||||
using ABI::Windows::Foundation::IUriRuntimeClass;
|
||||
using ABI::Windows::Foundation::IUriRuntimeClassFactory;
|
||||
using ABI::Windows::Foundation::Metadata::IApiInformationStatics;
|
||||
using ABI::Windows::Management::Deployment::DeploymentOptions;
|
||||
using ABI::Windows::Management::Deployment::
|
||||
DeploymentOptions_ForceApplicationShutdown;
|
||||
using ABI::Windows::Management::Deployment::
|
||||
DeploymentOptions_ForceTargetApplicationShutdown;
|
||||
using ABI::Windows::Management::Deployment::
|
||||
DeploymentOptions_ForceUpdateFromAnyVersion;
|
||||
using ABI::Windows::Management::Deployment::DeploymentOptions_None;
|
||||
using ABI::Windows::Management::Deployment::IAddPackageOptions;
|
||||
using ABI::Windows::Management::Deployment::IDeploymentResult;
|
||||
using ABI::Windows::Management::Deployment::IPackageManager;
|
||||
using ABI::Windows::Management::Deployment::IPackageManager5;
|
||||
using ABI::Windows::Management::Deployment::IPackageManager9;
|
||||
using Microsoft::WRL::Callback;
|
||||
using Microsoft::WRL::ComPtr;
|
||||
|
||||
// Type alias for deployment async operation
|
||||
// AddPackageByUriAsync returns IAsyncOperationWithProgress<DeploymentResult*,
|
||||
// DeploymentProgress>
|
||||
using DeploymentAsyncOp = ABI::Windows::Foundation::IAsyncOperationWithProgress<
|
||||
ABI::Windows::Management::Deployment::DeploymentResult*,
|
||||
ABI::Windows::Management::Deployment::DeploymentProgress>;
|
||||
using DeploymentCompletedHandler =
|
||||
ABI::Windows::Foundation::IAsyncOperationWithProgressCompletedHandler<
|
||||
ABI::Windows::Management::Deployment::DeploymentResult*,
|
||||
ABI::Windows::Management::Deployment::DeploymentProgress>;
|
||||
|
||||
// Helper function for debug logging
|
||||
void DebugLog(std::string_view log_msg) {
|
||||
if (electron::debug_msix_updater)
|
||||
@@ -84,32 +120,274 @@ struct RegisterPackageOptions {
|
||||
bool force_update_from_any_version = false;
|
||||
};
|
||||
|
||||
// Helper: Create PackageManager using RoActivateInstance
|
||||
//
|
||||
// Note on COM interface versioning: In COM/WinRT, each interface version
|
||||
// (IPackageManager, IPackageManager5, IPackageManager9, etc.) is a separate
|
||||
// interface that must be queried independently. Unlike C++ inheritance,
|
||||
// IPackageManager9 does NOT inherit methods from IPackageManager5 or the base
|
||||
// IPackageManager. Each version only contains the methods that were newly
|
||||
// added in that version. To call methods from different versions, you must
|
||||
// QueryInterface (or ComPtr::As) for each specific interface version needed.
|
||||
HRESULT CreatePackageManager(ComPtr<IPackageManager>* package_manager) {
|
||||
base::win::ScopedHString class_id = base::win::ScopedHString::Create(
|
||||
RuntimeClass_Windows_Management_Deployment_PackageManager);
|
||||
if (!class_id.is_valid()) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
ComPtr<IInspectable> inspectable;
|
||||
HRESULT hr = base::win::RoActivateInstance(class_id.get(), &inspectable);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return inspectable.As(package_manager);
|
||||
}
|
||||
|
||||
// Helper: Create URI using IUriRuntimeClassFactory
|
||||
HRESULT CreateUri(const std::wstring& uri_string,
|
||||
ComPtr<IUriRuntimeClass>* uri) {
|
||||
ComPtr<IUriRuntimeClassFactory> uri_factory;
|
||||
HRESULT hr =
|
||||
base::win::GetActivationFactory<IUriRuntimeClassFactory,
|
||||
RuntimeClass_Windows_Foundation_Uri>(
|
||||
&uri_factory);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
base::win::ScopedHString uri_hstring =
|
||||
base::win::ScopedHString::Create(uri_string);
|
||||
if (!uri_hstring.is_valid()) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
return uri_factory->CreateUri(uri_hstring.get(), uri->GetAddressOf());
|
||||
}
|
||||
|
||||
// Helper: Create and configure AddPackageOptions
|
||||
HRESULT CreateAddPackageOptions(const UpdateMsixOptions& opts,
|
||||
ComPtr<IAddPackageOptions>* package_options) {
|
||||
base::win::ScopedHString class_id = base::win::ScopedHString::Create(
|
||||
RuntimeClass_Windows_Management_Deployment_AddPackageOptions);
|
||||
if (!class_id.is_valid()) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
ComPtr<IInspectable> inspectable;
|
||||
HRESULT hr = base::win::RoActivateInstance(class_id.get(), &inspectable);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
hr = inspectable.As(package_options);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
// Configure options using ABI interface methods
|
||||
(*package_options)
|
||||
->put_DeferRegistrationWhenPackagesAreInUse(opts.defer_registration);
|
||||
(*package_options)->put_DeveloperMode(opts.developer_mode);
|
||||
(*package_options)->put_ForceAppShutdown(opts.force_shutdown);
|
||||
(*package_options)->put_ForceTargetAppShutdown(opts.force_target_shutdown);
|
||||
(*package_options)
|
||||
->put_ForceUpdateFromAnyVersion(opts.force_update_from_any_version);
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// Helper: Check if API contract is present
|
||||
HRESULT CheckApiContractPresent(UINT16 version, boolean* is_present) {
|
||||
ComPtr<IApiInformationStatics> api_info;
|
||||
HRESULT hr = base::win::GetActivationFactory<
|
||||
IApiInformationStatics,
|
||||
RuntimeClass_Windows_Foundation_Metadata_ApiInformation>(&api_info);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
base::win::ScopedHString contract_name = base::win::ScopedHString::Create(
|
||||
L"Windows.Foundation.UniversalApiContract");
|
||||
if (!contract_name.is_valid()) {
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
return api_info->IsApiContractPresentByMajor(contract_name.get(), version,
|
||||
is_present);
|
||||
}
|
||||
|
||||
// Helper: Get current package using IPackageStatics
|
||||
HRESULT GetCurrentPackage(ComPtr<IPackage>* package) {
|
||||
ComPtr<IPackageStatics> package_statics;
|
||||
HRESULT hr = base::win::GetActivationFactory<
|
||||
IPackageStatics, RuntimeClass_Windows_ApplicationModel_Package>(
|
||||
&package_statics);
|
||||
if (FAILED(hr)) {
|
||||
return hr;
|
||||
}
|
||||
|
||||
return package_statics->get_Current(package->GetAddressOf());
|
||||
}
|
||||
|
||||
// Structure to hold callback data for async operations
|
||||
struct DeploymentCallbackData {
|
||||
scoped_refptr<base::SingleThreadTaskRunner> reply_runner;
|
||||
gin_helper::Promise<void> promise;
|
||||
bool fire_and_forget;
|
||||
ComPtr<DeploymentAsyncOp> async_op; // Keep async_op alive
|
||||
std::string operation_name; // "Deployment" or "Registration" for logs
|
||||
};
|
||||
|
||||
// Handler for deployment/registration completion
|
||||
void OnDeploymentCompleted(std::unique_ptr<DeploymentCallbackData> data,
|
||||
DeploymentAsyncOp* async_op,
|
||||
AsyncStatus status) {
|
||||
std::string error;
|
||||
const std::string& op_name = data->operation_name;
|
||||
|
||||
if (data->fire_and_forget) {
|
||||
std::ostringstream oss;
|
||||
oss << op_name
|
||||
<< " initiated. Force shutdown or target shutdown requested. "
|
||||
"Good bye!";
|
||||
DebugLog(oss.str());
|
||||
// Don't wait for result in fire-and-forget mode
|
||||
data->reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise) { promise.Resolve(); },
|
||||
std::move(data->promise)));
|
||||
return;
|
||||
}
|
||||
|
||||
if (status == AsyncStatus::Error) {
|
||||
ComPtr<IDeploymentResult> result;
|
||||
HRESULT hr = async_op->GetResults(&result);
|
||||
if (SUCCEEDED(hr) && result) {
|
||||
HSTRING error_text_hstring;
|
||||
hr = result->get_ErrorText(&error_text_hstring);
|
||||
if (SUCCEEDED(hr)) {
|
||||
base::win::ScopedHString scoped_error(error_text_hstring);
|
||||
error = scoped_error.GetAsUTF8();
|
||||
}
|
||||
|
||||
ComPtr<IAsyncInfo> async_info;
|
||||
hr = async_op->QueryInterface(IID_PPV_ARGS(&async_info));
|
||||
if (SUCCEEDED(hr)) {
|
||||
HRESULT error_code;
|
||||
hr = async_info->get_ErrorCode(&error_code);
|
||||
if (SUCCEEDED(hr)) {
|
||||
error += " (" + std::to_string(static_cast<int>(error_code)) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
if (error.empty()) {
|
||||
error = op_name + " failed with unknown error";
|
||||
}
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << op_name << " failed: " << error;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
} else if (status == AsyncStatus::Canceled) {
|
||||
std::ostringstream oss;
|
||||
oss << op_name << " canceled";
|
||||
DebugLog(oss.str());
|
||||
error = op_name + " canceled";
|
||||
} else if (status == AsyncStatus::Completed) {
|
||||
std::ostringstream oss;
|
||||
oss << "MSIX " << op_name << " completed.";
|
||||
DebugLog(oss.str());
|
||||
} else {
|
||||
error = op_name + " status unknown";
|
||||
std::ostringstream oss;
|
||||
oss << op_name << " status unknown";
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
|
||||
// Post result back to UI thread
|
||||
data->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(data->promise), std::move(error)));
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Create PackageManager
|
||||
ComPtr<IPackageManager> package_manager;
|
||||
HRESULT hr = CreatePackageManager(&package_manager);
|
||||
if (FAILED(hr)) {
|
||||
error = "Failed to create PackageManager";
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get IPackageManager9 for AddPackageByUriAsync
|
||||
ComPtr<IPackageManager9> package_manager9;
|
||||
hr = package_manager.As(&package_manager9);
|
||||
if (FAILED(hr)) {
|
||||
error = "Failed to get IPackageManager9 interface";
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create URI
|
||||
std::wstring uri_wstring = base::UTF8ToWide(package_uri);
|
||||
ComPtr<IUriRuntimeClass> uri;
|
||||
hr = CreateUri(uri_wstring, &uri);
|
||||
if (FAILED(hr)) {
|
||||
error = "Failed to create URI";
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Create AddPackageOptions
|
||||
ComPtr<IAddPackageOptions> package_options;
|
||||
hr = CreateAddPackageOptions(opts, &package_options);
|
||||
if (FAILED(hr)) {
|
||||
error = "Failed to create AddPackageOptions";
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream oss;
|
||||
@@ -127,63 +405,54 @@ void DoUpdateMsix(const std::string& package_uri,
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
|
||||
auto deploymentOperation =
|
||||
packageManager.AddPackageByUriAsync(uri, packageOptions);
|
||||
|
||||
if (!deploymentOperation) {
|
||||
DebugLog("Deployment operation is null");
|
||||
// Start async operation
|
||||
ComPtr<DeploymentAsyncOp> async_op;
|
||||
hr = package_manager9->AddPackageByUriAsync(uri.Get(), package_options.Get(),
|
||||
&async_op);
|
||||
if (FAILED(hr) || !async_op) {
|
||||
DebugLog("AddPackageByUriAsync failed or returned 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!");
|
||||
}
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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));
|
||||
// Set up callback data
|
||||
auto callback_data = std::make_unique<DeploymentCallbackData>();
|
||||
callback_data->reply_runner = reply_runner;
|
||||
callback_data->promise = std::move(promise);
|
||||
callback_data->fire_and_forget =
|
||||
opts.force_shutdown || opts.force_target_shutdown;
|
||||
callback_data->async_op = async_op; // Keep async_op alive
|
||||
callback_data->operation_name = "Deployment";
|
||||
|
||||
// Register completion handler
|
||||
DeploymentCallbackData* raw_data = callback_data.get();
|
||||
hr = async_op->put_Completed(
|
||||
Callback<DeploymentCompletedHandler>([data = std::move(callback_data)](
|
||||
DeploymentAsyncOp* op,
|
||||
AsyncStatus status) mutable {
|
||||
OnDeploymentCompleted(std::move(data), op, status);
|
||||
return S_OK;
|
||||
}).Get());
|
||||
|
||||
if (FAILED(hr)) {
|
||||
DebugLog("Failed to register completion handler");
|
||||
raw_data->reply_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise) {
|
||||
promise.RejectWithErrorMessage(
|
||||
"Failed to register completion handler");
|
||||
},
|
||||
std::move(raw_data->promise)));
|
||||
}
|
||||
}
|
||||
|
||||
// Performs package registration on IO thread
|
||||
@@ -192,31 +461,67 @@ void DoRegisterPackage(const std::string& family_name,
|
||||
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)
|
||||
// Create PackageManager
|
||||
ComPtr<IPackageManager> package_manager;
|
||||
HRESULT hr = CreatePackageManager(&package_manager);
|
||||
if (FAILED(hr)) {
|
||||
error = "Failed to create PackageManager";
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Get IPackageManager5 for RegisterPackageByFamilyNameAsync
|
||||
ComPtr<IPackageManager5> package_manager5;
|
||||
hr = package_manager.As(&package_manager5);
|
||||
if (FAILED(hr)) {
|
||||
error = "Failed to get IPackageManager5 interface";
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// Build DeploymentOptions flags
|
||||
DeploymentOptions deployment_options = DeploymentOptions_None;
|
||||
if (opts.force_shutdown) {
|
||||
deploymentOptions |= DeploymentOptions::ForceApplicationShutdown;
|
||||
deployment_options = static_cast<DeploymentOptions>(
|
||||
deployment_options | DeploymentOptions_ForceApplicationShutdown);
|
||||
}
|
||||
if (opts.force_target_shutdown) {
|
||||
deploymentOptions |= DeploymentOptions::ForceTargetApplicationShutdown;
|
||||
deployment_options = static_cast<DeploymentOptions>(
|
||||
deployment_options | DeploymentOptions_ForceTargetApplicationShutdown);
|
||||
}
|
||||
if (opts.force_update_from_any_version) {
|
||||
deploymentOptions |= DeploymentOptions::ForceUpdateFromAnyVersion;
|
||||
deployment_options = static_cast<DeploymentOptions>(
|
||||
deployment_options | DeploymentOptions_ForceUpdateFromAnyVersion);
|
||||
}
|
||||
|
||||
// Create empty collections for dependency and optional packages
|
||||
IIterable<winrt::hstring> emptyDependencies{nullptr};
|
||||
IIterable<winrt::hstring> emptyOptional{nullptr};
|
||||
// Create HSTRING for family name
|
||||
base::win::ScopedHString family_name_hstring =
|
||||
base::win::ScopedHString::Create(family_name);
|
||||
if (!family_name_hstring.is_valid()) {
|
||||
error = "Failed to create family name string";
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
{
|
||||
std::ostringstream oss;
|
||||
@@ -233,63 +538,59 @@ void DoRegisterPackage(const std::string& family_name,
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
|
||||
auto deploymentOperation = packageManager.RegisterPackageByFamilyNameAsync(
|
||||
familyNameH, emptyDependencies, deploymentOptions, nullptr,
|
||||
emptyOptional);
|
||||
// RegisterPackageByFamilyNameAndOptionalPackagesAsync (ABI name)
|
||||
ComPtr<DeploymentAsyncOp> async_op;
|
||||
hr = package_manager5->RegisterPackageByFamilyNameAndOptionalPackagesAsync(
|
||||
family_name_hstring.get(),
|
||||
nullptr, // dependencyPackageFamilyNames
|
||||
deployment_options,
|
||||
nullptr, // appDataVolume
|
||||
nullptr, // optionalPackageFamilyNames
|
||||
&async_op);
|
||||
|
||||
if (!deploymentOperation) {
|
||||
if (FAILED(hr) || !async_op) {
|
||||
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!");
|
||||
}
|
||||
reply_runner->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise, std::string error) {
|
||||
promise.RejectWithErrorMessage(error);
|
||||
},
|
||||
std::move(promise), std::move(error)));
|
||||
return;
|
||||
}
|
||||
|
||||
// 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));
|
||||
// Set up callback data
|
||||
auto callback_data = std::make_unique<DeploymentCallbackData>();
|
||||
callback_data->reply_runner = reply_runner;
|
||||
callback_data->promise = std::move(promise);
|
||||
callback_data->fire_and_forget =
|
||||
opts.force_shutdown || opts.force_target_shutdown;
|
||||
callback_data->async_op = async_op; // Keep async_op alive
|
||||
callback_data->operation_name = "Registration";
|
||||
|
||||
// Register completion handler
|
||||
DeploymentCallbackData* raw_data = callback_data.get();
|
||||
hr = async_op->put_Completed(
|
||||
Callback<DeploymentCompletedHandler>([data = std::move(callback_data)](
|
||||
DeploymentAsyncOp* op,
|
||||
AsyncStatus status) mutable {
|
||||
OnDeploymentCompleted(std::move(data), op, status);
|
||||
return S_OK;
|
||||
}).Get());
|
||||
|
||||
if (FAILED(hr)) {
|
||||
DebugLog("Failed to register completion handler");
|
||||
raw_data->reply_runner->PostTask(
|
||||
FROM_HERE, base::BindOnce(
|
||||
[](gin_helper::Promise<void> promise) {
|
||||
promise.RejectWithErrorMessage(
|
||||
"Failed to register completion handler");
|
||||
},
|
||||
std::move(raw_data->promise)));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -307,6 +608,16 @@ v8::Local<v8::Promise> UpdateMsix(const std::string& package_uri,
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Check for required API contract (IPackageManager9 requires v10)
|
||||
boolean is_api_present = FALSE;
|
||||
if (FAILED(CheckApiContractPresent(10, &is_api_present)) || !is_api_present) {
|
||||
DebugLog("UpdateMsix: Required Windows API contract not present");
|
||||
promise.RejectWithErrorMessage(
|
||||
"This Windows version does not support MSIX updates via this API. "
|
||||
"Windows 10 version 2004 or later is required.");
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Parse options on UI thread (where V8 is available)
|
||||
UpdateMsixOptions opts;
|
||||
options.Get("deferRegistration", &opts.defer_registration);
|
||||
@@ -349,6 +660,16 @@ v8::Local<v8::Promise> RegisterPackage(const std::string& family_name,
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Check for required API contract (IPackageManager5 requires v3)
|
||||
boolean is_api_present = FALSE;
|
||||
if (FAILED(CheckApiContractPresent(3, &is_api_present)) || !is_api_present) {
|
||||
DebugLog("RegisterPackage: Required Windows API contract not present");
|
||||
promise.RejectWithErrorMessage(
|
||||
"This Windows version does not support package registration via this "
|
||||
"API. Windows 10 version 1607 or later is required.");
|
||||
return handle;
|
||||
}
|
||||
|
||||
// Parse options on UI thread (where V8 is available)
|
||||
RegisterPackageOptions opts;
|
||||
options.Get("forceShutdown", &opts.force_shutdown);
|
||||
@@ -384,32 +705,30 @@ bool RegisterRestartOnUpdate(const std::string& command_line) {
|
||||
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
|
||||
|
||||
// Convert command line to wide string (keep in scope for API call)
|
||||
std::wstring command_line_wide;
|
||||
const wchar_t* command_line_ptr = nullptr;
|
||||
if (!command_line.empty()) {
|
||||
std::wstring commandLineW =
|
||||
std::wstring(command_line.begin(), command_line.end());
|
||||
commandLine = commandLineW.c_str();
|
||||
command_line_wide = base::UTF8ToWide(command_line);
|
||||
command_line_ptr = command_line_wide.c_str();
|
||||
}
|
||||
|
||||
HRESULT hr = RegisterApplicationRestart(commandLine, dwFlags);
|
||||
HRESULT hr = RegisterApplicationRestart(command_line_ptr, dwFlags);
|
||||
if (FAILED(hr)) {
|
||||
{
|
||||
std::ostringstream oss;
|
||||
oss << "RegisterApplicationRestart failed with error code: " << hr;
|
||||
DebugLog(oss.str());
|
||||
}
|
||||
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());
|
||||
}
|
||||
|
||||
std::ostringstream oss;
|
||||
oss << "RegisterApplicationRestart succeeded"
|
||||
<< (command_line.empty() ? "" : " with command line");
|
||||
DebugLog(oss.str());
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
@@ -434,57 +753,119 @@ v8::Local<v8::Value> GetPackageInfo() {
|
||||
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();
|
||||
boolean is_present = FALSE;
|
||||
HRESULT hr = CheckApiContractPresent(7, &is_present);
|
||||
if (SUCCEEDED(hr) && is_present) {
|
||||
ComPtr<IPackage> package;
|
||||
hr = GetCurrentPackage(&package);
|
||||
if (SUCCEEDED(hr) && package) {
|
||||
// Query all needed package interface versions upfront.
|
||||
// Note: Like IPackageManager, each IPackage version (IPackage2,
|
||||
// IPackage4, IPackage6) is a separate COM interface. IPackage6 does NOT
|
||||
// inherit methods from earlier versions. We must query each version
|
||||
// separately to access its specific methods:
|
||||
// - IPackage2: get_IsDevelopmentMode
|
||||
// - IPackage4: get_SignatureKind
|
||||
// - IPackage6: GetAppInstallerInfo
|
||||
ComPtr<IPackage2> package2;
|
||||
ComPtr<IPackage4> package4;
|
||||
ComPtr<IPackage6> package6;
|
||||
package.As(&package2);
|
||||
package.As(&package4);
|
||||
package.As(&package6);
|
||||
|
||||
// Get package ID and family name
|
||||
std::string packageId = winrt::to_string(package.Id().FullName());
|
||||
std::string familyName = winrt::to_string(package.Id().FamilyName());
|
||||
// Get package ID (from base IPackage)
|
||||
ComPtr<IPackageId> package_id;
|
||||
hr = package->get_Id(&package_id);
|
||||
if (SUCCEEDED(hr) && package_id) {
|
||||
// Get FullName
|
||||
HSTRING full_name;
|
||||
hr = package_id->get_FullName(&full_name);
|
||||
if (SUCCEEDED(hr)) {
|
||||
base::win::ScopedHString scoped_name(full_name);
|
||||
result.Set("id", scoped_name.GetAsUTF8());
|
||||
}
|
||||
|
||||
result.Set("id", packageId);
|
||||
result.Set("familyName", familyName);
|
||||
result.Set("developmentMode", package.IsDevelopmentMode());
|
||||
// Get FamilyName
|
||||
HSTRING family_name;
|
||||
hr = package_id->get_FamilyName(&family_name);
|
||||
if (SUCCEEDED(hr)) {
|
||||
base::win::ScopedHString scoped_name(family_name);
|
||||
result.Set("familyName", scoped_name.GetAsUTF8());
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Get Version
|
||||
ABI::Windows::ApplicationModel::PackageVersion pkg_version;
|
||||
hr = package_id->get_Version(&pkg_version);
|
||||
if (SUCCEEDED(hr)) {
|
||||
std::string version = std::to_string(pkg_version.Major) + "." +
|
||||
std::to_string(pkg_version.Minor) + "." +
|
||||
std::to_string(pkg_version.Build) + "." +
|
||||
std::to_string(pkg_version.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 IsDevelopmentMode (from IPackage2)
|
||||
if (package2) {
|
||||
boolean is_dev_mode = FALSE;
|
||||
hr = package2->get_IsDevelopmentMode(&is_dev_mode);
|
||||
result.Set("developmentMode", SUCCEEDED(hr) && is_dev_mode != FALSE);
|
||||
} else {
|
||||
result.Set("developmentMode", false);
|
||||
}
|
||||
|
||||
// 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);
|
||||
// Get SignatureKind (from IPackage4)
|
||||
if (package4) {
|
||||
PackageSignatureKind sig_kind;
|
||||
hr = package4->get_SignatureKind(&sig_kind);
|
||||
if (SUCCEEDED(hr)) {
|
||||
std::string signature_kind;
|
||||
switch (sig_kind) {
|
||||
case PackageSignatureKind_Developer:
|
||||
signature_kind = "developer";
|
||||
break;
|
||||
case PackageSignatureKind_Enterprise:
|
||||
signature_kind = "enterprise";
|
||||
break;
|
||||
case PackageSignatureKind_None:
|
||||
signature_kind = "none";
|
||||
break;
|
||||
case PackageSignatureKind_Store:
|
||||
signature_kind = "store";
|
||||
break;
|
||||
case PackageSignatureKind_System:
|
||||
signature_kind = "system";
|
||||
break;
|
||||
default:
|
||||
signature_kind = "none";
|
||||
break;
|
||||
}
|
||||
result.Set("signatureKind", signature_kind);
|
||||
} else {
|
||||
result.Set("signatureKind", "none");
|
||||
}
|
||||
} else {
|
||||
result.Set("signatureKind", "none");
|
||||
}
|
||||
|
||||
// Get AppInstallerInfo (from IPackage6)
|
||||
if (package6) {
|
||||
ComPtr<IAppInstallerInfo> app_installer_info;
|
||||
hr = package6->GetAppInstallerInfo(&app_installer_info);
|
||||
if (SUCCEEDED(hr) && app_installer_info) {
|
||||
ComPtr<IUriRuntimeClass> uri;
|
||||
hr = app_installer_info->get_Uri(&uri);
|
||||
if (SUCCEEDED(hr) && uri) {
|
||||
HSTRING uri_string;
|
||||
hr = uri->get_AbsoluteUri(&uri_string);
|
||||
if (SUCCEEDED(hr)) {
|
||||
base::win::ScopedHString scoped_uri(uri_string);
|
||||
result.Set("appInstallerUri", scoped_uri.GetAsUTF8());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Windows version doesn't meet minimum API requirements
|
||||
|
||||
@@ -71,7 +71,8 @@ UtilityProcessWrapper::UtilityProcessWrapper(
|
||||
base::EnvironmentMap env_map,
|
||||
base::FilePath current_working_directory,
|
||||
bool use_plugin_helper,
|
||||
bool create_network_observer) {
|
||||
bool create_network_observer,
|
||||
bool disclaim_responsibility) {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
base::win::ScopedHandle stdout_write(nullptr);
|
||||
base::win::ScopedHandle stderr_write(nullptr);
|
||||
@@ -184,6 +185,7 @@ UtilityProcessWrapper::UtilityProcessWrapper(
|
||||
.WithChildFlags(use_plugin_helper
|
||||
? content::ChildProcessHost::CHILD_PLUGIN
|
||||
: content::ChildProcessHost::CHILD_NORMAL)
|
||||
.WithDisclaimResponsibility(disclaim_responsibility)
|
||||
#endif
|
||||
.WithProcessCallback(
|
||||
base::BindOnce(&UtilityProcessWrapper::OnServiceProcessLaunch,
|
||||
@@ -453,6 +455,7 @@ gin_helper::Handle<UtilityProcessWrapper> UtilityProcessWrapper::Create(
|
||||
std::u16string display_name;
|
||||
bool use_plugin_helper = false;
|
||||
bool create_network_observer = false;
|
||||
bool disclaim_responsibility = false;
|
||||
std::map<IOHandle, IOType> stdio;
|
||||
base::FilePath current_working_directory;
|
||||
base::EnvironmentMap env_map;
|
||||
@@ -496,13 +499,15 @@ gin_helper::Handle<UtilityProcessWrapper> UtilityProcessWrapper::Create(
|
||||
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
opts.Get("allowLoadingUnsignedLibraries", &use_plugin_helper);
|
||||
opts.Get("disclaim", &disclaim_responsibility);
|
||||
#endif
|
||||
}
|
||||
auto handle = gin_helper::CreateHandle(
|
||||
args->isolate(), new UtilityProcessWrapper(
|
||||
std::move(params), display_name, std::move(stdio),
|
||||
env_map, current_working_directory,
|
||||
use_plugin_helper, create_network_observer));
|
||||
args->isolate(),
|
||||
new UtilityProcessWrapper(
|
||||
std::move(params), display_name, std::move(stdio), env_map,
|
||||
current_working_directory, use_plugin_helper, create_network_observer,
|
||||
disclaim_responsibility));
|
||||
handle->Pin(args->isolate());
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -72,7 +72,8 @@ class UtilityProcessWrapper final
|
||||
base::EnvironmentMap env_map,
|
||||
base::FilePath current_working_directory,
|
||||
bool use_plugin_helper,
|
||||
bool create_network_observer);
|
||||
bool create_network_observer,
|
||||
bool disclaim_responsibility);
|
||||
void OnServiceProcessLaunch(const base::Process& process);
|
||||
void CloseConnectorPort();
|
||||
|
||||
|
||||
@@ -400,31 +400,47 @@ class FileSystemAccessPermissionContext::PermissionGrantImpl
|
||||
}
|
||||
}
|
||||
|
||||
// Updates the in-memory permission grant for the `new_path` in the `grants`
|
||||
// map using the same grant from the `old_path`, and removes the grant entry
|
||||
// for the `old_path`.
|
||||
// If `allow_overwrite` is true, this will replace any pre-existing grant at
|
||||
// `new_path`.
|
||||
static void UpdateGrantPath(
|
||||
std::map<base::FilePath, PermissionGrantImpl*>& grants,
|
||||
const content::PathInfo& old_path,
|
||||
const content::PathInfo& new_path) {
|
||||
const content::PathInfo& new_path,
|
||||
bool allow_overwrite) {
|
||||
DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
|
||||
auto entry_it =
|
||||
auto old_path_it =
|
||||
std::ranges::find_if(grants, [&old_path](const auto& entry) {
|
||||
return entry.first == old_path.path;
|
||||
});
|
||||
|
||||
if (entry_it == grants.end()) {
|
||||
// There must be an entry for an ancestor of this entry. Nothing to do
|
||||
// here.
|
||||
if (old_path_it == grants.end()) {
|
||||
return;
|
||||
}
|
||||
|
||||
DCHECK_EQ(entry_it->second->GetActivePermissionStatus(),
|
||||
DCHECK_EQ(old_path_it->second->GetActivePermissionStatus(),
|
||||
PermissionStatus::GRANTED);
|
||||
|
||||
auto* const grant_impl = entry_it->second;
|
||||
grant_impl->SetPath(new_path);
|
||||
auto* const grant_to_move = old_path_it->second;
|
||||
|
||||
// Update the permission grant's key in the map of active permissions.
|
||||
grants.erase(entry_it);
|
||||
grants.emplace(new_path.path, grant_impl);
|
||||
// See https://chromium-review.googlesource.com/4803165
|
||||
if (allow_overwrite) {
|
||||
auto new_path_it = grants.find(new_path.path);
|
||||
if (new_path_it != grants.end() && new_path_it->second != grant_to_move) {
|
||||
new_path_it->second->SetStatus(PermissionStatus::DENIED);
|
||||
}
|
||||
}
|
||||
|
||||
grant_to_move->SetPath(new_path);
|
||||
|
||||
grants.erase(old_path_it);
|
||||
if (allow_overwrite) {
|
||||
grants.insert_or_assign(new_path.path, grant_to_move);
|
||||
} else {
|
||||
grants.emplace(new_path.path, grant_to_move);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -930,12 +946,17 @@ void FileSystemAccessPermissionContext::NotifyEntryMoved(
|
||||
return;
|
||||
}
|
||||
|
||||
// It's possible `new_path` already has existing persistent permission.
|
||||
// See crbug.com/423663220.
|
||||
bool allow_overwrite = base::FeatureList::IsEnabled(
|
||||
features::kFileSystemAccessMoveWithOverwrite);
|
||||
|
||||
auto it = active_permissions_map_.find(origin);
|
||||
if (it != active_permissions_map_.end()) {
|
||||
PermissionGrantImpl::UpdateGrantPath(it->second.write_grants, old_path,
|
||||
new_path);
|
||||
new_path, allow_overwrite);
|
||||
PermissionGrantImpl::UpdateGrantPath(it->second.read_grants, old_path,
|
||||
new_path);
|
||||
new_path, allow_overwrite);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,14 @@ static NSDictionary* UNNotificationResponseToNSDictionary(
|
||||
}
|
||||
|
||||
- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
|
||||
return menu_controller_ ? menu_controller_.menu : nil;
|
||||
if (!menu_controller_)
|
||||
return nil;
|
||||
|
||||
// Manually refresh menu state since menuWillOpen: is not called
|
||||
// by macOS for dock menus for some reason before they are displayed.
|
||||
NSMenu* menu = menu_controller_.menu;
|
||||
[menu_controller_ refreshMenuTree:menu];
|
||||
return menu;
|
||||
}
|
||||
|
||||
- (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename {
|
||||
|
||||
@@ -50,6 +50,8 @@
|
||||
<string>This app needs access to the microphone</string>
|
||||
<key>NSCameraUsageDescription</key>
|
||||
<string>This app needs access to the camera</string>
|
||||
<key>NSAudioCaptureUsageDescription</key>
|
||||
<string>This app needs access to audio capture</string>
|
||||
<key>NSBluetoothAlwaysUsageDescription</key>
|
||||
<string>This app needs access to Bluetooth</string>
|
||||
<key>NSBluetoothPeripheralUsageDescription</key>
|
||||
|
||||
@@ -57,6 +57,10 @@ class ElectronMenuModel;
|
||||
// Whether the menu is currently open.
|
||||
- (BOOL)isMenuOpen;
|
||||
|
||||
// Recursively refreshes the menu tree starting from |menu|, applying the
|
||||
// model state (enabled, checked, hidden etc) to each menu item.
|
||||
- (void)refreshMenuTree:(NSMenu*)menu;
|
||||
|
||||
// NSMenuDelegate methods this class implements. Subclasses should call super
|
||||
// if extending the behavior.
|
||||
- (void)menuWillOpen:(NSMenu*)menu;
|
||||
|
||||
@@ -493,8 +493,6 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||
: NSControlStateValueOff;
|
||||
}
|
||||
|
||||
// Recursively refreshes the menu tree starting from |menu|, applying the
|
||||
// model state to each menu item.
|
||||
- (void)refreshMenuTree:(NSMenu*)menu {
|
||||
for (NSMenuItem* item in menu.itemArray) {
|
||||
[self applyStateToMenuItem:item];
|
||||
|
||||
@@ -150,6 +150,31 @@ bool ElectronDesktopWindowTreeHostWin::HandleMouseEvent(ui::MouseEvent* event) {
|
||||
return views::DesktopWindowTreeHostWin::HandleMouseEvent(event);
|
||||
}
|
||||
|
||||
bool ElectronDesktopWindowTreeHostWin::HandleIMEMessage(UINT message,
|
||||
WPARAM w_param,
|
||||
LPARAM l_param,
|
||||
LRESULT* result) {
|
||||
if ((message == WM_SYSCHAR) && (w_param == VK_SPACE)) {
|
||||
if (native_window_view_->widget() &&
|
||||
native_window_view_->widget()->non_client_view()) {
|
||||
const auto* frame =
|
||||
native_window_view_->widget()->non_client_view()->frame_view();
|
||||
auto location = frame->GetSystemMenuScreenPixelLocation();
|
||||
|
||||
bool prevent_default = false;
|
||||
native_window_view_->NotifyWindowSystemContextMenu(
|
||||
location.x(), location.y(), &prevent_default);
|
||||
|
||||
return prevent_default ||
|
||||
views::DesktopWindowTreeHostWin::HandleIMEMessage(message, w_param,
|
||||
l_param, result);
|
||||
}
|
||||
}
|
||||
|
||||
return views::DesktopWindowTreeHostWin::HandleIMEMessage(message, w_param,
|
||||
l_param, result);
|
||||
}
|
||||
|
||||
void ElectronDesktopWindowTreeHostWin::HandleVisibilityChanged(bool visible) {
|
||||
if (native_window_view_->widget())
|
||||
native_window_view_->widget()->OnNativeWidgetVisibilityChanged(visible);
|
||||
|
||||
@@ -44,6 +44,10 @@ class ElectronDesktopWindowTreeHostWin : public views::DesktopWindowTreeHostWin,
|
||||
int frame_thickness) const override;
|
||||
bool HandleMouseEventForCaption(UINT message) const override;
|
||||
bool HandleMouseEvent(ui::MouseEvent* event) override;
|
||||
bool HandleIMEMessage(UINT message,
|
||||
WPARAM w_param,
|
||||
LPARAM l_param,
|
||||
LRESULT* result) override;
|
||||
void HandleVisibilityChanged(bool visible) override;
|
||||
void SetAllowScreenshots(bool allow) override;
|
||||
|
||||
|
||||
@@ -106,7 +106,10 @@ bool ProcessSignatureIsSameWithCurrentApp(pid_t pid) {
|
||||
status = SecCodeCheckValidity(process_code.get(), kSecCSDefaultFlags,
|
||||
self_requirement.get());
|
||||
if (status != errSecSuccess && status != errSecCSReqFailed) {
|
||||
OSSTATUS_LOG(ERROR, status) << "SecCodeCheckValidity";
|
||||
// If the code is unsigned, don't log that (it's not an actual error).
|
||||
if (status != errSecCSUnsigned) {
|
||||
OSSTATUS_LOG(ERROR, status) << "SecCodeCheckValidity";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return status == errSecSuccess;
|
||||
|
||||
@@ -9,6 +9,7 @@ import * as cp from 'node:child_process';
|
||||
import * as fs from 'node:fs';
|
||||
import * as http from 'node:http';
|
||||
import { AddressInfo } from 'node:net';
|
||||
import * as os from 'node:os';
|
||||
import * as path from 'node:path';
|
||||
|
||||
import { copyMacOSFixtureApp, getCodesignIdentity, shouldRunCodesignTests, signApp, spawn } from './lib/codesign-helpers';
|
||||
@@ -67,6 +68,38 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
|
||||
}
|
||||
};
|
||||
|
||||
// Squirrel stores update directories in ~/Library/Caches/com.github.Electron.ShipIt/
|
||||
// as subdirectories named like update.XXXXXXX
|
||||
const getSquirrelCacheDirectory = () => {
|
||||
return path.join(os.homedir(), 'Library', 'Caches', 'com.github.Electron.ShipIt');
|
||||
};
|
||||
|
||||
const getUpdateDirectoriesInCache = async () => {
|
||||
const cacheDir = getSquirrelCacheDirectory();
|
||||
try {
|
||||
const entries = await fs.promises.readdir(cacheDir, { withFileTypes: true });
|
||||
return entries
|
||||
.filter(entry => entry.isDirectory() && entry.name.startsWith('update.'))
|
||||
.map(entry => path.join(cacheDir, entry.name));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const cleanSquirrelCache = async () => {
|
||||
const cacheDir = getSquirrelCacheDirectory();
|
||||
try {
|
||||
const entries = await fs.promises.readdir(cacheDir, { withFileTypes: true });
|
||||
for (const entry of entries) {
|
||||
if (entry.isDirectory() && entry.name.startsWith('update.')) {
|
||||
await fs.promises.rm(path.join(cacheDir, entry.name), { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// Cache dir may not exist yet
|
||||
}
|
||||
};
|
||||
|
||||
const cachedZips: Record<string, string> = {};
|
||||
|
||||
type Mutation = {
|
||||
@@ -340,6 +373,67 @@ ifdescribe(shouldRunCodesignTests)('autoUpdater behavior', function () {
|
||||
});
|
||||
});
|
||||
|
||||
it('should clean up old staged update directories when a new update is downloaded', async () => {
|
||||
// Clean up any existing update directories before the test
|
||||
await cleanSquirrelCache();
|
||||
|
||||
await withUpdatableApp({
|
||||
nextVersion: '2.0.0',
|
||||
startFixture: 'update-stack',
|
||||
endFixture: 'update-stack'
|
||||
}, async (appPath, updateZipPath2) => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '3.0.0',
|
||||
startFixture: 'update-stack',
|
||||
endFixture: 'update-stack'
|
||||
}, async (_, updateZipPath3) => {
|
||||
let updateCount = 0;
|
||||
let downloadCount = 0;
|
||||
let directoriesDuringSecondDownload: string[] = [];
|
||||
|
||||
server.get('/update-file', async (req, res) => {
|
||||
downloadCount++;
|
||||
// When the second download request arrives, Squirrel has already
|
||||
// called uniqueTemporaryDirectoryForUpdate which (with our patch)
|
||||
// cleans up old directories before creating the new one.
|
||||
// Without the patch, both directories would exist at this point.
|
||||
if (downloadCount === 2) {
|
||||
directoriesDuringSecondDownload = await getUpdateDirectoriesInCache();
|
||||
}
|
||||
res.download(updateCount > 1 ? updateZipPath3 : updateZipPath2);
|
||||
});
|
||||
server.get('/update-check', (req, res) => {
|
||||
updateCount++;
|
||||
res.json({
|
||||
url: `http://localhost:${port}/update-file`,
|
||||
name: 'My Release Name',
|
||||
notes: 'Theses are some release notes innit',
|
||||
pub_date: (new Date()).toString()
|
||||
});
|
||||
});
|
||||
const relaunchPromise = new Promise<void>((resolve) => {
|
||||
server.get('/update-check/updated/:version', (req, res) => {
|
||||
res.status(204).send();
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
const launchResult = await launchApp(appPath, [`http://localhost:${port}/update-check`]);
|
||||
logOnError(launchResult, () => {
|
||||
expect(launchResult).to.have.property('code', 0);
|
||||
expect(launchResult.out).to.include('Update Downloaded');
|
||||
});
|
||||
|
||||
await relaunchPromise;
|
||||
|
||||
// During the second download, the old staged update directory should
|
||||
// have been cleaned up. With our patch, there should be exactly 1
|
||||
// directory (the new one). Without the patch, there would be 2.
|
||||
expect(directoriesDuringSecondDownload).to.have.lengthOf(1,
|
||||
`Expected 1 update directory during second download but found ${directoriesDuringSecondDownload.length}: ${directoriesDuringSecondDownload.join(', ')}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should update to lower version numbers', async () => {
|
||||
await withUpdatableApp({
|
||||
nextVersion: '0.0.1',
|
||||
|
||||
@@ -43,6 +43,65 @@ describe('MenuItems', () => {
|
||||
expect(item).to.have.property('role').that.is.a('string');
|
||||
expect(item).to.have.property('icon');
|
||||
});
|
||||
|
||||
it('should have a default accelerator for certain roles', () => {
|
||||
const items: Record<string, Electron.MenuItem['accelerator']> = {
|
||||
undo: 'CommandOrControl+Z',
|
||||
redo: process.platform === 'win32' ? 'Control+Y' : 'Shift+CommandOrControl+Z',
|
||||
cut: 'CommandOrControl+X',
|
||||
copy: 'CommandOrControl+C',
|
||||
paste: 'CommandOrControl+V',
|
||||
pasteAndMatchStyle: process.platform === 'darwin' ? 'Cmd+Option+Shift+V' : 'Shift+CommandOrControl+V',
|
||||
delete: null,
|
||||
selectAll: 'CommandOrControl+A',
|
||||
reload: 'CmdOrCtrl+R',
|
||||
forceReload: 'Shift+CmdOrCtrl+R',
|
||||
toggleDevTools: process.platform === 'darwin' ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||
resetZoom: 'CommandOrControl+0',
|
||||
zoomIn: 'CommandOrControl+Plus',
|
||||
zoomOut: 'CommandOrControl+-',
|
||||
toggleSpellChecker: null,
|
||||
togglefullscreen: process.platform === 'darwin' ? 'Control+Command+F' : 'F11',
|
||||
window: null,
|
||||
minimize: 'CommandOrControl+M',
|
||||
close: 'CommandOrControl+W',
|
||||
help: null,
|
||||
about: null,
|
||||
services: null,
|
||||
hide: 'Command+H',
|
||||
hideOthers: 'Command+Alt+H',
|
||||
unhide: null,
|
||||
quit: process.platform === 'win32' ? null : 'CommandOrControl+Q',
|
||||
showSubstitutions: null,
|
||||
toggleSmartQuotes: null,
|
||||
toggleSmartDashes: null,
|
||||
toggleTextReplacement: null,
|
||||
startSpeaking: null,
|
||||
stopSpeaking: null,
|
||||
zoom: null,
|
||||
front: null,
|
||||
appMenu: null,
|
||||
fileMenu: null,
|
||||
editMenu: null,
|
||||
viewMenu: null,
|
||||
shareMenu: null,
|
||||
recentDocuments: null,
|
||||
toggleTabBar: null,
|
||||
selectNextTab: null,
|
||||
selectPreviousTab: null,
|
||||
showAllTabs: null,
|
||||
mergeAllWindows: null,
|
||||
clearRecentDocuments: null,
|
||||
moveTabToNewWindow: null,
|
||||
windowMenu: null
|
||||
};
|
||||
|
||||
for (const role in items) {
|
||||
if (!Object.hasOwn(items, role)) continue;
|
||||
const item = new MenuItem({ role: role as any });
|
||||
expect(item.accelerator).to.equal(items[role]);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('MenuItem.click', () => {
|
||||
@@ -480,7 +539,7 @@ describe('MenuItems', () => {
|
||||
|
||||
it('should display modifiers correctly for simple keys', () => {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{ label: 'text', accelerator: 'CmdOrCtrl+A' },
|
||||
{ label: 'text', accelerator: 'CommandOrControl+A' },
|
||||
{ label: 'text', accelerator: 'Shift+A' },
|
||||
{ label: 'text', accelerator: 'Alt+A' }
|
||||
]);
|
||||
@@ -492,7 +551,7 @@ describe('MenuItems', () => {
|
||||
|
||||
it('should display modifiers correctly for special keys', () => {
|
||||
const menu = Menu.buildFromTemplate([
|
||||
{ label: 'text', accelerator: 'CmdOrCtrl+Tab' },
|
||||
{ label: 'text', accelerator: 'CommandOrControl+Tab' },
|
||||
{ label: 'text', accelerator: 'Shift+Tab' },
|
||||
{ label: 'text', accelerator: 'Alt+Tab' }
|
||||
]);
|
||||
|
||||
@@ -863,5 +863,24 @@ describe('utilityProcess module', () => {
|
||||
await exit;
|
||||
}
|
||||
});
|
||||
|
||||
// Note: This doesn't test that disclaiming works (that requires stubbing / mocking TCC which is
|
||||
// just straight up not possible generically). This just tests that utility processes still launch
|
||||
// when disclaimed.
|
||||
ifit(process.platform === 'darwin')('supports disclaim option on macOS', async () => {
|
||||
const child = utilityProcess.fork(path.join(fixturesPath, 'post-message.js'), [], {
|
||||
disclaim: true
|
||||
});
|
||||
await once(child, 'spawn');
|
||||
expect(child.pid).to.be.a('number');
|
||||
// Verify the process can communicate normally
|
||||
const testMessage = 'test-disclaim';
|
||||
child.postMessage(testMessage);
|
||||
const [data] = await once(child, 'message');
|
||||
expect(data).to.equal(testMessage);
|
||||
const exit = once(child, 'exit');
|
||||
expect(child.kill()).to.be.true();
|
||||
await exit;
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
36
yarn.lock
36
yarn.lock
@@ -490,6 +490,7 @@ __metadata:
|
||||
webpack: "npm:^5.95.0"
|
||||
webpack-cli: "npm:^5.1.4"
|
||||
wrapper-webpack-plugin: "npm:^2.2.0"
|
||||
yaml: "npm:^2.8.1"
|
||||
dependenciesMeta:
|
||||
abstract-socket:
|
||||
built: true
|
||||
@@ -8704,7 +8705,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-core-commonmark@npm:2.0.3":
|
||||
"micromark-core-commonmark@npm:2.0.3, micromark-core-commonmark@npm:^2.0.0":
|
||||
version: 2.0.3
|
||||
resolution: "micromark-core-commonmark@npm:2.0.3"
|
||||
dependencies:
|
||||
@@ -8728,30 +8729,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-core-commonmark@npm:^2.0.0":
|
||||
version: 2.0.1
|
||||
resolution: "micromark-core-commonmark@npm:2.0.1"
|
||||
dependencies:
|
||||
decode-named-character-reference: "npm:^1.0.0"
|
||||
devlop: "npm:^1.0.0"
|
||||
micromark-factory-destination: "npm:^2.0.0"
|
||||
micromark-factory-label: "npm:^2.0.0"
|
||||
micromark-factory-space: "npm:^2.0.0"
|
||||
micromark-factory-title: "npm:^2.0.0"
|
||||
micromark-factory-whitespace: "npm:^2.0.0"
|
||||
micromark-util-character: "npm:^2.0.0"
|
||||
micromark-util-chunked: "npm:^2.0.0"
|
||||
micromark-util-classify-character: "npm:^2.0.0"
|
||||
micromark-util-html-tag-name: "npm:^2.0.0"
|
||||
micromark-util-normalize-identifier: "npm:^2.0.0"
|
||||
micromark-util-resolve-all: "npm:^2.0.0"
|
||||
micromark-util-subtokenize: "npm:^2.0.0"
|
||||
micromark-util-symbol: "npm:^2.0.0"
|
||||
micromark-util-types: "npm:^2.0.0"
|
||||
checksum: 10c0/a0b280b1b6132f600518e72cb29a4dd1b2175b85f5ed5b25d2c5695e42b876b045971370daacbcfc6b4ce8cf7acbf78dd3a0284528fb422b450144f4b3bebe19
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"micromark-extension-directive@npm:4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "micromark-extension-directive@npm:4.0.0"
|
||||
@@ -14492,6 +14469,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.8.1":
|
||||
version: 2.8.2
|
||||
resolution: "yaml@npm:2.8.2"
|
||||
bin:
|
||||
yaml: bin.mjs
|
||||
checksum: 10c0/703e4dc1e34b324aa66876d63618dcacb9ed49f7e7fe9b70f1e703645be8d640f68ab84f12b86df8ac960bac37acf5513e115de7c970940617ce0343c8c9cd96
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yamux-js@npm:0.1.2":
|
||||
version: 0.1.2
|
||||
resolution: "yamux-js@npm:0.1.2"
|
||||
|
||||
Reference in New Issue
Block a user