Compare commits

..

57 Commits

Author SHA1 Message Date
trop[bot]
44bc2c8cef fix: user resizable transparent windows on win32 (#50298)
test: revert win32 frameless and transparent resizable expectations

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Justin Mayfield <tooker@gmail.com>
2026-03-17 09:46:52 +01:00
trop[bot]
4e9e7335bc test: fix esm issue in node-spec-runner (#50295)
Chromium added a top-level package.json in CL:7485999 that sets
the type to module and breaks commonjs tests run via
node-spec-runner.js. This commit temporarily changes the type to
commonjs while running the tests, then changes it back to module when done.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-16 15:44:40 -04:00
trop[bot]
cd88382756 fix: add ASAR support to additional copy methods (#50286)
* fix: add ASAR support for additional copy methods

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

* test: add tests for ASAR support for additional copy messages

Co-authored-by: Noah Gregory <noahmgregory@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Noah Gregory <noahmgregory@gmail.com>
2026-03-16 13:56:00 -04:00
trop[bot]
105c5591d0 docs: update the example of webContents.setWindowOpenHandler to cla… (#50293)
docs: reorganize the comments for clarifying `webContents.setWindowOpenHandler` example

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: z0gSh1u <zx.cs@qq.com>
2026-03-16 12:53:05 -04:00
trop[bot]
90b3a2341d build: remove redundant bits of ncrypto node patch (#50279)
build: remove redundant ncrypto node patch

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-16 16:03:05 +01:00
electron-roller[bot]
21f9474f4f chore: bump chromium to 146.0.7680.80 (41-x-y) (#50262)
* chore: bump chromium in DEPS to 146.0.7680.80

* chore: fixup patch indices

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-16 15:18:28 +01:00
trop[bot]
c3e397ed2d fix: prefer browser runtime over node in DevTools HostRuntime detection (#50276)
Upstream DevTools' HostRuntime checks `IS_NODE` before `IS_BROWSER` when
selecting the platform runtime. In Electron, `process` is available in
renderer processes, so `IS_NODE` evaluates to `true` in the DevTools
context. This causes DevTools to dynamically import the Node.js platform
runtime, which uses `node:worker_threads`. DevTools Web Workers running
under the `devtools://` protocol cannot load Node.js built-in modules,
so the import fails and breaks features like the formatter worker.

Fix by swapping the check order to prefer `IS_BROWSER` when both are
true. This is safe because in pure Node.js environments (the only case
where the node runtime is needed), `window` and `self` are both
undefined, so `IS_BROWSER` is always `false` regardless of check order.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-16 12:55:46 +01:00
trop[bot]
d84dca2818 build: skip archiving patch conflict fix artifact (#50257)
The update-patches artifact is a single .patch file, so zipping it
is unnecessary overhead. With archive: false, gh run download fetches
the raw file directly without requiring a decompression step.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 18:51:49 -07:00
trop[bot]
bb20d0c352 docs: point pull requests guide to build tools (#50253)
* docs: point pull requests guide to build tools

Co-authored-by: Erick Zhao <erick@hotmail.ca>

* update for `--fork`

Co-authored-by: Erick Zhao <erick@hotmail.ca>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2026-03-13 16:16:09 -04:00
electron-roller[bot]
c66fc559b2 chore: bump chromium to 146.0.7680.76 (41-x-y) (#50244)
* chore: bump chromium in DEPS to 146.0.7680.76

* chore: update patches

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2026-03-13 15:00:47 -04:00
trop[bot]
76f34911f2 build: add patch conflict resolution workflow with CI artifacts (#50239)
ci: upload patch conflict fix as artifact in apply-patches

When patch-up.js cannot auto-push the 3-way-merged patch diff (e.g. on
fork PRs), the checkout action already writes patches/update-patches.patch
and tells the user to check CI artifacts — but nothing was uploading it.

This adds the missing upload-artifact step to the apply-patches job so
the resolved diff is available for download, and documents in CLAUDE.md
that pulling this artifact and applying it with `git am` is the fast
path for fixing patch conflicts on PR branches without a full local sync.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Claude <noreply@anthropic.com>
2026-03-13 10:10:48 +01:00
trop[bot]
5d381dd27e ci: update actions/cache to 5.0.3 (#50238)
chore: update actions/cache to 5.0.3

Needed due to https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-03-13 10:04:48 +01:00
Keeley Hammond
42d7f2783b chore: cherry-pick d5b0cb2acffe from v8 (#50233)
* chore: cherry-pick d5b0cb2acffe from v8

* chore: update patches

---------

Co-authored-by: PatchUp <73610968+patchup[bot]@users.noreply.github.com>
2026-03-13 03:11:14 +00:00
Keeley Hammond
61b4c6b93e chore: cherry-pick 248acd90d9a3 from skia (#50234)
* chore: add skia patch dir

* chore: cherry-pick 248acd90d9a3 from skia

* chore: fix the dumbest comma, patch
2026-03-13 03:04:12 +00:00
John Kleinschmidt
b9ca21156b ci: add timeout to test step (#50206)
ci: add timeout to test step (#50186)

Additionally, take a screenshot on timeout so that we can debug why there is a hang
2026-03-12 14:40:26 +01:00
trop[bot]
23960241f9 fix: preserve staged update dir when pruning orphaned updates on macOS (#50217)
* fix: preserve staged update dir when pruning orphaned update dirs on macOS

The previous squirrel.mac patch cleaned up all staged update directories
before starting a new download. This kept disk usage bounded but broke
quitAndInstall() if called while a subsequent checkForUpdates() was in
flight — the already-staged bundle would be deleted out from under it.

This reworks the patch to read ShipItState.plist and preserve the
directory it references, deleting only truly orphaned update.XXXXXXX
directories. Disk footprint stays bounded (at most 2 dirs: staged +
in-progress) and quitAndInstall() remains safe mid-check.

Also adds test coverage for the quitAndInstall/checkForUpdates race and
a triple-stack scenario where 3 updates arrive without a restart.

Refs https://github.com/electron/electron/issues/50200

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

* chore: update patches

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
2026-03-12 01:50:13 +00:00
trop[bot]
6d2986302c fix: prevent traffic light buttons flashing on deminiaturize (#50209)
* fix: prevent traffic light buttons flashing on deminiaturize

When a window with a custom `trafficLightPosition` is minimized and
restored, macOS re-layouts the title bar container during the
deminiaturize animation, causing the traffic light buttons to briefly
appear at their default position before being repositioned.

Fix this by hiding the buttons container in `windowWillMiniaturize` and
restoring them (with a redraw to the correct position) in
`windowDidDeminiaturize`.

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

* chore: address feedback from review

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

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-11 17:00:10 -04:00
trop[bot]
01b99cd9a9 docs: document Wayland frameless window shadow behaviour (#50195)
docs: update linux frameless window docs to account for hasShadow:false

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: George Xu <george.xu@slack-corp.com>
2026-03-11 15:37:32 -04:00
electron-roller[bot]
a8f64f684f chore: bump chromium to 146.0.7680.72 (41-x-y) (#50196)
chore: bump chromium in DEPS to 146.0.7680.72

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2026-03-11 15:35:29 -04:00
trop[bot]
ca1b77d9b7 fix: don't call TaskDialogIndirect with disabled parent windows (#50191)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Noah Gregory <noahmgregory@gmail.com>
2026-03-10 18:05:58 -07:00
trop[bot]
3678edfa37 feat: WebContents.getOrCreateDevToolsTargetId() (#50176)
* Feat: support getDevToolsId() on WebContents

* Rename to `getOrCreateDevToolsTargetId`

* build: use spawn instead of spawnSync for build (#49774)

* Fix build

* formatting

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Kyle Cutler <67761731+kycutler@users.noreply.github.com>
2026-03-10 12:22:32 -04:00
Shelley Vohr
cb4d31ae61 fix: bind offscreen paint callback to child WebContents (#50152)
fix: bind offscreen paint callback to child WebContents

Previously, MaybeOverrideCreateParamsForNewWindow bound the
OffScreenWebContentsView's paint callback to the parent WebContents
using base::Unretained(this). This was both unsafe (dangling pointer
risk if the parent is destroyed before the child) and semantically
incorrect — paint events belong to the child window, not the opener.

Replace the callback in MaybeOverrideCreateParamsForNewWindow with
base::DoNothing(), then rebind it to the child WebContents in
AddNewContents via a new SetCallback method on OffScreenWebContentsView.
2026-03-10 09:43:51 +01:00
trop[bot]
616a63bc73 refactor: use WHATWG URL instead of url.parse (#50143)
refactor: use WHATWG URL instead of url.parse

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-09 19:58:45 -07:00
Samuel Attard
e78e2ca996 fix: correct parsing of second-instance additionalData (#50154)
- 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-09 19:22:57 -07:00
trop[bot]
cea004c31e docs: fix ipc highlight lines (#50181)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2026-03-09 18:31:21 -07:00
trop[bot]
a14f661c58 fix: read nodeIntegrationInWorker from per-frame WebPreferences (#50134)
* fix: read nodeIntegrationInWorker from per-frame WebPreferences

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.

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

* chore: update patches

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-03-09 23:26:10 +00:00
trop[bot]
64354677bf fix: validate protocol scheme names in setAsDefaultProtocolClient (#50155)
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-09 23:06:57 +00:00
trop[bot]
15dd5dc396 fix: use requesting frame origin in permission helper and device choosers (#50148)
* 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 15:59:57 -07:00
trop[bot]
205cd53a06 fix: InspectorFrontendHost override in embedded windows (#50137)
fix: InspectorFrontendHost override in embedded windows

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-09 10:00:28 -05:00
trop[bot]
2c890e0adc fix: validate response header names and values before AddHeader (#50132)
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 12:34:05 +01:00
trop[bot]
58ea9554f6 build: pin diff.renames for deterministic patch export (#50128)
git format-patch honors diff.renames, which defaults to 'true' (rename
detection only). If a user has diff.renames=copies configured at the
system or global level, exported patches may encode new files as copies
of similar existing files, causing spurious diffs against patches
exported on other machines. Pin diff.renames=true to match git's
default.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-03-09 10:33:14 +01:00
trop[bot]
1ab3e54129 fix: strictly validate sender for internal IPC reply channels (#50126)
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: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-03-09 10:33:07 +01:00
trop[bot]
26fb36b7fb fix: screen.getCursorScreenPoint() crash on Wayland (#50105)
* docs: document that getCursorScreenPoint() needs a Window on Wayland

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* feat: add IsWayland() helper

Co-authored-by: Charles Kerr <charles@charleskerr.com>

* fix: Wayland crash in GetCursorScreenPoint()

fix: support Screen::GetCursorScreenPoint() on X11

Co-authored-by: Charles Kerr <charles@charleskerr.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-03-08 19:20:34 +01:00
Ryan Fitzgerald
332b5b4097 fix: delete temporary blink* globals after restoring Blink implementations (#50117)
fix: delete temporary blink* globals after restoring Blink implementations (#49991)

ElectronRendererClient::DidCreateScriptContext (and the worker
equivalent) save Blink's fetch/Response/FormData/Request/Headers/
EventSource as temporary globalThis.blink* properties before Node
initialization may overwrite them. node/init.ts and worker/init.ts
restore the originals but previously never deleted the temporary
blink* globals.

They persisted as non-standard global pollution visible to page
content when contextIsolation is disabled -- a minor fingerprinting
signal and a bypass for any preload that wraps window.fetch (page
could call blinkfetch() instead).

Co-authored-by: Samuel Attard <sam@electronjs.org>
2026-03-08 00:05:08 -08:00
electron-roller[bot]
ad84f5b888 chore: bump chromium to 146.0.7680.65 (41-x-y) (#50101)
* chore: bump chromium in DEPS to 146.0.7680.65

* chore: update patches

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-03-08 06:22:51 +09:00
trop[bot]
f40c1a5796 fix: Revert "updated Alt detection to explicitly exclude AltGraph/AltGr (#49778)" (#50111)
Revert "fix: updated Alt detection to explicitly exclude AltGraph/AltGr (#49778)"

This reverts commit 90c9de70ac.

Ref: https://github.com/electron/electron/issues/50050

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: clavin <clavin@electronjs.org>
2026-03-06 21:05:19 -08:00
trop[bot]
c5978261a4 docs: cleanup desktop-capturer doc after chromium audio capture additions (#50114)
* docs: cleanup desktop-capturer doc after chromium audio capture additions

Co-authored-by: Michaela Laurencin <mlaurencin@electronjs.org>

* Apply suggestions from code review

Co-authored-by: Erick Zhao <erick@hotmail.ca>

Co-authored-by: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>

* disable linter for list in note

Co-authored-by: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Michaela Laurencin <mlaurencin@electronjs.org>
Co-authored-by: Michaela Laurencin <35157522+mlaurencin@users.noreply.github.com>
2026-03-06 16:05:26 -05:00
trop[bot]
cb32daded3 docs: remove release schedule in favor of https://releases.electronjs.org/schedule (#50108)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-03-06 15:47:31 -05:00
trop[bot]
94bc0ec88c fix: prevent use-after-free in PowerMonitor via dangling OS callbacks (#50087)
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:24:12 -05:00
trop[bot]
1bd08111e7 docs: add more api history (C-D) (#50083)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Erick Zhao <erick@hotmail.ca>
2026-03-05 17:14:17 +01:00
trop[bot]
df065892fa fix: avoid redundant page-favicon-updated events on setBounds (#50085)
* fix: avoid duplicate calls in electron_api_web_contents

Co-authored-by: ANANYA542 <ananyashrma6512@gmail.com>

* Style: fix lint errors

Co-authored-by: ANANYA542 <ananyashrma6512@gmail.com>

* fix: prevent duplicate page-favicon-updated events and add regression test

Co-authored-by: Ananya542 <ananyashrma6512@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: ANANYA542 <ananyashrma6512@gmail.com>
2026-03-05 17:14:13 +01:00
trop[bot]
7520211f51 build: fix code-signing for MacOS x64 tests (#50073)
* fix: code-sign binaries for notification tests

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* test: remove redundent feedURL test

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* test: move squirrel feed tests to api-autoupdater

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* fix: fix SQRLShipItRequest.JSONKeyPathsByPropertyKey mappings

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* Revert "fix: fix SQRLShipItRequest.JSONKeyPathsByPropertyKey mappings"

This reverts commit 5ad9892a67.

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* test: unsign tests requiring no signed app

Co-authored-by: Keeley Hammond <khammond@slack-corp.com>

* fixup! fix: fix SQRLShipItRequest.JSONKeyPathsByPropertyKey mappings

chore: fix patch shear

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-03-05 10:12:33 -06:00
trop[bot]
198b70e4bd fix: FTBFS when printing is disabled (#50077)
these variables hit with-Werror,-Wunused when printing is disabled

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-03-05 08:57:09 -06:00
trop[bot]
ac54002bac feat: show toast dismissal reason on Windows (#50029)
* feat: show toast dismissal reason on Windows

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

* Update docs/api/notification.md

Co-authored-by: David Sanders <dsanders11@ucsbalum.com>

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

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-05 11:29:27 +01:00
trop[bot]
be87d0c08a fix: use proper quoting for exe paths and args on Windows (#50076)
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-04 13:37:50 -06:00
trop[bot]
d19eb6b07f fix: better shortcut registration and app icon matching on Wayland (#50051)
* fix: set default desktop name that matches exec name on linux

Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>

* chromium patches for global shortcuts

Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>

* use app name for shortcut description

Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>

* chore: fixup .patches after trop

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Mitchell Cohen <mitch.cohen@me.com>
Co-authored-by: John Kleinschmidt <kleinschmidtorama@gmail.com>
2026-03-04 16:43:44 +01:00
trop[bot]
83ef7e68c9 chore: remove applescript from trash (#50066)
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 16:14:12 +01:00
trop[bot]
1ec6624b0a fix: uaf in non-client hittest during view teardown (#50055)
* fix: uaf in non-client hittest during view teardown

Co-authored-by: deepak1556 <hop2deep@gmail.com>

* chore: update crash spec

Co-authored-by: deepak1556 <hop2deep@gmail.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: deepak1556 <hop2deep@gmail.com>
2026-03-03 14:15:36 -05:00
trop[bot]
d6e02c53ad fix(styles): add missing dot prefix to .hero-icon.loop-3 CSS selector (#50033)
Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Jaya Krishna <jayakrishnagundupu@gmail.com>
2026-03-03 09:29:47 -05:00
trop[bot]
429309b7c7 fix: validate USB device selection against filtered device list (#50049)
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.

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:27:30 -05:00
trop[bot]
632113662c fix: use destination context when wrapping VideoFrame in contextBridge (#50023)
Enter the destination context scope before creating the VideoFrame V8
wrapper, matching the sibling Element and Blob branches. Without this,
ScriptState::ForCurrentRealm resolved to the calling context instead of
the target context, producing an incorrect wrapper.

Also switch to ScriptState::From with an explicit context argument to
make the intent clearer.

Adds spec coverage for VideoFrame crossing the bridge in both
directions and adds VideoFrame to the existing prototype checks.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-03-03 09:15:37 -05:00
trop[bot]
95e0fc7f28 chore: remove unused commandLineSwitches flag (#50015)
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 10:45:18 +01:00
trop[bot]
c605b21af6 fix: use setter's creation context when proxying setter in OverrideGlobalPropertyFromIsolatedWorld (#50020)
The setter branch was deriving source_context from getter-> instead of
setter->. Currently latent since the only call site passes both from
the same preload context, but this would crash or mis-resolve if a
future call site passed a setter without a getter or from a different
context.

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Samuel Attard <sattard@anthropic.com>
2026-03-03 10:43:06 +01:00
trop[bot]
fcaf525050 fix: prevent use-after-free in permission request callbacks (#50034)
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: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Shelley Vohr <shelley.vohr@gmail.com>
2026-03-02 20:45:46 -08:00
trop[bot]
2fcd22e542 build: authenticate curl requests to googlesource in lint workflow (#50028)
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:38:05 -05:00
trop[bot]
0da6944cb6 fix: potential UAF in OnDownloadPathGenerated (#50010)
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-02 18:36:12 -05:00
trop[bot]
1a760a18e5 fix: deliver Universal Links when NSUserActivity.userInfo is nil (#50006)
* fix(mac): deliver Universal Links when NSUserActivity.userInfo is nil

Co-authored-by: Dante Issaias <dante@issaias.com>

* chore: format

Co-authored-by: Dante Issaias <dante@issaias.com>

---------

Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com>
Co-authored-by: Dante Issaias <dante@issaias.com>
2026-03-02 13:12:20 -05:00
123 changed files with 2028 additions and 583 deletions

View File

@@ -43,7 +43,7 @@ runs:
curl --unix-socket /var/run/sas/sas.sock --fail "http://foo/$CACHE_FILE?platform=${{ inputs.target-platform }}&getAccountName=true" > sas-token
- name: Save SAS Key
if: ${{ inputs.generate-sas-token == 'true' }}
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
uses: actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: sas-token
key: sas-key-${{ inputs.target-platform }}-${{ github.run_number }}-${{ github.run_attempt }}

View File

@@ -7,7 +7,7 @@ runs:
shell: bash
id: yarn-cache-dir-path
run: echo "dir=$(node src/electron/script/yarn.js config get cacheFolder)" >> $GITHUB_OUTPUT
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
- uses: actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
id: yarn-cache
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View File

@@ -71,3 +71,11 @@ jobs:
uses: ./src/electron/.github/actions/checkout
with:
target-platform: linux
- name: Upload Patch Conflict Fix
if: ${{ failure() }}
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
with:
name: update-patches
path: patches/update-patches.patch
if-no-files-found: ignore
archive: false

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 problem matchers

View File

@@ -191,15 +191,25 @@ jobs:
run: |
cd src/out/Default
unzip -:o dist.zip
#- name: Import & Trust Self-Signed Codesigning Cert on MacOS
# if: ${{ inputs.target-platform == 'macos' && inputs.target-arch == 'x64' }}
# run: |
# sudo security authorizationdb write com.apple.trust-settings.admin allow
# cd src/electron
# ./script/codesign/generate-identity.sh
- name: Import & Trust Self-Signed Codesigning Cert on MacOS
if: ${{ inputs.target-platform == 'macos' }}
run: |
cd src/electron
./script/codesign/generate-identity.sh
# Only sign on x64 — arm64 builds are already ad-hoc signed, and re-signing
# with an untrusted cert breaks macOS system integrations (e.g. dock bounce).
# Autoupdater tests sign their own fixture copies via signApp().
- name: Sign Electron.app for macOS tests
if: ${{ inputs.target-platform == 'macos' && inputs.target-arch == 'x64' }}
run: |
identity=$(src/electron/script/codesign/get-trusted-identity.sh)
if [ -n "$identity" ]; then
codesign -s "$identity" --deep --force src/out/Default/Electron.app
fi
- name: Run Electron Tests
shell: bash
timeout-minutes: 40
env:
MOCHA_REPORTER: mocha-multi-reporters
MOCHA_MULTI_REPORTERS: mocha-junit-reporter, tap
@@ -250,6 +260,19 @@ jobs:
fi
fi
- name: Take screenshot on timeout or cancellation
if: ${{ inputs.target-platform != 'linux' && (cancelled() || failure()) }}
shell: bash
run: |
screenshot_dir="src/electron/spec/artifacts"
mkdir -p "$screenshot_dir"
screenshot_file="$screenshot_dir/screenshot-timeout-$(date +%Y%m%d%H%M%S).png"
if [ "${{ inputs.target-platform }}" = "macos" ]; then
screencapture -x "$screenshot_file" || true
elif [ "${{ inputs.target-platform }}" = "win" ]; then
powershell -command "Add-Type -AssemblyName System.Windows.Forms; \$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds; \$bitmap = New-Object System.Drawing.Bitmap(\$screen.Width, \$screen.Height); \$graphics = [System.Drawing.Graphics]::FromImage(\$bitmap); \$graphics.CopyFromScreen(\$screen.Location, [System.Drawing.Point]::Empty, \$screen.Size); \$bitmap.Save('$screenshot_file')" || true
fi
- name: Upload Test results to Datadog
env:
DD_ENV: ci
@@ -265,7 +288,7 @@ jobs:
fi
if: always() && !cancelled()
- name: Upload Test Artifacts
if: always() && !cancelled()
if: always()
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
with:
name: test_artifacts_${{ env.ARTIFACT_KEY }}_${{ matrix.shard }}

View File

@@ -127,6 +127,22 @@ patches/{target}/*.patch → [e sync --3] → target repo commits
2. Create a git commit
3. Run `e patches <target>` to export
**Fixing patch conflicts on an existing PR:**
If asked to fix a patch conflict on a branch that already has an open PR, check the PR's failed **Apply Patches** CI run for an `update-patches` artifact before running `e sync` locally. CI has already performed the 3-way merge and exported the resolved patch diff — applying it is much faster than a full local sync.
```bash
# Find the failed Apply Patches run for the PR and download the artifact
gh run list --repo electron/electron --branch <pr-branch> --workflow "Apply Patches" --limit 1
gh run download <run-id> --repo electron/electron --name update-patches
# Apply the CI-generated fix, then push
git am update-patches.patch
git push
```
If no artifact exists (e.g. the 3-way merge itself failed), fall back to `e sync --3` and resolve manually.
## Testing
**Test location:** `spec/` directory

2
DEPS
View File

@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'146.0.7680.31',
'146.0.7680.80',
'node_version':
'v24.14.0',
'nan_version':

View File

@@ -111,7 +111,10 @@ async function loadApplicationPackage (packagePath: string) {
app.name = packageJson.name;
}
app.setDesktopName(packageJson.desktopName || `${app.name}.desktop`);
// Set application's desktop name (Linux). These usually match the executable name,
// so use it as the default to ensure the app gets the correct icon in the taskbar and application switcher.
const desktopName = packageJson.desktopName || `${path.basename(process.execPath)}.desktop`;
app.setDesktopName(desktopName);
// Set v8 flags, deliberately lazy load so that apps that do not use this
// feature do not pay the price
@@ -253,7 +256,7 @@ async function startRepl () {
if (option.file && !option.webdriver) {
const file = option.file;
// eslint-disable-next-line n/no-deprecated-api
const protocol = url.parse(file).protocol;
const protocol = URL.canParse(file) ? new URL(file).protocol : null;
const extension = path.extname(file);
if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') {
await loadApplicationByURL(file);

View File

@@ -41,7 +41,7 @@ h4 {
transform-origin: 50% 50%;
}
hero-icon.loop-3 {
.hero-icon.loop-3 {
transform: translate(79px, 21px);
opacity: 1;
}

View File

@@ -1,5 +1,14 @@
# clipboard
<!--
```YAML history
deprecated:
- pr-url: https://github.com/electron/electron/pull/48877
description: "Using the `clipboard` API directly in the renderer process is deprecated."
breaking-changes-header: deprecated-clipboard-api-access-from-renderer-processes
```
-->
> Perform copy and paste operations on the system clipboard.
Process: [Main](../glossary.md#main-process), [Renderer](../glossary.md#renderer-process) _Deprecated_ (non-sandboxed only)

View File

@@ -33,6 +33,15 @@ The `contentTracing` module has the following methods:
### `contentTracing.getCategories()`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/16583
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
```
-->
Returns `Promise<string[]>` - resolves with an array of category groups once all child processes have acknowledged the `getCategories` request
Get a set of category groups. The category groups can change as new code paths
@@ -44,6 +53,17 @@ are reached. See also the
### `contentTracing.startRecording(options)`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/13914
description: "The `options` parameter now accepts `TraceConfig` in addition to `TraceCategoriesAndOptions`."
- pr-url: https://github.com/electron/electron/pull/16584
description: "This function now returns a callback`Promise<void>`."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
```
-->
* `options` ([TraceConfig](structures/trace-config.md) | [TraceCategoriesAndOptions](structures/trace-categories-and-options.md))
Returns `Promise<void>` - resolved once all child processes have acknowledged the `startRecording` request.
@@ -58,6 +78,17 @@ only one trace operation can be in progress at a time.
### `contentTracing.stopRecording([resultFilePath])`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/16584
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
- pr-url: https://github.com/electron/electron/pull/18411
description: "The `resultFilePath` parameter is now optional."
```
-->
* `resultFilePath` string (optional)
Returns `Promise<string>` - resolves with a path to a file that contains the traced data once all child processes have acknowledged the `stopRecording` request
@@ -76,6 +107,15 @@ will be returned in the promise.
### `contentTracing.getTraceBufferUsage()`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/16600
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
```
-->
Returns `Promise<Object>` - Resolves with an object containing the `value` and `percentage` of trace buffer maximum usage
* `value` number

View File

@@ -50,6 +50,22 @@ The `crashReporter` module has the following methods:
### `crashReporter.start(options)`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/23062
description: "Added `rateLimit` and `compress` options."
- pr-url: https://github.com/electron/electron/pull/23265
description: "Deprecated calling this method in the renderer process."
breaking-changes-header: deprecated-crashreporter-methods-in-the-renderer-process
- pr-url: https://github.com/electron/electron/pull/25288
description: "Default value of `compress` option changed from `false` to `true`."
breaking-changes-header: default-changed-crashreporterstart-compress-true-
- pr-url: https://github.com/electron/electron/pull/28105
description: "The `submitURL` parameter is now optional when `uploadToServer` is `false`."
```
-->
* `options` Object
* `submitURL` string (optional) - URL that crash reports will be sent to as
POST. Required unless `uploadToServer` is `false`.
@@ -111,6 +127,15 @@ by the crash reporter.
### `crashReporter.getLastCrashReport()`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/23265
description: "Deprecated calling this method in the renderer process."
breaking-changes-header: deprecated-crashreporter-methods-in-the-renderer-process
```
-->
Returns [`CrashReport | null`](structures/crash-report.md) - The date and ID of the
last crash report. Only crash reports that have been uploaded will be returned;
even if a crash report is present on disk it will not be returned until it is
@@ -121,6 +146,15 @@ uploaded. In the case that there are no uploaded reports, `null` is returned.
### `crashReporter.getUploadedReports()`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/23265
description: "Deprecated calling this method in the renderer process."
breaking-changes-header: deprecated-crashreporter-methods-in-the-renderer-process
```
-->
Returns [`CrashReport[]`](structures/crash-report.md):
Returns all uploaded crash reports. Each report contains the date and uploaded
@@ -131,6 +165,15 @@ ID.
### `crashReporter.getUploadToServer()`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/23265
description: "Deprecated calling this method in the renderer process."
breaking-changes-header: deprecated-crashreporter-methods-in-the-renderer-process
```
-->
Returns `boolean` - Whether reports should be submitted to the server. Set through
the `start` method or `setUploadToServer`.
@@ -139,6 +182,15 @@ the `start` method or `setUploadToServer`.
### `crashReporter.setUploadToServer(uploadToServer)`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/23265
description: "Deprecated calling this method in the renderer process."
breaking-changes-header: deprecated-crashreporter-methods-in-the-renderer-process
```
-->
* `uploadToServer` boolean - Whether reports should be submitted to the server.
This would normally be controlled by user preferences. This has no effect if

View File

@@ -80,6 +80,17 @@ The `desktopCapturer` module has the following methods:
### `desktopCapturer.getSources(options)`
<!--
```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/2963
changes:
- pr-url: https://github.com/electron/electron/pull/16427
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
```
-->
* `options` Object
* `types` string[] - An array of strings that lists the types of desktop sources
to be captured, available types can be `screen` and `window`.
@@ -94,7 +105,7 @@ The `desktopCapturer` module has the following methods:
Returns `Promise<DesktopCapturerSource[]>` - Resolves with an array of [`DesktopCapturerSource`](structures/desktop-capturer-source.md) objects, each `DesktopCapturerSource` represents a screen or an individual window that can be captured.
> [!NOTE]
<!-- markdownlint-disable-next-line MD032 -->
> * Capturing audio requires `NSAudioCaptureUsageDescription` Info.plist key on macOS 14.2 Sonoma and higher - [read more](#macos-versions-142-or-higher).
> * Capturing the screen contents requires user consent on macOS 10.15 Catalina or higher, which can detected by [`systemPreferences.getMediaAccessStatus`][].
@@ -109,30 +120,41 @@ Returns `Promise<DesktopCapturerSource[]>` - Resolves with an array of [`Desktop
PipeWire supports a single capture for both screens and windows. If you request the window and screen type, the selected source will be returned as a window capture.
---
### macOS versions 14.2 or higher
### MacOS versions 14.2 or higher
`NSAudioCaptureUsageDescription` Info.plist key must be added in-order for audio to be captured by `desktopCapturer`. If instead you are running electron from another program like a terminal or IDE then that parent program must contain the Info.plist key.
`NSAudioCaptureUsageDescription` Info.plist key must be added in order for audio to be captured by
`desktopCapturer`. If instead you are running Electron from another program like a terminal or IDE
then that parent program must contain the Info.plist key.
This is in order to facillitate use of Apple's new [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps#Configure-the-sample-code-project) by Chromium.
> [!WARNING]
> Failure of `desktopCapturer` to start an audio stream due to `NSAudioCaptureUsageDescription` permission not present will still create a dead audio stream however no warnings or errors are displayed.
> Failure of `desktopCapturer` to start an audio stream due to `NSAudioCaptureUsageDescription`
> permission not present will still create a dead audio stream however no warnings or errors are
> displayed.
As of electron `v39.0.0-beta.4` Chromium [made Apple's new `CoreAudio Tap API` the default](https://source.chromium.org/chromium/chromium/src/+/ad17e8f8b93d5f34891b06085d373a668918255e) for desktop audio capture. There is no fallback to the older `Screen & System Audio Recording` permissions system even if [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps) stream creation fails.
As of Electron `v39.0.0-beta.4`, Chromium [made Apple's new `CoreAudio Tap API` the default](https://source.chromium.org/chromium/chromium/src/+/ad17e8f8b93d5f34891b06085d373a668918255e)
for desktop audio capture. There is no fallback to the older `Screen & System Audio Recording`
permissions system even if [CoreAudio Tap API](https://developer.apple.com/documentation/CoreAudio/capturing-system-audio-with-core-audio-taps) stream creation fails.
If you need to continue using `Screen & System Audio Recording` permissions for `desktopCapturer` on macOS versions 14.2 and later, you can apply a chromium feature flag to force use of that older permissions system:
If you need to continue using `Screen & System Audio Recording` permissions for `desktopCapturer`
on macOS versions 14.2 and later, you can apply a Chromium feature flag to force use of that older
permissions system:
```js
// main.js (right beneath your require/import statments)
app.commandLine.appendSwitch('disable-features', 'MacCatapLoopbackAudioForScreenShare')
```
---
### macOS versions 12.7.6 or lower
### MacOS versions 12.7.6 or lower
`navigator.mediaDevices.getUserMedia` does not work on macOS versions 12.7.6 and prior for audio
capture due to a fundamental limitation whereby apps that want to access the system's audio require
a [signed kernel extension](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html).
Chromium, and by extension Electron, does not provide this. Only in macOS 13 and onwards does Apple
provide APIs to capture desktop audio without the need for a signed kernel extension.
`navigator.mediaDevices.getUserMedia` does not work on macOS versions 12.7.6 and prior for audio capture due to a fundamental limitation whereby apps that want to access the system's audio require a [signed kernel extension](https://developer.apple.com/library/archive/documentation/Security/Conceptual/System_Integrity_Protection_Guide/KernelExtensions/KernelExtensions.html). Chromium, and by extension Electron, does not provide this. Only in macOS 13 and onwards does Apple provide APIs to capture desktop audio without the need for a signed kernel extension.
It is possible to circumvent this limitation by capturing system audio with another macOS app like [BlackHole](https://existential.audio/blackhole/) or [Soundflower](https://rogueamoeba.com/freebies/soundflower/) and passing it through a virtual audio input device. This virtual device can then be queried with `navigator.mediaDevices.getUserMedia`.
It is possible to circumvent this limitation by capturing system audio with another macOS app like
[BlackHole](https://existential.audio/blackhole/) or [Soundflower](https://rogueamoeba.com/freebies/soundflower/)
and passing it through a virtual audio input device. This virtual device can then be queried
with `navigator.mediaDevices.getUserMedia`.

View File

@@ -18,6 +18,13 @@ The `dialog` module has the following methods:
### `dialog.showOpenDialogSync([window, ]options)`
<!--
```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/16973
```
-->
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional)
@@ -90,6 +97,15 @@ dialog.showOpenDialogSync(mainWindow, {
### `dialog.showOpenDialog([window, ]options)`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/16973
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
```
-->
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional)
@@ -171,6 +187,13 @@ dialog.showOpenDialog(mainWindow, {
### `dialog.showSaveDialogSync([window, ]options)`
<!--
```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/17054
```
-->
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
@@ -202,6 +225,15 @@ The `filters` specifies an array of file types that can be displayed, see
### `dialog.showSaveDialog([window, ]options)`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/17054
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
```
-->
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
@@ -240,6 +272,13 @@ The `filters` specifies an array of file types that can be displayed, see
### `dialog.showMessageBoxSync([window, ]options)`
<!--
```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/17298
```
-->
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `message` string - Content of the message box.
@@ -283,6 +322,19 @@ If `window` is not shown dialog will not be attached to it. In such case it will
### `dialog.showMessageBox([window, ]options)`
<!--
```YAML history
changes:
- pr-url: https://github.com/electron/electron/pull/17298
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
- pr-url: https://github.com/electron/electron/pull/26102
description: "Added the `signal` option."
- pr-url: https://github.com/electron/electron/pull/30474
description: "Added the `textWidth` option."
```
-->
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `message` string - Content of the message box.
@@ -349,6 +401,17 @@ and no GUI dialog will appear.
### `dialog.showCertificateTrustDialog([window, ]options)` _macOS_ _Windows_
<!--
```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/9099
changes:
- pr-url: https://github.com/electron/electron/pull/17181
description: "This method now returns a Promise instead of using a callback function."
breaking-changes-header: api-changed-callback-based-versions-of-promisified-apis
```
-->
* `window` [BaseWindow](base-window.md) (optional)
* `options` Object
* `certificate` [Certificate](structures/certificate.md) - The certificate to trust/import.

View File

@@ -111,7 +111,8 @@ app.whenReady().then(() => {
Returns:
* `event` Event
* `details` Event\<\>
* `reason` _Windows_ string (optional) - The reason the notification was closed. This can be 'userCanceled', 'applicationHidden', or 'timedOut'.
Emitted when the notification is closed by manual intervention from the user.

View File

@@ -110,6 +110,8 @@ Returns [`Point`](structures/point.md)
The current absolute position of the mouse pointer.
Not supported on Wayland (Linux).
> [!NOTE]
> The return value is a DIP point, not a screen physical point.

View File

@@ -1485,6 +1485,11 @@ mainWindow.webContents.setWindowOpenHandler((details) => {
const browserView = new BrowserView(options)
mainWindow.addBrowserView(browserView)
browserView.setBounds({ x: 0, y: 0, width: 640, height: 480 })
// For `background-tab` disposition (e.g., when middle-clicking or ctrl/cmd-clicking a link),
// `options.webContents` is undefined because its creation can be deferred. So load the URL manually.
if (details.disposition === 'background-tab') {
browserView.webContents.loadURL(details.url)
}
return browserView.webContents
}
}
@@ -2235,6 +2240,16 @@ Returns `string` - The identifier of a WebContents stream. This identifier can b
with `navigator.mediaDevices.getUserMedia` using a `chromeMediaSource` of `tab`.
The identifier is restricted to the web contents that it is registered to and is only valid for 10 seconds.
#### `contents.getOrCreateDevToolsTargetId()`
Returns `string` - The Chrome DevTools Protocol
[TargetID](https://chromedevtools.github.io/devtools-protocol/tot/Target/#type-TargetID)
associated with this WebContents. This is the reverse of
[`webContents.fromDevToolsTargetId()`](#webcontentsfromdevtoolstargetidtargetid).
> [!NOTE]
> This method creates a new DevTools agent for this WebContents if one does not already exist.
#### `contents.getOSProcessId()`
Returns `Integer` - The operating system `pid` of the associated renderer

View File

@@ -21,24 +21,33 @@
### Step 1: Fork
Fork the project [on GitHub](https://github.com/electron/electron) and clone your fork
locally.
```sh
$ git clone git@github.com:username/electron.git
$ cd electron
$ git remote add upstream https://github.com/electron/electron.git
$ git fetch upstream
```
Fork Electron's [GitHub repository](https://github.com/electron/electron).
### Step 2: Build
Build steps and dependencies differ slightly depending on your operating system.
See these detailed guides on building Electron locally:
We recommend using [`@electron/build-tools`](https://github.com/electron/build-tools) to build
Electron itself.
* [Building on macOS](build-instructions-macos.md)
* [Building on Linux](build-instructions-linux.md)
* [Building on Windows](build-instructions-windows.md)
```sh
# Install build-tools package globally:
npm install -g @electron/build-tools
# Run the init script where you want to clone the project and point it to your fork:
e init --fork my-org/electron --bootstrap testing
```
This will create a new `electron` folder in your working directory and initialize the project.
Once the build completes, navigate to `electron/src/electron`, where your fork is actually cloned.
> [!IMPORTANT]
> Your Electron project has a complex folder structure with nested repositories.
> See the [Build Instructions](./build-instructions-gn.md) docs for detailed Build Tools
> usage instructions (e.g. how to sync dependencies or how to recompile the binary)
> and platform-specific notices.
There, you should have two `remote` URLs in git:
* `origin` will point to `electron/electron`
* `fork` will point to your fork (`my-org/electron`)
Once you've built the project locally, you're ready to start making changes!
@@ -48,7 +57,7 @@ To keep your development environment organized, create local branches to
hold your work. These should be branched directly off of the `main` branch.
```sh
$ git checkout -b my-branch -t upstream/main
git checkout -b my-branch
```
## Making Changes
@@ -60,7 +69,7 @@ changes to either the C/C++ code in the `shell/` folder,
the JavaScript code in the `lib/` folder, the documentation in `docs/api/`
or tests in the `spec/` folder.
Please be sure to run `npm run lint` from time to time on any code changes
Please be sure to run `yarn lint` from time to time on any code changes
to ensure that they follow the project's code style.
See [coding style](coding-style.md) for
@@ -75,8 +84,8 @@ across multiple commits. There is no limit to the number of commits in a
pull request.
```sh
$ git add my/changed/files
$ git commit
git add my/changed/files
git commit
```
Note that multiple commits get squashed when they are landed.
@@ -138,8 +147,8 @@ Once you have committed your changes, it is a good idea to use `git rebase`
(not `git merge`) to synchronize your work with the main repository.
```sh
$ git fetch upstream
$ git rebase upstream/main
git fetch origin
git rebase origin/main
```
This ensures that your working branch has the latest changes from `electron/electron`
@@ -156,7 +165,7 @@ Before submitting your changes in a pull request, always run the full
test suite. To run the tests:
```sh
$ npm run test
yarn test
```
Make sure the linter does not report any issues and that all tests pass.
@@ -165,7 +174,7 @@ Please do not submit patches that fail either check.
If you are updating tests and want to run a single spec to check it:
```sh
$ npm run test -match=menu
yarn test -match=menu
```
The above would only run spec modules matching `menu`, which is useful for
@@ -179,7 +188,7 @@ begin the process of opening a pull request by pushing your working branch
to your fork on GitHub.
```sh
$ git push origin my-branch
git push fork my-branch
```
### Step 9: Opening the Pull Request
@@ -203,9 +212,9 @@ branch, add a new commit with those changes, and push those to your fork.
GitHub will automatically update the pull request.
```sh
$ git add my/changed/files
$ git commit
$ git push origin my-branch
git add my/changed/files
git commit
git push fork my-branch
```
There are a number of more advanced mechanisms for managing commits using
@@ -213,8 +222,8 @@ There are a number of more advanced mechanisms for managing commits using
Feel free to post a comment in the pull request to ping reviewers if you are
awaiting an answer on something. If you encounter words or acronyms that
seem unfamiliar, refer to this
[glossary](https://sites.google.com/a/chromium.org/dev/glossary).
seem unfamiliar, refer to the
[Chromium glossary](https://sites.google.com/a/chromium.org/dev/glossary).
#### Approval and Request Changes Workflow

View File

@@ -12,6 +12,10 @@ To create a frameless window, set the [`BaseWindowContructorOptions`][] `frame`
```
On Wayland (Linux), frameless windows have GTK drop shadows and extended
resize boundaries by default. To create a fully frameless window with no
decorations, set `hasShadow: false` in the window constructor options.
## Transparent windows
![Transparent Window](../images/transparent-window.png)

View File

@@ -7,47 +7,7 @@ check out our [Electron Versioning](./electron-versioning.md) doc.
## Timeline
| Electron | Alpha | Beta | Stable | EOL | Chrome | Node | Supported |
| ------- | ----- | ------- | ------ | ------ | ---- | ---- | ---- |
| 40.0.0 | 2025-Oct-30 | 2025-Dec-03 | 2026-Jan-13 | 2026-Jun-30 | M144 | TBD | ✅ |
| 39.0.0 | 2025-Sep-04 | 2025-Oct-01 | 2025-Oct-28 | 2026-May-05 | M142 | v22.20 | ✅ |
| 38.0.0 | 2025-Jun-26 | 2025-Aug-06 | 2025-Sep-02 | 2026-Mar-10 | M140 | v22.18 | ✅ |
| 37.0.0 | 2025-May-01 | 2025-May-28 | 2025-Jun-24 | 2026-Jan-13 | M138 | v22.16 | ✅ |
| 36.0.0 | 2025-Mar-06 | 2025-Apr-02 | 2025-Apr-29 | 2025-Oct-28 | M136 | v22.14 | 🚫 |
| 35.0.0 | 2025-Jan-16 | 2025-Feb-05 | 2025-Mar-04 | 2025-Sep-02 | M134 | v22.14 | 🚫 |
| 34.0.0 | 2024-Oct-17 | 2024-Nov-13 | 2025-Jan-14 | 2025-Jun-24 | M132 | v20.18 | 🚫 |
| 33.0.0 | 2024-Aug-22 | 2024-Sep-18 | 2024-Oct-15 | 2025-Apr-29 | M130 | v20.18 | 🚫 |
| 32.0.0 | 2024-Jun-14 | 2024-Jul-24 | 2024-Aug-20 | 2025-Mar-04 | M128 | v20.16 | 🚫 |
| 31.0.0 | 2024-Apr-18 | 2024-May-15 | 2024-Jun-11 | 2025-Jan-14 | M126 | v20.14 | 🚫 |
| 30.0.0 | 2024-Feb-22 | 2024-Mar-20 | 2024-Apr-16 | 2024-Oct-15 | M124 | v20.11 | 🚫 |
| 29.0.0 | 2023-Dec-07 | 2024-Jan-24 | 2024-Feb-20 | 2024-Aug-20 | M122 | v20.9 | 🚫 |
| 28.0.0 | 2023-Oct-11 | 2023-Nov-06 | 2023-Dec-05 | 2024-Jun-11 | M120 | v18.18 | 🚫 |
| 27.0.0 | 2023-Aug-17 | 2023-Sep-13 | 2023-Oct-10 | 2024-Apr-16 | M118 | v18.17 | 🚫 |
| 26.0.0 | 2023-Jun-01 | 2023-Jun-27 | 2023-Aug-15 | 2024-Feb-20 | M116 | v18.16 | 🚫 |
| 25.0.0 | 2023-Apr-10 | 2023-May-02 | 2023-May-30 | 2023-Dec-05 | M114 | v18.15 | 🚫 |
| 24.0.0 | 2023-Feb-09 | 2023-Mar-07 | 2023-Apr-04 | 2023-Oct-10 | M112 | v18.14 | 🚫 |
| 23.0.0 | 2022-Dec-01 | 2023-Jan-10 | 2023-Feb-07 | 2023-Aug-15 | M110 | v18.12 | 🚫 |
| 22.0.0 | 2022-Sep-29 | 2022-Oct-25 | 2022-Nov-29 | 2023-Oct-10 | M108 | v16.17 | 🚫 |
| 21.0.0 | 2022-Aug-04 | 2022-Aug-30 | 2022-Sep-27 | 2023-Apr-04 | M106 | v16.16 | 🚫 |
| 20.0.0 | 2022-May-26 | 2022-Jun-21 | 2022-Aug-02 | 2023-Feb-07 | M104 | v16.15 | 🚫 |
| 19.0.0 | 2022-Mar-31 | 2022-Apr-26 | 2022-May-24 | 2022-Nov-29 | M102 | v16.14 | 🚫 |
| 18.0.0 | 2022-Feb-03 | 2022-Mar-03 | 2022-Mar-29 | 2022-Sep-27 | M100 | v16.13 | 🚫 |
| 17.0.0 | 2021-Nov-18 | 2022-Jan-06 | 2022-Feb-01 | 2022-Aug-02 | M98 | v16.13 | 🚫 |
| 16.0.0 | 2021-Sep-23 | 2021-Oct-20 | 2021-Nov-16 | 2022-May-24 | M96 | v16.9 | 🚫 |
| 15.0.0 | 2021-Jul-20 | 2021-Sep-01 | 2021-Sep-21 | 2022-May-24 | M94 | v16.5 | 🚫 |
| 14.0.0 | -- | 2021-May-27 | 2021-Aug-31 | 2022-Mar-29 | M93 | v14.17 | 🚫 |
| 13.0.0 | -- | 2021-Mar-04 | 2021-May-25 | 2022-Feb-01 | M91 | v14.16 | 🚫 |
| 12.0.0 | -- | 2020-Nov-19 | 2021-Mar-02 | 2021-Nov-16 | M89 | v14.16 | 🚫 |
| 11.0.0 | -- | 2020-Aug-27 | 2020-Nov-17 | 2021-Aug-31 | M87 | v12.18 | 🚫 |
| 10.0.0 | -- | 2020-May-21 | 2020-Aug-25 | 2021-May-25 | M85 | v12.16 | 🚫 |
| 9.0.0 | -- | 2020-Feb-06 | 2020-May-19 | 2021-Mar-02 | M83 | v12.14 | 🚫 |
| 8.0.0 | -- | 2019-Oct-24 | 2020-Feb-04 | 2020-Nov-17 | M80 | v12.13 | 🚫 |
| 7.0.0 | -- | 2019-Aug-01 | 2019-Oct-22 | 2020-Aug-25 | M78 | v12.8 | 🚫 |
| 6.0.0 | -- | 2019-Apr-25 | 2019-Jul-30 | 2020-May-19 | M76 | v12.14.0 | 🚫 |
| 5.0.0 | -- | 2019-Jan-22 | 2019-Apr-23 | 2020-Feb-04 | M73 | v12.0 | 🚫 |
| 4.0.0 | -- | 2018-Oct-11 | 2018-Dec-20 | 2019-Oct-22 | M69 | v10.11 | 🚫 |
| 3.0.0 | -- | 2018-Jun-21 | 2018-Sep-18 | 2019-Jul-30 | M66 | v10.2 | 🚫 |
| 2.0.0 | -- | 2018-Feb-21 | 2018-May-01 | 2019-Apr-23 | M61 | v8.9 | 🚫 |
[Electron's Release Schedule](https://releases.electronjs.org/schedule) lists a schedule of Electron major releases showing key milestones including alpha, beta, and stable release dates, as well as end-of-life dates and dependency versions.
:::info Official support dates may change

View File

@@ -50,7 +50,7 @@ sections.
In the main process, set an IPC listener on the `set-title` channel with the `ipcMain.on` API:
```js {6-10,22} title='main.js (Main Process)'
```js {7-11,23} title='main.js (Main Process)'
const { app, BrowserWindow, ipcMain } = require('electron')
const path = require('node:path')

View File

@@ -117,6 +117,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

@@ -125,8 +125,10 @@ if (packageJson.productName != null) {
app.name = `${packageJson.name}`.trim();
}
// Set application's desktop name.
app.setDesktopName(packageJson.desktopName || `${app.name}.desktop`);
// Set application's desktop name (Linux). These usually match the executable name,
// so use it as the default to ensure the app gets the correct icon in the taskbar and application switcher.
const desktopName = packageJson.desktopName || `${path.basename(process.execPath)}.desktop`;
app.setDesktopName(desktopName);
// Set v8 flags, deliberately lazy load so that apps that do not use this
// feature do not pay the price

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;
}
@@ -43,8 +43,8 @@ export function invokeInWebFrameMain<T> (sender: Electron.WebFrameMain, command:
const channel = `${command}_RESPONSE_${requestId}`;
const frameTreeNodeId = sender.frameTreeNodeId;
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
if (event.type === 'frame' && event.frameTreeNodeId !== frameTreeNodeId) {
console.error(`Reply to ${command} sent by unexpected WebFrameMain (${event.frameTreeNodeId})`);
if (event.type !== 'frame' || event.frameTreeNodeId !== frameTreeNodeId) {
console.error(`Reply to ${command} sent by unexpected sender`);
return;
}

View File

@@ -227,10 +227,9 @@ function validateHeader (name: any, value: any): void {
}
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
// eslint-disable-next-line n/no-deprecated-api
const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
const options: any = typeof optionsIn === 'string' ? new URL(optionsIn) : { ...optionsIn };
let urlStr: string = options.url;
let urlStr: string = options.url || options.href;
if (!urlStr) {
const urlObj: url.UrlObject = {};
@@ -260,8 +259,8 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
// an invalid request.
throw new TypeError('Request path contains unescaped characters');
}
// eslint-disable-next-line n/no-deprecated-api
const pathObj = url.parse(options.path || '/');
const pathObj = new URL(options.path || '/', 'http://localhost');
urlObj.pathname = pathObj.pathname;
urlObj.search = pathObj.search;
urlObj.hash = pathObj.hash;

View File

@@ -1232,6 +1232,8 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
// has filesystem caching.
overrideAPI(fs, 'copyFile');
overrideAPISync(fs, 'copyFileSync');
overrideAPI(fs, 'cp');
overrideAPISync(fs, 'cpSync');
overrideAPI(fs, 'open');
overrideAPISync(process, 'dlopen', 1);

View File

@@ -9,6 +9,7 @@ if ((globalThis as any).blinkfetch) {
const keys = ['fetch', 'Response', 'FormData', 'Request', 'Headers', 'EventSource'];
for (const key of keys) {
(globalThis as any)[key] = (globalThis as any)[`blink${key}`];
delete (globalThis as any)[`blink${key}`];
}
}

View File

@@ -23,11 +23,14 @@ export default contextBridge;
export const internalContextBridge = {
contextIsolationEnabled: process.contextIsolated,
tryOverrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => {
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true, true);
},
overrideGlobalValueFromIsolatedWorld: (keys: string[], value: any) => {
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false);
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, false, false);
},
overrideGlobalValueWithDynamicPropsFromIsolatedWorld: (keys: string[], value: any) => {
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true);
return binding._overrideGlobalValueFromIsolatedWorld(keys, value, true, false);
},
overrideGlobalPropertyFromIsolatedWorld: (keys: string[], getter: Function, setter?: Function) => {
return binding._overrideGlobalPropertyFromIsolatedWorld(keys, getter, setter || null);

View File

@@ -11,14 +11,12 @@ const { contextIsolationEnabled } = internalContextBridge;
* 1) Use menu API to show context menu.
*/
window.onload = function () {
if (window.InspectorFrontendHost) {
if (contextIsolationEnabled) {
internalContextBridge.overrideGlobalValueFromIsolatedWorld([
'InspectorFrontendHost', 'showContextMenuAtPoint'
], createMenu);
} else {
window.InspectorFrontendHost.showContextMenuAtPoint = createMenu;
}
if (contextIsolationEnabled) {
internalContextBridge.tryOverrideGlobalValueFromIsolatedWorld([
'InspectorFrontendHost', 'showContextMenuAtPoint'
], createMenu);
} else {
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
}
};

View File

@@ -22,6 +22,7 @@ if ((globalThis as any).blinkfetch) {
const keys = ['fetch', 'Response', 'FormData', 'Request', 'Headers', 'EventSource'];
for (const key of keys) {
(globalThis as any)[key] = (globalThis as any)[`blink${key}`];
delete (globalThis as any)[`blink${key}`];
}
}

View File

@@ -144,3 +144,6 @@ fix_linux_tray_id.patch
expose_gtk_ui_platform_field.patch
fix_os_crypt_async_cookie_encryption.patch
fix_update_dbus_signal_signature_for_xdg_globalshortcuts_portal.patch
fix_set_correct_app_id_on_linux.patch
fix_pass_trigger_for_global_shortcuts_on_wayland.patch
feat_plumb_node_integration_in_worker_through_workersettings.patch

View File

@@ -46,10 +46,10 @@ index 2fc3a991d89093ff9139eb09d74123197155caff..0862aa96c2a7b496338ac0593f84fcfa
# than here in :chrome_dll.
deps += [ "//chrome:packed_resources_integrity_header" ]
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index dbe8f32f53ff9dc8da619edc0f2c5316fcca75e5..0aae69570a538316cf66ee12249a27b0ebcfb048 100644
index 7d5a246787bc3cc3bcb883aa78121d3d3f124780..b5de35620bc636d5e1d0d5770d898f564843bcef 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7727,9 +7727,12 @@ test("unit_tests") {
@@ -7728,9 +7728,12 @@ test("unit_tests") {
"//chrome/notification_helper",
]
@@ -63,7 +63,7 @@ index dbe8f32f53ff9dc8da619edc0f2c5316fcca75e5..0aae69570a538316cf66ee12249a27b0
"//chrome//services/util_win:unit_tests",
"//chrome/app:chrome_dll_resources",
"//chrome/app:win_unit_tests",
@@ -8696,6 +8699,10 @@ test("unit_tests") {
@@ -8698,6 +8701,10 @@ test("unit_tests") {
"../browser/performance_manager/policies/background_tab_loading_policy_unittest.cc",
]
@@ -74,7 +74,7 @@ index dbe8f32f53ff9dc8da619edc0f2c5316fcca75e5..0aae69570a538316cf66ee12249a27b0
sources += [
# The importer code is not used on Android.
"../common/importer/firefox_importer_utils_unittest.cc",
@@ -8753,7 +8760,6 @@ test("unit_tests") {
@@ -8755,7 +8762,6 @@ test("unit_tests") {
# TODO(crbug.com/417513088): Maybe merge with the non-android `deps` declaration above?
deps += [
"../browser/screen_ai:screen_ai_install_state",

View File

@@ -9,10 +9,10 @@ potentially prevent a window from being created.
TODO(loc): this patch is currently broken.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index be7a71030dc5e30b3aa8945384c7a68ec7e49225..be203d9ed963adb7f452b737359d32f0d52978ca 100644
index 46368e70af175d8d0ab0fb5a36d258e48270371e..8d7be769a6c76650ae999338578215dcd324c199 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9989,6 +9989,7 @@ void RenderFrameHostImpl::CreateNewWindow(
@@ -9990,6 +9990,7 @@ void RenderFrameHostImpl::CreateNewWindow(
last_committed_origin_, params->window_container_type,
params->target_url, params->referrer.To<Referrer>(),
params->frame_name, params->disposition, *params->features,

View File

@@ -43,10 +43,10 @@ index 21d5ab99800c0830cc31ec4ebb24e3f05cd904d8..3f8f514519d6e4a0abe3690f5df35de8
// When the enterprise policy is not set, use finch/feature flag choice.
return base::FeatureList::IsEnabled(chrome_pdf::features::kPdfXfaSupport);
diff --git a/chrome/browser/pdf/pdf_extension_util.cc b/chrome/browser/pdf/pdf_extension_util.cc
index 6533640e786a3ef0c723e3252dfade2724c9e572..fa33a21b767a4f5976e3beee69736432f9650598 100644
index 83bc44f0c1928b9023efa54bfb57bed69d77484a..9c79f96931a0b2a05d98191ea8eb31a3a01818fc 100644
--- a/chrome/browser/pdf/pdf_extension_util.cc
+++ b/chrome/browser/pdf/pdf_extension_util.cc
@@ -257,10 +257,13 @@ bool IsPrintingEnabled(content::BrowserContext* context) {
@@ -259,10 +259,13 @@ bool IsPrintingEnabled(content::BrowserContext* context) {
#if BUILDFLAG(ENABLE_PDF_INK2)
bool IsPdfAnnotationsEnabledByPolicy(content::BrowserContext* context) {
@@ -60,7 +60,7 @@ index 6533640e786a3ef0c723e3252dfade2724c9e572..fa33a21b767a4f5976e3beee69736432
}
#endif // BUILDFLAG(ENABLE_PDF_INK2)
@@ -425,7 +428,7 @@ void DispatchShouldUpdateViewportEvent(content::RenderFrameHost* embedder_host,
@@ -459,7 +462,7 @@ void DispatchShouldUpdateViewportEvent(content::RenderFrameHost* embedder_host,
}
bool ShouldShowGlicSummarizeButton(content::BrowserContext* context) {

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 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0aa3183a0b 100644
index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..144788ceadea85c9d1fae12d1ba4dbc1fc7cd699 100644
--- a/chrome/browser/process_singleton_posix.cc
+++ b/chrome/browser/process_singleton_posix.cc
@@ -619,6 +619,7 @@ class ProcessSingleton::LinuxWatcher
@@ -106,22 +106,41 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
const size_t kMinMessageLength = kStartToken.length() + 4;
if (bytes_read_ < kMinMessageLength) {
buf_[bytes_read_] = 0;
@@ -745,10 +751,26 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
@@ -745,10 +751,45 @@ 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).subspan(2 + num_args),
+ std::string(1, kTokenDelimiter));
+ const auto adspan = base::as_byte_span(remaining_args).first(additional_data_size);
+ if (additional_data_size > remaining_args.size()) {
+ LOG(ERROR) << "additional_data_size exceeds payload length";
+ CleanupAndDeleteSelf();
+ return;
+ }
+ const auto adspan =
+ base::as_byte_span(remaining_args).first(additional_data_size);
+ additional_data.assign(adspan.begin(), adspan.end());
+ }
+
@@ -134,7 +153,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
fd_watch_controller_.reset();
// LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader
@@ -777,8 +799,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
@@ -777,8 +818,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
//
ProcessSingleton::ProcessSingleton(
const base::FilePath& user_data_dir,
@@ -145,7 +164,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
current_pid_(base::GetCurrentProcId()) {
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename);
@@ -899,7 +923,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
@@ -899,7 +942,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
sizeof(socket_timeout));
// Found another process, prepare our command line
@@ -155,7 +174,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
std::string to_send(kStartToken);
to_send.push_back(kTokenDelimiter);
@@ -909,11 +934,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
@@ -909,11 +953,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
to_send.append(current_dir.value());
const std::vector<std::string>& argv = cmd_line.argv();
@@ -178,10 +197,18 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
if (!WriteToSocket(socket.fd(), to_send)) {
// 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 ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f947e6bfc 100644
index ae659d84a5ae2f2e87ce288477506575f8d86839..274887d62ff8d008bb86815a11205fcaa5f2c2ff 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,
@@ -196,7 +223,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
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);
@@ -209,18 +236,20 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
+ 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;
@@ -238,7 +267,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
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,
@@ -250,7 +279,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
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;
}
@@ -260,10 +289,18 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
return PROCESS_NOTIFIED;
case NotifyChromeResult::kFailed:
diff --git a/chrome/browser/win/chrome_process_finder.cc b/chrome/browser/win/chrome_process_finder.cc
index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3e5b73942 100644
index 594f3bc08a4385c177fb488123cef79448e94850..28e5a18a19718b2e748ada6882341413a1ab0705 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());
}
@@ -274,7 +311,7 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
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.
@@ -286,14 +323,12 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
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

@@ -313,7 +313,7 @@ index 18f283e625101318ee14b50e6e765dfd1c9a1a44..44a3a55974c9e4b9e715574075f25661
auto DrawAsSinglePath = [&]() {
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
index d6016c8fda4ce7df4875f57dcd2e0321dddcb0fa..4f0e5e9fab2e62aff6c43b9714d5a29015b392b7 100644
index 70a7e2a5203d3cdddbad7eecca28d65945522fed..35751435ebe8205a5c9d73bed0422ccbe61ab8b4 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -214,6 +214,10 @@

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 8a558e1a1f84c3dbed07143680c9c088084051b4..8895e76170cd7e79a93477cd826dcd84fa102d81 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"
@@ -557,6 +558,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

@@ -13,10 +13,10 @@ messages in the legacy window handle layer.
These conditions are regularly hit with WCO-enabled windows on Windows.
diff --git a/content/browser/renderer_host/legacy_render_widget_host_win.cc b/content/browser/renderer_host/legacy_render_widget_host_win.cc
index 871bb720529690ef81fbcd27056393766b9525ff..471a5b3ecb184ef2bf23b0ec3066711925ddaa4b 100644
index c7794d6a969b407281db12acc027a933a4a82921..382d01ab2f0ad774f7c0d1dc214dd45b6998549a 100644
--- a/content/browser/renderer_host/legacy_render_widget_host_win.cc
+++ b/content/browser/renderer_host/legacy_render_widget_host_win.cc
@@ -371,12 +371,12 @@ LRESULT LegacyRenderWidgetHostHWND::OnKeyboardRange(UINT message,
@@ -377,12 +377,12 @@ LRESULT LegacyRenderWidgetHostHWND::OnKeyboardRange(UINT message,
LRESULT LegacyRenderWidgetHostHWND::OnMouseRange(UINT message,
WPARAM w_param,
LPARAM l_param) {
@@ -31,7 +31,7 @@ index 871bb720529690ef81fbcd27056393766b9525ff..471a5b3ecb184ef2bf23b0ec30667119
tme.hwndTrack = hwnd();
tme.dwHoverTime = 0;
TrackMouseEvent(&tme);
@@ -409,7 +409,10 @@ LRESULT LegacyRenderWidgetHostHWND::OnMouseRange(UINT message,
@@ -421,7 +421,10 @@ LRESULT LegacyRenderWidgetHostHWND::OnMouseRange(UINT message,
// the picture.
if (!msg_handled &&
(message >= WM_NCMOUSEMOVE && message <= WM_NCXBUTTONDBLCLK)) {

View File

@@ -0,0 +1,140 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mitchell Cohen <mitch.cohen@me.com>
Date: Sun, 1 Mar 2026 16:25:13 -0500
Subject: fix: pass trigger for global shortcuts on Wayland
Allows the global shortcut portal on Wayland to accept the trigger
values requested by Electron apps, instead of requiring user input.
This patch should be submitted upstream.
diff --git a/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener_linux.cc b/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener_linux.cc
index a5bb1271231bb092c5e35fcf0cc99f0a18164147..1f02bf54fc6a3c0feb37b51dd6c9be9615073bcf 100644
--- a/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener_linux.cc
+++ b/ui/base/accelerators/global_accelerator_listener/global_accelerator_listener_linux.cc
@@ -15,6 +15,7 @@
#include "base/logging.h"
#include "base/nix/xdg_util.h"
#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "components/dbus/thread_linux/dbus_thread_linux.h"
#include "components/dbus/xdg/portal.h"
@@ -49,6 +50,91 @@ std::string GetShortcutPrefix(const std::string& accelerator_group_id,
.substr(0, 32);
}
+// Converts a ui::Accelerator to an XDG shortcut string:
+// https://specifications.freedesktop.org/shortcuts-spec/latest/.
+std::string AcceleratorToXdgTrigger(const ui::Accelerator& accelerator) {
+ std::string trigger;
+
+ if (accelerator.IsCtrlDown()) {
+ trigger += "CTRL+";
+ }
+ if (accelerator.IsAltDown()) {
+ trigger += "ALT+";
+ }
+ if (accelerator.IsShiftDown()) {
+ trigger += "SHIFT+";
+ }
+ if (accelerator.IsCmdDown()) {
+ trigger += "LOGO+";
+ }
+
+ ui::KeyboardCode key = accelerator.key_code();
+ if (key >= ui::VKEY_A && key <= ui::VKEY_Z) {
+ trigger += base::ToLowerASCII(static_cast<char>(key));
+ } else if (key >= ui::VKEY_0 && key <= ui::VKEY_9) {
+ trigger += static_cast<char>(key);
+ } else if (key >= ui::VKEY_F1 && key <= ui::VKEY_F24) {
+ trigger += "F" + base::NumberToString(1 + (key - ui::VKEY_F1));
+ } else {
+ switch (key) {
+ case ui::VKEY_SPACE:
+ trigger += "space";
+ break;
+ case ui::VKEY_RETURN:
+ trigger += "Return";
+ break;
+ case ui::VKEY_TAB:
+ trigger += "Tab";
+ break;
+ case ui::VKEY_ESCAPE:
+ trigger += "Escape";
+ break;
+ case ui::VKEY_BACK:
+ trigger += "BackSpace";
+ break;
+ case ui::VKEY_DELETE:
+ trigger += "Delete";
+ break;
+ case ui::VKEY_INSERT:
+ trigger += "Insert";
+ break;
+ case ui::VKEY_HOME:
+ trigger += "Home";
+ break;
+ case ui::VKEY_END:
+ trigger += "End";
+ break;
+ case ui::VKEY_PRIOR:
+ trigger += "Page_Up";
+ break;
+ case ui::VKEY_NEXT:
+ trigger += "Page_Down";
+ break;
+ case ui::VKEY_UP:
+ trigger += "Up";
+ break;
+ case ui::VKEY_DOWN:
+ trigger += "Down";
+ break;
+ case ui::VKEY_LEFT:
+ trigger += "Left";
+ break;
+ case ui::VKEY_RIGHT:
+ trigger += "Right";
+ break;
+ case ui::VKEY_OEM_COMMA:
+ trigger += "comma";
+ break;
+ case ui::VKEY_OEM_PERIOD:
+ trigger += "period";
+ break;
+ default:
+ return "";
+ }
+ }
+ return trigger;
+}
+
} // namespace
GlobalAcceleratorListenerLinux::GlobalAcceleratorListenerLinux(
@@ -253,6 +339,12 @@ void GlobalAcceleratorListenerLinux::BindShortcuts(DbusShortcuts old_shortcuts,
new_props["description"] =
dbus_utils::Variant::Wrap<"s">(std::move(*description));
}
+ auto preferred_trigger =
+ TakeFromDict<std::string>(properties, "preferred_trigger");
+ if (preferred_trigger) {
+ new_props["preferred_trigger"] =
+ dbus_utils::Variant::Wrap<"s">(std::move(*preferred_trigger));
+ }
shortcuts.emplace_back(id, std::move(new_props));
}
@@ -260,6 +352,12 @@ void GlobalAcceleratorListenerLinux::BindShortcuts(DbusShortcuts old_shortcuts,
dbus_xdg::Dictionary props;
props["description"] = dbus_utils::Variant::Wrap<"s">(
base::UTF16ToUTF8(bound_cmd.command.description()));
+ std::string trigger =
+ AcceleratorToXdgTrigger(bound_cmd.command.accelerator());
+ if (!trigger.empty()) {
+ props["preferred_trigger"] =
+ dbus_utils::Variant::Wrap<"s">(std::move(trigger));
+ }
shortcuts.emplace_back(modified_id, std::move(props));
}

View File

@@ -0,0 +1,70 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Mitchell Cohen <mitch.cohen@me.com>
Date: Sun, 1 Mar 2026 16:22:00 -0500
Subject: fix: set correct app id on Linux
Sets the Electron app's actual XDG app ID and DBus path instead of org.chromium.Chromium..
This avoids conflicts with portals, e.g. the global shortcut portal.
diff --git a/base/version_info/nix/version_extra_utils.cc b/base/version_info/nix/version_extra_utils.cc
index e48fbf29760fb0b6d759a82132a6693db4d09929..f6b658d01e0ddf31e8dc66b0922444ad16747ad0 100644
--- a/base/version_info/nix/version_extra_utils.cc
+++ b/base/version_info/nix/version_extra_utils.cc
@@ -10,8 +10,29 @@
#include "base/containers/fixed_flat_map.h"
#include "base/environment.h"
#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
#include "build/branding_buildflags.h"
+namespace {
+
+constexpr std::string_view kDesktopSuffix = ".desktop";
+std::optional<std::string> GetChromeDesktopBaseName(base::Environment& env) {
+ auto desktop = env.GetVar("CHROME_DESKTOP");
+ if (!desktop.has_value() || desktop->empty()) {
+ return std::nullopt;
+ }
+ std::string_view name = *desktop;
+ if (name.ends_with(kDesktopSuffix)) {
+ name.remove_suffix(kDesktopSuffix.size());
+ }
+ if (name.empty()) {
+ return std::nullopt;
+ }
+ return std::string(name);
+}
+
+} // namespace
+
namespace version_info::nix {
version_info::Channel GetChannel(base::Environment& env) {
@@ -36,6 +57,10 @@ bool IsExtendedStable(base::Environment& env) {
}
std::string GetAppName(base::Environment& env) {
+ if (auto name = GetChromeDesktopBaseName(env)) {
+ return *name;
+ }
+
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
static constexpr std::string_view kAppName = "com.google.Chrome";
#else
@@ -61,6 +86,16 @@ std::string GetAppName(base::Environment& env) {
}
std::string GetSessionNamePrefix(base::Environment& env) {
+ if (auto name = GetChromeDesktopBaseName(env)) {
+ // DBus object paths have stricter requirements than names.
+ for (char& c : *name) {
+ if (!base::IsAsciiAlphaNumeric(c) && c != '_') {
+ c = '_';
+ }
+ }
+ return base::ToLowerASCII(*name);
+ }
+
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
static constexpr std::string_view kSessionNamePrefix = "chrome";
#else

View File

@@ -10,10 +10,10 @@ on Windows. We should refactor our code so that this patch isn't
necessary.
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
index 4836e5296231f4f40ccddf8b58849bd95b523b5f..ce4379d1db68a9e7d42df1e4be15060b250425bd 100644
index eecdbe8a279ef1a7d9aed4f5496e871d54092e0f..16ff3ffbe8300534cef76f857284ef92ad0a88f6 100644
--- a/testing/variations/fieldtrial_testing_config.json
+++ b/testing/variations/fieldtrial_testing_config.json
@@ -27062,6 +27062,21 @@
@@ -27059,6 +27059,21 @@
]
}
],

View File

@@ -15,10 +15,10 @@ Note that we also need to manually update embedder's
`api::WebContents::IsFullscreenForTabOrPending` value.
diff --git a/content/browser/renderer_host/render_frame_host_impl.cc b/content/browser/renderer_host/render_frame_host_impl.cc
index be203d9ed963adb7f452b737359d32f0d52978ca..c63ee00ad715b96d9c748a657e32463058008894 100644
index 8d7be769a6c76650ae999338578215dcd324c199..3e8021289c00ec6b15457b17173dfed386eac2fe 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9096,6 +9096,17 @@ void RenderFrameHostImpl::EnterFullscreen(
@@ -9097,6 +9097,17 @@ void RenderFrameHostImpl::EnterFullscreen(
}
}

View File

@@ -1 +1,2 @@
chore_expose_ui_to_allow_electron_to_set_dock_side.patch
fix_prefer_browser_runtime_over_node_in_hostruntime_detection.patch

View File

@@ -0,0 +1,36 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Shelley Vohr <shelley.vohr@gmail.com>
Date: Thu, 12 Mar 2026 17:03:29 +0100
Subject: fix: prefer browser runtime over node in HostRuntime detection
In Electron, the `process` global is available in renderer processes,
including the DevTools renderer. This causes the IS_NODE check to pass,
leading DevTools to attempt importing the Node.js platform runtime
(which uses `node:worker_threads`). However, DevTools Web Workers
running under the `devtools://` protocol don't have access to Node.js
built-in modules, resulting in a failed dynamic import.
Fix by checking IS_BROWSER first, since DevTools always runs in a
browser-like environment. The Node.js runtime is only needed when
DevTools runs under pure Node.js (e.g., CLI tooling or testing).
diff --git a/front_end/core/platform/HostRuntime.ts b/front_end/core/platform/HostRuntime.ts
index 91adba7c966a9c4c0e5315d2cfee07f8f622b731..16822b8d4ea74a4ffd6870e5e95948d75918f5d2 100644
--- a/front_end/core/platform/HostRuntime.ts
+++ b/front_end/core/platform/HostRuntime.ts
@@ -14,12 +14,12 @@ export const IS_BROWSER =
typeof window !== 'undefined' || (typeof self !== 'undefined' && typeof self.postMessage === 'function');
export const HOST_RUNTIME = await (async(): Promise<Api.HostRuntime.HostRuntime> => {
- if (IS_NODE) {
- return (await import('./node/node.js')).HostRuntime.HOST_RUNTIME;
- }
if (IS_BROWSER) {
return (await import('./browser/browser.js')).HostRuntime.HOST_RUNTIME;
}
+ if (IS_NODE) {
+ return (await import('./node/node.js')).HostRuntime.HOST_RUNTIME;
+ }
throw new Error('Unknown runtime!');
})();

View File

@@ -17,7 +17,7 @@ Upstreams:
- https://github.com/nodejs/node/pull/39136
diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
index 461819ce0fa732048e4365c40a86ef55d984c35f..fa55c980a9c4f373723a867fd41276d67b0b9413 100644
index 461819ce0fa732048e4365c40a86ef55d984c35f..f1c85e94cf526d0255f47c003664680d26413ec3 100644
--- a/deps/ncrypto/ncrypto.cc
+++ b/deps/ncrypto/ncrypto.cc
@@ -11,6 +11,7 @@
@@ -28,38 +28,6 @@ index 461819ce0fa732048e4365c40a86ef55d984c35f..fa55c980a9c4f373723a867fd41276d6
#if OPENSSL_VERSION_MAJOR >= 3
#include <openssl/core_names.h>
#include <openssl/params.h>
@@ -1130,7 +1131,9 @@ int64_t X509View::getValidToTime() const {
return tp;
#else
struct tm tp;
- ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp);
+#ifndef OPENSSL_IS_BORINGSSL
+ ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp);
+#endif
return PortableTimeGM(&tp);
#endif
}
@@ -1142,7 +1145,9 @@ int64_t X509View::getValidFromTime() const {
return tp;
#else
struct tm tp;
+#ifndef OPENSSL_IS_BORINGSSL
ASN1_TIME_to_tm(X509_get0_notBefore(cert_), &tp);
+#endif
return PortableTimeGM(&tp);
#endif
}
@@ -2886,10 +2891,6 @@ std::optional<uint32_t> SSLPointer::verifyPeerCertificate() const {
const char* SSLPointer::getClientHelloAlpn() const {
if (ssl_ == nullptr) return {};
#ifndef OPENSSL_IS_BORINGSSL
- const unsigned char* buf;
- size_t len;
- size_t rem;
-
if (!SSL_client_hello_get0_ext(
get(),
TLSEXT_TYPE_application_layer_protocol_negotiation,
@@ -3090,9 +3091,11 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm);
const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap);
const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap);

View File

@@ -9,4 +9,4 @@ refactor_use_non-deprecated_nskeyedarchiver_apis.patch
chore_turn_off_launchapplicationaturl_deprecation_errors_in_squirrel.patch
fix_crash_when_process_to_extract_zip_cannot_be_launched.patch
use_uttype_class_instead_of_deprecated_uttypeconformsto.patch
fix_clean_up_old_staged_updates_before_downloading_new_update.patch
fix_clean_up_orphaned_staged_updates_before_downloading_new_update.patch

View File

@@ -1,64 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andy Locascio <loc@anthropic.com>
Date: Tue, 6 Jan 2026 08:23:03 -0800
Subject: fix: clean up old staged updates before downloading new update
When checkForUpdates() is called while an update is already staged,
Squirrel creates a new temporary directory for the download without
cleaning up the old one. This can lead to significant disk usage if
the app keeps checking for updates without restarting.
This change adds a force parameter to pruneUpdateDirectories that
bypasses the AwaitingRelaunch state check. This is called before
creating a new temp directory, ensuring old staged updates are
cleaned up when a new download starts.
diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m
index d156616e81e6f25a3bded30e6216b8fc311f31bc..6cd4346bf43b191147aff819cb93387e71275a46 100644
--- a/Squirrel/SQRLUpdater.m
+++ b/Squirrel/SQRLUpdater.m
@@ -543,11 +543,17 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL
#pragma mark File Management
- (RACSignal *)uniqueTemporaryDirectoryForUpdate {
- return [[[RACSignal
+ // Clean up any old staged update directories before creating a new one.
+ // This prevents disk usage from growing when checkForUpdates() is called
+ // multiple times without the app restarting.
+ return [[[[[self
+ pruneUpdateDirectoriesWithForce:YES]
+ ignoreValues]
+ concat:[RACSignal
defer:^{
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
return [directoryManager storageURL];
- }]
+ }]]
flattenMap:^(NSURL *storageURL) {
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
@@ -643,7 +649,7 @@ - (BOOL)isRunningOnReadOnlyVolume {
- (RACSignal *)performHousekeeping {
return [[RACSignal
- merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]]
+ merge:@[ [self pruneUpdateDirectoriesWithForce:NO], [self truncateLogs] ]]
catch:^(NSError *error) {
NSLog(@"Error doing housekeeping: %@", error);
return [RACSignal empty];
@@ -658,11 +664,12 @@ - (RACSignal *)performHousekeeping {
///
/// Sends each removed directory then completes, or errors, on an unspecified
/// thread.
-- (RACSignal *)pruneUpdateDirectories {
+- (RACSignal *)pruneUpdateDirectoriesWithForce:(BOOL)force {
return [[[RACSignal
defer:^{
- // If we already have updates downloaded we don't wanna prune them.
- if (self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
+ // If we already have updates downloaded we don't wanna prune them,
+ // unless force is YES (used when starting a new download).
+ if (!force && self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
return [directoryManager storageURL];

View File

@@ -0,0 +1,130 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Andy Locascio <loc@anthropic.com>
Date: Tue, 6 Jan 2026 08:23:03 -0800
Subject: fix: clean up orphaned staged updates before downloading new update
When checkForUpdates() is called while an update is already staged,
Squirrel creates a new temporary directory for the download without
cleaning up the old one. This can lead to significant disk usage if
the app keeps checking for updates without restarting.
This change adds a pruneOrphanedUpdateDirectories step before creating
a new temp directory. Unlike a blanket prune, this reads the current
ShipItState.plist and preserves the directory it references, deleting
only truly orphaned update directories. This keeps the on-disk
footprint bounded (at most 2 dirs) while ensuring quitAndInstall
remains safe to call even when a new check is in progress.
Refs https://github.com/electron/electron/issues/50200
diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m
index d156616e81e6f25a3bded30e6216b8fc311f31bc..41856e5754228d33982db72f97f2ff241615a357 100644
--- a/Squirrel/SQRLUpdater.m
+++ b/Squirrel/SQRLUpdater.m
@@ -543,11 +543,19 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL
#pragma mark File Management
- (RACSignal *)uniqueTemporaryDirectoryForUpdate {
- return [[[RACSignal
+ // Clean up any orphaned update directories before creating a new one.
+ // This prevents disk usage from growing when checkForUpdates() is called
+ // multiple times without the app restarting. The currently staged update
+ // (referenced by ShipItState.plist) is always preserved so quitAndInstall
+ // remains safe to call while a new check is in progress.
+ return [[[[[self
+ pruneOrphanedUpdateDirectories]
+ ignoreValues]
+ concat:[RACSignal
defer:^{
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
return [directoryManager storageURL];
- }]
+ }]]
flattenMap:^(NSURL *storageURL) {
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
@@ -668,25 +676,68 @@ - (RACSignal *)pruneUpdateDirectories {
return [directoryManager storageURL];
}]
flattenMap:^(NSURL *storageURL) {
- NSFileManager *manager = [[NSFileManager alloc] init];
- NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
- NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
- return YES;
- }];
+ return [self removeUpdateDirectoriesInStorageURL:storageURL excludingURL:nil];
+ }]
+ setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
+}
- return [[enumerator.rac_sequence.signal
- filter:^(NSURL *enumeratedURL) {
- NSString *name = enumeratedURL.lastPathComponent;
- return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
- }]
- doNext:^(NSURL *directoryURL) {
- NSError *error = nil;
- if (![manager removeItemAtURL:directoryURL error:&error]) {
- NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
- }
+/// Lazily removes orphaned temporary directories upon subscription, always
+/// preserving the directory currently referenced by ShipItState.plist so that
+/// quitAndInstall remains safe to call mid-check.
+///
+/// Safe to call in any state. Sends each removed directory then completes on
+/// an unspecified thread. Errors reading the staged request are swallowed
+/// (treated as "nothing staged").
+- (RACSignal *)pruneOrphanedUpdateDirectories {
+ return [[[[[SQRLShipItRequest
+ readUsingURL:self.shipItStateURL]
+ map:^(SQRLShipItRequest *request) {
+ // The request holds the URL to the staged .app bundle; its parent
+ // is the update.XXXXXXX directory we must preserve.
+ return [request.updateBundleURL URLByDeletingLastPathComponent];
+ }]
+ catch:^(NSError *error) {
+ // No staged request (or unreadable) — nothing to preserve.
+ return [RACSignal return:nil];
+ }]
+ flattenMap:^(NSURL *stagedDirectoryURL) {
+ SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
+ return [[directoryManager storageURL]
+ flattenMap:^(NSURL *storageURL) {
+ return [self removeUpdateDirectoriesInStorageURL:storageURL excludingURL:stagedDirectoryURL];
}];
}]
- setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
+ setNameWithFormat:@"%@ -pruneOrphanedUpdateDirectories", self];
+}
+
+/// Shared enumerate-and-delete logic for update temp directories.
+///
+/// storageURL - The Squirrel storage root to enumerate. Must not be nil.
+/// excludedURL - Directory to skip (compared by standardized path). May be nil.
+- (RACSignal *)removeUpdateDirectoriesInStorageURL:(NSURL *)storageURL excludingURL:(NSURL *)excludedURL {
+ NSParameterAssert(storageURL != nil);
+
+ NSFileManager *manager = [[NSFileManager alloc] init];
+ NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
+ NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
+ return YES;
+ }];
+
+ NSString *excludedPath = excludedURL.URLByStandardizingPath.path;
+
+ return [[enumerator.rac_sequence.signal
+ filter:^(NSURL *enumeratedURL) {
+ NSString *name = enumeratedURL.lastPathComponent;
+ if (![name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix]) return NO;
+ if (excludedPath != nil && [enumeratedURL.URLByStandardizingPath.path isEqualToString:excludedPath]) return NO;
+ return YES;
+ }]
+ doNext:^(NSURL *directoryURL) {
+ NSError *error = nil;
+ if (![manager removeItemAtURL:directoryURL error:&error]) {
+ NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
+ }
+ }];
}

View File

@@ -0,0 +1,21 @@
#!/bin/sh
# Removes the codesigning keychain created by generate-identity.sh.
# Safe to run even if generate-identity.sh was never run (each step
# is guarded).
set -eo pipefail
KEYCHAIN="electron-codesign.keychain-db"
# delete-keychain also removes it from the search list
if security list-keychains -d user | grep -q "$KEYCHAIN"; then
security delete-keychain "$KEYCHAIN"
echo "Deleted keychain: $KEYCHAIN"
else
echo "Keychain not found, nothing to delete"
fi
# Clean up working directory
rm -rf "$(dirname $0)"/.working
echo "Cleanup complete"

View File

@@ -3,6 +3,8 @@
set -eo pipefail
dir="$(dirname $0)"/.working
KEYCHAIN="electron-codesign.keychain-db"
KEYCHAIN_TEMP="$(openssl rand -hex 12)"
cleanup() {
rm -rf "$dir"
@@ -18,30 +20,16 @@ mkdir -p "$dir"
# Generate Certs
openssl req -new -newkey rsa:2048 -x509 -days 7300 -nodes -config "$(dirname $0)"/codesign.cnf -extensions extended -batch -out "$dir"/certificate.cer -keyout "$dir"/certificate.key
sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain "$dir"/certificate.cer
sudo security import "$dir"/certificate.key -A -k /Library/Keychains/System.keychain
# restart(reload) taskgated daemon
sudo pkill -f /usr/libexec/taskgated
# macOS 15+ blocks modifications to the system keychain via SIP/TCC,
# so we use a custom user-scoped keychain instead.
# Refs https://github.com/electron/electron/issues/48182
security create-keychain -p "$KEYCHAIN_TEMP" "$KEYCHAIN"
security set-keychain-settings -t 3600 -u "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_TEMP" "$KEYCHAIN"
# need once
sudo security authorizationdb write system.privilege.taskport allow
# need once
DevToolsSecurity -enable
security list-keychains -d user -s "$KEYCHAIN" $(security list-keychains -d user | tr -d '"')
security import "$dir"/certificate.cer -k "$KEYCHAIN" -T /usr/bin/codesign
security import "$dir"/certificate.key -k "$KEYCHAIN" -T /usr/bin/codesign -A
# openssl req -newkey rsa:2048 -nodes -keyout "$dir"/private.pem -x509 -days 1 -out "$dir"/certificate.pem -extensions extended -config "$(dirname $0)"/codesign.cnf
# openssl x509 -inform PEM -in "$dir"/certificate.pem -outform DER -out "$dir"/certificate.cer
# openssl x509 -pubkey -noout -in "$dir"/certificate.pem > "$dir"/public.key
# rm -f "$dir"/certificate.pem
# Import Certs
# security import "$dir"/certificate.cer -k $KEY_CHAIN
# security import "$dir"/private.pem -k $KEY_CHAIN
# security import "$dir"/public.key -k $KEY_CHAIN
# Generate Trust Settings
# TODO: Remove NPX
npm_config_yes=true npx ts-node "$(dirname $0)"/gen-trust.ts "$dir"/certificate.cer "$dir"/trust.xml
# Import Trust Settings
sudo security trust-settings-import -d "$dir/trust.xml"
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_TEMP" "$KEYCHAIN"

View File

@@ -2,7 +2,7 @@
set -e
valid_certs=$(security find-identity -p codesigning -v)
valid_certs=$(security find-identity -p codesigning)
if [[ $valid_certs == *"1)"* ]]; then
first_valid_cert=$(echo $valid_certs | sed 's/ \".*//' | sed 's/.* //')
echo $first_valid_cert

View File

@@ -128,6 +128,11 @@ def format_patch(repo, since):
os.path.dirname(os.path.realpath(__file__)),
'electron.gitattributes',
),
# Pin rename/copy detection to git's default so that patch output is
# deterministic regardless of local or system-level diff.renames config
# (e.g. 'copies', which would encode similar new files as copies).
'-c',
'diff.renames=true',
# Ensure it is not possible to match anything
# Disabled for now as we have consistent chunk headers
# '-c',

View File

@@ -14,6 +14,7 @@ const args = minimist(process.argv.slice(2), {
const BASE = path.resolve(__dirname, '../..');
const ROOT_PACKAGE_JSON = path.resolve(BASE, 'package.json');
const NODE_DIR = path.resolve(BASE, 'third_party', 'electron_node');
const JUNIT_DIR = args.jUnitDir ? path.resolve(args.jUnitDir) : null;
const TAP_FILE_NAME = 'test.tap';
@@ -38,6 +39,18 @@ const defaultOptions = [
'-J'
];
// The root package.json is ESM, which breaks the test runner.
// Temporarily change it to CommonJS while running the tests, then
// change it back when done.
const resetPackageJson = ({ useESM }) => {
// This won't always exist in CI.
if (!fs.existsSync(ROOT_PACKAGE_JSON)) { return; }
const packageJson = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf-8'));
packageJson.type = useESM ? 'module' : 'commonjs';
fs.writeFileSync(ROOT_PACKAGE_JSON, JSON.stringify(packageJson, null, 2) + '\n');
};
const getCustomOptions = () => {
let customOptions = ['tools/test.py'];
@@ -79,6 +92,8 @@ async function main () {
const options = args.default ? defaultOptions : getCustomOptions();
resetPackageJson({ useESM: false });
const testChild = cp.spawn('python3', options, {
env: {
...process.env,
@@ -88,7 +103,10 @@ async function main () {
cwd: NODE_DIR,
stdio: 'inherit'
});
testChild.on('exit', (testCode) => {
resetPackageJson({ useESM: true });
if (JUNIT_DIR) {
fs.mkdirSync(JUNIT_DIR);
const converterStream = require('tap-xunit')();

View File

@@ -144,7 +144,9 @@ bool GlobalShortcut::Register(const ui::Accelerator& accelerator,
extensions::Command::AcceleratorToString(accelerator);
ui::CommandMap commands;
extensions::Command command(
command_str, base::UTF8ToUTF16("Electron shortcut " + command_str),
command_str,
base::UTF8ToUTF16(electron::Browser::Get()->GetName() +
" shortcut: " + command_str),
/*accelerator=*/std::string(), /*global=*/true);
command.set_accelerator(accelerator);
commands[command_str] = command;

View File

@@ -189,8 +189,23 @@ void Notification::NotificationFailed(const std::string& error) {
void Notification::NotificationDestroyed() {}
void Notification::NotificationClosed() {
Emit("close");
void Notification::NotificationClosed(const std::string& reason) {
if (reason.empty()) {
Emit("close");
} else {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
gin_helper::internal::Event* event =
gin_helper::internal::Event::New(isolate);
v8::Local<v8::Object> event_object =
event->GetWrapper(isolate).ToLocalChecked();
gin_helper::Dictionary dict(isolate, event_object);
dict.Set("reason", reason);
EmitWithoutEvent("close", event_object);
}
}
void Notification::Close() {

View File

@@ -50,7 +50,7 @@ class Notification final : public gin_helper::DeprecatedWrappable<Notification>,
void NotificationReplied(const std::string& reply) override;
void NotificationDisplayed() override;
void NotificationDestroyed() override;
void NotificationClosed() override;
void NotificationClosed(const std::string& reason) override;
void NotificationFailed(const std::string& error) override;
// gin_helper::Wrappable

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

@@ -32,10 +32,6 @@
#include "shell/browser/linux/x11_util.h"
#endif
#if defined(USE_OZONE)
#include "ui/ozone/public/ozone_platform.h"
#endif
namespace electron::api {
gin::DeprecatedWrapperInfo Screen::kWrapperInfo = {gin::kEmbedderNativeGin};
@@ -81,16 +77,9 @@ Screen::~Screen() {
}
gfx::Point Screen::GetCursorScreenPoint(v8::Isolate* isolate) {
#if defined(USE_OZONE)
// Wayland will crash unless a window is created prior to calling
// GetCursorScreenPoint.
if (!ui::OzonePlatform::IsInitialized()) {
gin_helper::ErrorThrower thrower(isolate);
thrower.ThrowError(
"screen.getCursorScreenPoint() cannot be called before a window has "
"been created.");
#if BUILDFLAG(IS_LINUX)
if (x11_util::IsWayland())
return {};
}
#endif
return screen_->GetCursorScreenPoint();
}

View File

@@ -50,6 +50,7 @@
#include "content/public/browser/context_menu_params.h"
#include "content/public/browser/desktop_media_id.h"
#include "content/public/browser/desktop_streams_registry.h"
#include "content/public/browser/devtools_agent_host.h"
#include "content/public/browser/download_request_utils.h"
#include "content/public/browser/favicon_status.h"
#include "content/public/browser/file_select_listener.h"
@@ -97,6 +98,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"
@@ -427,6 +429,7 @@ namespace {
// Global toggle for disabling draggable regions checks.
bool g_disable_draggable_regions = false;
#if BUILDFLAG(ENABLE_PRINTING)
// Constants we use for printing.
constexpr char kFrom[] = "from";
constexpr char kTo[] = "to";
@@ -456,6 +459,7 @@ constexpr char kFooterTemplate[] = "footerTemplate";
constexpr char kPreferCSSPageSize[] = "preferCSSPageSize";
constexpr char kGenerateTaggedPDF[] = "generateTaggedPDF";
constexpr char kGenerateDocumentOutline[] = "generateDocumentOutline";
#endif // BUILDFLAG(ENABLE_PRINTING)
constexpr std::string_view CursorTypeToString(
ui::mojom::CursorType cursor_type) {
@@ -1106,6 +1110,13 @@ 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);
@@ -1301,10 +1312,11 @@ void WebContents::MaybeOverrideCreateParamsForNewWindow(
dict.Get(options::kOffscreen, &is_offscreen) && is_offscreen);
if (is_offscreen) {
// Use a no-op callback here. The real OnPaint callback will be bound
// to the child WebContents in AddNewContents via SetCallback().
auto* view = new OffScreenWebContentsView(
false, offscreen_use_shared_texture_,
offscreen_shared_texture_pixel_format_,
base::BindRepeating(&WebContents::OnPaint, base::Unretained(this)));
offscreen_shared_texture_pixel_format_, base::DoNothing());
create_params->view = view;
create_params->delegate_view = view;
}
@@ -1332,6 +1344,15 @@ content::WebContents* WebContents::AddNewContents(
v8::HandleScope handle_scope(isolate);
auto api_web_contents = CreateAndTake(isolate, std::move(new_contents), type);
// Rebind the paint callback to the child WebContents. The
// OffScreenWebContentsView was initially created with the parent's OnPaint
// in MaybeOverrideCreateParamsForNewWindow, but the paint data
// belongs to the child.
if (auto* osr_view = api_web_contents->GetOffScreenWebContentsView()) {
osr_view->SetCallback(base::BindRepeating(&WebContents::OnPaint,
api_web_contents->GetWeakPtr()));
}
// We call RenderFrameCreated here as at this point the empty "about:blank"
// render frame has already been created. If the window never navigates again
// RenderFrameCreated won't be called and certain prefs like
@@ -1534,18 +1555,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());
@@ -1664,8 +1691,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() {
@@ -1695,8 +1721,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(
@@ -2235,6 +2261,11 @@ void WebContents::DidUpdateFaviconURL(
iter->icon_url.is_valid())
unique_urls.insert(iter->icon_url);
}
// Only emit if favicon URLs actually changed
if (unique_urls == last_favicon_urls_)
return;
last_favicon_urls_ = unique_urls;
Emit("page-favicon-updated", unique_urls);
}
@@ -2771,6 +2802,11 @@ std::string WebContents::GetMediaSourceID(
return id;
}
std::string WebContents::GetOrCreateDevToolsTargetId() {
auto agent_host = content::DevToolsAgentHost::GetOrCreateFor(web_contents());
return agent_host->GetId();
}
bool WebContents::IsCrashed() const {
return web_contents()->IsCrashed();
}
@@ -4637,6 +4673,8 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
&WebContents::SetWebRTCIPHandlingPolicy)
.SetMethod("setWebRTCUDPPortRange", &WebContents::SetWebRTCUDPPortRange)
.SetMethod("getMediaSourceId", &WebContents::GetMediaSourceID)
.SetMethod("getOrCreateDevToolsTargetId",
&WebContents::GetOrCreateDevToolsTargetId)
.SetMethod("getWebRTCIPHandlingPolicy",
&WebContents::GetWebRTCIPHandlingPolicy)
.SetMethod("getWebRTCUDPPortRange", &WebContents::GetWebRTCUDPPortRange)

View File

@@ -11,6 +11,7 @@
#include <string>
#include <vector>
#include "base/containers/flat_set.h"
#include "base/functional/callback_forward.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
@@ -24,6 +25,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"
@@ -228,6 +230,7 @@ class WebContents final : public ExclusiveAccessContext,
v8::Local<v8::Value> GetWebRTCUDPPortRange(v8::Isolate* isolate) const;
void SetWebRTCUDPPortRange(gin::Arguments* args);
std::string GetMediaSourceID(content::WebContents* request_web_contents);
std::string GetOrCreateDevToolsTargetId();
bool IsCrashed() const;
void ForcefullyCrashRenderer();
void SetUserAgent(const std::string& user_agent);
@@ -336,7 +339,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);
@@ -461,6 +464,9 @@ class WebContents final : public ExclusiveAccessContext,
WebContents& operator=(const WebContents&) = delete;
private:
// Store last emitted favicon URLs to avoid duplicate page-favicon-updated
// events
base::flat_set<GURL> last_favicon_urls_;
// Does not manage lifetime of |web_contents|.
WebContents(v8::Isolate* isolate, content::WebContents* web_contents);
// Takes over ownership of |web_contents|.

View File

@@ -83,13 +83,17 @@ void WebContentsView::ApplyBorderRadius() {
int WebContentsView::NonClientHitTest(const gfx::Point& point) {
if (api_web_contents_) {
auto* iwc = api_web_contents_->inspectable_web_contents();
if (!iwc)
return HTNOWHERE;
// Convert the point to the contents view's coordinate space rather than
// the InspectableWebContentsView's coordinate space, because the draggable
// region is relative to the web content area. When DevTools is docked
// (e.g. to the left), the contents view is offset within the parent,
// so we need to account for that offset.
auto* inspectable_view =
api_web_contents_->inspectable_web_contents()->GetView();
auto* inspectable_view = iwc->GetView();
if (!inspectable_view)
return HTNOWHERE;
auto* contents_view = inspectable_view->GetContentsView();
gfx::Point local_point(point);
views::View::ConvertPointFromWidget(contents_view, &local_point);

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

@@ -134,6 +134,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

@@ -233,7 +233,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];
@@ -249,7 +249,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"
@@ -79,13 +80,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;
@@ -153,9 +163,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;
@@ -410,7 +429,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
@@ -489,7 +508,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;
@@ -519,7 +538,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

@@ -169,6 +169,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

@@ -86,6 +86,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

@@ -87,13 +87,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

@@ -4,13 +4,27 @@
#include "shell/browser/linux/x11_util.h"
#include "build/build_config.h"
#include "ui/ozone/platform_selection.h" // nogncheck
namespace x11_util {
bool IsX11() {
static const bool is_x11 = ui::GetOzonePlatformId() == ui::kPlatformX11;
return is_x11;
#if BUILDFLAG(IS_LINUX)
static const bool is = ui::GetOzonePlatformId() == ui::kPlatformX11;
return is;
#else
return false;
#endif
}
bool IsWayland() {
#if BUILDFLAG(IS_LINUX)
static const bool is = ui::GetOzonePlatformId() == ui::kPlatformWayland;
return is;
#else
return false;
#endif
}
} // namespace x11_util

View File

@@ -7,7 +7,8 @@
namespace x11_util {
bool IsX11();
[[nodiscard]] bool IsX11();
[[nodiscard]] bool IsWayland();
} // namespace x11_util

View File

@@ -143,14 +143,12 @@ static NSDictionary* UNNotificationResponseToNSDictionary(
std::string activity_type(base::SysNSStringToUTF8(userActivity.activityType));
NSURL* url = userActivity.webpageURL;
NSDictionary* details = url ? @{@"webpageURL" : url.absoluteString} : @{};
if (!userActivity.userInfo)
return NO;
NSDictionary* user_info = userActivity.userInfo ?: @{};
electron::Browser* browser = electron::Browser::Get();
return browser->ContinueUserActivity(
activity_type,
electron::NSDictionaryToValue(userActivity.userInfo),
electron::NSDictionaryToValue(details))
return browser->ContinueUserActivity(activity_type,
electron::NSDictionaryToValue(user_info),
electron::NSDictionaryToValue(details))
? YES
: NO;
}

View File

@@ -170,6 +170,12 @@ class NativeWindowMac : public NativeWindow,
void NotifyWindowDidFailToEnterFullScreen();
void NotifyWindowWillLeaveFullScreen();
// Hide/show traffic light buttons around miniaturize/deminiaturize to
// prevent them from flashing at the default position during the restore
// animation when a custom trafficLightPosition is configured.
void HideTrafficLights();
void RestoreTrafficLights();
// Cleanup observers when window is getting closed. Note that the destructor
// can be called much later after window gets closed, so we should not do
// cleanup in destructor.

View File

@@ -1533,6 +1533,18 @@ void NativeWindowMac::RedrawTrafficLights() {
[buttons_proxy_ redraw];
}
void NativeWindowMac::HideTrafficLights() {
if (buttons_proxy_)
[buttons_proxy_ setVisible:NO];
}
void NativeWindowMac::RestoreTrafficLights() {
if (buttons_proxy_ && window_button_visibility_.value_or(true)) {
[buttons_proxy_ redraw];
[buttons_proxy_ setVisible:YES];
}
}
// In simpleFullScreen mode, update the frame for new bounds.
void NativeWindowMac::UpdateFrame() {
NSWindow* window = GetNativeWindow().GetNativeNSWindow();

View File

@@ -1017,17 +1017,13 @@ void NativeWindowViews::MoveTop() {
bool NativeWindowViews::CanResize() const {
#if BUILDFLAG(IS_WIN)
return resizable_ && thick_frame_;
return has_frame() ? resizable_ && thick_frame_ : resizable_;
#else
return resizable_;
#endif
}
bool NativeWindowViews::IsResizable() const {
#if BUILDFLAG(IS_WIN)
if (has_frame())
return ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME;
#endif
return CanResize();
}

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::DictValue 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

@@ -46,9 +46,10 @@ void Notification::NotificationClicked() {
Destroy();
}
void Notification::NotificationDismissed(bool should_destroy) {
void Notification::NotificationDismissed(bool should_destroy,
const std::string& close_reason) {
if (delegate())
delegate()->NotificationClosed();
delegate()->NotificationClosed(close_reason);
set_is_dismissed(true);

View File

@@ -76,7 +76,8 @@ class Notification {
// Should be called by derived classes.
void NotificationClicked();
void NotificationDismissed(bool should_destroy = true);
void NotificationDismissed(bool should_destroy = true,
const std::string& close_reason = "");
void NotificationFailed(const std::string& error = "");
// delete this.

View File

@@ -24,7 +24,7 @@ class NotificationDelegate {
virtual void NotificationAction(int action_index, int selection_index = -1) {}
virtual void NotificationClick() {}
virtual void NotificationClosed() {}
virtual void NotificationClosed(const std::string& reason = "") {}
virtual void NotificationDisplayed() {}
protected:

View File

@@ -60,7 +60,7 @@ class NotificationDelegateImpl final : public electron::NotificationDelegate {
->DispatchNonPersistentClickEvent(notification_id_, base::DoNothing());
}
void NotificationClosed() override {
void NotificationClosed(const std::string& reason) override {
content::NotificationEventDispatcher::GetInstance()
->DispatchNonPersistentCloseEvent(notification_id_, base::DoNothing());
}

View File

@@ -800,9 +800,24 @@ IFACEMETHODIMP ToastEventHandler::Invoke(
IFACEMETHODIMP ToastEventHandler::Invoke(
winui::Notifications::IToastNotification* sender,
winui::Notifications::IToastDismissedEventArgs* e) {
winui::Notifications::ToastDismissalReason reason;
std::string reason_string;
if (SUCCEEDED(e->get_Reason(&reason))) {
switch (reason) {
case winui::Notifications::ToastDismissalReason_UserCanceled:
reason_string = "userCanceled";
break;
case winui::Notifications::ToastDismissalReason_ApplicationHidden:
reason_string = "applicationHidden";
break;
case winui::Notifications::ToastDismissalReason_TimedOut:
reason_string = "timedOut";
break;
}
}
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&Notification::NotificationDismissed,
notification_, false));
notification_, false, reason_string));
DebugLog("Notification dismissed");
return S_OK;
}

View File

@@ -45,6 +45,10 @@ void OffScreenWebContentsView::SetWebContents(
view->InstallTransparency();
}
void OffScreenWebContentsView::SetCallback(const OnPaintCallback& callback) {
callback_ = callback;
}
void OffScreenWebContentsView::SetNativeWindow(NativeWindow* window) {
if (native_window_)
native_window_->RemoveObserver(this);

View File

@@ -43,6 +43,7 @@ class OffScreenWebContentsView : public content::WebContentsView,
void SetWebContents(content::WebContents*);
void SetNativeWindow(NativeWindow* window);
void SetCallback(const OnPaintCallback& callback);
// NativeWindowObserver:
void OnWindowResize() override;

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

@@ -256,6 +256,10 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
shell_->SetWindowLevel(NSNormalWindowLevel);
shell_->UpdateWindowOriginalFrame();
shell_->DetachChildren();
// Hide the traffic light buttons container before miniaturize so that
// when the window is restored, macOS does not render the buttons at
// their default position during the deminiaturize animation.
shell_->HideTrafficLights();
}
- (void)windowDidMiniaturize:(NSNotification*)notification {
@@ -273,6 +277,10 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
shell_->set_wants_to_be_visible(true);
shell_->AttachChildren();
shell_->SetWindowLevel(level_);
// Reposition traffic light buttons and make them visible again.
// They were hidden in windowWillMiniaturize to prevent a flash at
// the default (0,0) position during the restore animation.
shell_->RestoreTrafficLights();
shell_->NotifyWindowRestore();
}

View File

@@ -343,6 +343,10 @@ InspectableWebContents::InspectableWebContents(
}
InspectableWebContents::~InspectableWebContents() {
// Explicitly destroy the view first to ensure that any callbacks triggered
// during view teardown (like NonClientHitTest) does not access a
// partially-destroyed view.
view_.reset();
// Unsubscribe from devtools and Clean up resources.
if (GetDevToolsWebContents())
WebContentsDestroyed();

View File

@@ -158,7 +158,7 @@ DialogResult ShowTaskDialogWstr(gfx::AcceleratedWidget parent,
config.hInstance = GetModuleHandle(nullptr);
config.dwFlags = flags;
if (parent) {
if (parent && ::IsWindowEnabled(parent)) {
config.hwndParent = parent;
config.dwFlags |= TDF_POSITION_RELATIVE_TO_WINDOW;
}

View File

@@ -9,7 +9,6 @@
#include "components/input/native_web_keyboard_event.h"
#include "shell/browser/native_window.h"
#include "shell/browser/ui/views/menu_bar.h"
#include "ui/events/keycodes/dom/keycode_converter.h"
#include "ui/views/layout/box_layout.h"
namespace electron {
@@ -22,21 +21,9 @@ bool IsAltKey(const input::NativeWebKeyboardEvent& event) {
bool IsAltModifier(const input::NativeWebKeyboardEvent& event) {
using Mods = input::NativeWebKeyboardEvent::Modifiers;
// AltGraph (AltGr) should not be treated as a single Alt keypress for
// menu-bar toggling.
if (event.windows_key_code == ui::VKEY_ALTGR ||
ui::KeycodeConverter::DomKeyToKeyString(event.dom_key) == "AltGraph") {
return false;
}
return (event.GetModifiers() & Mods::kKeyModifiers) == Mods::kAltKey;
}
bool IsSingleAltKey(const input::NativeWebKeyboardEvent& event) {
return IsAltKey(event) && IsAltModifier(event);
}
} // namespace
RootView::RootView(NativeWindow* window)
@@ -111,7 +98,7 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
return;
// Show accelerator when "Alt" is pressed.
if (menu_bar_visible_ && IsSingleAltKey(event))
if (menu_bar_visible_ && IsAltKey(event))
menu_bar_->SetAcceleratorVisibility(
event.GetType() == blink::WebInputEvent::Type::kRawKeyDown);
@@ -134,11 +121,11 @@ void RootView::HandleKeyEvent(const input::NativeWebKeyboardEvent& event) {
// Toggle the menu bar only when a single Alt is released.
if (event.GetType() == blink::WebInputEvent::Type::kRawKeyDown &&
IsSingleAltKey(event)) {
IsAltKey(event)) {
// When a single Alt is pressed:
menu_bar_alt_pressed_ = true;
} else if (event.GetType() == blink::WebInputEvent::Type::kKeyUp &&
IsSingleAltKey(event) && menu_bar_alt_pressed_) {
IsAltKey(event) && menu_bar_alt_pressed_) {
// When a single Alt is released right after a Alt is pressed:
menu_bar_alt_pressed_ = false;
if (menu_bar_autohide_)

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 @@ gin::WeakCell<api::Session>* UsbChooserController::GetSession() {
void UsbChooserController::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {
if (DisplayDevice(device_info)) {
devices_.push_back(device_info.Clone());
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("usb-device-added", device_info.Clone(),
@@ -78,6 +79,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;
});
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("usb-device-removed", device_info.Clone(),
@@ -90,9 +94,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."}),
@@ -127,6 +133,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"
@@ -75,6 +76,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::DictValue 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;
@@ -204,7 +203,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_);
@@ -338,11 +336,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_);
@@ -350,9 +343,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

Some files were not shown because too many files have changed in this diff Show More