From 6a47bb47315d68570db2767e91cb2d6ff0bc996f Mon Sep 17 00:00:00 2001 From: Noah Gregory Date: Wed, 4 Feb 2026 14:43:36 -0500 Subject: [PATCH 01/10] fix: use `temp` directory for singleton test and clean up at end (#49604) * fix: create directory for singleton test in `temp` instead of `home` * fix: remove directory for singleton test at test end * refactor: avoid extraneous declarations in singleton test * refactor: reintroduce `userDataFolder` declaration in singleton test * refactor: move cleanup before app exit in singleton test * style: add missing semicolon * refactor: set the user data path after pre-test cleanup in singleton test * fix: release lock before cleanup in singleton test --- spec/fixtures/api/singleton-userdata/main.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/spec/fixtures/api/singleton-userdata/main.js b/spec/fixtures/api/singleton-userdata/main.js index a0ec2f90a2..569206e258 100644 --- a/spec/fixtures/api/singleton-userdata/main.js +++ b/spec/fixtures/api/singleton-userdata/main.js @@ -3,11 +3,17 @@ const { app } = require('electron'); const fs = require('node:fs'); const path = require('node:path'); +const userDataFolder = path.join(app.getPath('temp'), 'electron-test-singleton-userdata'); + // non-existent user data folder should not break requestSingleInstanceLock() // ref: https://github.com/electron/electron/issues/33547 -const userDataFolder = path.join(app.getPath('home'), 'electron-test-singleton-userdata'); -fs.rmSync(userDataFolder, { force: true, recursive: true }); -app.setPath('userData', userDataFolder); +fs.rmSync(userDataFolder, { recursive: true, force: true }); +// set the user data path after clearing out old state and right before we use it +app.setPath('userData', userDataFolder); const gotTheLock = app.requestSingleInstanceLock(); + +app.releaseSingleInstanceLock(); +fs.rmSync(userDataFolder, { recursive: true, force: true }); + app.exit(gotTheLock ? 0 : 1); From 4ea2d816b8e7296c58111054fe9e0ecff253b7eb Mon Sep 17 00:00:00 2001 From: John Kleinschmidt Date: Wed, 4 Feb 2026 14:55:20 -0500 Subject: [PATCH 02/10] revert(ci): use new case syntax in workflows (#49665) Revert "ci: use new case syntax in workflows (#49590)" This reverts commit def7854848d7b2bed6cd96d8b31d2a5de91048ec. --- .github/actions/build-electron/action.yml | 2 +- .github/workflows/pipeline-segment-electron-build.yml | 9 ++------- .../pipeline-segment-electron-clang-tidy.yml | 11 +++-------- .../workflows/pipeline-segment-electron-gn-check.yml | 7 +------ .github/workflows/pipeline-segment-electron-test.yml | 9 ++------- 5 files changed, 9 insertions(+), 29 deletions(-) diff --git a/.github/actions/build-electron/action.yml b/.github/actions/build-electron/action.yml index 762eee223b..ba5bef4f3f 100644 --- a/.github/actions/build-electron/action.yml +++ b/.github/actions/build-electron/action.yml @@ -40,7 +40,7 @@ runs: echo "GN_EXTRA_ARGS=$GN_APPENDED_ARGS" >> $GITHUB_ENV - name: Set GN_EXTRA_ARGS for Windows shell: bash - if: ${{ inputs.target-arch != 'x64' && inputs.target-platform == 'win' }} + if: ${{inputs.target-arch != 'x64' && inputs.target-platform == 'win' }} run: | GN_APPENDED_ARGS="$GN_EXTRA_ARGS target_cpu=\"${{ inputs.target-arch }}\"" echo "GN_EXTRA_ARGS=$GN_APPENDED_ARGS" >> $GITHUB_ENV diff --git a/.github/workflows/pipeline-segment-electron-build.yml b/.github/workflows/pipeline-segment-electron-build.yml index 1012a3d1a8..a70ac7e9d5 100644 --- a/.github/workflows/pipeline-segment-electron-build.yml +++ b/.github/workflows/pipeline-segment-electron-build.yml @@ -78,12 +78,7 @@ env: ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }} SUDOWOODO_EXCHANGE_URL: ${{ secrets.SUDOWOODO_EXCHANGE_URL }} SUDOWOODO_EXCHANGE_TOKEN: ${{ secrets.SUDOWOODO_EXCHANGE_TOKEN }} - GCLIENT_EXTRA_ARGS: |- - ${{ case( - 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' - ) }} + 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 }} @@ -206,7 +201,7 @@ jobs: with: target-arch: ${{ inputs.target-arch }} target-platform: ${{ inputs.target-platform }} - artifact-platform: ${{ case(inputs.target-platform == 'macos', 'darwin', 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 }}' diff --git a/.github/workflows/pipeline-segment-electron-clang-tidy.yml b/.github/workflows/pipeline-segment-electron-clang-tidy.yml index 27b2d79db9..7881e619d3 100644 --- a/.github/workflows/pipeline-segment-electron-clang-tidy.yml +++ b/.github/workflows/pipeline-segment-electron-clang-tidy.yml @@ -28,12 +28,7 @@ concurrency: cancel-in-progress: true env: - GCLIENT_EXTRA_ARGS: |- - ${{ case( - inputs.target-platform == 'macos', '--custom-var=checkout_mac=True --custom-var=host_os=mac', - inputs.target-platform == 'linux', '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True', - '--custom-var=checkout_win=True' - ) }} + GCLIENT_EXTRA_ARGS: ${{ inputs.target-platform == 'macos' && '--custom-var=checkout_mac=True --custom-var=host_os=mac' || (inputs.target-platform == 'linux' && '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True' || '--custom-var=checkout_win=True') }} ELECTRON_OUT_DIR: Default jobs: @@ -46,10 +41,10 @@ jobs: contents: read container: ${{ fromJSON(inputs.clang-tidy-container) }} env: - BUILD_TYPE: ${{ case(inputs.target-platform == 'macos', 'darwin', inputs.target-platform) }} + BUILD_TYPE: ${{ inputs.target-platform == 'macos' && 'darwin' || inputs.target-platform }} TARGET_ARCH: ${{ inputs.target-arch }} TARGET_PLATFORM: ${{ inputs.target-platform }} - ARTIFACT_KEY: ${{ case(inputs.target-platform == 'macos', 'darwin', inputs.target-platform) }}_${{ inputs.target-arch }} + ARTIFACT_KEY: ${{ inputs.target-platform == 'macos' && 'darwin' || inputs.target-platform }}_${{ inputs.target-arch }} steps: - name: Checkout Electron uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd diff --git a/.github/workflows/pipeline-segment-electron-gn-check.yml b/.github/workflows/pipeline-segment-electron-gn-check.yml index 9974edbaa9..0c28e2c8c1 100644 --- a/.github/workflows/pipeline-segment-electron-gn-check.yml +++ b/.github/workflows/pipeline-segment-electron-gn-check.yml @@ -34,12 +34,7 @@ concurrency: env: ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }} - GCLIENT_EXTRA_ARGS: |- - ${{ case( - inputs.target-platform == 'macos', '--custom-var=checkout_mac=True --custom-var=host_os=mac', - inputs.target-platform == 'linux', '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True', - '--custom-var=checkout_win=True' - ) }} + GCLIENT_EXTRA_ARGS: ${{ inputs.target-platform == 'macos' && '--custom-var=checkout_mac=True --custom-var=host_os=mac' || (inputs.target-platform == 'linux' && '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True' || '--custom-var=checkout_win=True') }} ELECTRON_OUT_DIR: Default jobs: diff --git a/.github/workflows/pipeline-segment-electron-test.yml b/.github/workflows/pipeline-segment-electron-test.yml index 532b9f1f12..68c46f3ef4 100644 --- a/.github/workflows/pipeline-segment-electron-test.yml +++ b/.github/workflows/pipeline-segment-electron-test.yml @@ -58,13 +58,8 @@ jobs: strategy: fail-fast: false matrix: - build-type: |- - ${{ case( - inputs.target-platform == 'macos', fromJSON('["darwin","mas"]'), - inputs.target-platform == 'win', fromJSON('["win"]'), - fromJSON('["linux"]') - ) }} - shard: ${{ case(inputs.target-platform == 'linux', fromJSON('[1, 2, 3]'), fromJSON('[1, 2]')) }} + build-type: ${{ inputs.target-platform == 'macos' && fromJSON('["darwin","mas"]') || (inputs.target-platform == 'win' && fromJSON('["win"]') || fromJSON('["linux"]')) }} + shard: ${{ inputs.target-platform == 'linux' && fromJSON('[1, 2, 3]') || fromJSON('[1, 2]') }} env: BUILD_TYPE: ${{ matrix.build-type }} TARGET_ARCH: ${{ inputs.target-arch }} From 9740c989da1ec4d7068bc26b3110a615894a45ad Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Wed, 4 Feb 2026 22:45:29 +0100 Subject: [PATCH 03/10] fix: default accelerator for role-based menu items (#49558) fix: apply default accelerator for role-based menu items Co-authored-by: Kimgun3383 --- docs/api/menu-item.md | 2 +- lib/browser/api/menu-item-roles.ts | 1 + lib/browser/api/menu-item.ts | 2 +- spec/api-menu-item-spec.ts | 63 +++++++++++++++++++++++++++++- 4 files changed, 64 insertions(+), 4 deletions(-) diff --git a/docs/api/menu-item.md b/docs/api/menu-item.md index 2f0ed2eb66..18af1af4bd 100644 --- a/docs/api/menu-item.md +++ b/docs/api/menu-item.md @@ -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_ diff --git a/lib/browser/api/menu-item-roles.ts b/lib/browser/api/menu-item-roles.ts index 1bb48b636e..2d8e8eb215 100644 --- a/lib/browser/api/menu-item-roles.ts +++ b/lib/browser/api/menu-item-roles.ts @@ -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) { diff --git a/lib/browser/api/menu-item.ts b/lib/browser/api/menu-item.ts index 6c58a80d33..d08fdb86cd 100644 --- a/lib/browser/api/menu-item.ts +++ b/lib/browser/api/menu-item.ts @@ -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'); diff --git a/spec/api-menu-item-spec.ts b/spec/api-menu-item-spec.ts index dd71140cac..22294d6992 100644 --- a/spec/api-menu-item-spec.ts +++ b/spec/api-menu-item-spec.ts @@ -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 = { + 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' } ]); From 41c7e9bb219987700db716d851b68a2226ecf9eb Mon Sep 17 00:00:00 2001 From: David Sanders Date: Wed, 4 Feb 2026 15:51:49 -0800 Subject: [PATCH 04/10] ci: use squash merge for apply patches workflow (#49667) --- .github/workflows/apply-patches.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/apply-patches.yml b/.github/workflows/apply-patches.yml index 980a3b3cba..181b085cd8 100644 --- a/.github/workflows/apply-patches.yml +++ b/.github/workflows/apply-patches.yml @@ -56,16 +56,16 @@ 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 - name: Checkout & Sync & Save uses: ./src/electron/.github/actions/checkout with: From 50381a6d579b35b436266ba9520e18a954b3b575 Mon Sep 17 00:00:00 2001 From: Noah Gregory Date: Thu, 5 Feb 2026 01:26:17 -0500 Subject: [PATCH 05/10] refactor: don't log error just for unsigned code (#49654) --- shell/common/mac/codesign_util.cc | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/shell/common/mac/codesign_util.cc b/shell/common/mac/codesign_util.cc index 3ad25421e6..d40275d897 100644 --- a/shell/common/mac/codesign_util.cc +++ b/shell/common/mac/codesign_util.cc @@ -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; From dec7f937ae20ac926e2c3ff51d5d01f99b5e3ea5 Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Thu, 5 Feb 2026 06:37:17 -0800 Subject: [PATCH 06/10] build: generate artifact attestions for released assets (#48239) * build: generate artifact attestions for released assets * chore: address review feedback --------- Co-authored-by: John Kleinschmidt --- .github/actions/build-electron/action.yml | 6 + .github/workflows/linux-publish.yml | 12 +- .github/workflows/macos-publish.yml | 16 +- .github/workflows/pipeline-electron-lint.yml | 6 +- .../pipeline-segment-electron-publish.yml | 242 ++++++++++++++++++ .github/workflows/windows-publish.yml | 12 +- package.json | 7 +- script/copy-pipeline-segment-publish.js | 31 +++ script/release/uploaders/upload.py | 8 + yarn.lock | 10 + 10 files changed, 338 insertions(+), 12 deletions(-) create mode 100644 .github/workflows/pipeline-segment-electron-publish.yml create mode 100644 script/copy-pipeline-segment-publish.js diff --git a/.github/actions/build-electron/action.yml b/.github/actions/build-electron/action.yml index ba5bef4f3f..81f4d631ea 100644 --- a/.github/actions/build-electron/action.yml +++ b/.github/actions/build-electron/action.yml @@ -219,6 +219,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 @@ -229,6 +230,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 diff --git a/.github/workflows/linux-publish.yml b/.github/workflows/linux-publish.yml index 0c197a23de..11ff9ac195 100644 --- a/.github/workflows/linux-publish.yml +++ b/.github/workflows/linux-publish.yml @@ -44,9 +44,11 @@ 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: + attestations: write contents: read + id-token: write needs: checkout-linux with: environment: production-release @@ -61,9 +63,11 @@ jobs: secrets: inherit publish-arm: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-linux with: environment: production-release @@ -78,9 +82,11 @@ jobs: secrets: inherit publish-arm64: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-linux with: environment: production-release diff --git a/.github/workflows/macos-publish.yml b/.github/workflows/macos-publish.yml index 6adb2a88a1..f1673ad64c 100644 --- a/.github/workflows/macos-publish.yml +++ b/.github/workflows/macos-publish.yml @@ -48,9 +48,11 @@ jobs: target-platform: macos publish-x64-darwin: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-macos with: environment: production-release @@ -65,9 +67,11 @@ jobs: secrets: inherit publish-x64-mas: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-macos with: environment: production-release @@ -82,9 +86,11 @@ jobs: secrets: inherit publish-arm64-darwin: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-macos with: environment: production-release @@ -99,9 +105,11 @@ jobs: secrets: inherit publish-arm64-mas: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-macos with: environment: production-release diff --git a/.github/workflows/pipeline-electron-lint.yml b/.github/workflows/pipeline-electron-lint.yml index 9bbb40b5cf..e850def965 100644 --- a/.github/workflows/pipeline-electron-lint.yml +++ b/.github/workflows/pipeline-electron-lint.yml @@ -85,4 +85,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 diff --git a/.github/workflows/pipeline-segment-electron-publish.yml b/.github/workflows/pipeline-segment-electron-publish.yml new file mode 100644 index 0000000000..805d1e7ca0 --- /dev/null +++ b/.github/workflows/pipeline-segment-electron-publish.yml @@ -0,0 +1,242 @@ +# 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 + upload-out-gen-artifacts: + description: Whether to upload the src/gen artifacts + 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: + 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd + 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@6044e13b5dc448c55e2357c09f80417699197238 + with: + node-version: 22.21.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@de0fac2e4500dabe0009e67214ff5f5447ce83dd + 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 }} + upload-out-gen-artifacts: ${{ inputs.upload-out-gen-artifacts }} + - 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) diff --git a/.github/workflows/windows-publish.yml b/.github/workflows/windows-publish.yml index 991c535e15..7b53c04213 100644 --- a/.github/workflows/windows-publish.yml +++ b/.github/workflows/windows-publish.yml @@ -52,9 +52,11 @@ jobs: target-platform: win publish-x64-win: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-windows with: environment: production-release @@ -68,9 +70,11 @@ jobs: secrets: inherit publish-arm64-win: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-windows with: environment: production-release @@ -84,9 +88,11 @@ jobs: secrets: inherit publish-x86-win: - uses: ./.github/workflows/pipeline-segment-electron-build.yml + uses: ./.github/workflows/pipeline-segment-electron-publish.yml permissions: + attestations: write contents: read + id-token: write needs: checkout-windows with: environment: production-release diff --git a/package.json b/package.json index 49cd225135..4ca93a3bfe 100644 --- a/package.json +++ b/package.json @@ -57,7 +57,8 @@ "url": "^0.11.4", "webpack": "^5.95.0", "webpack-cli": "^6.0.1", - "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": { diff --git a/script/copy-pipeline-segment-publish.js b/script/copy-pipeline-segment-publish.js new file mode 100644 index 0000000000..f210f245d5 --- /dev/null +++ b/script/copy-pipeline-segment-publish.js @@ -0,0 +1,31 @@ +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 = { + 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) + ); +} diff --git a/script/release/uploaders/upload.py b/script/release/uploaders/upload.py index 36b5a3dd7f..640ddf9e46 100755 --- a/script/release/uploaders/upload.py +++ b/script/release/uploaders/upload.py @@ -369,6 +369,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(filename) def upload_sha256_checksum(version, file_path, key_prefix=None): checksum_path = f'{file_path}.sha256sum' diff --git a/yarn.lock b/yarn.lock index 447278be9f..2fafb315ed 100644 --- a/yarn.lock +++ b/yarn.lock @@ -640,6 +640,7 @@ __metadata: webpack: "npm:^5.95.0" webpack-cli: "npm:^6.0.1" wrapper-webpack-plugin: "npm:^2.2.0" + yaml: "npm:^2.8.1" dependenciesMeta: abstract-socket: built: true @@ -15088,6 +15089,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" From 59e434a27fe80bd9182a4067f05e7f620d28f735 Mon Sep 17 00:00:00 2001 From: Keeley Hammond Date: Thu, 5 Feb 2026 10:42:28 -0800 Subject: [PATCH 07/10] refactor: use ComPtr pattern for MSIX to avoid exception handling (#49645) * Revert "fix: fix Windows MSIX release build errors (#49613)" This reverts commit 4b5d5f9dd5bb7eadec299060fc73ba1178388feb. * refactor: use WRL ComPtr pattern for MSIX to avoid exception handling The MSIX auto-updater code was using C++/WinRT (winrt::* namespace), which requires exception handling (/EHsc). Mixing exception and non-exception handling code in the same binary is problematic at runtime. This commit refactors electron_api_msix_updater.cc to use an upstream Chromium pattern and eliminates the need for special exception handling build flags * build: import correct packages * build: consolidate IPackage declarations * refactor: use IPackageManager/IPackageManager5/IPackageManager9 and IPackage/IPackage2/IPackage4/IPackage6 interfaces as needed for different API methods. Also consolidates duplicate completion handler logic, fixes a bug in RegisterRestartOnUpdate where the command line string could go out of scope, and removes unused includes. --- BUILD.gn | 32 - filenames.gni | 2 + .../browser/api/electron_api_msix_updater.cc | 807 +++++++++++++----- 3 files changed, 596 insertions(+), 245 deletions(-) diff --git a/BUILD.gn b/BUILD.gn index 781d80f718..9dc7f22a66 100644 --- a/BUILD.gn +++ b/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", diff --git a/filenames.gni b/filenames.gni index 3c351e260a..6b93b01b8e 100644 --- a/filenames.gni +++ b/filenames.gni @@ -279,6 +279,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", diff --git a/shell/browser/api/electron_api_msix_updater.cc b/shell/browser/api/electron_api_msix_updater.cc index 21ed3fc33e..e9b90446f4 100644 --- a/shell/browser/api/electron_api_msix_updater.cc +++ b/shell/browser/api/electron_api_msix_updater.cc @@ -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 #include #include -// 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 -#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 +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* 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 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* uri) { + ComPtr uri_factory; + HRESULT hr = + base::win::GetActivationFactory( + &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* 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 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 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* package) { + ComPtr 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 reply_runner; + gin_helper::Promise promise; + bool fire_and_forget; + ComPtr 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 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 promise) { promise.Resolve(); }, + std::move(data->promise))); + return; + } + + if (status == AsyncStatus::Error) { + ComPtr 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 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(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 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 reply_runner, gin_helper::Promise 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 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 promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Get IPackageManager9 for AddPackageByUriAsync + ComPtr 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 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 uri; + hr = CreateUri(uri_wstring, &uri); + if (FAILED(hr)) { + error = "Failed to create URI"; + reply_runner->PostTask( + FROM_HERE, + base::BindOnce( + [](gin_helper::Promise promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Create AddPackageOptions + ComPtr 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 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 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(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 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 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(); + 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([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 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 reply_runner, gin_helper::Promise 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 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 promise, std::string error) { + promise.RejectWithErrorMessage(error); + }, + std::move(promise), std::move(error))); + return; + } + + // Get IPackageManager5 for RegisterPackageByFamilyNameAsync + ComPtr 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 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( + deployment_options | DeploymentOptions_ForceApplicationShutdown); } if (opts.force_target_shutdown) { - deploymentOptions |= DeploymentOptions::ForceTargetApplicationShutdown; + deployment_options = static_cast( + deployment_options | DeploymentOptions_ForceTargetApplicationShutdown); } if (opts.force_update_from_any_version) { - deploymentOptions |= DeploymentOptions::ForceUpdateFromAnyVersion; + deployment_options = static_cast( + deployment_options | DeploymentOptions_ForceUpdateFromAnyVersion); } - // Create empty collections for dependency and optional packages - IIterable emptyDependencies{nullptr}; - IIterable 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 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 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(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 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 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(); + 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([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 promise) { + promise.RejectWithErrorMessage( + "Failed to register completion handler"); + }, + std::move(raw_data->promise))); + } } #endif @@ -307,6 +608,16 @@ v8::Local 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 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 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 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 package2; + ComPtr package4; + ComPtr 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 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 app_installer_info; + hr = package6->GetAppInstallerInfo(&app_installer_info); + if (SUCCEEDED(hr) && app_installer_info) { + ComPtr 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 From 15dc7170ef2e432e8f340ab7242dea2d5e932f74 Mon Sep 17 00:00:00 2001 From: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com> Date: Thu, 5 Feb 2026 14:27:46 -0500 Subject: [PATCH 08/10] ci: fix ai-pr label commenting (#49685) --- .github/workflows/pull-request-labeled.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/pull-request-labeled.yml b/.github/workflows/pull-request-labeled.yml index 0c540cf165..00ae179146 100644 --- a/.github/workflows/pull-request-labeled.yml +++ b/.github/workflows/pull-request-labeled.yml @@ -60,6 +60,7 @@ jobs: with: actions: 'create-comment' token: ${{ steps.generate-token.outputs.token }} + issue-number: ${{ github.event.pull_request.number }} body: | From 60d35bbaf4b52287c37b09e4f343fd8959998afb Mon Sep 17 00:00:00 2001 From: Samuel Attard Date: Thu, 5 Feb 2026 14:37:24 -0800 Subject: [PATCH 09/10] feat: add support for disclaiming utility processes (#49128) * feat: add support for disclaiming utility processes * chore: update patches --------- Co-authored-by: Keeley Hammond --- docs/api/utility-process.md | 6 + ...e_launch_options_for_service_process.patch | 104 +++++++++++++++--- ...g_exit_code_on_service_process_crash.patch | 6 +- ..._avoid_private_macos_api_usage.patch.patch | 17 ++- .../api/electron_api_utility_process.cc | 15 ++- .../api/electron_api_utility_process.h | 3 +- spec/api-utility-process-spec.ts | 19 ++++ 7 files changed, 141 insertions(+), 29 deletions(-) diff --git a/docs/api/utility-process.md b/docs/api/utility-process.md index e6cf1b79ba..bac030bbfa 100644 --- a/docs/api/utility-process.md +++ b/docs/api/utility-process.md @@ -36,6 +36,12 @@ Process: [Main](../glossary.md#main-process)
`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 diff --git a/patches/chromium/feat_configure_launch_options_for_service_process.patch b/patches/chromium/feat_configure_launch_options_for_service_process.patch index 36f15c90c7..5147528057 100644 --- a/patches/chromium/feat_configure_launch_options_for_service_process.patch +++ b/patches/chromium/feat_configure_launch_options_for_service_process.patch @@ -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..71df2459fd759a75be573449accd3a4d 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 1f848cd121b2ecef4892bb2563c593124337e7cf..5fa25d07cddca53177e82e5cba1cc834a40994d0 100644 +index 1f848cd121b2ecef4892bb2563c593124337e7cf..8ad0b2ebbe0e4e2a1a60efec7c4d7b9f8277b82c 100644 --- a/content/browser/service_host/utility_process_host.cc +++ b/content/browser/service_host/utility_process_host.cc @@ -241,13 +241,13 @@ UtilityProcessHost::Options& UtilityProcessHost::Options::WithFileToPreload( @@ -207,7 +213,7 @@ index 1f848cd121b2ecef4892bb2563c593124337e7cf..5fa25d07cddca53177e82e5cba1cc834 #if BUILDFLAG(USE_ZYGOTE) UtilityProcessHost::Options& UtilityProcessHost::Options::WithZygoteForTesting( -@@ -257,6 +257,36 @@ UtilityProcessHost::Options& UtilityProcessHost::Options::WithZygoteForTesting( +@@ -257,6 +257,45 @@ UtilityProcessHost::Options& UtilityProcessHost::Options::WithZygoteForTesting( } #endif // BUILDFLAG(USE_ZYGOTE) @@ -240,11 +246,20 @@ index 1f848cd121b2ecef4892bb2563c593124337e7cf..5fa25d07cddca53177e82e5cba1cc834 + 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) { -@@ -531,9 +561,26 @@ bool UtilityProcessHost::StartProcess() { +@@ -531,9 +570,30 @@ bool UtilityProcessHost::StartProcess() { } #endif // BUILDFLAG(ENABLE_GPU_CHANNEL_MEDIA_CAPTURE) && !BUILDFLAG(IS_WIN) @@ -269,11 +284,15 @@ index 1f848cd121b2ecef4892bb2563c593124337e7cf..5fa25d07cddca53177e82e5cba1cc834 +#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 dfdcb66d65f07f4543703396eb529a6ec02b3f4a..d731211d727f6e96533a058106c13f339263712d 100644 +index dfdcb66d65f07f4543703396eb529a6ec02b3f4a..96c0cadf5caf5bf27f2a767c43f0f1da04298800 100644 --- a/content/browser/service_host/utility_process_host.h +++ b/content/browser/service_host/utility_process_host.h @@ -30,6 +30,7 @@ @@ -284,7 +303,7 @@ index dfdcb66d65f07f4543703396eb529a6ec02b3f4a..d731211d727f6e96533a058106c13f33 #endif // BUILDFLAG(IS_WIN) namespace base { -@@ -133,14 +134,31 @@ class CONTENT_EXPORT UtilityProcessHost final +@@ -133,14 +134,36 @@ class CONTENT_EXPORT UtilityProcessHost final std::variant file); #endif @@ -315,11 +334,16 @@ index dfdcb66d65f07f4543703396eb529a6ec02b3f4a..d731211d727f6e96533a058106c13f33 + // 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 -@@ -184,6 +202,27 @@ class CONTENT_EXPORT UtilityProcessHost final +@@ -184,6 +207,32 @@ class CONTENT_EXPORT UtilityProcessHost final std::optional> zygote_for_testing_; #endif // BUILDFLAG(USE_ZYGOTE) @@ -343,12 +367,17 @@ index dfdcb66d65f07f4543703396eb529a6ec02b3f4a..d731211d727f6e96533a058106c13f33 + // 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 ada7034c8926c276ea1c7ebf8242c61b0a993c39..b852d40936f1e876681a00f2eb57c9077a086a1d 100644 +index ada7034c8926c276ea1c7ebf8242c61b0a993c39..aec3de8bd0c18666b33147779cad68c6b41fe1fe 100644 --- a/content/browser/service_host/utility_sandbox_delegate.cc +++ b/content/browser/service_host/utility_sandbox_delegate.cc @@ -39,17 +39,19 @@ UtilitySandboxedProcessLauncherDelegate:: @@ -406,8 +435,24 @@ index ada7034c8926c276ea1c7ebf8242c61b0a993c39..b852d40936f1e876681a00f2eb57c907 #if BUILDFLAG(USE_ZYGOTE) ZygoteCommunication* UtilitySandboxedProcessLauncherDelegate::GetZygote() { +@@ -189,6 +208,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 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> 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 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 site; std::optional child_flags; std::vector 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 diff --git a/patches/chromium/feat_enable_passing_exit_code_on_service_process_crash.patch b/patches/chromium/feat_enable_passing_exit_code_on_service_process_crash.patch index ed99acaafe..df0a50b4fb 100644 --- a/patches/chromium/feat_enable_passing_exit_code_on_service_process_crash.patch +++ b/patches/chromium/feat_enable_passing_exit_code_on_service_process_crash.patch @@ -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 5fa25d07cddca53177e82e5cba1cc834a40994d0..75f43420e80752be98af3f35f5a4b82aa8f3e8a8 100644 +index 8ad0b2ebbe0e4e2a1a60efec7c4d7b9f8277b82c..ac1a3d303aa8dbcdb47e6cbddbfd10b9035ef885 100644 --- a/content/browser/service_host/utility_process_host.cc +++ b/content/browser/service_host/utility_process_host.cc -@@ -635,7 +635,7 @@ void UtilityProcessHost::OnProcessCrashed(int exit_code) { +@@ -648,7 +648,7 @@ void UtilityProcessHost::OnProcessCrashed(int exit_code) { : Client::CrashType::kPreIpcInitialization; } #endif // BUILDFLAG(IS_WIN) @@ -97,7 +97,7 @@ index 5fa25d07cddca53177e82e5cba1cc834a40994d0..75f43420e80752be98af3f35f5a4b82a std::optional UtilityProcessHost::GetServiceName() { diff --git a/content/browser/service_host/utility_process_host.h b/content/browser/service_host/utility_process_host.h -index d731211d727f6e96533a058106c13f339263712d..19e35a0684746d6f5703ac4237de4d8aeddbaa4e 100644 +index 96c0cadf5caf5bf27f2a767c43f0f1da04298800..5a16fe5c01ae7777064168e8883ec8ec0b82a873 100644 --- a/content/browser/service_host/utility_process_host.h +++ b/content/browser/service_host/utility_process_host.h @@ -87,7 +87,7 @@ class CONTENT_EXPORT UtilityProcessHost final diff --git a/patches/chromium/mas_avoid_private_macos_api_usage.patch.patch b/patches/chromium/mas_avoid_private_macos_api_usage.patch.patch index 106a23ca00..f7826baa4a 100644 --- a/patches/chromium/mas_avoid_private_macos_api_usage.patch.patch +++ b/patches/chromium/mas_avoid_private_macos_api_usage.patch.patch @@ -130,7 +130,7 @@ index 419a051968c58ae5a761708e4d942e8975c70852..a77032dd43f5fcbe29c54b622b34607f } // namespace base::mac 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) @@ -184,15 +184,26 @@ index b63d58da9837ba4d1e4aff8f24f2cd977c5ed02d..8387fd7d2bcf8951b6cc024829c16d97 } #endif -@@ -301,7 +321,7 @@ Process LaunchProcess(const std::vector& argv, +@@ -301,16 +321,16 @@ Process LaunchProcess(const std::vector& 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 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 diff --git a/shell/browser/api/electron_api_utility_process.cc b/shell/browser/api/electron_api_utility_process.cc index 2fa5bb1750..474ee3f3b6 100644 --- a/shell/browser/api/electron_api_utility_process.cc +++ b/shell/browser/api/electron_api_utility_process.cc @@ -70,7 +70,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, @@ -451,6 +453,7 @@ gin_helper::Handle UtilityProcessWrapper::Create( std::u16string display_name; bool use_plugin_helper = false; bool create_network_observer = false; + bool disclaim_responsibility = false; std::map stdio; base::FilePath current_working_directory; base::EnvironmentMap env_map; @@ -494,13 +497,15 @@ gin_helper::Handle 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; } diff --git a/shell/browser/api/electron_api_utility_process.h b/shell/browser/api/electron_api_utility_process.h index c62fb96795..2dcd6fc71c 100644 --- a/shell/browser/api/electron_api_utility_process.h +++ b/shell/browser/api/electron_api_utility_process.h @@ -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(); diff --git a/spec/api-utility-process-spec.ts b/spec/api-utility-process-spec.ts index 37d0897f3d..484190200a 100644 --- a/spec/api-utility-process-spec.ts +++ b/spec/api-utility-process-spec.ts @@ -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; + }); }); }); From 9db2290bcddd5a6e1c1f40000e0272be49bd1a10 Mon Sep 17 00:00:00 2001 From: Keeley Hammond Date: Fri, 6 Feb 2026 07:48:37 -0800 Subject: [PATCH 10/10] fix: restore macos 12 support in Node 24 (#49697) --- patches/node/.patches | 1 + ...tore_macos_deployment_target_to_12_0.patch | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+) create mode 100644 patches/node/build_restore_macos_deployment_target_to_12_0.patch diff --git a/patches/node/.patches b/patches/node/.patches index 03db9c8c3d..0bc1e46fc2 100644 --- a/patches/node/.patches +++ b/patches/node/.patches @@ -46,3 +46,4 @@ test_make_buffer_sizes_32bit-aware_in.patch src_refactor_module_wrap_cc_to_update_fixedarray_get_params.patch src_refactor_wasmstreaming_finish_to_accept_a_callback.patch src_stop_using_v8_propertycallbackinfo_t_this.patch +build_restore_macos_deployment_target_to_12_0.patch diff --git a/patches/node/build_restore_macos_deployment_target_to_12_0.patch b/patches/node/build_restore_macos_deployment_target_to_12_0.patch new file mode 100644 index 0000000000..4cfa72e7f8 --- /dev/null +++ b/patches/node/build_restore_macos_deployment_target_to_12_0.patch @@ -0,0 +1,24 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Keeley Hammond +Date: Thu, 5 Feb 2026 15:29:44 -0800 +Subject: build: restore macos deployment target to 12.0 + +Partially reverts https://github.com/nodejs/node/commit/8b4022177750530d2c142a5a0349d98fb82f16e2 +Electron will follow Chromium's lead and deprecate macos 12 with +M151, and so we should allow for building until then. + +This patch can be removed at the M151 branch point. + +diff --git a/common.gypi b/common.gypi +index bdadbdaa607b2f668749fc484271de8d126bbd17..8df2802191b7fe6ae14edbd85cb3a5d16eb5a76a 100644 +--- a/common.gypi ++++ b/common.gypi +@@ -677,7 +677,7 @@ + 'GCC_ENABLE_PASCAL_STRINGS': 'NO', # No -mpascal-strings + 'GCC_STRICT_ALIASING': 'NO', # -fno-strict-aliasing + 'PREBINDING': 'NO', # No -Wl,-prebind +- 'MACOSX_DEPLOYMENT_TARGET': '13.5', # -mmacosx-version-min=13.5 ++ 'MACOSX_DEPLOYMENT_TARGET': '12.0', # -mmacosx-version-min=12.0 + 'USE_HEADERMAP': 'NO', + 'WARNING_CFLAGS': [ + '-Wall',