Compare commits

...

23 Commits

Author SHA1 Message Date
trop[bot]
fbc489c43b fix: validate protocol scheme names in setAsDefaultProtocolClient (#50157)
fix: validate protocol scheme names in setAsDefaultProtocolClient

On Windows, `app.setAsDefaultProtocolClient(protocol)` directly
concatenates the protocol string into the registry key path with no
validation. A protocol name containing `\` could write to an arbitrary
subkey under `HKCU\Software\Classes\`, potentially hijacking existing
protocol handlers.

To fix this, add `Browser::IsValidProtocolScheme()` which validates that a protocol
name conforms to the RFC 3986 scheme grammar:

  scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )

This rejects backslashes, forward slashes, whitespace, and any other
characters not permitted in URI schemes.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-10 07:28:57 -04:00
Keeley Hammond
af4f835276 fix: strictly validate sender for internal IPC reply channels (#50160)
fix: strictly validate sender for internal IPC reply channels (#50118)

The sender-mismatch check in invokeInWebContents and invokeInWebFrameMain
used a negative condition (`type === 'frame' && sender !== expected`),
which only rejected mismatched frame senders and accepted anything else.

Invert to a positive check so only the exact expected frame can resolve
the reply — matches the guard style used elsewhere in lib/browser/.

Co-authored-by: Samuel Attard <sam@electronjs.org>
2026-03-10 11:09:42 +00:00
Keeley Hammond
9d0c858be0 fix: validate USB device selection against filtered device list (#50159)
* fix: validate USB device selection against filtered device list (#50002)

* fix: validate USB device selection against filtered device list

Previously, UsbChooserController::OnDeviceChosen looked up the chosen
device_id via chooser_context_->GetDeviceInfo(), which searches all
known USB devices on the system rather than the filtered list shown to
the select-usb-device handler. This meant a device excluded by the
renderer's filters or exclusion_filters could still be granted
permission if the handler returned its GUID.

* bump for CI

---------

Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>

* chore: fixup, remove gin::WeakCell

---------

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-03-10 09:22:13 +00:00
trop[bot]
e6e8269352 fix: potential UAF in OnDownloadPathGenerated (#50150)
fix: potential UAF in OnDownloadPathGenerated

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-09 22:02:49 -07:00
Keeley Hammond
e17eef4d62 fix: read nodeIntegrationInWorker from per-frame WebPreferences (#50163)
* fix: read nodeIntegrationInWorker from per-frame WebPreferences (#50122)

Previously the renderer checked a process-wide command-line switch to
decide whether to create a Node.js environment for dedicated workers.
When a renderer process hosted multiple WebContents with different
nodeIntegrationInWorker values (e.g. via window.open with overridden
webPreferences in setWindowOpenHandler), all workers in the process
used whichever value the first WebContents set on the command line.

Instead, plumb the flag through blink's WorkerSettings at worker
creation time, copying it from the initiating frame's WebPreferences.
The check on the worker thread then reads the per-worker value. Nested
workers inherit the flag from their parent worker via
WorkerSettings::Copy.

The --node-integration-in-worker command-line switch is removed as it
is no longer consumed.

* chore: update patches

---------

Co-authored-by: Samuel Attard <sam@electronjs.org>
2026-03-09 22:02:19 -07:00
Samuel Attard
9ffc255d13 fix: correct parsing of second-instance additionalData (#50177)
- POSIX: validate StringToSizeT result and token count when splitting
  the socket message into argv and additionalData; previously a
  malformed message could produce incorrect slicing.
- Windows: base64-encode additionalData before embedding in the
  null-delimited wchar_t buffer. The prior reinterpret_cast approach
  dropped everything after the first aligned 0x0000 in the serialized
  payload, so complex objects could arrive truncated.

Manually backported from #50119
2026-03-10 04:59:29 +00:00
Keeley Hammond
07a1e9c775 fix: prevent use-after-free in permission request callbacks (#50153)
fix: prevent use-after-free in permission request callbacks (#50032)

EnterFullscreenModeForTab, RequestPointerLock, and RequestKeyboardLock
bind callbacks with base::Unretained(this); fullscreen also captures a
raw RenderFrameHost*. These callbacks may be invoked by the app's JS
permission handler after the WebContents or RenderFrameHost is destroyed.

Use GetWeakPtr() in all three call sites, and capture a
GlobalRenderFrameHostToken instead of the raw RenderFrameHost* for
fullscreen so the pointer is resolved and null-checked only when the
callback fires. Cancel in-flight permission requests from ~WebContents()
via a new ElectronPermissionManager::CancelPendingRequests()` so stale
callbacks are never handed back to JS.

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-09 17:20:38 -05:00
trop[bot]
567435b94d fix: use requesting frame origin in permission helper and device choosers (#50151)
* fix: use requesting frame origin instead of top-level URL for permissions

`WebContentsPermissionHelper::RequestPermission` passes
`web_contents_->GetLastCommittedURL()` as the origin to the permission
manager instead of the actual requesting frame's origin. This enables
origin confusion when granting permissions to embedded third-party iframes,
since app permission handlers see the top-level origin instead of the
iframe's. The same pattern exists in the HID, USB, and Serial device
choosers, where grants are keyed to the primary main frame's origin rather
than the requesting frame's.

Fix this by using `requesting_frame->GetLastCommittedOrigin()` in all
affected code paths, renaming `details.requestingUrl` to
`details.requestingOrigin`, and populating it with the serialized
origin only.

Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>

* chore: keep requestingUrl name in permission handler details

The previous commit changed the details.requestingUrl field to
details.requestingOrigin in permission request/check handlers. That
field was already populated from the requesting frame's RFH, so the
rename was unnecessary and would break apps that read the existing
property. Revert to requestingUrl to preserve the existing API shape.

The functional changes to use the requesting frame in
WebContentsPermissionHelper and the HID/USB/Serial choosers remain.

Co-authored-by: Samuel Attard <sattard@anthropic.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-03-09 17:27:14 -04:00
trop[bot]
5ee5aceaad fix: use proper quoting for exe paths and args on Windows (#50146)
Previously, GetProtocolLaunchPath and FormatCommandLineString in
browser_win.cc used naive quoting which could break when paths or
arguments contained backslashes, spaces, or embedded quotes.

Fix by extracting the CommandLineToArgvW-compatible quoting logic from
relauncher_win.cc into a shared utility and use it in both browser_win.cc
and relauncher_win.cc to properly quote the exe path and each argument
individually.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-09 17:24:12 -04:00
trop[bot]
2d9288632f fix: validate response header names and values before AddHeader (#50130)
Matches the existing validation applied to request headers in
electron_api_url_loader.cc.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-03-09 09:16:52 -07:00
trop[bot]
18ee76cc47 fix: prevent use-after-free in PowerMonitor via dangling OS callbacks (#50090)
PowerMonitor registered OS-level callbacks (HWND UserData and
WTS/suspend notifications on Windows, shutdown handler and lock-screen
observer on macOS) but never cleaned them up in its destructor. The JS
layer also only held the native object in a closure-local variable,
allowing GC to reclaim it while those registrations still referenced
freed memory.

Retain the native PowerMonitor at module level in power-monitor.ts so
it cannot be garbage-collected. Add DestroyPlatformSpecificMonitors()
to properly tear down OS registrations on destruction: on Windows,
unregister WTS and suspend notifications, clear GWLP_USERDATA, and
destroy the HWND; on macOS, remove the emitter from the global
MacLockMonitor and reset the Browser shutdown handler.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-05 15:52:38 -05:00
trop[bot]
38b309043a chore: remove applescript from trash (#50067)
Previously, when trashItemAtURL: failed (e.g. on network shares or
under app translocation), the code fell back to constructing an
AppleScript that interpolated the bundle path directly into a string
literal via %@ with no escaping. This was fragile and unnecessary —
trashItemAtURL: has been the standard API since 10.8 and covers the
relevant cases. The fix simply removes the AppleScript fallback
entirely, so Trash() now returns the result of trashItemAtURL: directly.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-04 14:32:48 -06:00
trop[bot]
ab7f97e887 chore: remove unused commandLineSwitches flag (#50014)
chore: remove unused commandLineSwitches flag

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-03 09:16:58 -05:00
trop[bot]
4d0e13b87c build: authenticate curl requests to googlesource in lint workflow (#50025)
fix: authenticate curl requests to googlesource in lint workflow

The "Download GN Binary" and "Download clang-format Binary" steps
fetch files from chromium.googlesource.com without passing
authentication cookies. When googlesource rate-limits or returns a
transient error (502), the HTML error page is piped into `base64 -d`,
causing `base64: invalid input`.

The `set-chromium-cookie` action already configures `~/.gitcookies`
in a prior step. Pass `-b ~/.gitcookies` to both `curl` calls so
they authenticate, matching what the cookie verification step itself
does.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-02 18:40:32 -05:00
trop[bot]
50c0081e05 build: exit upload with error code if github upload fails (#49944)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-02-26 15:36:28 -08:00
trop[bot]
1079f3bbfa fix: prevent GBytes leak in GdkPixbufFromSkBitmap on Linux/GTK (#49897)
Inside gtk_util::GdkPixbufFromSkBitmap, g_bytes_new() was called
inline as an argument to gdk_pixbuf_new_from_bytes(), which per
GTK docs does not take ownership of the GBytes - it adds its own
internal reference. The caller's GBytes* was never stored or
unreffed, leaking 4 x width x height bytes of pixel data on every
call.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: redeemer <marcin.probola@gmail.com>
2026-02-21 17:11:52 +01:00
trop[bot]
d91adea56f build: use spawn instead of spawnSync for build (#49828)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-02-17 15:58:03 -05:00
trop[bot]
b2b584a320 chore: add Copilot CLI instructions (#49823)
chore: add copilot-instructions

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-02-17 13:25:08 -05:00
trop[bot]
1778a26c46 build: generate artifact attestions for released assets (#49781)
* build: generate artifact attestions for released assets (#48239)

* build: generate artifact attestions for released assets

* chore: address review feedback

---------

Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
(cherry picked from commit dec7f937ae)

Co-authored-by: Samuel Attard <sam@electronjs.org>

* build: fixup attestation for release assets (#49732)

* build: fixup attestation for release assets

* Generate artifact attestation for generated artifacts

* set id-token for attestation

* Add artifact-metadata permission for attestation

* add permissions for testing attestations

* Revert "add permissions for testing attestations"

This reverts commit 0284bed175.

* Revert "set id-token for attestation"

This reverts commit 69a1b13a18.

* Revert "Generate artifact attestation for generated artifacts"

This reverts commit ee0536eceb.

(cherry picked from commit 0852893910)

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>

* chore: update publish workflow

Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sam@electronjs.org>
Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-02-16 10:51:09 +01:00
Keeley Hammond
9fed98cee5 chore: cherry-pick e045399a1ecb from chromium (#49792)
* chore: cherry-pick e045399a1ecb from chromium

* chore: update patch

* chore: fix older method in patch
2026-02-13 10:27:37 +01:00
trop[bot]
d4d1596d2f fix: menu state in macOS dock menus (#49627)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-02-04 18:54:16 +01:00
trop[bot]
356bba8060 fix: duplicate fullscreen macOS menu item (#49596)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-02-02 11:50:01 -05:00
trop[bot]
ecbe8ee08a test: remove split dependency (#49556)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: David Sanders <dsanders11@ucsbalum.com>
2026-01-28 11:49:51 -08:00
56 changed files with 1188 additions and 242 deletions

View File

@@ -180,6 +180,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
@@ -190,6 +191,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

View File

@@ -15,7 +15,7 @@ runs:
git config --global core.preloadindex true
git config --global core.longpaths true
fi
export BUILD_TOOLS_SHA=4430e4a505e0f4fa2a41b707a10a36f780bbdd26
export BUILD_TOOLS_SHA=a0cc95a1884a631559bcca0c948465b725d9295a
npm i -g @electron/build-tools
# Update depot_tools to ensure python
e d update_depot_tools

122
.github/copilot-instructions.md vendored Normal file
View File

@@ -0,0 +1,122 @@
# Copilot Instructions for Electron
## Build System
Electron uses `@electron/build-tools` (`e` CLI). Install with `npm i -g @electron/build-tools`.
```bash
e sync # Fetch sources and apply patches
e build # Build Electron (GN + Ninja)
e build -k 999 # Build, continuing through errors
e start # Run built Electron
e start --version # Verify Electron launches
e test # Run full test suite
e debug # Run in debugger (lldb on macOS, gdb on Linux)
```
### Linting
```bash
npm run lint # Run all linters (JS, C++, Python, GN, docs)
npm run lint:js # JavaScript/TypeScript only
npm run lint:clang-format # C++ formatting only
npm run lint:cpp # C++ linting only
npm run lint:docs # Documentation only
```
### Running a Single Test
```bash
npm run test -- -g "pattern" # Run tests matching a regex pattern
# Example: npm run test -- -g "ipc"
```
### Running a Single Node.js Test
```bash
node script/node-spec-runner.js parallel/test-crypto-keygen
```
## Architecture
Electron embeds Chromium (rendering) and Node.js (backend) to enable desktop apps with web technologies. The parent directory (`../`) is the Chromium source tree.
### Process Model
Electron has two primary process types, mirroring Chromium:
- **Main process** (`shell/browser/` + `lib/browser/`): Controls app lifecycle, creates windows, system APIs
- **Renderer process** (`shell/renderer/` + `lib/renderer/`): Runs web content in BrowserWindows
### Native ↔ JavaScript Bridge
Each API is implemented as a C++/JS pair:
- C++ side: `shell/browser/api/electron_api_{name}.cc/.h` — uses `gin::Wrappable` and `ObjectTemplateBuilder`
- JS side: `lib/browser/api/{name}.ts` — exports the module, registered in `lib/browser/api/module-list.ts`
- Binding: `NODE_LINKED_BINDING_CONTEXT_AWARE(electron_browser_{name}, Initialize)` in C++ and registered in `shell/common/node_bindings.cc`
- Type declaration: `typings/internal-ambient.d.ts` maps `process._linkedBinding('electron_browser_{name}')`
### Patches System
Electron patches upstream dependencies (Chromium, Node.js, V8, etc.) rather than forking them. Patches live in `patches/` organized by target, with `patches/config.json` mapping directories to repos.
```text
patches/{target}/*.patch → [e sync] → target repo commits
← [e patches] ←
```
Key rules:
- Fix existing patches rather than creating new ones
- Preserve original authorship in TODO comments — never change `TODO(name)` assignees
- Each patch commit message must explain why the patch exists
- After modifying patches, run `e patches {target}` to export
When working on the `roller/chromium/main` branch for Chromium upgrades, use `e sync --3` for 3-way merge conflict resolution.
## Conventions
### File Naming
- JS/TS files: kebab-case (`file-name.ts`)
- C++ files: snake_case with `electron_api_` prefix (`electron_api_safe_storage.cc`)
- Test files: `api-{module-name}-spec.ts` in `spec/`
- Source file lists are maintained in `filenames.gni` (with platform-specific sections)
### JavaScript/TypeScript
- Semicolons required (`"semi": ["error", "always"]`)
- `const` and `let` only (no `var`)
- Arrow functions preferred
- Import order enforced: `@electron/internal``@electron``electron` → external → builtin → relative
- API naming: `PascalCase` for classes (`BrowserWindow`), `camelCase` for module APIs (`globalShortcut`)
- Prefer getters/setters over jQuery-style `.text([text])` patterns
### C++
- Follows Chromium coding style, enforced by `clang-format` and `clang-tidy`
- Uses Chromium abstractions (`base::`, `content::`, etc.)
- Header guards: `#ifndef ELECTRON_SHELL_BROWSER_API_ELECTRON_API_{NAME}_H_`
- Platform-specific files: `_mac.mm`, `_win.cc`, `_linux.cc`
### Testing
- Framework: Mocha + Chai + Sinon
- Test helpers in `spec/lib/` (e.g., `spec-helpers.ts`, `window-helpers.ts`)
- Use `defer()` from spec-helpers for cleanup, `closeAllWindows()` for window teardown
- Tests import from `electron/main` or `electron/renderer`
### Documentation
- API docs in `docs/api/` as Markdown, parsed by `@electron/docs-parser` to generate `electron.d.ts`
- API history tracked via YAML blocks in HTML comments within doc files
- Docs must pass `npm run lint:docs`
### Build Configuration
- `BUILD.gn`: Main GN build config
- `buildflags/buildflags.gni`: Feature flags (PDF viewer, extensions, spellchecker)
- `build/args/`: Build argument profiles (`testing.gn`, `release.gn`, `all.gn`)
- `DEPS`: Dependency versions and checkout paths
- `chromium_src/`: Chromium source file overrides (compiled instead of originals)

View File

@@ -43,9 +43,12 @@ jobs:
uses: ./src/electron/.github/actions/checkout
publish-x64:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-linux
with:
environment: production-release
@@ -60,9 +63,12 @@ jobs:
secrets: inherit
publish-arm:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-linux
with:
environment: production-release
@@ -77,9 +83,12 @@ jobs:
secrets: inherit
publish-arm64:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-linux
with:
environment: production-release

View File

@@ -47,9 +47,12 @@ jobs:
target-platform: macos
publish-x64-darwin:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-macos
with:
environment: production-release
@@ -64,9 +67,12 @@ jobs:
secrets: inherit
publish-x64-mas:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-macos
with:
environment: production-release
@@ -81,9 +87,12 @@ jobs:
secrets: inherit
publish-arm64-darwin:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-macos
with:
environment: production-release
@@ -98,9 +107,12 @@ jobs:
secrets: inherit
publish-arm64-mas:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-macos
with:
environment: production-release

View File

@@ -46,7 +46,7 @@ jobs:
shell: bash
run: |
chromium_revision="$(grep -A1 chromium_version src/electron/DEPS | tr -d '\n' | cut -d\' -f4)"
gn_version="$(curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/DEPS?format=TEXT" | base64 -d | grep gn_version | head -n1 | cut -d\' -f4)"
gn_version="$(curl -sL -b ~/.gitcookies "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/DEPS?format=TEXT" | base64 -d | grep gn_version | head -n1 | cut -d\' -f4)"
cipd ensure -ensure-file - -root . <<-CIPD
\$ServiceURL https://chrome-infra-packages.appspot.com/
@@ -62,7 +62,7 @@ jobs:
chromium_revision="$(grep -A1 chromium_version src/electron/DEPS | tr -d '\n' | cut -d\' -f4)"
mkdir -p src/buildtools
curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/buildtools/DEPS?format=TEXT" | base64 -d > src/buildtools/DEPS
curl -sL -b ~/.gitcookies "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/buildtools/DEPS?format=TEXT" | base64 -d > src/buildtools/DEPS
gclient sync --spec="solutions=[{'name':'src/buildtools','url':None,'deps_file':'DEPS','custom_vars':{'process_deps':True},'managed':False}]"
- name: Add ESLint problem matcher
@@ -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

View File

@@ -0,0 +1,237 @@
# AUTOGENERATED FILE - DO NOT EDIT MANUALLY
# ONLY EDIT .github/workflows/pipeline-segment-electron-build.yml
name: Pipeline Segment - Electron Build
on:
workflow_call:
inputs:
environment:
description: using the production or testing environment
required: false
type: string
target-platform:
type: string
description: Platform to run on, can be macos, win or linux
required: true
target-arch:
type: string
description: Arch to build for, can be x64, arm64, ia32 or arm
required: true
target-variant:
type: string
description: Variant to build for, no effect on non-macOS target platforms. Can
be darwin, mas or all.
default: all
build-runs-on:
type: string
description: What host to run the build
required: true
build-container:
type: string
description: JSON container information for aks runs-on
required: false
default: '{"image":null}'
is-release:
description: Whether this build job is a release job
required: true
type: boolean
default: false
gn-build-type:
description: The gn build type - testing or release
required: true
type: string
default: testing
generate-symbols:
description: Whether or not to generate symbols
required: true
type: boolean
default: false
upload-to-storage:
description: Whether or not to upload build artifacts to external storage
required: true
type: string
default: "0"
is-asan:
description: Building the Address Sanitizer (ASan) Linux build
required: false
type: boolean
default: false
enable-ssh:
description: Enable SSH debugging
required: false
type: boolean
default: false
permissions: {}
concurrency:
group: electron-build-${{ inputs.target-platform }}-${{ inputs.target-arch
}}-${{ inputs.target-variant }}-${{ inputs.is-asan }}-${{
github.ref_protected == true && github.run_id || github.ref }}
cancel-in-progress: ${{ github.ref_protected != true }}
env:
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
CHROMIUM_GIT_COOKIE_WINDOWS_STRING: ${{ secrets.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }}
DD_API_KEY: ${{ secrets.DD_API_KEY }}
ELECTRON_ARTIFACTS_BLOB_STORAGE: ${{ secrets.ELECTRON_ARTIFACTS_BLOB_STORAGE }}
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
SUDOWOODO_EXCHANGE_URL: ${{ secrets.SUDOWOODO_EXCHANGE_URL }}
SUDOWOODO_EXCHANGE_TOKEN: ${{ secrets.SUDOWOODO_EXCHANGE_TOKEN }}
GCLIENT_EXTRA_ARGS: ${{ inputs.target-platform == 'macos' &&
'--custom-var=checkout_mac=True --custom-var=host_os=mac' ||
inputs.target-platform == 'win' && '--custom-var=checkout_win=True' ||
'--custom-var=checkout_arm=True --custom-var=checkout_arm64=True' }}
ELECTRON_OUT_DIR: Default
ACTIONS_STEP_DEBUG: ${{ secrets.ACTIONS_STEP_DEBUG }}
jobs:
build:
defaults:
run:
shell: bash
runs-on: ${{ inputs.build-runs-on }}
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
container: ${{ fromJSON(inputs.build-container) }}
environment: ${{ inputs.environment }}
env:
TARGET_ARCH: ${{ inputs.target-arch }}
TARGET_PLATFORM: ${{ inputs.target-platform }}
steps:
- name: Create src dir
run: |
mkdir src
- name: Checkout Electron
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
path: src/electron
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Setup SSH Debugging
if: ${{ inputs.target-platform == 'macos' && (inputs.enable-ssh ||
env.ACTIONS_STEP_DEBUG == 'true') }}
uses: ./src/electron/.github/actions/ssh-debug
with:
tunnel: "true"
env:
CLOUDFLARE_TUNNEL_CERT: ${{ secrets.CLOUDFLARE_TUNNEL_CERT }}
CLOUDFLARE_TUNNEL_HOSTNAME: ${{ vars.CLOUDFLARE_TUNNEL_HOSTNAME }}
CLOUDFLARE_USER_CA_CERT: ${{ secrets.CLOUDFLARE_USER_CA_CERT }}
AUTHORIZED_USERS: ${{ secrets.SSH_DEBUG_AUTHORIZED_USERS }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Free up space (macOS)
if: ${{ inputs.target-platform == 'macos' }}
uses: ./src/electron/.github/actions/free-space-macos
- name: Check disk space after freeing up space
if: ${{ inputs.target-platform == 'macos' }}
run: df -h
- name: Setup Node.js/npm
if: ${{ inputs.target-platform == 'macos' }}
uses: actions/setup-node@49933ea5288caeca8642d1e84afbd3f7d6820020
with:
node-version: 20.19.x
cache: yarn
cache-dependency-path: src/electron/yarn.lock
- name: Install Dependencies
uses: ./src/electron/.github/actions/install-dependencies
- name: Install AZCopy
if: ${{ inputs.target-platform == 'macos' }}
run: brew install azcopy
- name: Set GN_EXTRA_ARGS for Linux
if: ${{ inputs.target-platform == 'linux' }}
run: >
if [ "${{ inputs.target-arch }}" = "arm" ]; then
if [ "${{ inputs.is-release }}" = true ]; then
GN_EXTRA_ARGS='target_cpu="arm" build_tflite_with_xnnpack=false symbol_level=1'
else
GN_EXTRA_ARGS='target_cpu="arm" build_tflite_with_xnnpack=false'
fi
elif [ "${{ inputs.target-arch }}" = "arm64" ]; then
GN_EXTRA_ARGS='target_cpu="arm64" fatal_linker_warnings=false enable_linux_installer=false'
elif [ "${{ inputs.is-asan }}" = true ]; then
GN_EXTRA_ARGS='is_asan=true'
fi
echo "GN_EXTRA_ARGS=$GN_EXTRA_ARGS" >> $GITHUB_ENV
- name: Set Chromium Git Cookie
uses: ./src/electron/.github/actions/set-chromium-cookie
- name: Install Build Tools
uses: ./src/electron/.github/actions/install-build-tools
- name: Generate DEPS Hash
run: |
node src/electron/script/generate-deps-hash.js
DEPSHASH=v1-src-cache-$(cat src/electron/.depshash)
echo "DEPSHASH=$DEPSHASH" >> $GITHUB_ENV
echo "CACHE_PATH=$DEPSHASH.tar" >> $GITHUB_ENV
- name: Restore src cache via AZCopy
if: ${{ inputs.target-platform != 'linux' }}
uses: ./src/electron/.github/actions/restore-cache-azcopy
with:
target-platform: ${{ inputs.target-platform }}
- name: Restore src cache via AKS
if: ${{ inputs.target-platform == 'linux' }}
uses: ./src/electron/.github/actions/restore-cache-aks
- name: Checkout Electron
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
path: src/electron
fetch-depth: 0
ref: ${{ github.event.pull_request.head.sha }}
- name: Fix Sync
if: ${{ inputs.target-platform != 'linux' }}
uses: ./src/electron/.github/actions/fix-sync
with:
target-platform: ${{ inputs.target-platform }}
env:
ELECTRON_DEPOT_TOOLS_DISABLE_LOG: true
- name: Init Build Tools
run: >
e init -f --root=$(pwd) --out=Default ${{ inputs.gn-build-type }}
--import ${{ inputs.gn-build-type }} --target-cpu ${{
inputs.target-arch }} --remote-build siso
- name: Run Electron Only Hooks
run: |
e d gclient runhooks --spec="solutions=[{'name':'src/electron','url':None,'deps_file':'DEPS','custom_vars':{'process_deps':False},'managed':False}]"
- name: Regenerate DEPS Hash
run: >
(cd src/electron && git checkout .) && node
src/electron/script/generate-deps-hash.js
echo "DEPSHASH=$(cat src/electron/.depshash)" >> $GITHUB_ENV
- name: Add CHROMIUM_BUILDTOOLS_PATH to env
run: echo "CHROMIUM_BUILDTOOLS_PATH=$(pwd)/src/buildtools" >> $GITHUB_ENV
- name: Free up space (macOS)
if: ${{ inputs.target-platform == 'macos' }}
uses: ./src/electron/.github/actions/free-space-macos
- name: Build Electron
if: ${{ inputs.target-platform != 'macos' || (inputs.target-variant == 'all' ||
inputs.target-variant == 'darwin') }}
uses: ./src/electron/.github/actions/build-electron
with:
target-arch: ${{ inputs.target-arch }}
target-platform: ${{ inputs.target-platform }}
artifact-platform: ${{ inputs.target-platform == 'macos' && 'darwin' ||
inputs.target-platform }}
is-release: ${{ inputs.is-release }}
generate-symbols: ${{ inputs.generate-symbols }}
upload-to-storage: ${{ inputs.upload-to-storage }}
is-asan: ${{ inputs.is-asan }}
- name: Set GN_EXTRA_ARGS for MAS Build
if: ${{ inputs.target-platform == 'macos' && (inputs.target-variant == 'all' ||
inputs.target-variant == 'mas') }}
run: |
echo "MAS_BUILD=true" >> $GITHUB_ENV
GN_EXTRA_ARGS='is_mas_build=true'
echo "GN_EXTRA_ARGS=$GN_EXTRA_ARGS" >> $GITHUB_ENV
- name: Build Electron (MAS)
if: ${{ inputs.target-platform == 'macos' && (inputs.target-variant == 'all' ||
inputs.target-variant == 'mas') }}
uses: ./src/electron/.github/actions/build-electron
with:
target-arch: ${{ inputs.target-arch }}
target-platform: ${{ inputs.target-platform }}
artifact-platform: mas
is-release: ${{ inputs.is-release }}
generate-symbols: ${{ inputs.generate-symbols }}
upload-to-storage: ${{ inputs.upload-to-storage }}
step-suffix: (mas)

View File

@@ -51,9 +51,12 @@ jobs:
target-platform: win
publish-x64-win:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-windows
with:
environment: production-release
@@ -67,9 +70,12 @@ jobs:
secrets: inherit
publish-arm64-win:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-windows
with:
environment: production-release
@@ -83,9 +89,12 @@ jobs:
secrets: inherit
publish-x86-win:
uses: ./.github/workflows/pipeline-segment-electron-build.yml
uses: ./.github/workflows/pipeline-segment-electron-publish.yml
permissions:
artifact-metadata: write
attestations: write
contents: read
id-token: write
needs: checkout-windows
with:
environment: production-release

View File

@@ -113,6 +113,8 @@ filenames = {
"shell/browser/win/scoped_hstring.h",
"shell/common/api/electron_api_native_image_win.cc",
"shell/common/application_info_win.cc",
"shell/common/command_line_util_win.cc",
"shell/common/command_line_util_win.h",
"shell/common/language_util_win.cc",
"shell/common/node_bindings_win.cc",
"shell/common/node_bindings_win.h",

View File

@@ -8,13 +8,19 @@ const {
isOnBatteryPower
} = process._linkedBinding('electron_browser_power_monitor');
// Hold the native PowerMonitor at module level so it is never garbage-collected
// while this module is alive. The C++ side registers OS-level callbacks (HWND
// user-data on Windows, shutdown handler on macOS, notification observers) that
// prevent safe collection of the C++ wrapper while those registrations exist.
let pm: any;
class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
constructor () {
super();
// Don't start the event source until both a) the app is ready and b)
// there's a listener registered for a powerMonitor event.
this.once('newListener', () => {
const pm = createPowerMonitor();
pm = createPowerMonitor();
pm.emit = this.emit.bind(this);
if (process.platform === 'linux') {

View File

@@ -19,8 +19,8 @@ export function invokeInWebContents<T> (sender: Electron.WebContents, command: s
const requestId = ++nextId;
const channel = `${command}_RESPONSE_${requestId}`;
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
if (event.type === 'frame' && event.sender !== sender) {
console.error(`Reply to ${command} sent by unexpected WebContents (${event.sender.id})`);
if (event.type !== 'frame' || event.sender !== sender) {
console.error(`Reply to ${command} sent by unexpected sender`);
return;
}

View File

@@ -57,7 +57,8 @@
"url": "^0.11.4",
"webpack": "^5.95.0",
"webpack-cli": "^5.1.4",
"wrapper-webpack-plugin": "^2.2.0"
"wrapper-webpack-plugin": "^2.2.0",
"yaml": "^2.8.1"
},
"private": true,
"scripts": {
@@ -132,6 +133,10 @@
"DEPS": [
"node script/gen-hunspell-filenames.js",
"node script/gen-libc++-filenames.js"
],
".github/workflows/pipeline-segment-electron-build.yml": [
"node script/copy-pipeline-segment-publish.js",
"git add .github/workflows/pipeline-segment-electron-publish.yml"
]
},
"resolutions": {

View File

@@ -140,3 +140,5 @@ band-aid_over_an_issue_with_using_deprecated_nsopenpanel_api.patch
inspectorpageagent_provisional_frame_speculative_fix.patch
expose_referrerscriptinfo_hostdefinedoptionsindex.patch
fix_release_mouse_buttons_on_focus_loss_on_wayland.patch
cherry-pick-e045399a1ecb.patch
feat_plumb_node_integration_in_worker_through_workersettings.patch

View File

@@ -0,0 +1,133 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Dominik=20R=C3=B6ttsches?= <drott@chromium.org>
Date: Thu, 12 Feb 2026 06:35:36 -0800
Subject: Avoid stale iteration in CSSFontFeatureValuesMap
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
To avoid invalid iterator state, take a snapshot of the
map when creating the iteration source. This addresses
the immediate problem of iterating while modifying.
Remaining work tracked in https://crbug.com/483936078
Fixed: 483569511
Change-Id: Ie29cfdf7ed94bbe189b44c842a5efce571bb2cee
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/7566570
Commit-Queue: Dominik Röttsches <drott@chromium.org>
Reviewed-by: Anders Hartvoll Ruud <andruud@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1583927}
diff --git a/third_party/blink/renderer/core/css/css_font_feature_values_map.cc b/third_party/blink/renderer/core/css/css_font_feature_values_map.cc
index 24303069e2531afebec29977378ba708051e117d..1862dae14a63769f0fe1fe1cf5f6f880148ce37b 100644
--- a/third_party/blink/renderer/core/css/css_font_feature_values_map.cc
+++ b/third_party/blink/renderer/core/css/css_font_feature_values_map.cc
@@ -13,17 +13,16 @@ class FontFeatureValuesMapIterationSource final
: public PairSyncIterable<CSSFontFeatureValuesMap>::IterationSource {
public:
FontFeatureValuesMapIterationSource(const CSSFontFeatureValuesMap& map,
- const FontFeatureAliases* aliases)
- : map_(map), aliases_(aliases), iterator_(aliases->begin()) {}
+ const FontFeatureAliases aliases)
+ : map_(map),
+ aliases_(std::move(aliases)),
+ iterator_(aliases_.begin()) {}
bool FetchNextItem(ScriptState* script_state,
String& map_key,
Vector<uint32_t>& map_value,
ExceptionState&) override {
- if (!aliases_) {
- return false;
- }
- if (iterator_ == aliases_->end()) {
+ if (iterator_ == aliases_.end()) {
return false;
}
map_key = iterator_->key;
@@ -38,9 +37,13 @@ class FontFeatureValuesMapIterationSource final
}
private:
- // Needs to be kept alive while we're iterating over it.
const Member<const CSSFontFeatureValuesMap> map_;
- const FontFeatureAliases* aliases_;
+ // Create a copy to keep the iterator from becoming invalid if there are
+ // modifications to the aliases HashMap while iterating.
+ // TODO(https://crbug.com/483936078): Implement live/stable iteration over
+ // FontFeatureAliases by changing its storage type, avoiding taking a copy
+ // here.
+ const FontFeatureAliases aliases_;
FontFeatureAliases::const_iterator iterator_;
};
@@ -50,8 +53,8 @@ uint32_t CSSFontFeatureValuesMap::size() const {
PairSyncIterable<CSSFontFeatureValuesMap>::IterationSource*
CSSFontFeatureValuesMap::CreateIterationSource(ScriptState*, ExceptionState&) {
- return MakeGarbageCollected<FontFeatureValuesMapIterationSource>(*this,
- aliases_);
+ return MakeGarbageCollected<FontFeatureValuesMapIterationSource>(
+ *this, aliases_ ? *aliases_ : FontFeatureAliases());
}
bool CSSFontFeatureValuesMap::GetMapEntry(ScriptState*,
diff --git a/third_party/blink/web_tests/external/wpt/css/css-fonts/font_feature_values_map_iteration.html b/third_party/blink/web_tests/external/wpt/css/css-fonts/font_feature_values_map_iteration.html
new file mode 100644
index 0000000000000000000000000000000000000000..eac7198b0b4a58007cbcc77ad3e9357a1009117c
--- /dev/null
+++ b/third_party/blink/web_tests/external/wpt/css/css-fonts/font_feature_values_map_iteration.html
@@ -0,0 +1,52 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <title>CSSFontFeatureValuesMap Iteration and Modification</title>
+ <link
+ rel="help"
+ href="https://drafts.csswg.org/css-fonts-4/#om-fontfeaturevalues"
+ />
+ <meta
+ name="assert"
+ content="Iteration while modifying CSSFontFeatureValuesMap does not crash."
+ />
+ <script type="text/javascript" src="/resources/testharness.js"></script>
+ <script
+ type="text/javascript"
+ src="/resources/testharnessreport.js"
+ ></script>
+ </head>
+ <body>
+ <style>
+ @font-feature-values TestFont {
+ @styleset {
+ a: 1;
+ b: 2;
+ c: 3;
+ }
+ }
+ </style>
+ <script>
+ test(() => {
+ const rule = document.styleSheets[0].cssRules[0];
+ const map = rule.styleset;
+ const iterator = map.entries();
+ let count = 0;
+
+ while (count < 10) {
+ const { value: entry, done } = iterator.next();
+ if (done) break;
+
+ const [key, value] = entry;
+
+ map.delete(key);
+ for (let i = 0; i < 100; i++) {
+ map.set(`newkey_${count}_${i}`, i);
+ }
+
+ count++;
+ }
+ }, "Iteration of the CSSFontFeatureValuesMap does not crash.");
+ </script>
+ </body>
+</html>

View File

@@ -65,7 +65,7 @@ index 2748dd196fe1f56357348a204e24f0b8a28b97dd..5800dd00b47c657d9e6766f3fc5a3065
#if BUILDFLAG(IS_WIN)
bool EscapeVirtualization(const base::FilePath& user_data_dir);
diff --git a/chrome/browser/process_singleton_posix.cc b/chrome/browser/process_singleton_posix.cc
index 08cbe32a258bf478f1da0a07064d3e9ef14c44a5..b9f2a43cb90fac4b031a4b4da38d6435a50990d2 100644
index 08cbe32a258bf478f1da0a07064d3e9ef14c44a5..c683575aab2154b46b1fa9cc9e5e30aa8ed4f0ee 100644
--- a/chrome/browser/process_singleton_posix.cc
+++ b/chrome/browser/process_singleton_posix.cc
@@ -614,6 +614,7 @@ class ProcessSingleton::LinuxWatcher
@@ -106,21 +106,39 @@ index 08cbe32a258bf478f1da0a07064d3e9ef14c44a5..b9f2a43cb90fac4b031a4b4da38d6435
const size_t kMinMessageLength = std::size(kStartToken) + 4;
if (bytes_read_ < kMinMessageLength) {
buf_[bytes_read_] = 0;
@@ -757,10 +763,28 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
@@ -757,10 +763,46 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
tokens.erase(tokens.begin());
tokens.erase(tokens.begin());
+ size_t num_args;
+ base::StringToSizeT(tokens[0], &num_args);
+ std::vector<std::string> command_line(tokens.begin() + 1, tokens.begin() + 1 + num_args);
+ if (!base::StringToSizeT(tokens[0], &num_args) ||
+ num_args > tokens.size() - 1) {
+ LOG(ERROR) << "Invalid num_args in socket message";
+ CleanupAndDeleteSelf();
+ return;
+ }
+ std::vector<std::string> command_line(tokens.begin() + 1,
+ tokens.begin() + 1 + num_args);
+
+ std::vector<uint8_t> additional_data;
+ if (tokens.size() >= 3 + num_args) {
+ // After consuming [num_args, argv...], two more tokens are needed for
+ // additional data: [size, payload]. Subtract to avoid overflow when
+ // num_args is large.
+ if (tokens.size() - 1 - num_args >= 2) {
+ size_t additional_data_size;
+ base::StringToSizeT(tokens[1 + num_args], &additional_data_size);
+ if (!base::StringToSizeT(tokens[1 + num_args], &additional_data_size)) {
+ LOG(ERROR) << "Invalid additional_data_size in socket message";
+ CleanupAndDeleteSelf();
+ return;
+ }
+ std::string remaining_args = base::JoinString(
+ base::span(tokens.begin() + 2 + num_args, tokens.end()),
+ std::string(1, kTokenDelimiter));
+ if (additional_data_size > remaining_args.size()) {
+ LOG(ERROR) << "additional_data_size exceeds payload length";
+ CleanupAndDeleteSelf();
+ return;
+ }
+ const uint8_t* additional_data_bits =
+ reinterpret_cast<const uint8_t*>(remaining_args.c_str());
+ additional_data = std::vector<uint8_t>(
@@ -136,7 +154,7 @@ index 08cbe32a258bf478f1da0a07064d3e9ef14c44a5..b9f2a43cb90fac4b031a4b4da38d6435
fd_watch_controller_.reset();
// LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader
@@ -789,8 +813,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
@@ -789,8 +831,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
//
ProcessSingleton::ProcessSingleton(
const base::FilePath& user_data_dir,
@@ -147,7 +165,7 @@ index 08cbe32a258bf478f1da0a07064d3e9ef14c44a5..b9f2a43cb90fac4b031a4b4da38d6435
current_pid_(base::GetCurrentProcId()) {
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename);
@@ -911,7 +937,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
@@ -911,7 +955,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
sizeof(socket_timeout));
// Found another process, prepare our command line
@@ -157,7 +175,7 @@ index 08cbe32a258bf478f1da0a07064d3e9ef14c44a5..b9f2a43cb90fac4b031a4b4da38d6435
std::string to_send(kStartToken);
to_send.push_back(kTokenDelimiter);
@@ -921,11 +948,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
@@ -921,11 +966,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
to_send.append(current_dir.value());
const std::vector<std::string>& argv = cmd_line.argv();
@@ -180,10 +198,18 @@ index 08cbe32a258bf478f1da0a07064d3e9ef14c44a5..b9f2a43cb90fac4b031a4b4da38d6435
if (!WriteToSocket(socket.fd(), to_send.data(), to_send.length())) {
// Try to kill the other process, because it might have been dead.
diff --git a/chrome/browser/process_singleton_win.cc b/chrome/browser/process_singleton_win.cc
index 64ebf49c0d1b6396d1cfbe3bf91480f61b47688d..bec94d4039379400ae8b00f1adbbb16a02ccbac0 100644
index 64ebf49c0d1b6396d1cfbe3bf91480f61b47688d..3458909dd20e2b7fdf624a0e0eaeed6e8271475b 100644
--- a/chrome/browser/process_singleton_win.cc
+++ b/chrome/browser/process_singleton_win.cc
@@ -81,10 +81,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
@@ -9,6 +9,7 @@
#include <shellapi.h>
#include <stddef.h>
+#include "base/base64.h"
#include "base/base_paths.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
@@ -81,10 +82,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
bool ParseCommandLine(const COPYDATASTRUCT* cds,
base::CommandLine* parsed_command_line,
@@ -198,7 +224,7 @@ index 64ebf49c0d1b6396d1cfbe3bf91480f61b47688d..bec94d4039379400ae8b00f1adbbb16a
static const int min_message_size = 7;
if (cds->cbData < min_message_size * sizeof(wchar_t) ||
cds->cbData % sizeof(wchar_t) != 0) {
@@ -134,6 +136,23 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
@@ -134,6 +137,25 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
const std::wstring cmd_line =
msg.substr(second_null + 1, third_null - second_null);
*parsed_command_line = base::CommandLine::FromString(cmd_line);
@@ -211,18 +237,20 @@ index 64ebf49c0d1b6396d1cfbe3bf91480f61b47688d..bec94d4039379400ae8b00f1adbbb16a
+ return true;
+ }
+
+ // Get the actual additional data.
+ const std::wstring additional_data =
+ msg.substr(third_null + 1, fourth_null - third_null);
+ base::span<const uint8_t> additional_data_bytes =
+ base::as_byte_span(additional_data);
+ *parsed_additional_data = std::vector<uint8_t>(
+ additional_data_bytes.begin(), additional_data_bytes.end());
+ // Get the actual additional data. It is base64-encoded so it can
+ // safely traverse the null-delimited wchar_t buffer.
+ const std::wstring encoded_w =
+ msg.substr(third_null + 1, fourth_null - third_null - 1);
+ std::string encoded = base::WideToASCII(encoded_w);
+ std::optional<std::vector<uint8_t>> decoded = base::Base64Decode(encoded);
+ if (decoded) {
+ *parsed_additional_data = std::move(*decoded);
+ }
+
return true;
}
return false;
@@ -155,13 +174,14 @@ bool ProcessLaunchNotification(
@@ -155,13 +177,14 @@ bool ProcessLaunchNotification(
base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM);
base::FilePath current_directory;
@@ -240,7 +268,7 @@ index 64ebf49c0d1b6396d1cfbe3bf91480f61b47688d..bec94d4039379400ae8b00f1adbbb16a
return true;
}
@@ -265,9 +285,11 @@ bool ProcessSingleton::EscapeVirtualization(
@@ -265,9 +288,11 @@ bool ProcessSingleton::EscapeVirtualization(
ProcessSingleton::ProcessSingleton(
const std::string& program_name,
const base::FilePath& user_data_dir,
@@ -252,7 +280,7 @@ index 64ebf49c0d1b6396d1cfbe3bf91480f61b47688d..bec94d4039379400ae8b00f1adbbb16a
program_name_(program_name),
is_app_sandboxed_(is_app_sandboxed),
is_virtualized_(false),
@@ -294,7 +316,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
@@ -294,7 +319,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
return PROCESS_NONE;
}
@@ -262,10 +290,18 @@ index 64ebf49c0d1b6396d1cfbe3bf91480f61b47688d..bec94d4039379400ae8b00f1adbbb16a
return PROCESS_NOTIFIED;
case NotifyChromeResult::NOTIFY_FAILED:
diff --git a/chrome/browser/win/chrome_process_finder.cc b/chrome/browser/win/chrome_process_finder.cc
index 58a4c5adfda49fb4bd1b5351bd02d358946043bd..adaa070eb0f3cf8f771b57743a7436fd48a1e576 100644
index 58a4c5adfda49fb4bd1b5351bd02d358946043bd..6f21585faca6e98d2ed08be6a91df88947da6b3a 100644
--- a/chrome/browser/win/chrome_process_finder.cc
+++ b/chrome/browser/win/chrome_process_finder.cc
@@ -39,7 +39,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
@@ -11,6 +11,7 @@
#include <string>
#include <string_view>
+#include "base/base64.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/files/file_path.h"
@@ -39,7 +40,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
return base::win::MessageWindow::FindWindow(user_data_dir.value());
}
@@ -276,7 +312,7 @@ index 58a4c5adfda49fb4bd1b5351bd02d358946043bd..adaa070eb0f3cf8f771b57743a7436fd
TRACE_EVENT0("startup", "AttemptToNotifyRunningChrome");
DCHECK(remote_window);
@@ -70,12 +72,24 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
@@ -70,12 +73,22 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
new_command_line.AppendSwitch(switches::kSourceAppId);
}
// Send the command line to the remote chrome window.
@@ -288,14 +324,12 @@ index 58a4c5adfda49fb4bd1b5351bd02d358946043bd..adaa070eb0f3cf8f771b57743a7436fd
std::wstring_view{L"\0", 1}, new_command_line.GetCommandLineString(),
std::wstring_view{L"\0", 1}});
+ size_t additional_data_size = additional_data.size_bytes();
+ if (additional_data_size) {
+ size_t padded_size = additional_data_size / sizeof(wchar_t);
+ if (additional_data_size % sizeof(wchar_t) != 0) {
+ padded_size++;
+ }
+ to_send.append(reinterpret_cast<const wchar_t*>(additional_data.data()),
+ padded_size);
+ if (!additional_data.empty()) {
+ // Base64-encode so the payload survives the null-delimited wchar_t
+ // framing; raw serialized bytes can contain 0x0000 sequences which
+ // would otherwise terminate the field early.
+ std::string encoded = base::Base64Encode(additional_data);
+ to_send.append(base::ASCIIToWide(encoded));
+ to_send.append(L"\0", 1); // Null separator.
+ }
+

View File

@@ -0,0 +1,73 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Samuel Attard <sattard@anthropic.com>
Date: Sat, 7 Mar 2026 23:07:30 -0800
Subject: feat: plumb node_integration_in_worker through WorkerSettings
Copy the node_integration_in_worker flag from the initiating frame's
WebPreferences into WorkerSettings at dedicated worker creation time,
so the value is readable per-worker on the worker thread rather than
relying on a process-wide command line switch. The value is also
propagated to nested workers via WorkerSettings::Copy.
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.cc b/third_party/blink/renderer/core/workers/dedicated_worker.cc
index fffe252477c521216086db3a15c6c26d8ccc25dc..defc359ccf7097ad7783b31b8260be6d8b1b3e03 100644
--- a/third_party/blink/renderer/core/workers/dedicated_worker.cc
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.cc
@@ -37,6 +37,7 @@
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
#include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
+#include "third_party/blink/renderer/core/exported/web_view_impl.h"
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
#include "third_party/blink/renderer/core/loader/document_loader.h"
@@ -558,6 +559,12 @@ DedicatedWorker::CreateGlobalScopeCreationParams(
auto* frame = window->GetFrame();
parent_devtools_token = frame->GetDevToolsFrameToken();
settings = std::make_unique<WorkerSettings>(frame->GetSettings());
+ if (auto* web_local_frame = WebLocalFrameImpl::FromFrame(frame)) {
+ if (auto* web_view = web_local_frame->ViewImpl()) {
+ settings->SetNodeIntegrationInWorker(
+ web_view->GetWebPreferences().node_integration_in_worker);
+ }
+ }
agent_group_scheduler_compositor_task_runner =
execution_context->GetScheduler()
->ToFrameScheduler()
diff --git a/third_party/blink/renderer/core/workers/worker_settings.cc b/third_party/blink/renderer/core/workers/worker_settings.cc
index 45680c5f6ea0c7e89ccf43eb88f8a11e3318c02e..3fa3af62f4e7ba8186441c5e3184b1c04fe32d12 100644
--- a/third_party/blink/renderer/core/workers/worker_settings.cc
+++ b/third_party/blink/renderer/core/workers/worker_settings.cc
@@ -40,6 +40,8 @@ std::unique_ptr<WorkerSettings> WorkerSettings::Copy(
old_settings->strictly_block_blockable_mixed_content_;
new_settings->generic_font_family_settings_ =
old_settings->generic_font_family_settings_;
+ new_settings->node_integration_in_worker_ =
+ old_settings->node_integration_in_worker_;
return new_settings;
}
diff --git a/third_party/blink/renderer/core/workers/worker_settings.h b/third_party/blink/renderer/core/workers/worker_settings.h
index 45c60dd2c44b05fdd279f759069383479823c7f2..33a2a0337efb9a46293e11d0d09b3fc182ab9618 100644
--- a/third_party/blink/renderer/core/workers/worker_settings.h
+++ b/third_party/blink/renderer/core/workers/worker_settings.h
@@ -43,6 +43,11 @@ class CORE_EXPORT WorkerSettings {
return generic_font_family_settings_;
}
+ bool NodeIntegrationInWorker() const { return node_integration_in_worker_; }
+ void SetNodeIntegrationInWorker(bool value) {
+ node_integration_in_worker_ = value;
+ }
+
private:
void CopyFlagValuesFromSettings(Settings*);
@@ -54,6 +59,7 @@ class CORE_EXPORT WorkerSettings {
bool strict_mixed_content_checking_ = false;
bool allow_running_of_insecure_content_ = false;
bool strictly_block_blockable_mixed_content_ = false;
+ bool node_integration_in_worker_ = false;
GenericFontFamilySettings generic_font_family_settings_;
};

View File

@@ -0,0 +1,32 @@
const yaml = require('yaml');
const fs = require('node:fs');
const path = require('node:path');
const PREFIX = '# AUTOGENERATED FILE - DO NOT EDIT MANUALLY\n# ONLY EDIT .github/workflows/pipeline-segment-electron-build.yml\n\n';
const base = path.resolve(__dirname, '../.github/workflows/pipeline-segment-electron-build.yml');
const target = path.resolve(__dirname, '../.github/workflows/pipeline-segment-electron-publish.yml');
const baseContents = fs.readFileSync(base, 'utf-8');
const parsedBase = yaml.parse(baseContents);
parsedBase.jobs.build.permissions = {
'artifact-metadata': 'write',
attestations: 'write',
contents: 'read',
'id-token': 'write'
};
if (process.argv.includes('--check')) {
if (fs.readFileSync(target, 'utf-8') !== PREFIX + yaml.stringify(parsedBase)) {
console.error(`${target} is out of date`);
console.error('Please run "copy-pipeline-segment-publish.js" to update it');
process.exit(1);
}
} else {
fs.writeFileSync(
target,
PREFIX + yaml.stringify(parsedBase)
);
}

View File

@@ -367,7 +367,18 @@ def upload_io_to_github(release, filename, filepath, version):
for c in iter(lambda: upload_process.stdout.read(1), b""):
sys.stdout.buffer.write(c)
sys.stdout.flush()
upload_process.wait()
if upload_process.returncode != 0:
sys.exit(upload_process.returncode)
if "GITHUB_OUTPUT" in os.environ:
output_path = os.environ["GITHUB_OUTPUT"]
with open(output_path, "r+", encoding='utf-8') as github_output:
if len(github_output.readlines()) > 0:
github_output.write(",")
else:
github_output.write('UPLOADED_PATHS=')
github_output.write(filepath)
def upload_sha256_checksum(version, file_path, key_prefix=None):
checksum_path = f'{file_path}.sha256sum'

View File

@@ -80,6 +80,14 @@ PowerMonitor::PowerMonitor(v8::Isolate* isolate) {
}
PowerMonitor::~PowerMonitor() {
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
DestroyPlatformSpecificMonitors();
#endif
#if BUILDFLAG(IS_MAC)
Browser::Get()->SetShutdownHandler(base::RepeatingCallback<bool()>());
#endif
auto* power_monitor = base::PowerMonitor::GetInstance();
power_monitor->RemovePowerStateObserver(this);
power_monitor->RemovePowerSuspendObserver(this);

View File

@@ -49,6 +49,7 @@ class PowerMonitor final : public gin_helper::DeprecatedWrappable<PowerMonitor>,
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
void InitPlatformSpecificMonitors();
void DestroyPlatformSpecificMonitors();
#endif
// base::PowerStateObserver implementations:

View File

@@ -15,6 +15,7 @@
}
- (void)addEmitter:(electron::api::PowerMonitor*)monitor_;
- (void)removeEmitter:(electron::api::PowerMonitor*)monitor_;
@end
@@ -62,6 +63,10 @@
self->emitters.push_back(monitor_);
}
- (void)removeEmitter:(electron::api::PowerMonitor*)monitor_ {
std::erase(self->emitters, monitor_);
}
- (void)onScreenLocked:(NSNotification*)notification {
for (auto* emitter : self->emitters) {
emitter->Emit("lock-screen");
@@ -98,4 +103,9 @@ void PowerMonitor::InitPlatformSpecificMonitors() {
[g_lock_monitor addEmitter:this];
}
void PowerMonitor::DestroyPlatformSpecificMonitors() {
if (g_lock_monitor)
[g_lock_monitor removeEmitter:this];
}
} // namespace electron::api

View File

@@ -49,6 +49,20 @@ void PowerMonitor::InitPlatformSpecificMonitors() {
DEVICE_NOTIFY_WINDOW_HANDLE);
}
void PowerMonitor::DestroyPlatformSpecificMonitors() {
if (window_) {
WTSUnRegisterSessionNotification(window_);
UnregisterSuspendResumeNotification(static_cast<HANDLE>(window_));
gfx::SetWindowUserData(window_, nullptr);
DestroyWindow(window_);
window_ = nullptr;
}
if (atom_) {
UnregisterClass(MAKEINTATOM(atom_), instance_);
atom_ = 0;
}
}
LRESULT CALLBACK PowerMonitor::WndProcStatic(HWND hwnd,
UINT message,
WPARAM wparam,
@@ -76,7 +90,7 @@ LRESULT CALLBACK PowerMonitor::WndProc(HWND hwnd,
}
if (should_treat_as_current_session) {
if (wparam == WTS_SESSION_LOCK) {
// Unretained is OK because this object is eternally pinned.
// SelfKeepAlive prevents GC of this object, so Unretained is safe.
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE,
base::BindOnce([](PowerMonitor* pm) { pm->Emit("lock-screen"); },

View File

@@ -97,6 +97,7 @@
#include "shell/browser/electron_browser_context.h"
#include "shell/browser/electron_browser_main_parts.h"
#include "shell/browser/electron_navigation_throttle.h"
#include "shell/browser/electron_permission_manager.h"
#include "shell/browser/file_select_helper.h"
#include "shell/browser/native_window.h"
#include "shell/browser/osr/osr_render_widget_host_view.h"
@@ -1034,6 +1035,16 @@ void WebContents::InitWithWebContents(
}
WebContents::~WebContents() {
if (web_contents()) {
auto* permission_manager = static_cast<ElectronPermissionManager*>(
web_contents()->GetBrowserContext()->GetPermissionControllerDelegate());
if (permission_manager)
permission_manager->CancelPendingRequests(web_contents());
}
if (inspectable_web_contents_)
inspectable_web_contents_->GetView()->SetDelegate(nullptr);
if (owner_window_) {
owner_window_->RemoveBackgroundThrottlingSource(this);
}
@@ -1459,18 +1470,24 @@ void WebContents::EnterFullscreenModeForTab(
auto* permission_helper =
WebContentsPermissionHelper::FromWebContents(source);
auto callback =
base::BindRepeating(&WebContents::OnEnterFullscreenModeForTab,
base::Unretained(this), requesting_frame, options);
permission_helper->RequestFullscreenPermission(requesting_frame, callback);
base::BindOnce(&WebContents::OnEnterFullscreenModeForTab, GetWeakPtr(),
requesting_frame->GetGlobalFrameToken(), options);
permission_helper->RequestFullscreenPermission(requesting_frame,
std::move(callback));
}
void WebContents::OnEnterFullscreenModeForTab(
content::RenderFrameHost* requesting_frame,
const content::GlobalRenderFrameHostToken& frame_token,
const blink::mojom::FullscreenOptions& options,
bool allowed) {
if (!allowed || !owner_window())
return;
auto* requesting_frame =
content::RenderFrameHost::FromFrameToken(frame_token);
if (!requesting_frame)
return;
auto* source = content::WebContents::FromRenderFrameHost(requesting_frame);
if (IsFullscreenForTabOrPending(source)) {
DCHECK_EQ(fullscreen_frame_, source->GetFocusedFrame());
@@ -1588,8 +1605,7 @@ void WebContents::RequestPointerLock(content::WebContents* web_contents,
WebContentsPermissionHelper::FromWebContents(web_contents);
permission_helper->RequestPointerLockPermission(
user_gesture, last_unlocked_by_target,
base::BindOnce(&WebContents::OnRequestPointerLock,
base::Unretained(this)));
base::BindOnce(&WebContents::OnRequestPointerLock, GetWeakPtr()));
}
void WebContents::LostPointerLock() {
@@ -1619,8 +1635,8 @@ void WebContents::RequestKeyboardLock(content::WebContents* web_contents,
auto* permission_helper =
WebContentsPermissionHelper::FromWebContents(web_contents);
permission_helper->RequestKeyboardLockPermission(
esc_key_locked, base::BindOnce(&WebContents::OnRequestKeyboardLock,
base::Unretained(this)));
esc_key_locked,
base::BindOnce(&WebContents::OnRequestKeyboardLock, GetWeakPtr()));
}
void WebContents::CancelKeyboardLockRequest(

View File

@@ -24,6 +24,7 @@
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/frame_tree_node_id.h"
#include "content/public/browser/global_routing_id.h"
#include "content/public/browser/javascript_dialog_manager.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/web_contents_delegate.h"
@@ -333,7 +334,7 @@ class WebContents final : public ExclusiveAccessContext,
// Callback triggered on permission response.
void OnEnterFullscreenModeForTab(
content::RenderFrameHost* requesting_frame,
const content::GlobalRenderFrameHostToken& frame_token,
const blink::mojom::FullscreenOptions& options,
bool allowed);

View File

@@ -9,7 +9,9 @@
#include <utility>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/path_service.h"
#include "base/strings/string_util.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_restrictions.h"
#include "chrome/common/chrome_paths.h"
@@ -71,6 +73,29 @@ Browser* Browser::Get() {
return ElectronBrowserMainParts::Get()->browser();
}
// static
bool Browser::IsValidProtocolScheme(const std::string& scheme) {
// RFC 3986 Section 3.1:
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
if (scheme.empty()) {
LOG(ERROR) << "Protocol scheme must not be empty";
return false;
}
if (!base::IsAsciiAlpha(scheme[0])) {
LOG(ERROR) << "Protocol scheme must start with an ASCII letter";
return false;
}
for (size_t i = 1; i < scheme.size(); ++i) {
const char c = scheme[i];
if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && c != '+' &&
c != '-' && c != '.') {
LOG(ERROR) << "Protocol scheme contains invalid character: '" << c << "'";
return false;
}
}
return true;
}
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
void Browser::Focus(gin::Arguments* args) {
// Focus on the first visible window.

View File

@@ -133,6 +133,10 @@ class Browser : private WindowListObserver {
void SetAppUserModelID(const std::wstring& name);
#endif
// Validate that a protocol scheme conforms to RFC 3986:
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
static bool IsValidProtocolScheme(const std::string& scheme);
// Remove the default protocol handler registry key
bool RemoveAsDefaultProtocolClient(const std::string& protocol,
gin::Arguments* args);

View File

@@ -103,16 +103,19 @@ void Browser::ClearRecentDocuments() {}
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
gin::Arguments* args) {
if (!IsValidProtocolScheme(protocol))
return false;
return SetDefaultWebClient(protocol);
}
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
gin::Arguments* args) {
auto env = base::Environment::Create();
if (protocol.empty())
if (!IsValidProtocolScheme(protocol))
return false;
auto env = base::Environment::Create();
std::vector<std::string> argv = {kXdgSettings, "check",
kXdgSettingsDefaultSchemeHandler, protocol};
if (std::optional<std::string> desktop_name = env->GetVar("CHROME_DESKTOP")) {

View File

@@ -228,7 +228,7 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
gin::Arguments* args) {
if (protocol.empty())
if (!IsValidProtocolScheme(protocol))
return false;
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];
@@ -244,7 +244,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
gin::Arguments* args) {
if (protocol.empty())
if (!IsValidProtocolScheme(protocol))
return false;
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];

View File

@@ -37,6 +37,7 @@
#include "shell/browser/ui/win/jump_list.h"
#include "shell/browser/window_list.h"
#include "shell/common/application_info.h"
#include "shell/common/command_line_util_win.h"
#include "shell/common/gin_converters/file_path_converter.h"
#include "shell/common/gin_converters/image_converter.h"
#include "shell/common/gin_converters/login_item_settings_converter.h"
@@ -78,13 +79,22 @@ bool GetProtocolLaunchPath(gin::Arguments* args, std::wstring* exe) {
return false;
}
// Strip surrounding double quotes before re-quoting with AddQuoteForArg.
if (exe->size() >= 2 && exe->front() == L'"' && exe->back() == L'"') {
*exe = exe->substr(1, exe->size() - 2);
}
// Read in optional args arg
std::vector<std::wstring> launch_args;
if (args->GetNext(&launch_args) && !launch_args.empty()) {
std::wstring joined_args = base::JoinString(launch_args, L"\" \"");
*exe = base::StrCat({L"\"", *exe, L"\" \"", joined_args, L"\" \"%1\""});
std::wstring result = electron::AddQuoteForArg(*exe);
for (const auto& arg : launch_args) {
result += L' ';
result += electron::AddQuoteForArg(arg);
}
*exe = base::StrCat({result, L" \"%1\""});
} else {
*exe = base::StrCat({L"\"", *exe, L"\" \"%1\""});
*exe = base::StrCat({electron::AddQuoteForArg(*exe), L" \"%1\""});
}
return true;
@@ -152,9 +162,18 @@ bool FormatCommandLineString(std::wstring* exe,
return false;
}
// Strip surrounding double quotes before re-quoting with AddQuoteForArg.
if (exe->size() >= 2 && exe->front() == L'"' && exe->back() == L'"') {
*exe = exe->substr(1, exe->size() - 2);
}
*exe = electron::AddQuoteForArg(*exe);
if (!launch_args.empty()) {
std::u16string joined_launch_args = base::JoinString(launch_args, u" ");
*exe = base::StrCat({*exe, L" ", base::AsWStringView(joined_launch_args)});
for (const auto& arg : launch_args) {
*exe += L' ';
*exe += electron::AddQuoteForArg(std::wstring(base::AsWStringView(arg)));
}
}
return true;
@@ -409,7 +428,7 @@ bool Browser::SetUserTasks(const std::vector<UserTask>& tasks) {
bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
gin::Arguments* args) {
if (protocol.empty())
if (!IsValidProtocolScheme(protocol))
return false;
// Main Registry Key
@@ -488,7 +507,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
// Software\Classes", which is inherited by "HKEY_CLASSES_ROOT"
// anyway, and can be written by unprivileged users.
if (protocol.empty())
if (!IsValidProtocolScheme(protocol))
return false;
std::wstring exe;
@@ -518,7 +537,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
gin::Arguments* args) {
if (protocol.empty())
if (!IsValidProtocolScheme(protocol))
return false;
std::wstring exe;

View File

@@ -268,7 +268,7 @@ void ElectronDownloadManagerDelegate::OnDownloadPathGenerated(
gin_helper::Promise<gin_helper::Dictionary> dialog_promise(isolate);
auto dialog_callback = base::BindOnce(
&ElectronDownloadManagerDelegate::OnDownloadSaveDialogDone,
base::Unretained(this), download_guid, std::move(callback));
weak_ptr_factory_.GetWeakPtr(), download_guid, std::move(callback));
std::ignore = dialog_promise.Then(std::move(dialog_callback));
file_dialog::ShowSaveDialog(settings, std::move(dialog_promise));

View File

@@ -150,6 +150,23 @@ bool ElectronPermissionManager::HasPermissionCheckHandler() const {
return !check_handler_.is_null();
}
void ElectronPermissionManager::CancelPendingRequests(
content::WebContents* web_contents) {
std::vector<int> ids_to_remove;
for (PendingRequestsMap::iterator iter(&pending_requests_); !iter.IsAtEnd();
iter.Advance()) {
auto* pending_request = iter.GetCurrentValue();
content::RenderFrameHost* rfh = pending_request->GetRenderFrameHost();
if (!rfh ||
content::WebContents::FromRenderFrameHost(rfh) == web_contents) {
ids_to_remove.push_back(iter.GetCurrentKey());
}
}
for (int id : ids_to_remove) {
pending_requests_.Remove(id);
}
}
void ElectronPermissionManager::RequestPermissionWithDetails(
blink::mojom::PermissionDescriptorPtr permission,
content::RenderFrameHost* render_frame_host,

View File

@@ -85,6 +85,8 @@ class ElectronPermissionManager : public content::PermissionControllerDelegate {
bool HasPermissionRequestHandler() const;
bool HasPermissionCheckHandler() const;
void CancelPendingRequests(content::WebContents* web_contents);
void CheckBluetoothDevicePair(gin_helper::Dictionary details,
PairCallback pair_callback) const;

View File

@@ -88,13 +88,9 @@ HidChooserController::HidChooserController(
exclusion_filters_(std::move(exclusion_filters)),
callback_(std::move(callback)),
initiator_document_(render_frame_host->GetWeakDocumentPtr()),
origin_(content::WebContents::FromRenderFrameHost(render_frame_host)
->GetPrimaryMainFrame()
->GetLastCommittedOrigin()),
origin_(render_frame_host->GetLastCommittedOrigin()),
hid_delegate_(hid_delegate),
render_frame_host_id_(render_frame_host->GetGlobalId()) {
// The use above of GetMainFrame is safe as content::HidService instances are
// not created for fenced frames.
DCHECK(!render_frame_host->IsNestedWithinFencedFrame());
chooser_context_ = HidChooserContextFactory::GetForBrowserContext(

View File

@@ -56,11 +56,6 @@ static NSDictionary* UNNotificationResponseToNSDictionary(
}
- (void)applicationWillFinishLaunching:(NSNotification*)notify {
// Don't add the "Enter Full Screen" menu item automatically.
[[NSUserDefaults standardUserDefaults]
setBool:NO
forKey:@"NSFullScreenMenuItemEverywhere"];
[[[NSWorkspace sharedWorkspace] notificationCenter]
addObserver:self
selector:@selector(willPowerOff:)
@@ -114,7 +109,14 @@ static NSDictionary* UNNotificationResponseToNSDictionary(
}
- (NSMenu*)applicationDockMenu:(NSApplication*)sender {
return menu_controller_ ? menu_controller_.menu : nil;
if (!menu_controller_)
return nil;
// Manually refresh menu state since menuWillOpen: is not called
// by macOS for dock menus for some reason before they are displayed.
NSMenu* menu = menu_controller_.menu;
[menu_controller_ refreshMenuTree:menu];
return menu;
}
- (BOOL)application:(NSApplication*)sender openFile:(NSString*)filename {

View File

@@ -24,6 +24,7 @@
#include "net/base/filename_util.h"
#include "net/http/http_request_headers.h"
#include "net/http/http_status_code.h"
#include "net/http/http_util.h"
#include "net/url_request/redirect_util.h"
#include "services/network/public/cpp/resource_request.h"
#include "services/network/public/cpp/shared_url_loader_factory.h"
@@ -138,13 +139,17 @@ network::mojom::URLResponseHeadPtr ToResponseHead(
base::Value::Dict headers;
if (dict.Get("headers", &headers)) {
for (const auto iter : headers) {
if (!net::HttpUtil::IsValidHeaderName(iter.first))
continue;
if (iter.second.is_string()) {
// key, value
head->headers->AddHeader(iter.first, iter.second.GetString());
if (net::HttpUtil::IsValidHeaderValue(iter.second.GetString()))
head->headers->AddHeader(iter.first, iter.second.GetString());
} else if (iter.second.is_list()) {
// key: [values...]
for (const auto& item : iter.second.GetList()) {
if (item.is_string())
if (item.is_string() &&
net::HttpUtil::IsValidHeaderValue(item.GetString()))
head->headers->AddHeader(iter.first, item.GetString());
}
} else {

View File

@@ -14,6 +14,7 @@
#include "base/win/scoped_handle.h"
#include "sandbox/win/src/nt_internals.h"
#include "sandbox/win/src/win_utils.h"
#include "shell/common/command_line_util_win.h"
namespace relauncher::internal {
@@ -50,49 +51,6 @@ HANDLE GetParentProcessHandle(base::ProcessHandle handle) {
return ::OpenProcess(PROCESS_ALL_ACCESS, TRUE, ppid);
}
StringType AddQuoteForArg(const StringType& arg) {
// We follow the quoting rules of CommandLineToArgvW.
// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
std::wstring quotable_chars(L" \\\"");
if (arg.find_first_of(quotable_chars) == std::wstring::npos) {
// No quoting necessary.
return arg;
}
std::wstring out;
out.push_back(L'"');
for (size_t i = 0; i < arg.size(); ++i) {
if (arg[i] == '\\') {
// Find the extent of this run of backslashes.
size_t start = i, end = start + 1;
for (; end < arg.size() && arg[end] == '\\'; ++end) {
}
size_t backslash_count = end - start;
// Backslashes are escapes only if the run is followed by a double quote.
// Since we also will end the string with a double quote, we escape for
// either a double quote or the end of the string.
if (end == arg.size() || arg[end] == '"') {
// To quote, we need to output 2x as many backslashes.
backslash_count *= 2;
}
for (size_t j = 0; j < backslash_count; ++j)
out.push_back('\\');
// Advance i to one before the end to balance i++ in loop.
i = end - 1;
} else if (arg[i] == '"') {
out.push_back('\\');
out.push_back('"');
} else {
out.push_back(arg[i]);
}
}
out.push_back('"');
return out;
}
} // namespace
StringType GetWaitEventName(base::ProcessId pid) {
@@ -105,7 +63,7 @@ StringType ArgvToCommandLineString(const StringVector& argv) {
for (const StringType& arg : argv) {
if (!command_line.empty())
command_line += L' ';
command_line += AddQuoteForArg(arg);
command_line += electron::AddQuoteForArg(arg);
}
return command_line;
}

View File

@@ -52,25 +52,21 @@ bool ElectronSerialDelegate::CanRequestPortPermission(
auto* permission_helper =
WebContentsPermissionHelper::FromWebContents(web_contents);
return permission_helper->CheckSerialAccessPermission(
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin());
frame->GetLastCommittedOrigin());
}
bool ElectronSerialDelegate::HasPortPermission(
content::RenderFrameHost* frame,
const device::mojom::SerialPortInfo& port) {
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
return GetChooserContext(frame)->HasPortPermission(
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), port,
frame);
frame->GetLastCommittedOrigin(), port, frame);
}
void ElectronSerialDelegate::RevokePortPermissionWebInitiated(
content::RenderFrameHost* frame,
const base::UnguessableToken& token) {
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
return GetChooserContext(frame)->RevokePortPermissionWebInitiated(
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), token,
frame);
frame->GetLastCommittedOrigin(), token, frame);
}
const device::mojom::SerialPortInfo* ElectronSerialDelegate::GetPortInfo(

View File

@@ -125,7 +125,7 @@ SerialChooserController::SerialChooserController(
std::move(allowed_bluetooth_service_class_ids)),
callback_(std::move(callback)),
initiator_document_(render_frame_host->GetWeakDocumentPtr()) {
origin_ = web_contents_->GetPrimaryMainFrame()->GetLastCommittedOrigin();
origin_ = render_frame_host->GetLastCommittedOrigin();
chooser_context_ = SerialChooserContextFactory::GetForBrowserContext(
web_contents_->GetBrowserContext())

View File

@@ -270,34 +270,10 @@ void Relaunch(NSString* destinationPath) {
}
bool Trash(NSString* path) {
bool result = false;
if (floor(NSAppKitVersionNumber) >= NSAppKitVersionNumber10_8) {
result = [[NSFileManager defaultManager]
trashItemAtURL:[NSURL fileURLWithPath:path]
resultingItemURL:nil
error:nil];
}
// As a last resort try trashing with AppleScript.
// This allows us to trash the app in macOS Sierra even when the app is
// running inside an app translocation image.
if (!result) {
auto* code = R"str(
set theFile to POSIX file "%@"
tell application "Finder"
move theFile to trash
end tell
)str";
NSAppleScript* appleScript = [[NSAppleScript alloc]
initWithSource:[NSString stringWithFormat:@(code), path]];
NSDictionary* errorDict = nil;
NSAppleEventDescriptor* scriptResult =
[appleScript executeAndReturnError:&errorDict];
result = (scriptResult != nil);
}
return result;
return [[NSFileManager defaultManager]
trashItemAtURL:[NSURL fileURLWithPath:path]
resultingItemURL:nil
error:nil];
}
bool DeleteOrTrash(NSString* path) {

View File

@@ -57,6 +57,10 @@ class ElectronMenuModel;
// Whether the menu is currently open.
- (BOOL)isMenuOpen;
// Recursively refreshes the menu tree starting from |menu|, applying the
// model state (enabled, checked, hidden etc) to each menu item.
- (void)refreshMenuTree:(NSMenu*)menu;
// NSMenuDelegate methods this class implements. Subclasses should call super
// if extending the behavior.
- (void)menuWillOpen:(NSMenu*)menu;

View File

@@ -490,8 +490,6 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
: NSControlStateValueOff;
}
// Recursively refreshes the menu tree starting from |menu|, applying the
// model state to each menu item.
- (void)refreshMenuTree:(NSMenu*)menu {
for (NSMenuItem* item in menu.itemArray) {
[self applyStateToMenuItem:item];
@@ -557,6 +555,14 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
- (void)menuWillOpen:(NSMenu*)menu {
isMenuOpen_ = YES;
// macOS automatically injects a duplicate "Toggle Full Screen" menu item
// when we set menu.delegate on submenus. Remove hidden duplicates.
for (NSMenuItem* item in menu.itemArray) {
if (item.isHidden && item.action == @selector(toggleFullScreenMode:))
[menu removeItem:item];
}
[self refreshMenuTree:menu];
if (model_)
model_->MenuWillShow();

View File

@@ -82,11 +82,13 @@ GdkPixbuf* GdkPixbufFromSkBitmap(const SkBitmap& bitmap) {
constexpr GdkColorspace kColorspace = GDK_COLORSPACE_RGB;
constexpr gboolean kHasAlpha = true;
constexpr int kBitsPerSample = 8;
return gdk_pixbuf_new_from_bytes(
g_bytes_new(std::data(bytes), std::size(bytes)), kColorspace, kHasAlpha,
kBitsPerSample, width, height,
GBytes* gbytes = g_bytes_new(std::data(bytes), std::size(bytes));
GdkPixbuf* pixbuf = gdk_pixbuf_new_from_bytes(
gbytes, kColorspace, kHasAlpha, kBitsPerSample, width, height,
gdk_pixbuf_calculate_rowstride(kColorspace, kHasAlpha, kBitsPerSample,
width, height));
g_bytes_unref(gbytes);
return pixbuf;
}
} // namespace gtk_util

View File

@@ -43,7 +43,7 @@ UsbChooserController::UsbChooserController(
: WebContentsObserver(web_contents),
options_(std::move(options)),
callback_(std::move(callback)),
origin_(render_frame_host->GetMainFrame()->GetLastCommittedOrigin()),
origin_(render_frame_host->GetLastCommittedOrigin()),
usb_delegate_(usb_delegate),
render_frame_host_id_(render_frame_host->GetGlobalId()) {
chooser_context_ = UsbChooserContextFactory::GetForBrowserContext(
@@ -68,6 +68,7 @@ api::Session* UsbChooserController::GetSession() {
void UsbChooserController::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {
if (DisplayDevice(device_info)) {
devices_.push_back(device_info.Clone());
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-added", device_info.Clone(), web_contents());
@@ -77,6 +78,9 @@ void UsbChooserController::OnDeviceAdded(
void UsbChooserController::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {
std::erase_if(devices_, [&device_info](const auto& device) {
return device->guid == device_info.guid;
});
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-removed", device_info.Clone(), web_contents());
@@ -88,9 +92,11 @@ void UsbChooserController::OnDeviceChosen(gin::Arguments* args) {
if (!args->GetNext(&device_id) || device_id.empty()) {
RunCallback(/*device_info=*/nullptr);
} else {
auto* device_info = chooser_context_->GetDeviceInfo(device_id);
if (device_info) {
RunCallback(device_info->Clone());
const auto it = std::ranges::find_if(
devices_,
[&device_id](const auto& device) { return device->guid == device_id; });
if (it != devices_.end()) {
RunCallback((*it)->Clone());
} else {
util::EmitWarning(
base::StrCat({"The device id ", device_id, " was not found."}),
@@ -125,6 +131,11 @@ void UsbChooserController::GotUsbDeviceList(
return !DisplayDevice(*device_info);
});
devices_.clear();
for (const auto& device : devices) {
devices_.push_back(device->Clone());
}
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
.Set("deviceList", devices)
.Set("frame", rfh)

View File

@@ -5,6 +5,7 @@
#ifndef ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTROLLER_H_
#define ELECTRON_SHELL_BROWSER_USB_USB_CHOOSER_CONTROLLER_H_
#include <string>
#include <vector>
#include "base/memory/weak_ptr.h"
@@ -73,6 +74,9 @@ class UsbChooserController final : private UsbChooserContext::DeviceObserver,
base::WeakPtr<ElectronUsbDelegate> usb_delegate_;
// Filtered list of devices that passed DisplayDevice()
std::vector<device::mojom::UsbDeviceInfoPtr> devices_;
content::GlobalRenderFrameHostId render_frame_host_id_;
base::WeakPtrFactory<UsbChooserController> weak_factory_{this};

View File

@@ -219,7 +219,7 @@ void WebContentsPermissionHelper::RequestPermission(
base::Value::Dict details) {
auto* permission_manager = static_cast<ElectronPermissionManager*>(
web_contents_->GetBrowserContext()->GetPermissionControllerDelegate());
auto origin = web_contents_->GetLastCommittedURL();
auto origin = requesting_frame->GetLastCommittedOrigin().GetURL();
permission_manager->RequestPermissionWithDetails(
content::PermissionDescriptorUtil::
CreatePermissionDescriptorForPermissionType(permission),

View File

@@ -135,7 +135,6 @@ void WebContentsPreferences::Clear() {
default_encoding_ = std::nullopt;
is_webview_ = false;
custom_args_.clear();
custom_switches_.clear();
enable_blink_features_ = std::nullopt;
disable_blink_features_ = std::nullopt;
disable_popups_ = false;
@@ -203,7 +202,6 @@ void WebContentsPreferences::SetFromDictionary(
if (web_preferences.Get("defaultEncoding", &encoding))
default_encoding_ = encoding;
web_preferences.Get(options::kCustomArgs, &custom_args_);
web_preferences.Get("commandLineSwitches", &custom_switches_);
web_preferences.Get("disablePopups", &disable_popups_);
web_preferences.Get("disableDialogs", &disable_dialogs_);
web_preferences.Get("safeDialogs", &safe_dialogs_);
@@ -335,11 +333,6 @@ void WebContentsPreferences::AppendCommandLineSwitches(
if (!arg.empty())
command_line->AppendArg(arg);
// Custom command line switches.
for (const auto& arg : custom_switches_)
if (!arg.empty())
command_line->AppendSwitch(arg);
if (enable_blink_features_)
command_line->AppendSwitchASCII(::switches::kEnableBlinkFeatures,
*enable_blink_features_);
@@ -347,9 +340,6 @@ void WebContentsPreferences::AppendCommandLineSwitches(
command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures,
*disable_blink_features_);
if (node_integration_in_worker_)
command_line->AppendSwitch(switches::kNodeIntegrationInWorker);
// We are appending args to a webContents so let's save the current state
// of our preferences object so that during the lifetime of the WebContents
// we can fetch the options used to initially configure the WebContents

View File

@@ -121,7 +121,6 @@ class WebContentsPreferences
std::optional<std::string> default_encoding_;
bool is_webview_;
std::vector<std::string> custom_args_;
std::vector<std::string> custom_switches_;
std::optional<std::string> enable_blink_features_;
std::optional<std::string> disable_blink_features_;
bool disable_popups_;

View File

@@ -0,0 +1,54 @@
// Copyright (c) 2026 Microsoft GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/common/command_line_util_win.h"
#include <string>
namespace electron {
std::wstring AddQuoteForArg(const std::wstring& arg) {
// We follow the quoting rules of CommandLineToArgvW.
// http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
constexpr wchar_t kQuotableChars[] = L" \\\"";
if (arg.find_first_of(kQuotableChars) == std::wstring::npos) {
// No quoting necessary.
return arg;
}
std::wstring out;
out.push_back(L'"');
for (size_t i = 0; i < arg.size(); ++i) {
if (arg[i] == '\\') {
// Find the extent of this run of backslashes.
size_t start = i, end = start + 1;
for (; end < arg.size() && arg[end] == '\\'; ++end) {
}
size_t backslash_count = end - start;
// Backslashes are escapes only if the run is followed by a double quote.
// Since we also will end the string with a double quote, we escape for
// either a double quote or the end of the string.
if (end == arg.size() || arg[end] == '"') {
// To quote, we need to output 2x as many backslashes.
backslash_count *= 2;
}
for (size_t j = 0; j < backslash_count; ++j)
out.push_back('\\');
// Advance i to one before the end to balance i++ in loop.
i = end - 1;
} else if (arg[i] == '"') {
out.push_back('\\');
out.push_back('"');
} else {
out.push_back(arg[i]);
}
}
out.push_back('"');
return out;
}
} // namespace electron

View File

@@ -0,0 +1,20 @@
// Copyright (c) 2026 Microsoft GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_COMMON_COMMAND_LINE_UTIL_WIN_H_
#define ELECTRON_SHELL_COMMON_COMMAND_LINE_UTIL_WIN_H_
#include <string>
namespace electron {
// Quotes |arg| using CommandLineToArgvW-compatible quoting rules so that
// the argument round-trips correctly through CreateProcess →
// CommandLineToArgvW. If no quoting is necessary the string is returned
// unchanged. See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
std::wstring AddQuoteForArg(const std::wstring& arg);
} // namespace electron
#endif // ELECTRON_SHELL_COMMON_COMMAND_LINE_UTIL_WIN_H_

View File

@@ -19,6 +19,7 @@
#include "net/cert/x509_certificate.h"
#include "net/cert/x509_util.h"
#include "net/http/http_response_headers.h"
#include "net/http/http_util.h"
#include "net/http/http_version.h"
#include "net/url_request/redirect_info.h"
#include "services/network/public/cpp/data_element.h"
@@ -197,6 +198,10 @@ bool Converter<net::HttpResponseHeaders*>::FromV8(
}
std::string value;
gin::ConvertFromV8(isolate, localStrVal, &value);
if (!net::HttpUtil::IsValidHeaderName(key) ||
!net::HttpUtil::IsValidHeaderValue(value)) {
return false;
}
out->AddHeader(key, value);
return true;
};

View File

@@ -271,10 +271,6 @@ inline constexpr base::cstring_view kAppPath = "app-path";
// The command line switch versions of the options.
inline constexpr base::cstring_view kScrollBounce = "scroll-bounce";
// Command switch passed to renderer process to control nodeIntegration.
inline constexpr base::cstring_view kNodeIntegrationInWorker =
"node-integration-in-worker";
// Widevine options
// Path to Widevine CDM binaries.
inline constexpr base::cstring_view kWidevineCdmPath = "widevine-cdm-path";

View File

@@ -26,6 +26,8 @@
#include "third_party/blink/public/web/web_local_frame.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h" // nogncheck
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h" // nogncheck
#include "third_party/blink/renderer/core/workers/worker_global_scope.h" // nogncheck
#include "third_party/blink/renderer/core/workers/worker_settings.h" // nogncheck
#if BUILDFLAG(IS_LINUX) && (defined(ARCH_CPU_X86_64) || defined(ARCH_CPU_ARM64))
#define ENABLE_WEB_ASSEMBLY_TRAP_HANDLER_LINUX
@@ -207,44 +209,54 @@ void ElectronRendererClient::WillReleaseScriptContext(
electron_bindings_->EnvironmentDestroyed(env);
}
void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread(
v8::Local<v8::Context> context) {
namespace {
bool WorkerHasNodeIntegration(blink::ExecutionContext* ec) {
// We do not create a Node.js environment in service or shared workers
// owing to an inability to customize sandbox policies in these workers
// given that they're run out-of-process.
// Also avoid creating a Node.js environment for worklet global scope
// created on the main thread.
auto* ec = blink::ExecutionContext::From(context);
if (ec->IsServiceWorkerGlobalScope() || ec->IsSharedWorkerGlobalScope() ||
ec->IsMainThreadWorkletGlobalScope())
return false;
auto* wgs = blink::DynamicTo<blink::WorkerGlobalScope>(ec);
if (!wgs)
return false;
// Read the nodeIntegrationInWorker preference from the worker's settings,
// which were copied from the initiating frame's WebPreferences at worker
// creation time. This ensures that in-process child windows with different
// webPreferences get the correct per-frame value rather than a process-wide
// value.
auto* worker_settings = wgs->GetWorkerSettings();
return worker_settings && worker_settings->NodeIntegrationInWorker();
}
} // namespace
void ElectronRendererClient::WorkerScriptReadyForEvaluationOnWorkerThread(
v8::Local<v8::Context> context) {
auto* ec = blink::ExecutionContext::From(context);
if (!WorkerHasNodeIntegration(ec))
return;
// This won't be correct for in-process child windows with webPreferences
// that have a different value for nodeIntegrationInWorker
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNodeIntegrationInWorker)) {
auto* current = WebWorkerObserver::GetCurrent();
if (current)
return;
WebWorkerObserver::Create()->WorkerScriptReadyForEvaluation(context);
}
auto* current = WebWorkerObserver::GetCurrent();
if (current)
return;
WebWorkerObserver::Create()->WorkerScriptReadyForEvaluation(context);
}
void ElectronRendererClient::WillDestroyWorkerContextOnWorkerThread(
v8::Local<v8::Context> context) {
auto* ec = blink::ExecutionContext::From(context);
if (ec->IsServiceWorkerGlobalScope() || ec->IsSharedWorkerGlobalScope() ||
ec->IsMainThreadWorkletGlobalScope())
if (!WorkerHasNodeIntegration(ec))
return;
// TODO(loc): Note that this will not be correct for in-process child windows
// with webPreferences that have a different value for nodeIntegrationInWorker
if (base::CommandLine::ForCurrentProcess()->HasSwitch(
switches::kNodeIntegrationInWorker)) {
auto* current = WebWorkerObserver::GetCurrent();
if (current)
current->ContextWillDestroy(context);
}
auto* current = WebWorkerObserver::GetCurrent();
if (current)
current->ContextWillDestroy(context);
}
void ElectronRendererClient::SetUpWebAssemblyTrapHandler() {

View File

@@ -2,7 +2,6 @@ import { app, BrowserWindow, Menu, session, net as electronNet, WebContents, uti
import { assert, expect } from 'chai';
import * as semver from 'semver';
import split = require('split')
import * as cp from 'node:child_process';
import { once } from 'node:events';
@@ -11,6 +10,7 @@ import * as http from 'node:http';
import * as https from 'node:https';
import * as net from 'node:net';
import * as path from 'node:path';
import * as readline from 'node:readline';
import { setTimeout } from 'node:timers/promises';
import { promisify } from 'node:util';
@@ -260,11 +260,11 @@ describe('app module', () => {
const firstExited = once(first, 'exit');
// Wait for the first app to boot.
const firstStdoutLines = first.stdout.pipe(split());
while ((await once(firstStdoutLines, 'data')).toString() !== 'started') {
const firstStdoutLines = readline.createInterface({ input: first.stdout });
while ((await once(firstStdoutLines, 'line')).toString() !== 'started') {
// wait.
}
const additionalDataPromise = once(firstStdoutLines, 'data');
const additionalDataPromise = once(firstStdoutLines, 'line');
const secondInstanceArgs = [process.execPath, appPath, ...testArgs.args, '--some-switch', 'some-arg'];
const second = cp.spawn(secondInstanceArgs[0], secondInstanceArgs.slice(1));
@@ -1470,6 +1470,29 @@ describe('app module', () => {
});
});
describe('protocol scheme validation', () => {
it('rejects empty protocol names', () => {
expect(app.setAsDefaultProtocolClient('')).to.equal(false);
expect(app.isDefaultProtocolClient('')).to.equal(false);
expect(app.removeAsDefaultProtocolClient('')).to.equal(false);
});
it('rejects non-conformant protocol names ', () => {
// Starting with a digit.
expect(app.setAsDefaultProtocolClient('0badscheme')).to.equal(false);
// Starting with a hyphen.
expect(app.setAsDefaultProtocolClient('-badscheme')).to.equal(false);
// Containing backslashes.
expect(app.setAsDefaultProtocolClient('http\\shell\\open\\command')).to.equal(false);
// Containing forward slashes.
expect(app.setAsDefaultProtocolClient('bad/protocol')).to.equal(false);
// Containing spaces.
expect(app.setAsDefaultProtocolClient('bad protocol')).to.equal(false);
// Containing colons.
expect(app.setAsDefaultProtocolClient('bad:protocol')).to.equal(false);
});
});
ifdescribe(process.platform === 'win32')('app launch through uri', () => {
it('does not launch for argument following a URL', async () => {
const appPath = path.join(fixturesPath, 'api', 'quit-app');

View File

@@ -1372,6 +1372,89 @@ describe('chromium features', () => {
expect(data).to.equal('object function object function');
});
it('Worker does not have node integration when nodeIntegrationInWorker is disabled via setWindowOpenHandler', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
});
w.webContents.setWindowOpenHandler(() => ({
action: 'allow',
overrideBrowserWindowOptions: {
show: false,
webPreferences: {
nodeIntegration: false,
nodeIntegrationInWorker: false,
contextIsolation: true
}
}
}));
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
const childCreated = once(app, 'browser-window-created') as Promise<[any, BrowserWindow]>;
w.webContents.executeJavaScript(`window.open(${JSON.stringify(`file://${fixturesPath}/pages/blank.html`)}); void 0;`);
const [, child] = await childCreated;
await once(child.webContents, 'did-finish-load');
const data = await child.webContents.executeJavaScript(`
const worker = new Worker('../workers/worker_node.js');
new Promise((resolve) => { worker.onmessage = e => resolve(e.data); })
`);
expect(data).to.equal('undefined undefined undefined undefined');
});
it('Worker has node integration when nodeIntegrationInWorker is enabled via setWindowOpenHandler', async () => {
const w = new BrowserWindow({
show: false,
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: false,
contextIsolation: false
}
});
w.webContents.setWindowOpenHandler(() => ({
action: 'allow',
overrideBrowserWindowOptions: {
show: false,
webPreferences: {
nodeIntegration: true,
nodeIntegrationInWorker: true,
contextIsolation: false
}
}
}));
await w.loadURL(`file://${fixturesPath}/pages/blank.html`);
// Parent's workers should NOT have node integration.
const parentData = await w.webContents.executeJavaScript(`
new Promise((resolve) => {
const worker = new Worker('../workers/worker_node.js');
worker.onmessage = e => resolve(e.data);
})
`);
expect(parentData).to.equal('undefined undefined undefined undefined');
const childCreated = once(app, 'browser-window-created') as Promise<[any, BrowserWindow]>;
w.webContents.executeJavaScript(`window.open(${JSON.stringify(`file://${fixturesPath}/pages/blank.html`)}); void 0;`);
const [, child] = await childCreated;
await once(child.webContents, 'did-finish-load');
// Child's workers should have node integration.
const childData = await child.webContents.executeJavaScript(`
new Promise((resolve) => {
const worker = new Worker('../workers/worker_node.js');
worker.onmessage = e => resolve(e.data);
})
`);
expect(childData).to.equal('object function object function');
});
it('Worker has access to fetch-dependent interfaces with nodeIntegrationInWorker', async () => {
const w = new BrowserWindow({
show: false,

View File

@@ -23,7 +23,6 @@
"@types/mocha": "^7.0.2",
"@types/send": "^0.14.5",
"@types/sinon": "^9.0.4",
"@types/split": "^1.0.5",
"@types/uuid": "^3.4.6",
"@types/w3c-web-serial": "^1.0.7",
"@types/ws": "^7.2.0",
@@ -46,7 +45,6 @@
"q": "^1.5.1",
"send": "^0.19.0",
"sinon": "^9.0.1",
"split": "^1.0.1",
"uuid": "^3.3.3",
"winreg": "1.2.4",
"ws": "^7.5.10",

View File

@@ -490,6 +490,7 @@ __metadata:
webpack: "npm:^5.95.0"
webpack-cli: "npm:^5.1.4"
wrapper-webpack-plugin: "npm:^2.2.0"
yaml: "npm:^2.8.1"
dependenciesMeta:
abstract-socket:
built: true
@@ -1990,16 +1991,6 @@ __metadata:
languageName: node
linkType: hard
"@types/split@npm:^1.0.5":
version: 1.0.5
resolution: "@types/split@npm:1.0.5"
dependencies:
"@types/node": "npm:*"
"@types/through": "npm:*"
checksum: 10c0/eb187a3b07e5064928e49bffd5c45ad1f1109135fee52344bb7623cdb55e2ebb16bd6ca009a30a0a6e2b262f7ebb7bf18030ff873819e80fafd4cbb51dba1a74
languageName: node
linkType: hard
"@types/stream-chain@npm:*":
version: 2.0.0
resolution: "@types/stream-chain@npm:2.0.0"
@@ -2042,15 +2033,6 @@ __metadata:
languageName: node
linkType: hard
"@types/through@npm:*":
version: 0.0.33
resolution: "@types/through@npm:0.0.33"
dependencies:
"@types/node": "npm:*"
checksum: 10c0/6a8edd7f40cd7e197318e86310a40e568cddd380609dde59b30d5cc6c5f8276ddc698905eac4b3b429eb39f2e8ee326bc20dc6e95a2cdc41c4d3fc9a1ebd4929
languageName: node
linkType: hard
"@types/unist@npm:*, @types/unist@npm:^2.0.0":
version: 2.0.6
resolution: "@types/unist@npm:2.0.6"
@@ -4543,7 +4525,6 @@ __metadata:
"@types/mocha": "npm:^7.0.2"
"@types/send": "npm:^0.14.5"
"@types/sinon": "npm:^9.0.4"
"@types/split": "npm:^1.0.5"
"@types/uuid": "npm:^3.4.6"
"@types/w3c-web-serial": "npm:^1.0.7"
"@types/ws": "npm:^7.2.0"
@@ -4566,7 +4547,6 @@ __metadata:
q: "npm:^1.5.1"
send: "npm:^0.19.0"
sinon: "npm:^9.0.1"
split: "npm:^1.0.1"
uuid: "npm:^3.3.3"
winreg: "npm:1.2.4"
ws: "npm:^7.5.10"
@@ -14377,6 +14357,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"