Compare commits

...

25 Commits

Author SHA1 Message Date
David Sanders
dcba53cdfe fix: align process.exit() behavior in main process with Node.js 2026-04-30 18:59:11 -07:00
Niklas Wenzel
aaf328930d docs: fix version of deprecation notice (#51406) 2026-04-30 16:14:15 -07:00
Asish Kumar
d0612e2c92 fix: preserve mouse hook handle when UnhookWindowsHookEx fails (#51098)
* fix: preserve mouse hook handle when UnhookWindowsHookEx fails

NativeWindowViews::SetForwardMouseMessages() installs a low-level mouse
hook when mouse forwarding begins and unhooks it once no window needs
forwarding. The previous code reset the shared `mouse_hook_` handle to
`nullptr` unconditionally after calling UnhookWindowsHookEx, even when
the unhook call failed.

When unhooking fails, the hook is still installed in the system. Because
`mouse_hook_` is nulled out anyway, the next call to
SetForwardMouseMessages(true) evaluates `if (!mouse_hook_)` as true and
installs a second, duplicate hook via SetWindowsHookEx, so every mouse
message is processed by MouseHookProc multiple times.

Check the return value of UnhookWindowsHookEx and only null the handle
on success. When the call fails, leave `mouse_hook_` pointing at the
existing hook so the next activation reuses it rather than stacking a
new one on top, and log the failure via PLOG to surface the underlying
Windows error.

Fixes: #51064
Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>

* fix: clear invalid mouse hook handles

Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>

---------

Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
2026-04-30 15:34:45 -04:00
Robo
8f0f08e818 feat: add session support to requests from utility process (#51279)
feat: add http cache support to requests from utility process

Add `session` and `partition` options to `utilityProcess.fork()` to
allow utility processes to use a session-specific network context
instead of the system network context. This enables HTTP caching,
cookie isolation, and webRequest interception for utility process
network requests.

When `respondToAuthRequestsFromMainProcess` is true and a session is
provided, HTTP 401/407 auth challenges now emit a `login` event on
the UtilityProcess instance rather than on `app`. Without a session,
auth challenges continue to emit on `app` for backward compatibility.
2026-04-30 15:03:20 -04:00
Om Ghante
4cad9c868d fix: use bundled devtools frontend URL for remote debugging (#51236)
fix: add ShouldUseBundledFrontendResources delegate for remote debugging

Co-authored-by: John Kleinschmidt <jkleinsc@electronjs.org>
2026-04-30 12:48:21 -05:00
Dylan Hubbard
a1d6918b59 feat: Add view.setBackgroundBlur (#51076)
* feat: blur views

* spec: add tests, limit values to positive

* docs: be explicit in units for blurRadius

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

* lint: trailing space

---------

Co-authored-by: Erick Zhao <erick@hotmail.ca>
2026-04-30 12:52:11 -04:00
Samuel Attard
00f0989f49 feat: support WebAuthn Touch ID platform authenticator on macOS (#51255)
* feat: support WebAuthn Touch ID platform authenticator on macOS

Adds `app.configureWebAuthn({ touchID: { keychainAccessGroup } })` to enable
the Secure Enclave platform authenticator for `navigator.credentials`.
Credentials are stored under the app-supplied keychain access group with a
per-session metadata secret that is generated on first use and persisted in
prefs.

Also introduces `ElectronAuthenticatorRequestClientDelegate` and wires it via
`ContentBrowserClient::GetWebAuthenticationRequestDelegate()` so that
discoverable-credential `get()` calls with multiple matches emit a new
`select-webauthn-account` session event instead of DCHECK-failing in the base
delegate. If no listener is registered (or the callback is invoked with no
credential), the request is cancelled with NotAllowedError rather than
silently auto-selecting.

Tests use the DevTools virtual authenticator so the account-selection flow is
exercised in CI without entitlements or real hardware.

* fix: register request delegate as FidoRequestHandlerBase observer

The base AuthenticatorRequestClientDelegate::StartObserving() is a no-op, so
observer() on the request handler stayed null. MakeCredentialRequestHandler::
SpecializeRequestForAuthenticator dereferences observer()->SupportsPIN() when
residentKey is 'preferred', crashing with SEGV when a real FIDO2 HID key is
dispatched.

Override StartObserving/StopObserving to register via a ScopedObservation like
ChromeAuthenticatorRequestDelegate does. Added a virtual-authenticator
regression test for create() with residentKey: 'preferred'.

* chore: update copyright attribution for new webauthn files

* fix: address review feedback on webauthn account-select event

- Encode credentialId and userHandle as URL-safe base64 without padding so
  the values match PublicKeyCredential.id from navigator.credentials.get()
  byte-for-byte; tests now assert the equality rather than transcoding.
- Cancel the pending request when the listener invokes the callback with a
  credentialId that does not match any account, instead of leaving the
  request hanging while the listener retries. The TypeError still surfaces
  so the misuse remains visible to the developer.
- DCHECK that the Touch ID config helpers run on the UI thread, encoding
  the threading invariant the read-then-write metadata-secret pref relies
  on.

* fix: oxfmt formatting in webauthn spec

* fix: use out-param form of base::Base64UrlEncode

* fix: silently cancel webauthn account select on unknown credentialId

Throwing back into the listener bubbles up as an unhandled exception in
the main process. Match the no-args branch exactly so the listener sees a
single consistent failure mode (cancel + NotAllowedError) whether it
declines deliberately or by mistake.
2026-04-30 12:40:03 -04:00
Robo
f6dc41a911 refactor: gin_helper::Promise managed by cppgc (#51386)
* refactor: gin_helper::Promise managed by cppgc

* fix: broken liveness test

* refactor: move handle dependent members to base class
2026-04-30 09:48:39 -04:00
Noah Gregory
edd3b27063 feat: add accessible labels for macOS menus (#50240)
* feat: add accessible labels for macOS menus

* fix: wire `MenuItem` accessible label for runtime state changes

* fix: remove insert-time tracking of accessible menu item labels

* fix: don't set empty accessible menu item labels

* fix: make linter happy

* docs: add clarification to accessible label documentation

* fix: rename `accessibleLabel` to `accessibilityLabel`

* fix: move `NSString`'s for accessibility labels inside smaller scopes

* Revert "fix: move `NSString`'s for accessibility labels inside smaller scopes"

This reverts commit db3068401a.

* fix: actually move `NSString`'s for accessibility labels inside smaller scopes
2026-04-30 09:39:43 -04:00
Calvin
212b53c63e fix: constrain AllowUniversalAccessFromFileURLs to file: origins in agent cluster key assignment (#50789)
* fix: constrain AllowUniversalAccessFromFileURLs to file: origins in agent cluster key assignment

Fixes #50242

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>

* sync patch with upstream CL

* add test

---------

Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-30 09:36:50 -04:00
Charles Kerr
4e56da6d30 refactor: use upstream's chrome.scripting impl (#51376)
Remove our implementation of the scripting api and use upstream's
version. It was recently moved to `extensions/` by
https://chromium-review.googlesource.com/c/chromium/src/+/7784831,
so we link it directly.

Update `ElectronExtensionsBrowserClient` to overrides `IsValidTabId()`
and `GetScriptExecutorForTab()` to provide tab validation and
script-executor hooks.

Remove now-redundant local copy of `scripting.idl`.
Upstream now provides everything we used this for.

Updated breaking-changes.md to document a CSS matching difference.

Co-authored-by: GitHub Copilot <github-copilot[bot]@users.noreply.github.com>
2026-04-29 17:15:04 -05:00
dependabot[bot]
d58c5a5562 build(deps-dev): bump @xmldom/xmldom from 0.8.12 to 0.8.13 in the npm_and_yarn group across 1 directory (#51379)
build(deps-dev): bump @xmldom/xmldom

Bumps the npm_and_yarn group with 1 update in the / directory: [@xmldom/xmldom](https://github.com/xmldom/xmldom).


Updates `@xmldom/xmldom` from 0.8.12 to 0.8.13
- [Release notes](https://github.com/xmldom/xmldom/releases)
- [Changelog](https://github.com/xmldom/xmldom/blob/master/CHANGELOG.md)
- [Commits](https://github.com/xmldom/xmldom/compare/0.8.12...0.8.13)

---
updated-dependencies:
- dependency-name: "@xmldom/xmldom"
  dependency-version: 0.8.13
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-29 12:12:59 +02:00
dependabot[bot]
d4e6b41f3d build(deps): bump the npm_and_yarn group across 2 directories with 6 updates (#51367)
Bumps the npm_and_yarn group with 6 updates in the / directory:

| Package | From | To |
| --- | --- | --- |
| [uuid](https://github.com/uuidjs/uuid) | `3.4.0` | `14.0.0` |
| [axios](https://github.com/axios/axios) | `1.13.6` | `1.15.1` |
| [basic-ftp](https://github.com/patrickjuchli/basic-ftp) | `5.2.0` | `5.3.0` |
| [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) | `5.5.9` | `5.7.1` |
| [follow-redirects](https://github.com/follow-redirects/follow-redirects) | `1.15.11` | `1.16.0` |
| [lodash](https://github.com/lodash/lodash) | `4.17.23` | `4.18.1` |

Bumps the npm_and_yarn group with 1 update in the /spec directory: [uuid](https://github.com/uuidjs/uuid).


Updates `uuid` from 3.4.0 to 14.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v3.4.0...v14.0.0)

Updates `axios` from 1.13.6 to 1.15.1
- [Release notes](https://github.com/axios/axios/releases)
- [Changelog](https://github.com/axios/axios/blob/v1.x/CHANGELOG.md)
- [Commits](https://github.com/axios/axios/compare/v1.13.6...v1.15.1)

Updates `basic-ftp` from 5.2.0 to 5.3.0
- [Release notes](https://github.com/patrickjuchli/basic-ftp/releases)
- [Changelog](https://github.com/patrickjuchli/basic-ftp/blob/master/CHANGELOG.md)
- [Commits](https://github.com/patrickjuchli/basic-ftp/compare/v5.2.0...v5.3.0)

Updates `fast-xml-parser` from 5.5.9 to 5.7.1
- [Release notes](https://github.com/NaturalIntelligence/fast-xml-parser/releases)
- [Changelog](https://github.com/NaturalIntelligence/fast-xml-parser/blob/master/CHANGELOG.md)
- [Commits](https://github.com/NaturalIntelligence/fast-xml-parser/compare/v5.5.9...v5.7.1)

Updates `follow-redirects` from 1.15.11 to 1.16.0
- [Release notes](https://github.com/follow-redirects/follow-redirects/releases)
- [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.15.11...v1.16.0)

Updates `lodash` from 4.17.23 to 4.18.1
- [Release notes](https://github.com/lodash/lodash/releases)
- [Commits](https://github.com/lodash/lodash/compare/4.17.23...4.18.1)

Updates `uuid` from 3.4.0 to 14.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v3.4.0...v14.0.0)

Updates `uuid` from 3.4.0 to 14.0.0
- [Release notes](https://github.com/uuidjs/uuid/releases)
- [Changelog](https://github.com/uuidjs/uuid/blob/main/CHANGELOG.md)
- [Commits](https://github.com/uuidjs/uuid/compare/v3.4.0...v14.0.0)

---
updated-dependencies:
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: axios
  dependency-version: 1.15.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: basic-ftp
  dependency-version: 5.3.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: fast-xml-parser
  dependency-version: 5.7.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: follow-redirects
  dependency-version: 1.16.0
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: lodash
  dependency-version: 4.18.1
  dependency-type: indirect
  dependency-group: npm_and_yarn
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
- dependency-name: uuid
  dependency-version: 14.0.0
  dependency-type: direct:development
  dependency-group: npm_and_yarn
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 15:28:40 -05:00
Parth Taneja
cc7ebe542d feat: allow --experimental-inspector-network-resource node flag (#49689)
* feat: allow --experimental-inspector-network-resource node flag

* docs: document --experimental-inspector-network-resource flag

* docs: note network requests for --experimental-inspector-network-resource

---------

Co-authored-by: Parth Taneja <parthtaneja@gmail.com>
2026-04-28 16:23:15 -04:00
Charles Kerr
cef313a172 fix: include permission element string resources in locale paks (#51346)
The `<geolocation>` HTML element looks up IDS_PERMISSION_REQUEST_GEOLOCATION
via ResourceBundle::GetLocalizedString(). These string IDs are defined in
third_party/blink/public/strings/permission_element_strings.grd.
Electron didn't include that in its pak file, causing CHECK(!data->empty()).

Ths PR adds the per-locale permission_element_strings paks and the
aggregated permission_element_generated_strings pak to electron_paks.gni.
This matches how it's done in `chrome/chrome_repack_locales.gni` and
in `chrome/chrome_paks.gni`.

Xref: https://chromium-review.googlesource.com/c/chromium/src/+/5907626
2026-04-28 14:25:30 -04:00
Noah Gregory
1766370311 fix: use no-op header client for Fetch-intercepted requests (#50744)
* fix: use the non-pass-through path for Fetch-intercepted requests

* Revert "fix: use the non-pass-through path for Fetch-intercepted requests"

This reverts commit 395fb8bb8c.

* fix: use no-op header client for Fetch-intercepted requests

* fix: bring back `DCHECK` that was prematurely removed

* style: reformat code
2026-04-28 14:07:49 -04:00
Athul Iddya
2b341e5e5b refactor: replace ClientFrameViewLinux with NativeFrameViewLinux (#51160)
Replace ClientFrameViewLinux with electron::NativeFrameViewLinux, a thin
wrapper over views::NativeFrameViewLinux. The wrapper provides Electron
integration, such as draggable region support in NonClientHitTest,
and adapting to Electron's sizing conventions.

ElectronDesktopWindowTreeHostLinux and NativeWindowViews now use
FrameViewLinux to query frame geometry and update window states in
addition to LinuxFrameLayout.

Assisted-By: Claude Opus 4.6, Claude Code
2026-04-28 12:29:25 -04:00
Asish Kumar
e235c3fff0 fix: honor ELECTRON_INSTALL_PLATFORM in getPlatformPath (#51029)
The postinstall script resolves two things from the target platform:

1. Which artifact to download, via `downloadArtifact({ platform, ... })`.
   Since #49981, `platform` is derived from
   `ELECTRON_INSTALL_PLATFORM || npm_config_platform || process.platform`.
2. Which executable path to use for the `isInstalled()` cache check and
   for the `path.txt` marker written after extraction, via
   `getPlatformPath()`.

`getPlatformPath()` was not updated with the rest of that change and
still falls back to `npm_config_platform || os.platform()` only.

As a result, passing `ELECTRON_INSTALL_PLATFORM` (as documented in
`docs/tutorial/installation.md`) causes the two to disagree: the
download fetches the requested platform's zip, but `path.txt` and the
path sanity check are written against the host platform's executable
name. That in turn makes `isInstalled()` always return `false` on
subsequent runs (forcing redundant re-downloads) and makes the
executable path recorded in `path.txt` wrong for the artifact that
was actually extracted (e.g. `electron` written alongside a
`darwin`/`win32` build).

Check `ELECTRON_INSTALL_PLATFORM` first, matching the resolution used
for `downloadArtifact`.

Signed-off-by: Asish Kumar <officialasishkumar@gmail.com>
2026-04-28 12:07:05 -04:00
Michaela Laurencin
bbdeb50405 docs: add further disposition description (#50246)
* docs: add further disposition description

* add option descriptions

* fix linter
2026-04-28 12:03:49 -04:00
electron-roller[bot]
b08931c957 chore: bump chromium to 149.0.7813.0 (main) (#51362)
* chore: bump chromium in DEPS to 149.0.7813.0

* chore: e patches all (trivial only)

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-04-28 09:44:47 -04:00
loufultoncz-coder
3f0c48f567 fix: validate header name and value in webRequest.onBeforeSendHeaders (#51340)
* fix: validate header name and value in webRequest.onBeforeSendHeaders

Chromium's net::HttpRequestHeaders::SetHeader() uses CHECK() to enforce
valid header names and values, which causes a fatal crash if the caller
passes invalid strings. When users modify requestHeaders in the
onBeforeSendHeaders callback with invalid header names (e.g. containing
spaces) or invalid header values (e.g. containing CRLF), the
gin::Converter<net::HttpRequestHeaders>::FromV8() calls SetHeader()
directly, triggering the CHECK and crashing the process.

This change adds pre-validation using net::HttpUtil::IsValidHeaderName()
and net::HttpUtil::IsValidHeaderValue() before calling SetHeader(),
silently skipping invalid headers instead of crashing.

* Update shell/common/gin_converters/net_converter.cc

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

* Update spec/api-web-request-spec.ts

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

* fix: lint

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-04-28 09:38:15 -04:00
electron-roller[bot]
8066df1817 chore: bump chromium to 149.0.7812.3 (main) (#51361)
chore: bump chromium in DEPS to 149.0.7812.3

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
2026-04-28 12:24:14 +02:00
dependabot[bot]
69a4fa20e1 build(deps): bump electron/github-app-auth-action from 2.0.0 to 2.1.0 (#51358)
Bumps [electron/github-app-auth-action](https://github.com/electron/github-app-auth-action) from 2.0.0 to 2.1.0.
- [Release notes](https://github.com/electron/github-app-auth-action/releases)
- [Commits](e14e47722e...5f70a3726a)

---
updated-dependencies:
- dependency-name: electron/github-app-auth-action
  dependency-version: 2.1.0
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 10:14:11 +02:00
dependabot[bot]
4b90a3fd78 build(deps): bump slackapi/slack-github-action from 3.0.1 to 3.0.2 (#51359)
Bumps [slackapi/slack-github-action](https://github.com/slackapi/slack-github-action) from 3.0.1 to 3.0.2.
- [Release notes](https://github.com/slackapi/slack-github-action/releases)
- [Changelog](https://github.com/slackapi/slack-github-action/blob/main/CHANGELOG.md)
- [Commits](af78098f53...03ea5433c1)

---
updated-dependencies:
- dependency-name: slackapi/slack-github-action
  dependency-version: 3.0.2
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-28 10:14:08 +02:00
electron-roller[bot]
966e932efa chore: bump chromium to 149.0.7812.0 (main) (#51357)
* chore: bump chromium in DEPS to 149.0.7812.0

* chore: update patches (trivial only)

Co-Authored-By: GitHub Copilot <copilot@github.com>

* fix(patch): declare abort in Node builtin_info

Node's builtin_info.cc uses abort() but doesn't include <cstdlib>.
It used to pick up the declaration by a transitive include, but
that broke in this libc++ roll.

This patch can be removed after it's been upstreamed to Node.js.

* SharedWorker: Enforce same-origin check for IWA and Extensions

Xref: https://chromium-review.googlesource.com/c/chromium/src/+/7784632

* chore: node script/gen-libc++-filenames.js

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Charles Kerr <charles@charleskerr.com>
Co-authored-by: GitHub Copilot <copilot@github.com>
2026-04-28 10:13:10 +02:00
123 changed files with 2906 additions and 2691 deletions

View File

@@ -157,7 +157,7 @@ jobs:
await core.summary.write();
- name: Send Slack message if errors
if: ${{ always() && steps.audit-errors.outputs.errorsFound && github.ref == 'refs/heads/main' }}
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
uses: slackapi/slack-github-action@03ea5433c137af7c0495bc0cad1af10403fc800c # v3.0.2
with:
payload: |
link: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"

View File

@@ -98,7 +98,7 @@ jobs:
done
- name: Generate GitHub App token
if: ${{ steps.check-major-version.outputs.MAJOR }}
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.RELEASE_BOARD_GH_APP_CREDS }}

View File

@@ -21,7 +21,7 @@ jobs:
AUTHOR_ASSOCIATION=$(gh api /repos/electron/electron/issues/comments/${{ github.event.comment.id }} --jq '.author_association')
echo "author_association=$AUTHOR_ASSOCIATION" >> "$GITHUB_OUTPUT"
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
if: ${{ !contains(fromJSON('["MEMBER", "OWNER", "COLLABORATOR"]'), steps.get-author-association.outputs.author_association) }}
id: generate-token
with:
@@ -45,7 +45,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: *get-author-association
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
if: ${{ contains(fromJSON('["MEMBER", "OWNER"]'), steps.get-author-association.outputs.author_association) }}
id: generate-token
with:

View File

@@ -15,7 +15,7 @@ jobs:
contents: read
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
@@ -36,7 +36,7 @@ jobs:
contents: read
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
@@ -70,7 +70,7 @@ jobs:
fi
- name: Generate GitHub App token
if: ${{ steps.check-for-comment.outputs.SHOULD_COMMENT }}
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}

View File

@@ -14,7 +14,7 @@ jobs:
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
@@ -32,7 +32,7 @@ jobs:
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}

View File

@@ -14,7 +14,7 @@ jobs:
if: ${{ !github.event.changes.new_repository.private }}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}

View File

@@ -26,7 +26,7 @@ jobs:
fi
- name: Generate GitHub App token
if: ${{ steps.check-for-blocked-labels.outputs.NOT_BLOCKED }}
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}

View File

@@ -31,7 +31,7 @@ jobs:
contents: read
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}

View File

@@ -18,7 +18,7 @@ jobs:
permissions: {}
steps:
- name: Trigger Slack workflow
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
uses: slackapi/slack-github-action@03ea5433c137af7c0495bc0cad1af10403fc800c # v3.0.2
with:
webhook: ${{ secrets.BACKPORT_REQUESTED_SLACK_WEBHOOK_URL }}
webhook-type: webhook-trigger
@@ -36,7 +36,7 @@ jobs:
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.RELEASE_BOARD_GH_APP_CREDS }}
@@ -55,7 +55,7 @@ jobs:
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}

View File

@@ -15,7 +15,7 @@ jobs:
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.RELEASE_BOARD_GH_APP_CREDS }}

View File

@@ -14,7 +14,7 @@ jobs:
permissions: {}
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
@@ -38,7 +38,7 @@ jobs:
needs: stale
steps:
- name: Generate GitHub App token
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
uses: electron/github-app-auth-action@5f70a3726af01b612f29aac96d05aa524389c9e9 # v2.1.0
id: generate-token
with:
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}

View File

@@ -528,6 +528,7 @@ source_set("electron_lib") {
"//content/public/utility",
"//device/bluetooth",
"//device/bluetooth/public/cpp",
"//device/fido",
"//gin",
"//gpu/ipc/client",
"//media/capture/mojom:video_capture",

2
DEPS
View File

@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'149.0.7811.0',
'149.0.7813.0',
'node_version':
'v24.15.0',
'nan_version':

View File

@@ -65,6 +65,7 @@ template("electron_extra_paks") {
"$root_gen_dir/net/net_resources.pak",
"$root_gen_dir/third_party/blink/public/resources/blink_resources.pak",
"$root_gen_dir/third_party/blink/public/resources/inspector_overlay_resources.pak",
"$root_gen_dir/third_party/blink/public/strings/permission_element_generated_strings.pak",
"$target_gen_dir/electron_resources.pak",
]
deps = [
@@ -83,6 +84,7 @@ template("electron_extra_paks") {
"//net:net_resources",
"//third_party/blink/public:devtools_inspector_resources",
"//third_party/blink/public:resources",
"//third_party/blink/public/strings:permission_element_generated_strings",
"//ui/webui/resources",
]
if (defined(invoker.deps)) {
@@ -187,6 +189,7 @@ template("electron_paks") {
"${root_gen_dir}/extensions/strings/extensions_strings_",
"${root_gen_dir}/services/strings/services_strings_",
"${root_gen_dir}/third_party/blink/public/strings/blink_strings_",
"${root_gen_dir}/third_party/blink/public/strings/permission_element_strings_",
"${root_gen_dir}/ui/strings/app_locale_settings_",
"${root_gen_dir}/ui/strings/auto_image_annotation_strings_",
"${root_gen_dir}/ui/strings/ax_strings_",
@@ -204,6 +207,7 @@ template("electron_paks") {
"//extensions/strings",
"//services/strings",
"//third_party/blink/public/strings",
"//third_party/blink/public/strings:permission_element_strings",
"//ui/strings:app_locale_settings",
"//ui/strings:auto_image_annotation_strings",
"//ui/strings:ax_strings",

View File

@@ -1233,6 +1233,51 @@ This API must be called after the `ready` event is emitted.
[doh-providers]: https://source.chromium.org/chromium/chromium/src/+/main:net/dns/public/doh_provider_entry.cc;l=31?q=%22DohProviderEntry::GetList()%22&ss=chromium%2Fchromium%2Fsrc
[RFC8484 § 3]: https://datatracker.ietf.org/doc/html/rfc8484#section-3
### `app.configureWebAuthn(options)` _macOS_
* `options` Object
* `touchID` Object (optional) - Enables the Touch ID / Secure Enclave platform
authenticator for [Web Authentication](https://www.w3.org/TR/webauthn-2/)
requests.
* `keychainAccessGroup` string - The keychain access group that WebAuthn
credentials will be stored under. This value **must** also be present in
your app's `keychain-access-groups` code-signing entitlement, and is
typically of the form `<TEAM_ID>.<BUNDLE_ID>.webauthn`.
Configures platform authenticators for the Web Authentication API
(`navigator.credentials.create()` / `navigator.credentials.get()`). Until this
is called, `PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()`
resolves to `false` and platform-authenticator requests are not serviced.
When `touchID` is provided, WebAuthn credentials are stored in the macOS
keychain and bound to this device's Secure Enclave. Electron automatically
generates and persists a per-[`session`](session.md) metadata secret so that
credentials created in one partition are not visible to another.
```js
const { app } = require('electron')
app.configureWebAuthn({
touchID: {
keychainAccessGroup: 'A1B2C3D4E5.com.example.app.webauthn'
}
})
```
With the matching entitlement in your app's `entitlements.plist`:
```xml
<key>keychain-access-groups</key>
<array>
<string>A1B2C3D4E5.com.example.app.webauthn</string>
</array>
```
> [!NOTE]
> Touch ID WebAuthn credentials are device-bound and are not synced via iCloud
> Keychain. They are only available on Macs with a Secure Enclave (Apple
> silicon, or Intel Macs with a T2 chip).
### `app.disableHardwareAcceleration()`
Disables hardware acceleration for current app.

View File

@@ -319,6 +319,17 @@ By default inspector websocket url is available in stderr and under /json/list e
Enable support for DevTools network inspector events, for visibility into requests made by the nodejs `http` and `https` modules.
### `--experimental-inspector-network-resource`
Enable support for resolving source maps over the network when using the Node.js inspector.
When enabled, DevTools can retrieve remote source maps for main and utility
process scripts via the Node.js inspector.
**Note:** When enabled, the Node.js inspector will make network requests to
URLs specified in source maps. Be mindful of this in environments where the
process has access to internal networks.
### `--no-deprecation`
Silence deprecation warnings.

View File

@@ -31,6 +31,7 @@ See [`Menu`](menu.md) for examples.
* `header` - Only available on macOS 14 and up.
* `palette` - Only available on macOS 14 and up.
* `label` string (optional)
* `accessibilityLabel` string (optional) _macOS_
* `sublabel` string (optional) _macOS_ - Available in macOS >= 14.4
* `toolTip` string (optional) _macOS_ - Hover text for this menu item.
* `accelerator` string (optional) - An [Accelerator](../tutorial/keyboard-shortcuts.md#accelerators) string.
@@ -83,6 +84,12 @@ A `string` indicating the item's visible label.
This property can be dynamically changed.
#### `menuItem.accessibilityLabel` _macOS_
A `string` indicating the item's accessibility label (used by assistive technology), if set.
This property can be dynamically changed.
#### `menuItem.click`
A `Function` that is fired when the MenuItem receives a click event.

View File

@@ -629,6 +629,54 @@ Emitted after `USBDevice.forget()` has been called. This event can be used
to help maintain persistent storage of permissions when
`setDevicePermissionHandler` is used.
#### Event: 'select-webauthn-account'
Returns:
* `event` Event
* `details` Object
* `relyingPartyId` string - The relying party identifier from the WebAuthn request.
* `accounts` [WebAuthnAccount[]](structures/webauthn-account.md)
* `frame` [WebFrameMain](web-frame-main.md) | null - The frame initiating this event.
May be `null` if accessed after the frame has either navigated or been destroyed.
* `callback` Function
* `credentialId` string | null (optional)
Emitted when a call to `navigator.credentials.get()` resolves multiple
discoverable WebAuthn credentials and the user must choose one. `callback`
should be called with the `credentialId` of the selected account; passing no
arguments — or a `credentialId` that does not match one of the provided
accounts — will cancel the request and the page will receive a
`NotAllowedError`. If no listener is registered for this event, the request is
cancelled with the same error. The credential request remains pending until
the listener invokes the callback, so always invoke it exactly once — typically
from a `try { … } finally { callback(…) }` block.
On macOS, the Touch ID platform authenticator surfaces accounts via this event
once it has been configured with
[`app.configureWebAuthn`](app.md#appconfigurewebauthnoptions-macos). The event
may also fire on other platforms when a roaming FIDO2 authenticator returns
multiple discoverable credentials.
```js
const { app, BrowserWindow } = require('electron')
let win = null
app.whenReady().then(() => {
app.configureWebAuthn({
touchID: { keychainAccessGroup: 'A1B2C3D4E5.com.example.app.webauthn' }
})
win = new BrowserWindow()
win.webContents.session.on('select-webauthn-account', (event, details, callback) => {
const selected = details.accounts.find((a) => a.name === 'alice@example.com')
callback(selected?.credentialId)
})
})
```
### Instance Methods
The following methods are available on instances of `Session`:

View File

@@ -0,0 +1,9 @@
# WebAuthnAccount Object
* `credentialId` string - URL-safe base64-encoded (no padding) credential ID of
the discoverable credential. Matches `PublicKeyCredential.id` returned by
`navigator.credentials.get()` in the renderer.
* `userHandle` string (optional) - URL-safe base64-encoded (no padding) user
handle (`user.id`) that was provided when the credential was created.
* `name` string (optional) - Human-palatable identifier for the account (for example, an email address or username).
* `displayName` string (optional) - Human-palatable name for the account, intended for display.

View File

@@ -17,6 +17,16 @@ Process: [Main](../glossary.md#main-process)<br />
* `env` Object (optional) - Environment key-value pairs. Default is `process.env`.
* `execArgv` string[] (optional) - List of string arguments passed to the executable.
* `cwd` string (optional) - Current working directory of the child process.
* `session` [Session](session.md) (optional) - Sets the session used by the process for network
requests. By default, network requests from the utility process will use the system network
context which does not have HTTP cache support. Setting a session enables HTTP caching and
other session-specific network features. See [session](session.md) for more information.
* `partition` string (optional) - Sets the session used by the process according to the
session's partition string. If `partition` starts with `persist:`, the process will use a
persistent session available to all pages in the app with the same `partition`. If there is
no `persist:` prefix, the process will use an in-memory session. By assigning the same
`partition`, multiple processes can share the same session. If the `session` option is set,
this option is ignored.
* `stdio` (string[] | string) (optional) - Allows configuring the mode for `stdout` and `stderr`
of the child process. Default is `inherit`.
String value can be one of `pipe`, `ignore`, `inherit`, for more details on these values you can refer to
@@ -44,7 +54,9 @@ Process: [Main](../glossary.md#main-process)<br />
that run third-party or otherwise untrusted code. Default is `false`.
* `respondToAuthRequestsFromMainProcess` boolean (optional) - With this flag, all HTTP 401 and 407 network
requests created via the [net module](net.md) will allow responding to them via the
[`app#login`](app.md#event-login) event in the main process instead of the default
[`login`](#event-login) event on the `UtilityProcess` instance when a `session` is provided, or via
the [`app#login`](app.md#event-login) event in the main process when using the default system network
context. Without this flag, auth challenges are handled by the default
[`login`](client-request.md#event-login) event on the [`ClientRequest`](client-request.md) object. Default is
`false`.
@@ -176,6 +188,45 @@ Returns:
Emitted when the child process sends a message using [`process.parentPort.postMessage()`](process.md#processparentport).
#### Event: 'login'
Returns:
* `authenticationResponseDetails` Object
* `url` URL
* `pid` number
* `authInfo` Object
* `isProxy` boolean
* `scheme` string
* `host` string
* `port` Integer
* `realm` string
* `callback` Function
* `username` string (optional)
* `password` string (optional)
Emitted when the utility process encounters an HTTP 401 or 407 authentication challenge, if the
process was created with both `respondToAuthRequestsFromMainProcess: true` and a `session` option.
The `callback` should be called with credentials to respond to the challenge. Calling `callback`
without arguments will cancel the request.
This behaves the same as the [`login` event on `app`](app.md#event-login) but is scoped to the
individual utility process instance.
```js
const { session, utilityProcess } = require('electron')
const ses = session.defaultSession
const child = utilityProcess.fork('./worker.js', [], {
session: ses,
respondToAuthRequestsFromMainProcess: true
})
child.on('login', (authenticationResponseDetails, authInfo, callback) => {
callback('username', 'password')
})
```
[`child_process.fork`]: https://nodejs.org/dist/latest-v16.x/docs/api/child_process.html#child_processforkmodulepath-args-options
[Services API]: https://chromium.googlesource.com/chromium/src/+/main/docs/mojo_and_services.md
[stdio]: https://nodejs.org/dist/latest/docs/api/child_process.html#optionsstdio

View File

@@ -117,6 +117,13 @@ Examples of valid `color` values:
> [!NOTE]
> The area cutout of the view's border still captures clicks.
#### `view.setBackgroundBlur(blurRadius)`
* `blurRadius` Integer - The radius of the background blur effect (in pixels).
> [!NOTE]
> You must set a background color with an alpha channel (e.g. `#80ffffff`) in order for the blur effect to be visible.
#### `view.setVisible(visible)`
* `visible` boolean - If false, the view will be hidden from display.

View File

@@ -226,7 +226,16 @@ Returns:
Only defined when the window is being created by a form that set
`target=_blank`.
* `disposition` string - Can be `default`, `foreground-tab`,
`background-tab`, `new-window` or `other`.
`background-tab`, `new-window` or `other`. Corresponds to the manner
an associated link was clicked. See Chromium's
[WindowOpenDisposition](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/window_open_disposition.h).
* `default` - Indicates Chromium deems in-window navigation valid
for a window open call.
* `foreground-tab` - Corresponds to a left click or shift + middle click.
* `background-tab` - Corresponds to a middle click or ctrl/cmd + click.
* `new-window` - Corresponds to a shift + left click.
* `other` - A catch-all for the remaining Chromium dispositions not
handled by Electron.
Emitted _after_ successful creation of a window via `window.open` in the renderer.
Not emitted if the creation of the window is canceled from
@@ -1449,8 +1458,17 @@ Ignore application menu shortcuts while this web contents is focused.
* `url` string - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
* `frameName` string - Name of the window provided in `window.open()`
* `features` string - Comma separated list of window features provided to `window.open()`.
* `disposition` string - Can be `default`, `foreground-tab`, `background-tab`,
`new-window` or `other`.
* `disposition` string - Can be `default`, `foreground-tab`,
`background-tab`, `new-window` or `other`. Corresponds to the manner
an associated link was clicked. See Chromium's
[WindowOpenDisposition](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/window_open_disposition.h).
* `default` - Indicates Chromium deems in-window navigation valid
for a window open call.
* `foreground-tab` - Corresponds to a left click or shift + middle click.
* `background-tab` - Corresponds to a middle click or ctrl/cmd + click.
* `new-window` - Corresponds to a shift + left click.
* `other` - A catch-all for the remaining Chromium dispositions not
handled by Electron.
* `referrer` [Referrer](structures/referrer.md) - The referrer that will be
passed to the new window. May or may not result in the `Referer` header being
sent, depending on the referrer policy.

View File

@@ -14,6 +14,17 @@ This document uses the following convention to categorize breaking changes:
## Planned Breaking API Changes (43.0)
### Behavior Changed: `chrome.scripting` CSS injection matches more fallback frames
Extensions using `chrome.scripting.insertCSS()` or `chrome.scripting.removeCSS()`
now follow Chrome's behavior when Electron cannot match a frame's URL directly,
such as with `about:blank` or `data:` frames. If the extension has access to the
page that created the frame, CSS may now be inserted into or removed from those
fallback frames as well.
Apps or extensions that relied on Electron skipping those frames should narrow their
injection target, frame IDs, or match patterns.
### Behavior Changed: Dialog methods default to Downloads directory
The `defaultPath` option for the following methods now defaults to the user's Downloads folder (or their home directory if Downloads doesn't exist) when not explicitly provided:
@@ -123,6 +134,12 @@ When a cookie is deleted, the change cause remains `explicit`.
When the cookie being set is identical to an existing one (same name, domain, path, and value, with no actual changes), the change cause is `inserted-no-change-overwrite`.
When the value of the cookie being set remains unchanged but some of its attributes are updated, such as the expiration attribute, the change cause will be `inserted-no-value-change-overwrite`.
### Deprecated: `showHiddenFiles` in Dialogs on Linux
This property will still be honored on macOS and Windows, but support on Linux
will be removed in Electron 42. GTK intends for this to be a user choice rather
than an app choice and has removed the API to do this programmatically.
## Planned Breaking API Changes (40.0)
### Deprecated: `clipboard` API access from renderer processes
@@ -136,12 +153,6 @@ your preload script and expose it using the [contextBridge](https://www.electron
Debug symbols for MacOS (dSYM) now use xz compression in order to handle larger file sizes. `dsym.zip` files are now
`dsym.tar.xz` files. End users using debug symbols may need to update their zip utilities.
### Deprecated: `showHiddenFiles` in Dialogs on Linux
This property will still be honored on macOS and Windows, but support on Linux
will be removed in Electron 42. GTK intends for this to be a user choice rather
than an app choice and has removed the API to do this programmatically.
## Planned Breaking API Changes (39.0)
### Deprecated: `--host-rules` command line switch

View File

@@ -171,6 +171,7 @@ auto_filenames = {
"docs/api/structures/web-preferences.md",
"docs/api/structures/web-request-filter.md",
"docs/api/structures/web-source.md",
"docs/api/structures/webauthn-account.md",
"docs/api/structures/window-open-handler-response.md",
"docs/api/structures/window-session-end-event.md",
]
@@ -190,7 +191,6 @@ auto_filenames = {
"../third_party/electron_node/src/base_object_types.h",
"../third_party/electron_node/src/blob_serializer_deserializer-inl.h",
"../third_party/electron_node/src/blob_serializer_deserializer.h",
"../third_party/electron_node/src/builtin_info.h",
"../third_party/electron_node/src/callback_queue-inl.h",
"../third_party/electron_node/src/callback_queue.h",
"../third_party/electron_node/src/cares_wrap.h",
@@ -254,7 +254,6 @@ auto_filenames = {
"../third_party/electron_node/src/inspector/protocol_helper.h",
"../third_party/electron_node/src/inspector/runtime_agent.h",
"../third_party/electron_node/src/inspector/target_agent.h",
"../third_party/electron_node/src/inspector/target_manager.h",
"../third_party/electron_node/src/inspector/tracing_agent.h",
"../third_party/electron_node/src/inspector/worker_agent.h",
"../third_party/electron_node/src/inspector/worker_inspector.h",
@@ -290,7 +289,6 @@ auto_filenames = {
"../third_party/electron_node/src/node_contextify.h",
"../third_party/electron_node/src/node_crypto.h",
"../third_party/electron_node/src/node_debug.h",
"../third_party/electron_node/src/node_diagnostics_channel.h",
"../third_party/electron_node/src/node_dir.h",
"../third_party/electron_node/src/node_dotenv.h",
"../third_party/electron_node/src/node_errors.h",

View File

@@ -48,8 +48,8 @@ filenames = {
"shell/browser/ui/views/opaque_frame_view.h",
"shell/browser/ui/views/caption_button_placeholder_container.cc",
"shell/browser/ui/views/caption_button_placeholder_container.h",
"shell/browser/ui/views/client_frame_view_linux.cc",
"shell/browser/ui/views/client_frame_view_linux.h",
"shell/browser/ui/views/native_frame_view_linux.cc",
"shell/browser/ui/views/native_frame_view_linux.h",
"shell/browser/ui/views/linux_frame_layout.cc",
"shell/browser/ui/views/linux_frame_layout.h",
"shell/common/application_info_linux.cc",
@@ -440,8 +440,6 @@ filenames = {
"shell/browser/microtasks_runner.h",
"shell/browser/native_window.cc",
"shell/browser/native_window.h",
"shell/browser/native_window_features.cc",
"shell/browser/native_window_features.h",
"shell/browser/native_window_observer.h",
"shell/browser/net/asar/asar_file_validator.cc",
"shell/browser/net/asar/asar_file_validator.h",
@@ -566,6 +564,8 @@ filenames = {
"shell/browser/web_view_guest_delegate.h",
"shell/browser/web_view_manager.cc",
"shell/browser/web_view_manager.h",
"shell/browser/webauthn/electron_authenticator_request_client_delegate.cc",
"shell/browser/webauthn/electron_authenticator_request_client_delegate.h",
"shell/browser/webauthn/electron_authenticator_request_delegate.cc",
"shell/browser/webauthn/electron_authenticator_request_delegate.h",
"shell/browser/window_list.cc",
@@ -774,8 +774,6 @@ filenames = {
"shell/browser/extensions/api/resources_private/resources_private_api.h",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.cc",
"shell/browser/extensions/api/runtime/electron_runtime_api_delegate.h",
"shell/browser/extensions/api/scripting/scripting_api.cc",
"shell/browser/extensions/api/scripting/scripting_api.h",
"shell/browser/extensions/api/streams_private/streams_private_api.cc",
"shell/browser/extensions/api/streams_private/streams_private_api.h",
"shell/browser/extensions/api/tabs/tabs_api.cc",

View File

@@ -158,6 +158,8 @@ libcxx_headers = [
"//third_party/libc++/src/include/__algorithm/ranges_set_intersection.h",
"//third_party/libc++/src/include/__algorithm/ranges_set_symmetric_difference.h",
"//third_party/libc++/src/include/__algorithm/ranges_set_union.h",
"//third_party/libc++/src/include/__algorithm/ranges_shift_left.h",
"//third_party/libc++/src/include/__algorithm/ranges_shift_right.h",
"//third_party/libc++/src/include/__algorithm/ranges_shuffle.h",
"//third_party/libc++/src/include/__algorithm/ranges_sort.h",
"//third_party/libc++/src/include/__algorithm/ranges_sort_heap.h",
@@ -215,6 +217,7 @@ libcxx_headers = [
"//third_party/libc++/src/include/__atomic/atomic_lock_free.h",
"//third_party/libc++/src/include/__atomic/atomic_ref.h",
"//third_party/libc++/src/include/__atomic/atomic_sync.h",
"//third_party/libc++/src/include/__atomic/atomic_sync_timed.h",
"//third_party/libc++/src/include/__atomic/atomic_waitable_traits.h",
"//third_party/libc++/src/include/__atomic/check_memory_order.h",
"//third_party/libc++/src/include/__atomic/contention_t.h",
@@ -331,11 +334,14 @@ libcxx_headers = [
"//third_party/libc++/src/include/__config",
"//third_party/libc++/src/include/__config_site.in",
"//third_party/libc++/src/include/__configuration/abi.h",
"//third_party/libc++/src/include/__configuration/attributes.h",
"//third_party/libc++/src/include/__configuration/availability.h",
"//third_party/libc++/src/include/__configuration/compiler.h",
"//third_party/libc++/src/include/__configuration/diagnostic_suppression.h",
"//third_party/libc++/src/include/__configuration/experimental.h",
"//third_party/libc++/src/include/__configuration/hardening.h",
"//third_party/libc++/src/include/__configuration/language.h",
"//third_party/libc++/src/include/__configuration/namespace.h",
"//third_party/libc++/src/include/__configuration/platform.h",
"//third_party/libc++/src/include/__coroutine/coroutine_handle.h",
"//third_party/libc++/src/include/__coroutine/coroutine_traits.h",
@@ -976,6 +982,7 @@ libcxx_headers = [
"//third_party/libc++/src/include/__format/format_to_n_result.h",
"//third_party/libc++/src/include/__format/formatter.h",
"//third_party/libc++/src/include/__format/formatter_bool.h",
"//third_party/libc++/src/include/__format/formatter_bool_impl.h",
"//third_party/libc++/src/include/__format/formatter_char.h",
"//third_party/libc++/src/include/__format/formatter_floating_point.h",
"//third_party/libc++/src/include/__format/formatter_integer.h",
@@ -1053,6 +1060,7 @@ libcxx_headers = [
"//third_party/libc++/src/include/__iterator/aliasing_iterator.h",
"//third_party/libc++/src/include/__iterator/back_insert_iterator.h",
"//third_party/libc++/src/include/__iterator/bounded_iter.h",
"//third_party/libc++/src/include/__iterator/capacity_aware_iterator.h",
"//third_party/libc++/src/include/__iterator/common_iterator.h",
"//third_party/libc++/src/include/__iterator/concepts.h",
"//third_party/libc++/src/include/__iterator/counted_iterator.h",
@@ -1299,6 +1307,7 @@ libcxx_headers = [
"//third_party/libc++/src/include/__ranges/empty_view.h",
"//third_party/libc++/src/include/__ranges/enable_borrowed_range.h",
"//third_party/libc++/src/include/__ranges/enable_view.h",
"//third_party/libc++/src/include/__ranges/enumerate_view.h",
"//third_party/libc++/src/include/__ranges/filter_view.h",
"//third_party/libc++/src/include/__ranges/from_range.h",
"//third_party/libc++/src/include/__ranges/iota_view.h",
@@ -1496,6 +1505,7 @@ libcxx_headers = [
"//third_party/libc++/src/include/__utility/as_lvalue.h",
"//third_party/libc++/src/include/__utility/auto_cast.h",
"//third_party/libc++/src/include/__utility/cmp.h",
"//third_party/libc++/src/include/__utility/constant_wrapper.h",
"//third_party/libc++/src/include/__utility/convert_to_integral.h",
"//third_party/libc++/src/include/__utility/declval.h",
"//third_party/libc++/src/include/__utility/default_three_way_comparator.h",

View File

@@ -30,6 +30,7 @@ const MenuItem = function (this: any, options: any) {
this.overrideProperty('icon');
this.overrideProperty('label', roles.getDefaultLabel(this.role));
this.overrideProperty('accessibilityLabel', '');
this.overrideProperty('sublabel', '');
this.overrideProperty('toolTip', '');
this.overrideProperty('enabled', true);

View File

@@ -57,6 +57,10 @@ Menu.prototype._getLabelForCommandId = function (id) {
return this.commandsMap[id]?.label ?? '';
};
Menu.prototype._getAccessibilityLabelForCommandId = function (id) {
return this.commandsMap[id]?.accessibilityLabel ?? '';
};
Menu.prototype._getSecondaryLabelForCommandId = function (id) {
return this.commandsMap[id]?.sublabel ?? '';
};

View File

@@ -72,8 +72,17 @@ if (process.platform === 'win32') {
}
}
// Map process.exit to app.exit, which quits gracefully.
process.exit = app.exit as () => never;
// Map process.exit to app.exit, which quits gracefully. When called without
// an explicit code, fall back to process.exitCode like Node.js does.
process.exit = ((code: number | string | undefined | null) => {
// Refs https://github.com/nodejs/node/blob/fc192ee030ee076b948ce7d9d72cba6c101989b8/lib/internal/process/per_thread.js#L229-L252
if (code !== undefined) {
// Node.js handles any string to number conversion here for us
process.exitCode = code;
}
app.exit(process.exitCode || 0);
}) as typeof process.exit;
// Load the RPC server.
require('@electron/internal/browser/rpc-server');

View File

@@ -96,7 +96,7 @@ function extractFile(zipPath) {
}
function getPlatformPath() {
const platform = process.env.npm_config_platform || os.platform();
const platform = process.env.ELECTRON_INSTALL_PLATFORM || process.env.npm_config_platform || os.platform();
switch (platform) {
case 'mas':

View File

@@ -21,7 +21,7 @@
"@types/semver": "^7.5.8",
"@types/stream-json": "^1.7.8",
"@types/temp": "^0.9.4",
"@xmldom/xmldom": "^0.8.12",
"@xmldom/xmldom": "^0.8.13",
"buffer": "^6.0.3",
"chalk": "^4.1.0",
"check-for-leaks": "^1.2.1",

View File

@@ -152,4 +152,5 @@ chore_register_node_as_a_dynamic_trace_category_prefix.patch
fix_make_macos_text_replacement_work_on_contenteditable.patch
fix_allow_reentrancy_on_downloadmanagerimpl_observer_list.patch
build_gn_arg_to_support_linker_wrapper_script_on_windows.patch
chore_exclude_upstream_scripting_api_when_building_electron.patch
fix_use_bundled_devtools_frontend_url_for_remote_debugging.patch
fix_constrain_allowuniversalaccessfromfileurls_to_file_origins_in.patch

View File

@@ -6,12 +6,12 @@ Subject: allow disabling blink scheduler throttling per RenderView
This allows us to disable throttling for hidden windows.
diff --git a/content/browser/renderer_host/navigation_controller_impl_unittest.cc b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
index 913c92c8b224dc749c25864311d401b51b41d9b9..5c9772ede144b35792c59c4173935813cad7d0dd 100644
index bdbcb809c4d0081aca93c4ed92912604abbf62bc..8c70713583e8a37201b6e037dae2e89838d382a6 100644
--- a/content/browser/renderer_host/navigation_controller_impl_unittest.cc
+++ b/content/browser/renderer_host/navigation_controller_impl_unittest.cc
@@ -168,6 +168,12 @@ class MockPageBroadcast : public blink::mojom::PageBroadcast {
(bool supports_draggable_regions),
(override));
@@ -170,6 +170,12 @@ class MockPageBroadcast : public blink::mojom::PageBroadcast {
MOCK_METHOD(void, UpgradePrerenderUntilScriptToFullPrerender, (), (override));
+ MOCK_METHOD(
+ void,
@@ -80,10 +80,10 @@ index 782bed0fdc08d57eceb059f398f253fab9233b1b..f1ab5b981ea68af1b11313e67f2c5060
// This interface should only be implemented inside content.
friend class RenderViewHostImpl;
diff --git a/content/test/test_page_broadcast.h b/content/test/test_page_broadcast.h
index 4c8d44cdb2fde8e174b78aee7defb980651da18e..f8bf421b5b32af4cd197cbf23f4bd281c3a12514 100644
index 7f6223791c492d56412df57639ef988001b313fe..c6abcd08228a4fb0d9bc1bd4e7738864a281dd2c 100644
--- a/content/test/test_page_broadcast.h
+++ b/content/test/test_page_broadcast.h
@@ -52,6 +52,7 @@ class TestPageBroadcast : public blink::mojom::PageBroadcast {
@@ -53,6 +53,7 @@ class TestPageBroadcast : public blink::mojom::PageBroadcast {
void UpdateColorProviders(
const blink::ColorProviderColorMaps& color_provider_colors) override;
void SetSupportsDraggableRegions(bool supports_draggable_regions) override;
@@ -92,10 +92,10 @@ index 4c8d44cdb2fde8e174b78aee7defb980651da18e..f8bf421b5b32af4cd197cbf23f4bd281
mojo::AssociatedReceiver<blink::mojom::PageBroadcast> receiver_;
};
diff --git a/third_party/blink/public/mojom/page/page.mojom b/third_party/blink/public/mojom/page/page.mojom
index b00bc8a8a5044fbf46f627f9db56cea7f09d7ef6..114c3a4522d11c1348f681af500c487ccd97eea9 100644
index de0291118714296eb3a3114478fa4dbef15be06c..6c7012c00df45fb057113501b756ef3a57ee0c38 100644
--- a/third_party/blink/public/mojom/page/page.mojom
+++ b/third_party/blink/public/mojom/page/page.mojom
@@ -180,4 +180,7 @@ interface PageBroadcast {
@@ -186,4 +186,7 @@ interface PageBroadcast {
// Indicates that the page's main frame should collect draggable regions set
// using the app-region CSS property.
SetSupportsDraggableRegions(bool supports_draggable_regions);
@@ -116,7 +116,7 @@ index 932658273154ef2e022358e493a8e7c00c86e732..57bbfb5cde62c9496c351c861880a189
// Visibility -----------------------------------------------------------
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 135110e1ea397d52623bd1cd71759a2a014b454a..42b9ec3ca008be91318c843aba66a7a2e1c75ea5 100644
index 9b650e7079869dd074d24fd2e0f0d807af114c1f..756a6531f5db61adb5b17ce261719f29a9b6183a 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -2468,6 +2468,10 @@ void WebViewImpl::SetPageLifecycleStateInternal(
@@ -130,7 +130,7 @@ index 135110e1ea397d52623bd1cd71759a2a014b454a..42b9ec3ca008be91318c843aba66a7a2
bool storing_in_bfcache = new_state->is_in_back_forward_cache &&
!old_state->is_in_back_forward_cache;
bool restoring_from_bfcache = !new_state->is_in_back_forward_cache &&
@@ -4016,10 +4020,23 @@ PageScheduler* WebViewImpl::Scheduler() const {
@@ -4022,10 +4026,23 @@ PageScheduler* WebViewImpl::Scheduler() const {
return GetPage()->GetPageScheduler();
}
@@ -155,10 +155,10 @@ index 135110e1ea397d52623bd1cd71759a2a014b454a..42b9ec3ca008be91318c843aba66a7a2
// Do not throttle if the page should be painting.
bool is_visible =
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.h b/third_party/blink/renderer/core/exported/web_view_impl.h
index f638ff2647fdeee4e1e180e1394b49a91227022b..698d1f78a0b2cabee59e3a7f0619935bea010b36 100644
index 20212a57bb4dc13d9e384dffa5c564ed64655574..dd9e21d3e73f6ff14659b1e1fe7d837c5410695d 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.h
+++ b/third_party/blink/renderer/core/exported/web_view_impl.h
@@ -437,6 +437,7 @@ class CORE_EXPORT WebViewImpl final : public WebView,
@@ -438,6 +438,7 @@ class CORE_EXPORT WebViewImpl final : public WebView,
LocalDOMWindow* PagePopupWindow() const;
PageScheduler* Scheduler() const override;
@@ -166,7 +166,7 @@ index f638ff2647fdeee4e1e180e1394b49a91227022b..698d1f78a0b2cabee59e3a7f0619935b
void SetVisibilityState(mojom::blink::PageVisibilityState visibility_state,
bool is_initial_state) override;
mojom::blink::PageVisibilityState GetVisibilityState() override;
@@ -927,6 +928,8 @@ class CORE_EXPORT WebViewImpl final : public WebView,
@@ -928,6 +929,8 @@ class CORE_EXPORT WebViewImpl final : public WebView,
// If true, we send IPC messages when |preferred_size_| changes.
bool send_preferred_size_changes_ = false;

View File

@@ -11,10 +11,10 @@ This patch can (and should) be removed when we can prevent those symbols
from being stripped in the release build.
diff --git a/build/config/compiler/compiler.gni b/build/config/compiler/compiler.gni
index d9cbf64ce68e21ea06a6bf2c213daa7d67b931f5..150736bcabec57bd262e0ebe1649096ba656b386 100644
index 36fe79942794239edd00e7be0d94c33892acc5cc..68e4d13e785333b3bbd906f18b164686b67d223e 100644
--- a/build/config/compiler/compiler.gni
+++ b/build/config/compiler/compiler.gni
@@ -151,7 +151,7 @@ declare_args() {
@@ -154,7 +154,7 @@ declare_args() {
# Chrome's clang. crbug.com/1033839
use_thin_lto =
is_cfi || (is_clang && is_official_build && chrome_pgo_phase != 1 &&

View File

@@ -11,7 +11,7 @@ if we ever align our .pak file generation with Chrome we can remove this
patch.
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 48f28a61eac4b31c3d9be62100c00cadff84174c..6c2632d85db3d2115bc0c0fc3dc6970e680877c2 100644
index 171faeaa1c3ba08cc0cd166b492765c8204bb674..a4c8d2d33823e4825b80854db199d2f6542c3e2d 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -201,6 +201,12 @@ if (!is_android && !is_mac) {
@@ -28,10 +28,10 @@ index 48f28a61eac4b31c3d9be62100c00cadff84174c..6c2632d85db3d2115bc0c0fc3dc6970e
":chrome_dll",
":chrome_exe_version",
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
index 684cc6c47f61e02831cfdb977b1719a0b0f899a5..ddbb1f4b58d8bee0b80410294c5a54d432d65a4f 100644
index a824761a0bf0a3d827cc78bc644e8da877d603d3..dbf5e98c588a32265f65fa569f261129800c3375 100644
--- a/chrome/test/BUILD.gn
+++ b/chrome/test/BUILD.gn
@@ -7745,6 +7745,10 @@ test("unit_tests") {
@@ -7761,6 +7761,10 @@ test("unit_tests") {
"//chrome/notification_helper",
]
@@ -42,7 +42,7 @@ index 684cc6c47f61e02831cfdb977b1719a0b0f899a5..ddbb1f4b58d8bee0b80410294c5a54d4
deps += [
"//chrome:other_version",
"//chrome//services/util_win:unit_tests",
@@ -8683,6 +8687,10 @@ test("unit_tests") {
@@ -8696,6 +8700,10 @@ test("unit_tests") {
"../browser/performance_manager/policies/background_tab_loading_policy_unittest.cc",
]
@@ -53,7 +53,7 @@ index 684cc6c47f61e02831cfdb977b1719a0b0f899a5..ddbb1f4b58d8bee0b80410294c5a54d4
sources += [
# The importer code is not used on Android.
"../common/importer/firefox_importer_utils_unittest.cc",
@@ -8726,7 +8734,7 @@ test("unit_tests") {
@@ -8739,7 +8747,7 @@ 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

@@ -7,10 +7,10 @@ Build libc++ as static library to compile and pass
nan tests
diff --git a/buildtools/third_party/libc++/BUILD.gn b/buildtools/third_party/libc++/BUILD.gn
index ce38758afaa191b394a014a68aded12417aa72a1..d846b89156182073cff1aaac6cd32888a73867b2 100644
index 62547f7df1860fdb49c3c09b9976da12743f5e24..9ac0b985a8f4175d5f0e8aefff002e0dc693fa47 100644
--- a/buildtools/third_party/libc++/BUILD.gn
+++ b/buildtools/third_party/libc++/BUILD.gn
@@ -456,6 +456,7 @@ target(libcxx_target_type, "libc++") {
@@ -477,6 +477,7 @@ target(libcxx_target_type, "libc++") {
# need to explicitly depend on libc++.
visibility = [
"//build/config:common_deps",

View File

@@ -15,7 +15,7 @@ References:
* third_party/libc++/src/include/__configuration/abi.h
diff --git a/buildtools/third_party/libc++/__config_site b/buildtools/third_party/libc++/__config_site
index c94ace80e45de876a175506b66d4b96dba2f7c02..359f8688158fd5b57e819893047524dbbb1b1b45 100644
index a9ab1614b67e2df7b379c1d8a4147265a15747db..f1c60c7d0a320e5a1e9a9e454ef0e084c594258c 100644
--- a/buildtools/third_party/libc++/__config_site
+++ b/buildtools/third_party/libc++/__config_site
@@ -18,7 +18,11 @@

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 b4b82c13852232443f15785a1e8f2820c20fa0ca..80d3b463984813ac1459b02fac41ee995b437353 100644
index 8b04c911b759c45b94dfb0c5c859e9ddbf012937..944bb9ee39682f4e623477f98b1c407b0c6b0d6a 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -10262,6 +10262,7 @@ void RenderFrameHostImpl::CreateNewWindow(
@@ -10268,6 +10268,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,
@@ -77,7 +77,7 @@ index a2566982ff81db5166e41243232e23af94dc3c05..ff5c410e78fb171963122f8a24802b7b
// Operation result when the renderer asks the browser to create a new window.
diff --git a/content/public/browser/content_browser_client.cc b/content/public/browser/content_browser_client.cc
index 0713f0cbbbd93be954758f0b1ddb20b9f9701228..9d2e8a70a4307f1d055b356e6bccd98f3e56109c 100644
index 4aed38dd5f1c1d95d04ab49dd6937114788dcb70..d98b527556464a60d9f0324410824ae1c468764a 100644
--- a/content/public/browser/content_browser_client.cc
+++ b/content/public/browser/content_browser_client.cc
@@ -858,6 +858,8 @@ bool ContentBrowserClient::CanCreateWindow(
@@ -90,10 +90,10 @@ index 0713f0cbbbd93be954758f0b1ddb20b9f9701228..9d2e8a70a4307f1d055b356e6bccd98f
bool opener_suppressed,
bool* no_javascript_access) {
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 124994cc22f2923247b831e30c3241751c7b4c92..9d164c9386fe1906216db656e3972ef5dc50c583 100644
index 5ce463ef59a60feb82a608e60744d97934f8e604..972d1966d2d4c86e3258bc8d48009fbaf9086d79 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -204,6 +204,7 @@ class NetworkService;
@@ -205,6 +205,7 @@ class NetworkService;
class TrustedURLLoaderHeaderClient;
} // namespace mojom
struct ResourceRequest;
@@ -101,7 +101,7 @@ index 124994cc22f2923247b831e30c3241751c7b4c92..9d164c9386fe1906216db656e3972ef5
} // namespace network
namespace sandbox {
@@ -1411,6 +1412,8 @@ class CONTENT_EXPORT ContentBrowserClient {
@@ -1412,6 +1413,8 @@ class CONTENT_EXPORT ContentBrowserClient {
const std::string& frame_name,
WindowOpenDisposition disposition,
const blink::mojom::WindowFeatures& features,

View File

@@ -1,34 +0,0 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Electron Scripts <scripts@electron>
Date: Sat, 25 Apr 2026 21:10:03 -0500
Subject: chore: exclude upstream scripting API when building Electron
Electron provides its own implementation of the Scripting API at
shell/browser/extensions/api/scripting/ that includes Electron-specific
modifications. CL 7784831 moved the upstream implementation from
//chrome to //extensions, which caused it to be transitively linked
into Electron, resulting in duplicate symbols.
Exclude the upstream sources when is_electron_build is set.
diff --git a/extensions/browser/api/scripting/BUILD.gn b/extensions/browser/api/scripting/BUILD.gn
index 14d18bfbc36673f3eb9ffe0777d34ddd2163af0d..cff84c1a9e713e2845b2331913dde9be730305b1 100644
--- a/extensions/browser/api/scripting/BUILD.gn
+++ b/extensions/browser/api/scripting/BUILD.gn
@@ -12,6 +12,16 @@ source_set("scripting") {
"scripting_api.cc",
"scripting_api.h",
]
+
+ # Electron provides its own scripting API implementation in
+ # shell/browser/extensions/api/scripting/.
+ if (is_electron_build) {
+ sources -= [
+ "scripting_api.cc",
+ "scripting_api.h",
+ ]
+ }
+
public_deps = [
"//extensions/browser:browser_sources",
"//extensions/common/api",

View File

@@ -6,7 +6,7 @@ Subject: disable_hidden.patch
Electron uses this to disable background throttling for hidden windows.
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index 91c1bd0ee59e6f6819434631de3ed8b6b290540f..edd98668b9e8220ec26c901f69b8d3bee866aba5 100644
index de47fa819ae4012d893bb561b4507a3e847c8b0b..91f5aa62f256a061199e7091069607ceafc02e0d 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -828,6 +828,10 @@ void RenderWidgetHostImpl::WasHidden() {
@@ -21,10 +21,10 @@ index 91c1bd0ee59e6f6819434631de3ed8b6b290540f..edd98668b9e8220ec26c901f69b8d3be
// Prompts should remain open and functional across tab switches.
if (!delegate_ || !delegate_->IsWaitingForPointerLockPrompt(this)) {
diff --git a/content/browser/renderer_host/render_widget_host_impl.h b/content/browser/renderer_host/render_widget_host_impl.h
index ae9afcf250cd6de6fcf83a19f6be2a9294e020af..7e88b070bfaae3d6430fe66fdc5f1070b15ac68c 100644
index ac87b21a1cec649d01adbe92e08ef4bd5870336e..0b94b2bcf0b0fe643328a37ab8e8b29d8477d0f4 100644
--- a/content/browser/renderer_host/render_widget_host_impl.h
+++ b/content/browser/renderer_host/render_widget_host_impl.h
@@ -1061,6 +1061,8 @@ class CONTENT_EXPORT RenderWidgetHostImpl
@@ -1063,6 +1063,8 @@ class CONTENT_EXPORT RenderWidgetHostImpl
base::TimeDelta GetHungRendererDelayForTesting();

View File

@@ -33,7 +33,7 @@ index 0ab8187b0db8ae6db46d81738f653a2bc4c566f6..de3d55e85c22317f7f9375eb94d0d5d4
} // namespace net
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index 4bf222c4e6688384c034ef16170ad4cd64c379b0..97e59f9453659a5bd6868cfceaaaa3ddcfe0e5c2 100644
index 2cd26265e717998c690aa277ff6ca6aac7b7ad0d..78661bd270ae364b1a85035df8a7c56c42752919 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -2013,6 +2013,13 @@ void NetworkContext::SetNetworkConditions(
@@ -51,7 +51,7 @@ index 4bf222c4e6688384c034ef16170ad4cd64c379b0..97e59f9453659a5bd6868cfceaaaa3dd
// This may only be called on NetworkContexts created with the constructor
// that calls MakeURLRequestContext().
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 18a5062bac18be40ecd9e4de12ca4556802403c7..4ef2d2034a32bc1b70797bc5897fa354ac9e8c84 100644
index b4239d2ed5632e9c93f3b395091344164b8468cb..4e9927a5681c7276c747e1351788a44f9f3caada 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -334,6 +334,7 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
@@ -63,7 +63,7 @@ index 18a5062bac18be40ecd9e4de12ca4556802403c7..4ef2d2034a32bc1b70797bc5897fa354
void SetEnableReferrers(bool enable_referrers) override;
#if BUILDFLAG(IS_CT_SUPPORTED)
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 88ac7593a8317dc646a128cce81f163554407bbc..f6bb0b757b65002ff86fd1b3ba8f117d76c02344 100644
index a7bb27ab245729413b5624c382115a8072e764a5..cf59da2795f425ebebb6e16ff72e414c4f65c8fc 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -1309,6 +1309,9 @@ interface NetworkContext {
@@ -77,7 +77,7 @@ index 88ac7593a8317dc646a128cce81f163554407bbc..f6bb0b757b65002ff86fd1b3ba8f117d
SetAcceptLanguage(string new_accept_language);
diff --git a/services/network/test/test_network_context.h b/services/network/test/test_network_context.h
index af46691e72faa7e1c59de51b099fa7923eb89ec6..e1077e04b223adb4d25f12fcef2184f76c8b023c 100644
index 69a62289cf6a9eff3265b16ea4fdf3bee7efbc7d..8fa03b0cceee589cc00a781cb1ec2ca6edcce5b4 100644
--- a/services/network/test/test_network_context.h
+++ b/services/network/test/test_network_context.h
@@ -157,6 +157,7 @@ class TestNetworkContext : public mojom::NetworkContext {

View File

@@ -15,7 +15,7 @@ Ideally we could add an embedder observer pattern here but that can be
done in future work.
diff --git a/third_party/blink/renderer/core/exported/web_view_impl.cc b/third_party/blink/renderer/core/exported/web_view_impl.cc
index 42b9ec3ca008be91318c843aba66a7a2e1c75ea5..932e5f8b417a75df41eee430a00564f629b0e4f1 100644
index 756a6531f5db61adb5b17ce261719f29a9b6183a..3605673e00c0ad5b54f0a22c7e739c9bcb07f5c5 100644
--- a/third_party/blink/renderer/core/exported/web_view_impl.cc
+++ b/third_party/blink/renderer/core/exported/web_view_impl.cc
@@ -1848,6 +1848,8 @@ void WebView::ApplyWebPreferences(const web_pref::WebPreferences& prefs,

View File

@@ -46,7 +46,7 @@ index 1c01e4d6699da8760e074a5825784e139ed58fd4..c678a3df0b9e5a8c9e0bd99c1aa540b2
'internal-forced-visited-'):
internal_visited_order = 0
diff --git a/third_party/blink/renderer/core/css/css_properties.json5 b/third_party/blink/renderer/core/css/css_properties.json5
index e6731ee14be233e023768fc8433a1cade4e12284..4d1b79641cb465225b56b8f7d261ba783762232e 100644
index 7e78dc9d943ab3e9a77e41000e27f002ccbfdb64..fc910bf0c2720424604a4fc653e3223b54ecdb38 100644
--- a/third_party/blink/renderer/core/css/css_properties.json5
+++ b/third_party/blink/renderer/core/css/css_properties.json5
@@ -9787,6 +9787,27 @@
@@ -91,7 +91,7 @@ index 19cf78803d61a33f08d023bef8799f18c52b6fe3..9f267dbb92ae6496da8f0f14e94d90ea
return a.EmptyCells() == b.EmptyCells();
case CSSPropertyID::kFill:
diff --git a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
index 25028c2b98dc45a1f10feca152f1f2d7524ffbe7..392e343d97711f9909a8002f68e2d29732aa2c73 100644
index dc3bf090a63c79fd59461f55d0aac99c2f8aac85..1604aa8993368dd825c473d77c4f4b6baa2c907f 100644
--- a/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
+++ b/third_party/blink/renderer/core/css/properties/longhands/longhands_custom.cc
@@ -12380,5 +12380,36 @@ const CSSValue* InternalEmptyLineHeight::ParseSingleValue(
@@ -203,7 +203,7 @@ index 19cda703154dab9397827ab6ea66c2ca446c644d..dd5943c511886f4e39b2e7f10e67e60f
return result;
}
diff --git a/third_party/blink/renderer/platform/BUILD.gn b/third_party/blink/renderer/platform/BUILD.gn
index b98b5459691c91367933e352a32a590a92b190fe..5b1b9036192ee6db2196c7dbadf3b0344a37bfc8 100644
index e9b598083915794b32733fa576c5733834596854..3349bee194d7e4d6e5f3913d1f2b90ea13275166 100644
--- a/third_party/blink/renderer/platform/BUILD.gn
+++ b/third_party/blink/renderer/platform/BUILD.gn
@@ -1677,6 +1677,8 @@ component("platform") {
@@ -314,7 +314,7 @@ index 398fc8badaaf1dddc3183211c9302460393da85f..b35c8261b27a2d5c5f6bd84b606925c9
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 4112cd5508122424bf1a87ba3a670352bfb9a3fa..23ac385af23d58832467fdb59db9b9b7f6dee881 100644
index fed46bb94e8a5753a4309ec4ad3d17390d97e8d6..0e806f15414eb2fc60d539d6a16dcc3e8eb77442 100644
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
@@ -215,6 +215,10 @@

View File

@@ -90,7 +90,7 @@ index 8af69cac78b7488d28f1f05ccb174793fe5148cd..9f74e511c263d147b5fbe81fe100d217
private:
const HWND hwnd_;
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index c1be2f1a8dc4d834ffa2e5944f7dd2753740a4c6..982327d24f0c3d1a4b90bea9a0e93c684aeab17f 100644
index 44072bd19a908aa0604e440e89c303cd8044f342..820c23869139d4293c26b6a854b7c08576c07a3c 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -178,6 +178,8 @@ viz_component("service") {

View File

@@ -0,0 +1,142 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Calvin Watford <cwatford@makenotion.com>
Date: Sun, 12 Apr 2026 21:24:57 -0700
Subject: fix: constrain AllowUniversalAccessFromFileURLs to file: origins in
agent assignment
HasPotentialUniversalAccessPrivilege() was treating the
AllowUniversalAccessFromFileURLs setting as unconditional, returning
true for all origins regardless of scheme. This caused non-file origins
(http://, https://, and custom schemes) to lose their browser-provided
AgentClusterKey when the setting was enabled, overriding it with a
universal file agent key and routing them to universal_access_agent_
which carries no key.
Electron enables AllowUniversalAccessFromFileURLs in all renderers via
the grant_file_protocol_extra_privileges fuse (on by default). After
https://crrev.com/c/7079680 moved cross-origin isolation status to the
renderer's per-context agent cluster key, this caused
self.crossOriginIsolated to return false even with COOP + COEP headers
correctly set on non-file origins.
The fix splits the single has_potential_universal_access_privilege
boolean in WindowAgentFactory::GetAgentForAgentClusterKey() into two
separate parameters:
- web_security_disabled: applies to all origins when web security is
off (--disable-web-security, --run-web-tests).
- allow_universal_access_from_file_urls: only takes effect for file:
scheme origins (those with IsUniversalFileAgent() agent cluster
keys), preserving their existing routing to universal_access_agent_.
This aligns the agent assignment logic with the origin privilege
granting code in the same file (DocumentLoader::CalculateOrigin), which
already correctly gates AllowUniversalAccessFromFileURLs behind
origin->IsLocal().
This patch should be upstreamed, with updates to surrounding doc
comments + tests.
Upstream CL: https://chromium-review.googlesource.com/c/chromium/src/+/7795303
Chromium bug: https://crbug.com/505299810
diff --git a/third_party/blink/renderer/core/execution_context/window_agent_factory.cc b/third_party/blink/renderer/core/execution_context/window_agent_factory.cc
index 86ce2d493dc833dd1601787fc042b8a4a612fc2e..b9bd1f459f240401d7a6b0bce03cf82b6b54e9fc 100644
--- a/third_party/blink/renderer/core/execution_context/window_agent_factory.cc
+++ b/third_party/blink/renderer/core/execution_context/window_agent_factory.cc
@@ -20,19 +20,27 @@ WindowAgentFactory::WindowAgentFactory(
AgentGroupScheduler& agent_group_scheduler)
: agent_group_scheduler_(agent_group_scheduler) {}
+WindowAgent* WindowAgentFactory::GetOrCreateUniversalAccessAgent() {
+ if (!universal_access_agent_) {
+ universal_access_agent_ =
+ MakeGarbageCollected<WindowAgent>(*agent_group_scheduler_);
+ }
+ return universal_access_agent_.Get();
+}
+
WindowAgent* WindowAgentFactory::GetAgentForAgentClusterKey(
- bool has_potential_universal_access_privilege,
+ bool is_web_security_disabled,
+ bool allow_universal_access_from_file_urls,
const AgentClusterKey& agent_cluster_key) {
- if (has_potential_universal_access_privilege) {
- if (!universal_access_agent_) {
- universal_access_agent_ =
- MakeGarbageCollected<WindowAgent>(*agent_group_scheduler_);
- }
- return universal_access_agent_.Get();
+ if (is_web_security_disabled) {
+ return GetOrCreateUniversalAccessAgent();
}
// For `file:` scheme origins.
if (agent_cluster_key.IsUniversalFileAgent()) {
+ if (allow_universal_access_from_file_urls) {
+ return GetOrCreateUniversalAccessAgent();
+ }
if (!file_url_agent_) {
file_url_agent_ = MakeGarbageCollected<WindowAgent>(
*agent_group_scheduler_, agent_cluster_key);
diff --git a/third_party/blink/renderer/core/execution_context/window_agent_factory.h b/third_party/blink/renderer/core/execution_context/window_agent_factory.h
index b5464975a3c365a29e342b0dd57a8a73a311924a..72ebd275330e61a8abebfe6d2c06f42544a83bec 100644
--- a/third_party/blink/renderer/core/execution_context/window_agent_factory.h
+++ b/third_party/blink/renderer/core/execution_context/window_agent_factory.h
@@ -40,12 +40,15 @@ class WindowAgentFactory final : public GarbageCollected<WindowAgentFactory> {
// * --run-web-tests is set,
// * or, the Blink instance is running for Android WebView.
WindowAgent* GetAgentForAgentClusterKey(
- bool has_potential_universal_access_privilege,
+ bool is_web_security_disabled,
+ bool allow_universal_access_from_file_urls,
const AgentClusterKey& agent_cluster_key);
void Trace(Visitor*) const;
private:
+ WindowAgent* GetOrCreateUniversalAccessAgent();
+
// Use a shared instance of Agent for all frames if a frame may have the
// universal access privilege.
WeakMember<WindowAgent> universal_access_agent_;
diff --git a/third_party/blink/renderer/core/loader/document_loader.cc b/third_party/blink/renderer/core/loader/document_loader.cc
index 2344e1d62f4a2d7fdf139b64b6c71ede1e3094ce..f79f870408a71e107933219f30ea6dc3afaa8716 100644
--- a/third_party/blink/renderer/core/loader/document_loader.cc
+++ b/third_party/blink/renderer/core/loader/document_loader.cc
@@ -2600,22 +2600,16 @@ bool ShouldReuseDOMWindow(LocalDOMWindow* window,
return *window_coi_key == *navigation_coi_key;
}
-namespace {
-
-bool HasPotentialUniversalAccessPrivilege(LocalFrame* frame) {
- return !frame->GetSettings()->GetWebSecurityEnabled() ||
- frame->GetSettings()->GetAllowUniversalAccessFromFileURLs();
-}
-
-} // namespace
-
WindowAgent* GetWindowAgentForAgentClusterKey(
LocalFrame* frame,
const AgentClusterKey& agent_cluster_key) {
// TODO(keishi): Also check if AllowUniversalAccessFromFileURLs might
// dynamically change.
+ Settings* settings = frame->GetSettings();
return frame->window_agent_factory().GetAgentForAgentClusterKey(
- HasPotentialUniversalAccessPrivilege(frame), agent_cluster_key);
+ /*is_web_security_disabled=*/!settings->GetWebSecurityEnabled(),
+ /*allow_universal_access_from_file_urls=*/
+ settings->GetAllowUniversalAccessFromFileURLs(), agent_cluster_key);
}
// Inheriting cases use their agent's AgentClusterKey value, which is set
@@ -2716,7 +2710,7 @@ void DocumentLoader::InitializeWindow(Document* owner_document) {
// WindowAgentFactory::GetAgentForOrigin(), as the two conditions below hand
// out universal WindowAgent objects, and thus override the AgentClusterKey
// provided by the browser process.
- } else if (HasPotentialUniversalAccessPrivilege(frame_.Get()) ||
+ } else if (!frame_->GetSettings()->GetWebSecurityEnabled() ||
security_origin->IsLocal()) {
// In this case we either have AllowUniversalAccessFromFileURLs enabled, or
// WebSecurity is disabled, or it's a local scheme such as file://; any of

View File

@@ -28,10 +28,10 @@ The patch should be removed in favor of either:
Upstream bug https://bugs.chromium.org/p/chromium/issues/detail?id=1081397.
diff --git a/content/browser/renderer_host/navigation_request.cc b/content/browser/renderer_host/navigation_request.cc
index 6eb61524c22e9fd60ebfd9cf6484622ed3ab28d4..62b5787e4bc3a9932125950b10ae6d3d0722a70c 100644
index 764142259d94c2ed32f05e3998b6be5bf651299a..4bce504ef8fbf2dda21b5c043496a22f48365a72 100644
--- a/content/browser/renderer_host/navigation_request.cc
+++ b/content/browser/renderer_host/navigation_request.cc
@@ -11958,6 +11958,11 @@ url::Origin NavigationRequest::GetOriginForURLLoaderFactoryUnchecked() {
@@ -11946,6 +11946,11 @@ url::Origin NavigationRequest::GetOriginForURLLoaderFactoryUnchecked() {
target_rph_id);
}

View File

@@ -11,10 +11,10 @@ This patch should be upstreamed as a conditional revert of the logic in desktop
vs mobile runtimes. i.e. restore the old logic only on desktop platforms
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index c71887ba0658c98b7a3cf80064efb65f778ff426..ddafdbe57d3ba8f178f8a193a4ae1b59d1b47edf 100644
index a5c13ea50845fede85741de7a6f3b1c327698110..50edc218dc16c85c3c870c744b9c14936a0e99d2 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -2165,9 +2165,8 @@ RenderWidgetHostImpl::GetWidgetInputHandler() {
@@ -2173,9 +2173,8 @@ RenderWidgetHostImpl::GetWidgetInputHandler() {
void RenderWidgetHostImpl::NotifyScreenInfoChanged() {
// The resize message (which may not happen immediately) will carry with it
// the screen info as well as the new size (if the screen has changed scale

View File

@@ -0,0 +1,74 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Om Ghante <omghante@users.noreply.github.com>
Date: Tue, 21 Apr 2026 23:30:00 +0530
Subject: fix: add ShouldUseBundledFrontendResources delegate for remote
debugging
Adds a new `ShouldUseBundledFrontendResources()` virtual method to
`DevToolsManagerDelegate` that embedders can override to control whether
the bundled DevTools frontend URL is used in the `/json` response,
regardless of `CHROMIUM_GIT_REVISION`.
This is needed because embedders like Electron ship bundled frontend
resources, but the remote frontend on the CDN may not be available for
their custom Chromium revisions or may expire before their extended
release cycles end.
Without this, `GetFrontendURLInternal()` generates a
`devtoolsFrontendUrl` pointing to
`chrome-devtools-frontend.appspot.com`, which returns 404 for
embedder-specific Chromium builds, causing Chrome's `chrome://inspect`
page to show an empty DevTools window.
The default implementation returns `false` to preserve existing
behavior for Chrome and other embedders.
Fixes: https://github.com/electron/electron/issues/51035
diff --git a/content/browser/devtools/devtools_http_handler.cc b/content/browser/devtools/devtools_http_handler.cc
index fbfe1abebb19119387d55a1296aba5fd1ca7d99b..2c2d263bc026a0b63010498fb02d9bb897852bc8 100644
--- a/content/browser/devtools/devtools_http_handler.cc
+++ b/content/browser/devtools/devtools_http_handler.cc
@@ -544,7 +544,8 @@ std::string DevToolsHttpHandler::GetFrontendURLInternal(
const std::string& host) {
std::string frontend_url;
const std::string git_revision = CHROMIUM_GIT_REVISION;
- if (git_revision == kMissingGitRevision &&
+ if ((git_revision == kMissingGitRevision ||
+ delegate_->ShouldUseBundledFrontendResources()) &&
delegate_->HasBundledFrontendResources()) {
frontend_url = "/devtools/inspector.html";
} else {
diff --git a/content/public/browser/devtools_manager_delegate.cc b/content/public/browser/devtools_manager_delegate.cc
index 748e96014ce6144b38a8452774d520503721e59e..7cdeab0c1de9c6bbd400afd434b1603318af0c2e 100644
--- a/content/public/browser/devtools_manager_delegate.cc
+++ b/content/public/browser/devtools_manager_delegate.cc
@@ -111,6 +111,10 @@ bool DevToolsManagerDelegate::HasBundledFrontendResources() {
return false;
}
+bool DevToolsManagerDelegate::ShouldUseBundledFrontendResources() {
+ return false;
+}
+
bool DevToolsManagerDelegate::IsBrowserTargetDiscoverable() {
return false;
}
diff --git a/content/public/browser/devtools_manager_delegate.h b/content/public/browser/devtools_manager_delegate.h
index 1a053f63d3f2c16dd24a59537d5f381f55eb1ae7..78f8d70e0e8b56cc5a2d9f28e33d78e6a161aecc 100644
--- a/content/public/browser/devtools_manager_delegate.h
+++ b/content/public/browser/devtools_manager_delegate.h
@@ -151,6 +151,13 @@ class CONTENT_EXPORT DevToolsManagerDelegate {
// Returns whether frontend resources are bundled within the binary.
virtual bool HasBundledFrontendResources();
+ // Returns whether the bundled frontend resources should be preferred over
+ // the remote frontend served from the CDN. Embedders that ship bundled
+ // frontend resources and whose Chromium revisions may not be available on
+ // the CDN (or may expire before their release cycle ends) should override
+ // this to return true.
+ virtual bool ShouldUseBundledFrontendResources();
+
// Makes browser target easily discoverable for remote debugging.
// This should only return true when remote debugging endpoint is not
// accessible by the web (for example in Chrome for Android where it is

View File

@@ -20,10 +20,10 @@ index 9e1cfd02a369d865d24aa95d28b2597eec9dc614..63bd6220e49e933fca9258e1c01ab5c2
}
diff --git a/content/public/browser/content_browser_client.h b/content/public/browser/content_browser_client.h
index 9d164c9386fe1906216db656e3972ef5dc50c583..8305651861ad5978cc91763da8b99da2c07adfd5 100644
index 972d1966d2d4c86e3258bc8d48009fbaf9086d79..d5ead99c3efd2f0ca5b2252e3971c48a45c0c282 100644
--- a/content/public/browser/content_browser_client.h
+++ b/content/public/browser/content_browser_client.h
@@ -349,6 +349,11 @@ class CONTENT_EXPORT ContentBrowserClient {
@@ -350,6 +350,11 @@ class CONTENT_EXPORT ContentBrowserClient {
virtual ~ContentBrowserClient() = default;

View File

@@ -6,10 +6,10 @@ Subject: gritsettings_resource_ids.patch
Add electron resources file to the list of resource ids generation.
diff --git a/tools/gritsettings/resource_ids.spec b/tools/gritsettings/resource_ids.spec
index d97cd9f349477ffaed02073948f4d93c9b10a49a..21be21d0bd82ccfcf79007d6ad58a0f89d7fa193 100644
index d0620514c19f330dac4e33cb19bccea063919236..b20a1b116d8d9ed046193361149cf00023c48876 100644
--- a/tools/gritsettings/resource_ids.spec
+++ b/tools/gritsettings/resource_ids.spec
@@ -1709,6 +1709,11 @@
@@ -1717,6 +1717,11 @@
"includes": [12000],
},

View File

@@ -50,7 +50,7 @@ system font by checking if it's kCTFontPriorityAttribute is set to
system priority.
diff --git a/base/BUILD.gn b/base/BUILD.gn
index b6eda46513f34c3000c53eff9cee40f587483054..36590c346e64a55ee07e25e8cc1939eb12ca3c82 100644
index 16fd234a12b251e943e1193998ac599cfa027860..57d283b024fa1d1f225ca76c7c7bff604be493bf 100644
--- a/base/BUILD.gn
+++ b/base/BUILD.gn
@@ -1124,6 +1124,7 @@ component("base") {
@@ -869,10 +869,10 @@ index d58c5eff9f8fbca96d0912ab9a19d06974fd8016..94ee727830545ff1576685722c0fd0dd
void NativeWidgetNSWindowBridge::SetColorMode(
diff --git a/components/viz/service/BUILD.gn b/components/viz/service/BUILD.gn
index 86c7e67902a7e9c0f90c4d10ebabfef2f53c1ec4..c1be2f1a8dc4d834ffa2e5944f7dd2753740a4c6 100644
index 1001fc872ae76c80c285a00f96a43c12910937cc..44072bd19a908aa0604e440e89c303cd8044f342 100644
--- a/components/viz/service/BUILD.gn
+++ b/components/viz/service/BUILD.gn
@@ -393,6 +393,7 @@ viz_component("service") {
@@ -395,6 +395,7 @@ viz_component("service") {
"frame_sinks/external_begin_frame_source_mojo_mac.h",
]
}
@@ -880,7 +880,7 @@ index 86c7e67902a7e9c0f90c4d10ebabfef2f53c1ec4..c1be2f1a8dc4d834ffa2e5944f7dd275
}
if (is_ios) {
@@ -738,6 +739,7 @@ viz_source_set("unit_tests") {
@@ -740,6 +741,7 @@ viz_source_set("unit_tests") {
"display_embedder/software_output_device_mac_unittest.mm",
]
frameworks = [ "IOSurface.framework" ]
@@ -1342,7 +1342,7 @@ index 2939a4183785608041041642ee81287d0fbd605d..89081a4d8e1dedbe8d45a824c5823601
if (is_ios) {
diff --git a/media/audio/apple/audio_low_latency_input.cc b/media/audio/apple/audio_low_latency_input.cc
index 75178516b53665c82195f795c5e4498c588e51c9..10e453a18813d3078dc4f01ab040acc66bf12bec 100644
index 7cd1fdaf15b847e5241144b47a47fe6c1d3907a5..004fd96906f258e193ed5acd80e2405db011e039 100644
--- a/media/audio/apple/audio_low_latency_input.cc
+++ b/media/audio/apple/audio_low_latency_input.cc
@@ -26,6 +26,7 @@
@@ -2495,7 +2495,7 @@ index 2914e7149f24fa903ca9861934a93e77f397e979..a258b94a8122c74b6f98f4b88b710371
}
diff --git a/ui/views/controls/webview/BUILD.gn b/ui/views/controls/webview/BUILD.gn
index dfcff42888a014a1e1325fe164243051b616604a..603a18f125d1b2bf23a017c1ed97716e90842854 100644
index e741b52a4db7ff481fe0f9c6d915465ccad8733d..2dd0d120848e8f2236f20bdaee20dd4b244891ff 100644
--- a/ui/views/controls/webview/BUILD.gn
+++ b/ui/views/controls/webview/BUILD.gn
@@ -48,6 +48,12 @@ component("webview") {

View File

@@ -7,7 +7,7 @@ This adds a callback from the network service that's used to implement
session.setCertificateVerifyCallback.
diff --git a/services/network/network_context.cc b/services/network/network_context.cc
index bab46a7eb7b58356ea37702880a46d4e7bbc8f2f..4bf222c4e6688384c034ef16170ad4cd64c379b0 100644
index 57155f8fe60279f1ac0c0cccf41ba4a468bf3bdb..2cd26265e717998c690aa277ff6ca6aac7b7ad0d 100644
--- a/services/network/network_context.cc
+++ b/services/network/network_context.cc
@@ -174,6 +174,11 @@
@@ -148,7 +148,7 @@ index bab46a7eb7b58356ea37702880a46d4e7bbc8f2f..4bf222c4e6688384c034ef16170ad4cd
void NetworkContext::CreateURLLoaderFactory(
mojo::PendingReceiver<mojom::URLLoaderFactory> receiver,
mojom::URLLoaderFactoryParamsPtr params) {
@@ -2819,6 +2936,10 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
@@ -2820,6 +2937,10 @@ URLRequestContextOwner NetworkContext::MakeURLRequestContext(
cert_verifier = std::make_unique<net::CachingCertVerifier>(
std::make_unique<net::CoalescingCertVerifier>(
std::move(cert_verifier)));
@@ -160,7 +160,7 @@ index bab46a7eb7b58356ea37702880a46d4e7bbc8f2f..4bf222c4e6688384c034ef16170ad4cd
builder.SetCertVerifier(IgnoreErrorsCertVerifier::MaybeWrapCertVerifier(
diff --git a/services/network/network_context.h b/services/network/network_context.h
index 7de41be5dfb099b9e47d65416c548e033fcae684..18a5062bac18be40ecd9e4de12ca4556802403c7 100644
index 6bdcb6fac47513e8c4c4b6d0e24c74a8baad2b37..b4239d2ed5632e9c93f3b395091344164b8468cb 100644
--- a/services/network/network_context.h
+++ b/services/network/network_context.h
@@ -123,6 +123,7 @@ class SimpleUrlPatternMatcher;
@@ -180,7 +180,7 @@ index 7de41be5dfb099b9e47d65416c548e033fcae684..18a5062bac18be40ecd9e4de12ca4556
void ResetURLLoaderFactories() override;
void GetViaObliviousHttp(
mojom::ObliviousHttpRequestPtr request,
@@ -1010,6 +1013,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
@@ -1011,6 +1014,8 @@ class COMPONENT_EXPORT(NETWORK_SERVICE) NetworkContext
std::vector<base::OnceClosure> dismount_closures_;
#endif // BUILDFLAG(IS_DIRECTORY_TRANSFER_REQUIRED)
@@ -190,7 +190,7 @@ index 7de41be5dfb099b9e47d65416c548e033fcae684..18a5062bac18be40ecd9e4de12ca4556
std::unique_ptr<HostResolver> internal_host_resolver_;
std::set<std::unique_ptr<HostResolver>, base::UniquePtrComparator>
diff --git a/services/network/public/mojom/network_context.mojom b/services/network/public/mojom/network_context.mojom
index 065da0cd7ec38f52c45f515f47ecf1ec81104598..88ac7593a8317dc646a128cce81f163554407bbc 100644
index 20314a191f4ba858b374921230b940802aaee56a..a7bb27ab245729413b5624c382115a8072e764a5 100644
--- a/services/network/public/mojom/network_context.mojom
+++ b/services/network/public/mojom/network_context.mojom
@@ -331,6 +331,17 @@ struct SocketBrokerRemotes {
@@ -222,7 +222,7 @@ index 065da0cd7ec38f52c45f515f47ecf1ec81104598..88ac7593a8317dc646a128cce81f1635
CreateURLLoaderFactory(
pending_receiver<URLLoaderFactory> url_loader_factory,
diff --git a/services/network/test/test_network_context.h b/services/network/test/test_network_context.h
index 61d23511df68e2efd1e5eb41d5a94457aca038ad..af46691e72faa7e1c59de51b099fa7923eb89ec6 100644
index 6bef01cbd02ba9bb665dcec76aa1eedd655ba56b..69a62289cf6a9eff3265b16ea4fdf3bee7efbc7d 100644
--- a/services/network/test/test_network_context.h
+++ b/services/network/test/test_network_context.h
@@ -63,6 +63,8 @@ class TestNetworkContext : public mojom::NetworkContext {

View File

@@ -7,7 +7,7 @@ Pass RenderFrameHost through to PlatformNotificationService
so Electron can identify which renderer a notification came from.
diff --git a/chrome/browser/notifications/platform_notification_service_impl.cc b/chrome/browser/notifications/platform_notification_service_impl.cc
index 16ef14e1fba28b43fe23214fb299eee3b152673d..5127f296bcf5f65c83b91defb1133de281a3ac19 100644
index e617356dc4864d7e0fede7d2e1e104e6bd91b701..6f82bb909d24ae1d8f7e90f82b034cd142fc166e 100644
--- a/chrome/browser/notifications/platform_notification_service_impl.cc
+++ b/chrome/browser/notifications/platform_notification_service_impl.cc
@@ -265,6 +265,7 @@ bool PlatformNotificationServiceImpl::WasClosedProgrammatically(

View File

@@ -30,7 +30,7 @@ index d83118cbc1e4c1ecf4ba3f4df2d730800c5cf7dd..37b9dde276f27ce6aa8c7e8e5ecb9fba
// RenderWidgetHost on the primary main frame, and false otherwise.
virtual bool IsWidgetForPrimaryMainFrame(RenderWidgetHostImpl*);
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index edd98668b9e8220ec26c901f69b8d3bee866aba5..c71887ba0658c98b7a3cf80064efb65f778ff426 100644
index 91f5aa62f256a061199e7091069607ceafc02e0d..a5c13ea50845fede85741de7a6f3b1c327698110 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -2076,6 +2076,9 @@ void RenderWidgetHostImpl::SetCursor(const ui::Cursor& cursor) {

View File

@@ -8,7 +8,7 @@ it in Electron and prevent drift from Chrome's blocklist. We should look for a w
to upstream this change to Chrome.
diff --git a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
index 069196b656b5273317934280b77a2a9160d0136a..429e862e6ee46b8a4fbcc6ba6c9ac1b2147a1596 100644
index 434db30ba5d8853147f36513049feb8b7f2e2fe1..2deaf6a1a187023febfff7398e20dea15bd18be9 100644
--- a/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
+++ b/chrome/browser/file_system_access/chrome_file_system_access_permission_context.cc
@@ -84,11 +84,13 @@
@@ -16,7 +16,7 @@ index 069196b656b5273317934280b77a2a9160d0136a..429e862e6ee46b8a4fbcc6ba6c9ac1b2
#include "chrome/browser/ui/tabs/public/tab_features.h"
#include "chrome/browser/ui/views/file_system_access/file_system_access_page_action_controller.h"
+#if 0
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h"
#include "chrome/browser/web_applications/proto/web_app_install_state.pb.h" // nogncheck
#include "chrome/browser/web_applications/web_app_install_manager.h"
#include "chrome/browser/web_applications/web_app_install_manager_observer.h"
#include "chrome/browser/web_applications/web_app_provider.h"

View File

@@ -52,10 +52,10 @@ Some alternatives to this patch:
None of these options seems like a substantial maintainability win over this patch to me (@nornagon).
diff --git a/chrome/BUILD.gn b/chrome/BUILD.gn
index 1f0485537a00754d1aa0064d7a51d3e3a2a5fd3b..48f28a61eac4b31c3d9be62100c00cadff84174c 100644
index a2fe0be293dfe7c0633651e64da1320047d102bb..171faeaa1c3ba08cc0cd166b492765c8204bb674 100644
--- a/chrome/BUILD.gn
+++ b/chrome/BUILD.gn
@@ -1566,7 +1566,7 @@ if (is_chrome_branded && !is_android) {
@@ -1565,7 +1565,7 @@ if (is_chrome_branded && !is_android) {
}
}
@@ -64,7 +64,7 @@ index 1f0485537a00754d1aa0064d7a51d3e3a2a5fd3b..48f28a61eac4b31c3d9be62100c00cad
chrome_paks("packed_resources") {
if (is_mac) {
output_dir = "$root_gen_dir/repack"
@@ -1594,6 +1594,12 @@ repack("browser_tests_pak") {
@@ -1593,6 +1593,12 @@ repack("browser_tests_pak") {
deps = [ "//chrome/test/data/webui:resources" ]
}

View File

@@ -39,7 +39,7 @@ index 37b9dde276f27ce6aa8c7e8e5ecb9fba05324ca6..56df8d4d5db8b68f3a0b043096065aaf
// event before sending it to the renderer. See enum for details on return
// value.
diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc
index ddafdbe57d3ba8f178f8a193a4ae1b59d1b47edf..5303507537061de5f515f199f95fbae16fcd996c 100644
index 50edc218dc16c85c3c870c744b9c14936a0e99d2..92d8b68a76cfba407b3337693718121c4edf9fbf 100644
--- a/content/browser/renderer_host/render_widget_host_impl.cc
+++ b/content/browser/renderer_host/render_widget_host_impl.cc
@@ -1572,6 +1572,10 @@ void RenderWidgetHostImpl::ForwardMouseEventWithLatencyInfo(

View File

@@ -15,7 +15,7 @@ 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 80d3b463984813ac1459b02fac41ee995b437353..abdcf9cd75790f06e5aa7bc3edc3b9ec3b7e6ec3 100644
index 944bb9ee39682f4e623477f98b1c407b0c6b0d6a..c29ece68e6fa5344e20db967b4acaf5229e9a8df 100644
--- a/content/browser/renderer_host/render_frame_host_impl.cc
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
@@ -9293,6 +9293,17 @@ void RenderFrameHostImpl::EnterFullscreen(

View File

@@ -46,3 +46,4 @@ src_stop_using_v8_propertycallbackinfo_t_this.patch
build_restore_macos_deployment_target_to_12_0.patch
fix_add_externalpointertypetag_to_v8_external_api_calls.patch
test_update_v8_serialization_wire_format_version_to_16.patch
fix_add_missing_cstdlib_include_to_builtin_info.patch

View File

@@ -0,0 +1,32 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Charles Kerr <charles@charleskerr.com>
Date: Mon, 27 Apr 2026 21:17:16 -0500
Subject: fix: add missing cstdlib include to builtin_info
Node's builtin_info.cc uses abort() but doesn't include <cstdlib>.
It used to pick up the declaration by a transitive include, but
that broke in this libc++ roll.
This patch can be removed after it's been upstreamed to Node.js.
diff --git a/src/builtin_info.cc b/src/builtin_info.cc
index d5309265ac373310c20ba4f2438a778c9753254a..2c8b2b62704aadac6d8f3afbb56a9d4d5461ad58 100644
--- a/src/builtin_info.cc
+++ b/src/builtin_info.cc
@@ -1,5 +1,7 @@
#include "builtin_info.h"
+#include <cstdlib>
+
namespace node {
namespace builtins {
@@ -41,7 +43,7 @@ std::string GetBuiltinSourceTypeName(BuiltinSourceType type) {
case BuiltinSourceType::kSourceTextModule:
return "kSourceTextModule";
}
- abort();
+ std::abort();
}
} // namespace builtins

View File

@@ -15,10 +15,10 @@ Rather than disabling builtins PGO entirely, warn and skip mismatched
builtins so all other builtins still benefit from PGO.
diff --git a/BUILD.gn b/BUILD.gn
index eedd49aa7f990912f46a27df5115917bd93e4c91..58c7fdfffba619637c430e783b57291f8cc586c8 100644
index ebff41d33352be25a548efcbacdc97ae6494b416..0af77eb0b597022b3d8262b6eb76c0bdf38a61ce 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -2826,9 +2826,11 @@ template("run_mksnapshot") {
@@ -2825,9 +2825,11 @@ template("run_mksnapshot") {
"--turbo-profiling-input",
rebase_path(v8_builtins_profiling_log_file, root_build_dir),

View File

@@ -101,6 +101,7 @@
#include "content/browser/mac_helpers.h"
#include "shell/browser/electron_child_process_host_flags.h"
#include "shell/browser/ui/cocoa/electron_bundle_mover.h"
#include "shell/browser/webauthn/electron_authenticator_request_delegate.h"
#include "shell/common/process_util.h"
#endif
@@ -1664,6 +1665,29 @@ bool App::IsInApplicationsFolder() {
return ElectronBundleMover::IsCurrentAppInApplicationsFolder();
}
void App::ConfigureWebAuthn(gin_helper::ErrorThrower thrower,
gin::Arguments* args) {
gin_helper::Dictionary options;
if (!args->GetNext(&options)) {
thrower.ThrowTypeError("configureWebAuthn requires an options object");
return;
}
gin_helper::Dictionary touch_id;
if (options.Get("touchID", &touch_id)) {
std::string keychain_access_group;
if (!touch_id.Get("keychainAccessGroup", &keychain_access_group) ||
keychain_access_group.empty()) {
thrower.ThrowTypeError(
"configureWebAuthn: 'touchID.keychainAccessGroup' must be a "
"non-empty string");
return;
}
ElectronWebAuthenticationDelegate::SetTouchIdKeychainAccessGroup(
std::move(keychain_access_group));
}
}
int DockBounce(gin::Arguments* args) {
int request_id = -1;
std::string type = "informational";
@@ -1893,6 +1917,7 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
.SetMethod("moveToApplicationsFolder", &App::MoveToApplicationsFolder)
.SetMethod("isInApplicationsFolder", &App::IsInApplicationsFolder)
.SetMethod("setActivationPolicy", &App::SetActivationPolicy)
.SetMethod("configureWebAuthn", &App::ConfigureWebAuthn)
#endif
.SetMethod("setAboutPanelOptions",
base::BindRepeating(&Browser::SetAboutPanelOptions, browser))

View File

@@ -244,6 +244,8 @@ class App final : public gin::Wrappable<App>,
#if BUILDFLAG(IS_MAC)
void SetActivationPolicy(gin_helper::ErrorThrower thrower,
const std::string& policy);
void ConfigureWebAuthn(gin_helper::ErrorThrower thrower,
gin::Arguments* args);
bool MoveToApplicationsFolder(gin_helper::ErrorThrower, gin::Arguments* args);
bool IsInApplicationsFolder();
v8::Local<v8::Value> GetDockAPI(v8::Isolate* isolate);

View File

@@ -121,6 +121,18 @@ std::u16string Menu::GetLabelForCommandId(int command_id) const {
return label;
}
std::u16string Menu::GetAccessibilityLabelForCommandId(int command_id) const {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Value> val =
gin_helper::CallMethod(isolate, const_cast<Menu*>(this),
"_getAccessibilityLabelForCommandId", command_id);
std::u16string label;
if (!gin::ConvertFromV8(isolate, val, &label))
label.clear();
return label;
}
std::u16string Menu::GetSecondaryLabelForCommandId(int command_id) const {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);

View File

@@ -74,6 +74,8 @@ class Menu : public gin::Wrappable<Menu>,
bool IsCommandIdEnabled(int command_id) const override;
bool IsCommandIdVisible(int command_id) const override;
std::u16string GetLabelForCommandId(int command_id) const override;
std::u16string GetAccessibilityLabelForCommandId(
int command_id) const override;
std::u16string GetSecondaryLabelForCommandId(int command_id) const override;
ui::ImageModel GetIconForCommandId(int command_id) const override;
bool ShouldCommandIdWorkWhenHidden(int command_id) const override;

View File

@@ -18,13 +18,16 @@
#include "content/browser/network_service_instance_impl.h" // nogncheck
#include "content/public/browser/child_process_host.h"
#include "content/public/browser/service_process_host.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/common/result_codes.h"
#include "gin/object_template_builder.h"
#include "gin/persistent.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "services/network/public/cpp/originating_process_id.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/api/message_port.h"
#include "shell/browser/browser.h"
#include "shell/browser/electron_browser_context.h"
#include "shell/browser/electron_child_process_host_flags.h"
#include "shell/browser/javascript_environment.h"
#include "shell/browser/net/system_network_context_manager.h"
@@ -92,8 +95,9 @@ UtilityProcessWrapper::UtilityProcessWrapper(
base::FilePath current_working_directory,
bool use_plugin_helper,
bool create_network_observer,
bool disclaim_responsibility)
: create_network_observer_(create_network_observer) {
bool disclaim_responsibility,
Session* session)
: create_network_observer_(create_network_observer), session_(session) {
auto& allocation_handle =
JavascriptEnvironment::GetIsolate()->GetCppHeap()->GetAllocationHandle();
#if BUILDFLAG(IS_WIN)
@@ -451,6 +455,7 @@ UtilityProcessWrapper::CreateURLLoaderFactoryParams() {
node::mojom::URLLoaderFactoryParamsPtr params =
node::mojom::URLLoaderFactoryParams::New();
mojo::PendingRemote<network::mojom::URLLoaderFactory> url_loader_factory;
network::mojom::NetworkContext* network_context;
network::mojom::URLLoaderFactoryParamsPtr loader_params =
network::mojom::URLLoaderFactoryParams::New();
loader_params->process_id = network::OriginatingProcessId::browser();
@@ -462,11 +467,27 @@ UtilityProcessWrapper::CreateURLLoaderFactoryParams() {
url_loader_network_observer_->Bind();
}
network::mojom::NetworkContext* network_context =
g_browser_process->system_network_context_manager()->GetContext();
network_context->CreateURLLoaderFactory(
url_loader_factory.InitWithNewPipeAndPassReceiver(),
std::move(loader_params));
if (session_) {
auto* browser_context = session_->browser_context();
network_context =
browser_context->GetDefaultStoragePartition()->GetNetworkContext();
// Build a factory through CreateURLLoaderFactoryBuilder so requests go
// through ProxyingURLLoaderFactory (enabling webRequest interception).
auto [factory_builder, header_client] =
browser_context->CreateURLLoaderFactoryBuilder();
loader_params->header_client = std::move(header_client);
url_loader_factory =
std::move(factory_builder)
.Finish<mojo::PendingRemote<network::mojom::URLLoaderFactory>>(
network_context, std::move(loader_params));
} else {
network_context =
g_browser_process->system_network_context_manager()->GetContext();
network_context->CreateURLLoaderFactory(
url_loader_factory.InitWithNewPipeAndPassReceiver(),
std::move(loader_params));
}
params->url_loader_factory = std::move(url_loader_factory);
mojo::PendingRemote<network::mojom::HostResolver> host_resolver;
network_context->CreateHostResolver(
@@ -503,6 +524,7 @@ UtilityProcessWrapper* UtilityProcessWrapper::Create(
bool use_plugin_helper = false;
bool create_network_observer = false;
bool disclaim_responsibility = false;
api::Session* session = nullptr;
std::map<IOHandle, IOType> stdio;
base::FilePath current_working_directory;
base::EnvironmentMap env_map;
@@ -548,12 +570,19 @@ UtilityProcessWrapper* UtilityProcessWrapper::Create(
opts.Get("allowLoadingUnsignedLibraries", &use_plugin_helper);
opts.Get("disclaim", &disclaim_responsibility);
#endif
std::string partition;
if (opts.Get("session", &session) && session) {
} else if (opts.Get("partition", &partition)) {
session = Session::FromPartition(args->isolate(), partition);
}
}
v8::Isolate* isolate = args->isolate();
return cppgc::MakeGarbageCollected<UtilityProcessWrapper>(
isolate->GetCppHeap()->GetAllocationHandle(), std::move(params),
display_name, std::move(stdio), env_map, current_working_directory,
use_plugin_helper, create_network_observer, disclaim_responsibility);
use_plugin_helper, create_network_observer, disclaim_responsibility,
session);
}
gin::ObjectTemplateBuilder UtilityProcessWrapper::GetObjectTemplateBuilder(
@@ -567,6 +596,7 @@ gin::ObjectTemplateBuilder UtilityProcessWrapper::GetObjectTemplateBuilder(
void UtilityProcessWrapper::Trace(cppgc::Visitor* visitor) const {
gin::Wrappable<UtilityProcessWrapper>::Trace(visitor);
visitor->Trace(session_);
visitor->Trace(weak_factory_);
}

View File

@@ -38,6 +38,8 @@ class Connector;
namespace electron::api {
class Session;
class UtilityProcessWrapper final
: public gin::Wrappable<UtilityProcessWrapper>,
public gin_helper::EventEmitterMixin<UtilityProcessWrapper>,
@@ -55,7 +57,8 @@ class UtilityProcessWrapper final
base::FilePath current_working_directory,
bool use_plugin_helper,
bool create_network_observer,
bool disclaim_responsibility);
bool disclaim_responsibility,
Session* session);
~UtilityProcessWrapper() override;
static UtilityProcessWrapper* Create(gin::Arguments* args);
@@ -63,6 +66,8 @@ class UtilityProcessWrapper final
void Shutdown(uint32_t exit_code);
bool has_session() const { return session_.Get(); }
// gin::Wrappable
static const gin::WrapperInfo kWrapperInfo;
static const char* GetClassName() { return "UtilityProcess"; }
@@ -126,6 +131,7 @@ class UtilityProcessWrapper final
GC_PLUGIN_IGNORE(
"Context tracking of remote is not needed in the browser process.")
mojo::Remote<node::mojom::NodeService> node_service_remote_;
cppgc::Member<Session> session_;
std::optional<electron::URLLoaderNetworkObserver>
url_loader_network_observer_;
base::CallbackListSubscription network_service_gone_subscription_;

View File

@@ -517,6 +517,21 @@ void View::ApplyBorderRadius() {
}
}
void View::SetBackgroundBlur(int blur_radius) {
if (!view_)
return;
if (blur_radius < 0)
blur_radius = 0;
ui::Layer* layer = GetLayer();
if (!layer)
return;
layer->SetBackgroundBlur(blur_radius);
}
void View::SetVisible(bool visible) {
if (!view_)
return;
@@ -584,6 +599,7 @@ void View::BuildPrototype(v8::Isolate* isolate,
.SetMethod("getBounds", &View::GetBounds)
.SetMethod("setBackgroundColor", &View::SetBackgroundColor)
.SetMethod("setBorderRadius", &View::SetBorderRadius)
.SetMethod("setBackgroundBlur", &View::SetBackgroundBlur)
.SetMethod("setLayout", &View::SetLayout)
.SetMethod("setVisible", &View::SetVisible)
.SetMethod("getVisible", &View::GetVisible);

View File

@@ -43,6 +43,7 @@ class View : public gin_helper::EventEmitter<View>,
std::vector<v8::Local<v8::Value>> GetChildren();
void SetBackgroundColor(std::optional<WrappedSkColor> color);
void SetBorderRadius(int radius);
void SetBackgroundBlur(int blur_radius);
void SetVisible(bool visible);
bool GetVisible() const;

View File

@@ -115,6 +115,7 @@
#include "shell/browser/usb/electron_usb_delegate.h"
#include "shell/browser/web_contents_permission_helper.h"
#include "shell/browser/web_contents_preferences.h"
#include "shell/browser/webauthn/electron_authenticator_request_client_delegate.h"
#include "shell/browser/webauthn/electron_authenticator_request_delegate.h"
#include "shell/browser/window_list.h"
#include "shell/common/api/api.mojom.h"
@@ -1127,6 +1128,7 @@ void ElectronBrowserClient::
RegisterNonNetworkWorkerMainResourceURLLoaderFactories(
content::BrowserContext* browser_context,
const std::optional<url::Origin>& request_initiator,
network::mojom::RequestDestination request_destination,
NonNetworkURLLoaderFactoryMap* factories) {
auto* protocol_registry =
ProtocolRegistry::FromBrowserContext(browser_context);
@@ -1924,4 +1926,13 @@ ElectronBrowserClient::GetWebAuthenticationDelegate() {
return web_authentication_delegate_.get();
}
#if !BUILDFLAG(IS_ANDROID)
std::unique_ptr<content::AuthenticatorRequestClientDelegate>
ElectronBrowserClient::GetWebAuthenticationRequestDelegate(
content::RenderFrameHost* render_frame_host) {
return std::make_unique<ElectronAuthenticatorRequestClientDelegate>(
render_frame_host);
}
#endif
} // namespace electron

View File

@@ -127,6 +127,11 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
content::UsbDelegate* GetUsbDelegate() override;
content::WebAuthenticationDelegate* GetWebAuthenticationDelegate() override;
#if !BUILDFLAG(IS_ANDROID)
std::unique_ptr<content::AuthenticatorRequestClientDelegate>
GetWebAuthenticationRequestDelegate(
content::RenderFrameHost* render_frame_host) override;
#endif
#if BUILDFLAG(IS_MAC)
std::string GetChildProcessSuffix(int child_flags) override;
@@ -226,6 +231,7 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
void RegisterNonNetworkWorkerMainResourceURLLoaderFactories(
content::BrowserContext* browser_context,
const std::optional<url::Origin>& request_initiator,
network::mojom::RequestDestination request_destination,
NonNetworkURLLoaderFactoryMap* factories) override;
void RegisterNonNetworkSubresourceURLLoaderFactories(
int render_process_id,

View File

@@ -502,6 +502,11 @@ void ElectronBrowserContext::InitPrefs() {
// Unique uuid for global shortcuts.
registry->RegisterStringPref(electron::kElectronGlobalShortcutsUuid,
std::string());
#if BUILDFLAG(IS_MAC)
registry->RegisterStringPref(electron::kWebAuthnTouchIdMetadataSecretPrefName,
std::string());
#endif
}
void ElectronBrowserContext::SetUserAgent(const std::string& user_agent) {

View File

@@ -98,6 +98,10 @@ class ElectronBrowserContext : public content::BrowserContext {
scoped_refptr<network::SharedURLLoaderFactory> InterceptURLLoaderFactory(
scoped_refptr<network::SharedURLLoaderFactory> factory);
std::pair<network::URLLoaderFactoryBuilder,
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>>
CreateURLLoaderFactoryBuilder();
std::string GetMediaDeviceIDSalt();
// content::BrowserContext:
@@ -187,10 +191,6 @@ class ElectronBrowserContext : public content::BrowserContext {
content::MediaResponseCallback callback,
gin::Arguments* args);
std::pair<network::URLLoaderFactoryBuilder,
mojo::PendingRemote<network::mojom::TrustedURLLoaderHeaderClient>>
CreateURLLoaderFactoryBuilder();
// Initialize pref registry.
void InitPrefs();

View File

@@ -14,7 +14,6 @@ function_registration("api_registration") {
"//electron/shell/common/extensions/api/action.json",
"//electron/shell/common/extensions/api/extension.json",
"//electron/shell/common/extensions/api/resources_private.idl",
"//electron/shell/common/extensions/api/scripting.idl",
"//electron/shell/common/extensions/api/tabs.json",
]

File diff suppressed because it is too large Load Diff

View File

@@ -1,203 +0,0 @@
// Copyright 2023 Microsoft, GmbH
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_
#define ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_
#include <memory>
#include <optional>
#include <set>
#include <string>
#include <vector>
#include "extensions/browser/extension_function.h"
#include "extensions/browser/script_executor.h"
#include "extensions/browser/scripting_utils.h"
#include "extensions/common/api/scripting.h"
#include "extensions/common/mojom/code_injection.mojom.h"
#include "extensions/common/user_script.h"
namespace extensions {
class ScriptingExecuteScriptFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.executeScript", SCRIPTING_EXECUTESCRIPT)
ScriptingExecuteScriptFunction();
ScriptingExecuteScriptFunction(const ScriptingExecuteScriptFunction&) =
delete;
ScriptingExecuteScriptFunction& operator=(
const ScriptingExecuteScriptFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingExecuteScriptFunction() override;
// Called when the resource files to be injected has been loaded.
void DidLoadResources(std::vector<scripting::InjectedFileSource> file_sources,
std::optional<std::string> load_error);
// Triggers the execution of `sources` in the appropriate context.
// Returns true on success; on failure, populates `error`.
bool Execute(std::vector<mojom::JSSourcePtr> sources, std::string* error);
// Invoked when script execution is complete.
void OnScriptExecuted(std::vector<ScriptExecutor::FrameResult> frame_results);
api::scripting::ScriptInjection injection_;
};
class ScriptingInsertCSSFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.insertCSS", SCRIPTING_INSERTCSS)
ScriptingInsertCSSFunction();
ScriptingInsertCSSFunction(const ScriptingInsertCSSFunction&) = delete;
ScriptingInsertCSSFunction& operator=(const ScriptingInsertCSSFunction&) =
delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingInsertCSSFunction() override;
// Called when the resource files to be injected has been loaded.
void DidLoadResources(std::vector<scripting::InjectedFileSource> file_sources,
std::optional<std::string> load_error);
// Triggers the execution of `sources` in the appropriate context.
// Returns true on success; on failure, populates `error`.
bool Execute(std::vector<mojom::CSSSourcePtr> sources, std::string* error);
// Called when the CSS insertion is complete.
void OnCSSInserted(std::vector<ScriptExecutor::FrameResult> results);
api::scripting::CSSInjection injection_;
};
class ScriptingRemoveCSSFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.removeCSS", SCRIPTING_REMOVECSS)
ScriptingRemoveCSSFunction();
ScriptingRemoveCSSFunction(const ScriptingRemoveCSSFunction&) = delete;
ScriptingRemoveCSSFunction& operator=(const ScriptingRemoveCSSFunction&) =
delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingRemoveCSSFunction() override;
// Called when the CSS removal is complete.
void OnCSSRemoved(std::vector<ScriptExecutor::FrameResult> results);
};
class ScriptingRegisterContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.registerContentScripts",
SCRIPTING_REGISTERCONTENTSCRIPTS)
ScriptingRegisterContentScriptsFunction();
ScriptingRegisterContentScriptsFunction(
const ScriptingRegisterContentScriptsFunction&) = delete;
ScriptingRegisterContentScriptsFunction& operator=(
const ScriptingRegisterContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingRegisterContentScriptsFunction() override;
// Called when script files have been checked.
void OnContentScriptFilesValidated(
std::set<std::string> persistent_script_ids,
scripting::ValidateScriptsResult result);
// Called when content scripts have been registered.
void OnContentScriptsRegistered(const std::optional<std::string>& error);
};
class ScriptingGetRegisteredContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.getRegisteredContentScripts",
SCRIPTING_GETREGISTEREDCONTENTSCRIPTS)
ScriptingGetRegisteredContentScriptsFunction();
ScriptingGetRegisteredContentScriptsFunction(
const ScriptingGetRegisteredContentScriptsFunction&) = delete;
ScriptingGetRegisteredContentScriptsFunction& operator=(
const ScriptingGetRegisteredContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingGetRegisteredContentScriptsFunction() override;
};
class ScriptingUnregisterContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.unregisterContentScripts",
SCRIPTING_UNREGISTERCONTENTSCRIPTS)
ScriptingUnregisterContentScriptsFunction();
ScriptingUnregisterContentScriptsFunction(
const ScriptingUnregisterContentScriptsFunction&) = delete;
ScriptingUnregisterContentScriptsFunction& operator=(
const ScriptingUnregisterContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingUnregisterContentScriptsFunction() override;
// Called when content scripts have been unregistered.
void OnContentScriptsUnregistered(const std::optional<std::string>& error);
};
class ScriptingUpdateContentScriptsFunction : public ExtensionFunction {
public:
DECLARE_EXTENSION_FUNCTION("scripting.updateContentScripts",
SCRIPTING_UPDATECONTENTSCRIPTS)
ScriptingUpdateContentScriptsFunction();
ScriptingUpdateContentScriptsFunction(
const ScriptingUpdateContentScriptsFunction&) = delete;
ScriptingUpdateContentScriptsFunction& operator=(
const ScriptingUpdateContentScriptsFunction&) = delete;
// ExtensionFunction:
ResponseAction Run() override;
private:
~ScriptingUpdateContentScriptsFunction() override;
// Returns a UserScript object by updating the `original_script` with the
// `new_script` given delta. If the updated script cannot be parsed, populates
// `parse_error` and returns nullptr.
std::unique_ptr<UserScript> ApplyUpdate(
std::set<std::string>* script_ids_to_persist,
api::scripting::RegisteredContentScript& new_script,
api::scripting::RegisteredContentScript& original_script,
std::u16string* parse_error);
// Called when script files have been checked.
void OnContentScriptFilesValidated(
std::set<std::string> persistent_script_ids,
scripting::ValidateScriptsResult result);
// Called when content scripts have been updated.
void OnContentScriptsUpdated(const std::optional<std::string>& error);
};
} // namespace extensions
#endif // ELECTRON_SHELL_BROWSER_EXTENSIONS_API_SCRIPTING_SCRIPTING_API_H_

View File

@@ -8,7 +8,6 @@
#include "extensions/browser/extension_function_registry.h"
#include "shell/browser/extensions/api/extension_action/extension_action_api.h"
#include "shell/browser/extensions/api/generated_api_registration.h"
#include "shell/browser/extensions/api/scripting/scripting_api.h"
#include "shell/browser/extensions/api/tabs/tabs_api.h"
namespace extensions {

View File

@@ -35,6 +35,7 @@
#include "extensions/common/manifest_handlers/devtools_page_handler.h"
#include "extensions/common/manifest_url_handlers.h"
#include "services/network/public/mojom/url_loader.mojom.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/browser.h"
#include "shell/browser/electron_browser_client.h"
#include "shell/browser/electron_browser_context.h"
@@ -42,6 +43,7 @@
#include "shell/browser/extensions/electron_component_extension_resource_manager.h"
#include "shell/browser/extensions/electron_extension_host_delegate.h"
#include "shell/browser/extensions/electron_extension_system_factory.h"
#include "shell/browser/extensions/electron_extension_tab_util.h"
#include "shell/browser/extensions/electron_extension_web_contents_observer.h"
#include "shell/browser/extensions/electron_extensions_api_client.h"
#include "shell/browser/extensions/electron_extensions_browser_api_provider.h"
@@ -381,6 +383,28 @@ ElectronExtensionsBrowserClient::GetExtensionWebContentsObserver(
web_contents);
}
bool ElectronExtensionsBrowserClient::IsValidTabId(
content::BrowserContext* browser_context,
const int tab_id,
const bool include_incognito,
content::WebContents** web_contents) const {
auto* contents = extensions::GetElectronTabById(tab_id, browser_context);
if (!contents)
return false;
if (web_contents)
*web_contents = contents->web_contents();
return true;
}
extensions::ScriptExecutor*
ElectronExtensionsBrowserClient::GetScriptExecutorForTab(
content::WebContents& web_contents) {
auto* contents = electron::api::WebContents::From(&web_contents);
return contents ? contents->script_executor() : nullptr;
}
extensions::KioskDelegate* ElectronExtensionsBrowserClient::GetKioskDelegate() {
if (!kiosk_delegate_)
kiosk_delegate_ = std::make_unique<ElectronKioskDelegate>();

View File

@@ -139,6 +139,12 @@ class ElectronExtensionsBrowserClient
content::WebContents* web_contents) override;
extensions::ExtensionWebContentsObserver* GetExtensionWebContentsObserver(
content::WebContents* web_contents) override;
bool IsValidTabId(content::BrowserContext* browser_context,
int tab_id,
bool include_incognito,
content::WebContents** web_contents) const override;
extensions::ScriptExecutor* GetScriptExecutorForTab(
content::WebContents& web_contents) override;
extensions::KioskDelegate* GetKioskDelegate() override;
std::string GetApplicationLocale() override;
void RegisterBrowserInterfaceBindersForFrame(

View File

@@ -11,6 +11,7 @@
#include "gin/arguments.h"
#include "gin/dictionary.h"
#include "shell/browser/api/electron_api_app.h"
#include "shell/browser/api/electron_api_utility_process.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/gin_converters/callback_converter.h"
@@ -100,6 +101,17 @@ void LoginHandler::EmitEvent(
api_web_contents->Emit("login", std::move(details), auth_info,
base::BindOnce(&LoginHandler::CallbackFromJS,
weak_factory_.GetWeakPtr()));
} else if (auto* utility_process =
api::UtilityProcessWrapper::FromProcessId(process_id);
utility_process && utility_process->has_session()) {
// Route auth to the utility process wrapper when the request originated
// from a utility process with a session and
// respondToAuthRequestsFromMainProcess. Without a session, auth falls
// through to app.on('login') for backward compatibility.
default_prevented =
utility_process->Emit("login", std::move(details), auth_info,
base::BindOnce(&LoginHandler::CallbackFromJS,
weak_factory_.GetWeakPtr()));
} else {
default_prevented =
api::App::Get()->Emit("login", nullptr, std::move(details), auth_info,

View File

@@ -16,7 +16,6 @@
#include "shell/browser/background_throttling_source.h"
#include "shell/browser/browser.h"
#include "shell/browser/draggable_region_provider.h"
#include "shell/browser/native_window_features.h"
#include "shell/browser/ui/drag_util.h"
#include "shell/browser/window_list.h"
#include "shell/common/color_util.h"
@@ -29,6 +28,7 @@
#if !BUILDFLAG(IS_MAC)
#include "shell/browser/ui/views/frameless_view.h"
#include "ui/views/view_utils.h"
#endif
#if defined(USE_OZONE)
@@ -649,11 +649,13 @@ int NativeWindow::NonClientHitTest(const gfx::Point& point) {
#if !BUILDFLAG(IS_MAC)
// We need to ensure we account for resizing borders on Windows and Linux.
if ((!has_frame() || has_client_frame()) && IsResizable()) {
auto* frame =
static_cast<FramelessView*>(widget()->non_client_view()->frame_view());
int border_hit = frame->ResizingBorderHitTest(point);
if (border_hit != HTNOWHERE)
return border_hit;
auto* frame = views::AsViewClass<FramelessView>(
widget()->non_client_view()->frame_view());
if (frame) {
int border_hit = frame->ResizingBorderHitTest(point);
if (border_hit != HTNOWHERE)
return border_hit;
}
}
#endif
@@ -780,7 +782,6 @@ bool NativeWindow::PlatformHasClientFrame() {
// Ozone X11 likes to prefer custom frames,
// but we don't need them unless on Wayland.
static const bool has_client_frame =
base::FeatureList::IsEnabled(features::kWaylandWindowDecorations) &&
!ui::OzonePlatform::GetInstance()
->GetPlatformRuntimeProperties()
.supports_server_side_window_decorations;

View File

@@ -1,11 +0,0 @@
// Copyright (c) 2022 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/native_window_features.h"
namespace features {
BASE_FEATURE(kWaylandWindowDecorations,
"WaylandWindowDecorations",
base::FEATURE_ENABLED_BY_DEFAULT);
}

View File

@@ -1,14 +0,0 @@
// Copyright (c) 2022 Slack Technologies, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_
#define ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_
#include "base/feature_list.h"
namespace features {
extern const base::Feature kWaylandWindowDecorations;
}
#endif // ELECTRON_SHELL_BROWSER_NATIVE_WINDOW_FEATURES_H_

View File

@@ -64,12 +64,15 @@
#include "shell/browser/linux/unity_service.h"
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
#include "shell/browser/ui/views/client_frame_view_linux.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "shell/browser/ui/views/native_frame_view.h"
#include "shell/browser/ui/views/native_frame_view_linux.h"
#include "shell/browser/ui/views/opaque_frame_view.h"
#include "shell/common/platform_util.h"
#include "ui/linux/linux_ui.h"
#include "ui/linux/linux_ui_factory.h"
#include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h"
#include "ui/views/window/frame_view_linux.h"
#include "ui/views/window/native_frame_view.h"
#if BUILDFLAG(SUPPORTS_OZONE_X11)
@@ -508,9 +511,10 @@ void NativeWindowViews::SetTitleBarOverlay(
// If anything was updated, ensure the overlay is repainted.
if (updated) {
auto* frame_view =
static_cast<FramelessView*>(widget()->non_client_view()->frame_view());
frame_view->InvalidateCaptionButtons();
auto* fv = widget()->non_client_view()->frame_view();
if (auto* frameless = views::AsViewClass<FramelessView>(fv)) {
frameless->InvalidateCaptionButtons();
}
}
}
@@ -1283,9 +1287,11 @@ void NativeWindowViews::SetBackgroundColor(SkColor background_color) {
SkColor compositor_color = background_color;
#if BUILDFLAG(IS_LINUX)
// Widget background needs to stay transparent for CSD shadow regions.
auto* fvl = GetFrameViewLinux();
LinuxFrameLayout* frame_layout = GetLinuxFrameLayout();
const bool uses_csd =
frame_layout && frame_layout->SupportsClientFrameShadow();
(fvl && fvl->ShouldDrawRestoredFrameShadow()) ||
(frame_layout && frame_layout->SupportsClientFrameShadow());
if (transparent() || uses_csd)
compositor_color = SK_ColorTRANSPARENT;
#endif
@@ -1510,9 +1516,13 @@ gfx::Insets NativeWindowViews::GetRestoredFrameBorderInsets() const {
if (!frame_view)
return gfx::Insets();
if (auto* frameless = views::AsViewClass<FramelessView>(frame_view)) {
if (auto* frameless = views::AsViewClass<FramelessView>(frame_view))
return frameless->RestoredFrameBorderInsets();
}
#if BUILDFLAG(IS_LINUX)
if (auto* fvl = views::AsViewClass<views::FrameViewLinux>(frame_view))
return fvl->GetRestoredFrameBorderInsets();
#endif
return gfx::Insets();
}
@@ -1925,8 +1935,21 @@ std::unique_ptr<views::FrameView> NativeWindowViews::CreateFrameView(
if (!has_frame())
return std::make_unique<OpaqueFrameView>(this, widget);
if (has_client_frame())
return std::make_unique<ClientFrameViewLinux>(this, widget);
if (has_client_frame()) {
auto* linux_ui_theme = ui::LinuxUiTheme::GetForProfile(nullptr);
auto getter = base::BindRepeating(
[](ui::LinuxUiTheme* theme, bool tiled,
bool maximized) -> ui::WindowFrameProvider* {
return theme->GetWindowFrameProvider(ui::FrameType::kDefault,
/*solid_frame=*/false, tiled,
maximized);
},
base::Unretained(linux_ui_theme));
auto nav_button_provider =
linux_ui_theme->CreateNavButtonProvider(ui::FrameType::kDefault);
return std::make_unique<NativeFrameViewLinux>(
this, widget, std::move(nav_button_provider), std::move(getter));
}
return std::make_unique<NativeFrameView>(this, widget);
#endif
@@ -1940,6 +1963,13 @@ LinuxFrameLayout* NativeWindowViews::GetLinuxFrameLayout() {
auto* view = views::AsViewClass<FramelessView>(ncv->frame_view());
return view ? view->GetLinuxFrameLayout() : nullptr;
}
views::FrameViewLinux* NativeWindowViews::GetFrameViewLinux() const {
auto* ncv = widget()->non_client_view();
if (!ncv)
return nullptr;
return views::AsViewClass<views::FrameViewLinux>(ncv->frame_view());
}
#endif
void NativeWindowViews::OnWidgetMove() {

View File

@@ -30,10 +30,16 @@ namespace gin {
class Arguments;
} // namespace gin
#if BUILDFLAG(IS_LINUX)
namespace views {
class FrameViewLinux;
} // namespace views
#endif
namespace electron {
#if BUILDFLAG(IS_LINUX)
class ClientFrameViewLinux;
class NativeFrameViewLinux;
class GlobalMenuBarX11;
class LinuxFrameLayout;
#endif
@@ -202,6 +208,7 @@ class NativeWindowViews : public NativeWindow,
#if BUILDFLAG(IS_LINUX)
LinuxFrameLayout* GetLinuxFrameLayout();
views::FrameViewLinux* GetFrameViewLinux() const;
#endif
private:

View File

@@ -6,6 +6,7 @@
#include <shellapi.h>
#include <wrl/client.h>
#include "base/logging.h"
#include "base/win/atl.h" // Must be before UIAutomationCore.h
#include "base/win/registry.h"
#include "base/win/scoped_handle.h"
@@ -677,8 +678,21 @@ void NativeWindowViews::SetForwardMouseMessages(bool forward) {
RemoveWindowSubclass(legacy_window_, SubclassProc, 1);
if (forwarding_windows_->empty()) {
UnhookWindowsHookEx(mouse_hook_);
mouse_hook_ = nullptr;
// If UnhookWindowsHookEx fails, the hook is still installed in the
// system. Leave |mouse_hook_| pointing at the existing hook so that a
// subsequent SetForwardMouseMessages(true) reuses it instead of
// installing a duplicate hook.
if (UnhookWindowsHookEx(mouse_hook_)) {
mouse_hook_ = nullptr;
} else {
const DWORD error = ::GetLastError();
if (error == ERROR_INVALID_HOOK_HANDLE) {
mouse_hook_ = nullptr;
} else {
LOG(WARNING) << "Failed to unhook low-level mouse hook: "
<< logging::SystemErrorCodeToString(error);
}
}
}
}
}

View File

@@ -13,6 +13,7 @@
#include "base/strings/string_split.h"
#include "content/public/browser/browser_context.h"
#include "extensions/browser/extension_navigation_ui_data.h"
#include "mojo/public/cpp/bindings/self_owned_receiver.h"
#include "net/base/completion_repeating_callback.h"
#include "net/base/load_flags.h"
#include "net/http/http_response_headers.h"
@@ -29,6 +30,30 @@
namespace electron {
namespace {
class NoOpHeaderClient final : public network::mojom::TrustedHeaderClient {
public:
NoOpHeaderClient() = default;
NoOpHeaderClient(const NoOpHeaderClient&) = delete;
NoOpHeaderClient& operator=(const NoOpHeaderClient&) = delete;
~NoOpHeaderClient() override = default;
void OnBeforeSendHeaders(const net::HttpRequestHeaders& headers,
OnBeforeSendHeadersCallback callback) override {
std::move(callback).Run(net::OK, std::nullopt);
}
void OnHeadersReceived(const std::string& headers,
const net::IPEndPoint& remote_endpoint,
const std::optional<net::SSLInfo>& ssl_info,
OnHeadersReceivedCallback callback) override {
std::move(callback).Run(net::OK, std::nullopt, std::nullopt);
}
};
} // namespace
ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
FollowRedirectParams() = default;
ProxyingURLLoaderFactory::InProgressRequest::FollowRedirectParams::
@@ -869,8 +894,14 @@ void ProxyingURLLoaderFactory::OnLoaderCreated(
int32_t request_id,
mojo::PendingReceiver<network::mojom::TrustedHeaderClient> receiver) {
auto it = network_request_id_to_web_request_id_.find(request_id);
if (it == network_request_id_to_web_request_id_.end())
if (it == network_request_id_to_web_request_id_.end()) {
// Chromium can require the header client pipe to be bound even when
// Electron is using the pass-through path. Dropping the receiver here
// disconnects the URLLoader and causes the request to fail with ERR_FAILED.
mojo::MakeSelfOwnedReceiver(std::make_unique<NoOpHeaderClient>(),
std::move(receiver));
return;
}
auto request_it = requests_.find(it->second);
DCHECK(request_it != requests_.end());

View File

@@ -323,11 +323,17 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
fromModel:(electron::ElectronMenuModel*)model {
std::u16string label16 = model->GetLabelAt(index);
auto rawSecondaryLabel = model->GetSecondaryLabelAt(index);
std::u16string accessibility_label16 = model->GetAccessibilityLabelAt(index);
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
NSMenuItem* item = [[NSMenuItem alloc] initWithTitle:label
action:@selector(itemSelected:)
keyEquivalent:@""];
if (!accessibility_label16.empty()) {
NSString* accessibility_label =
base::SysUTF16ToNSString(accessibility_label16);
item.accessibilityLabel = accessibility_label;
}
if (!rawSecondaryLabel.empty()) {
if (@available(macOS 14.4, *)) {
@@ -499,8 +505,14 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
item.state = model->IsItemCheckedAt(index) ? NSControlStateValueOn
: NSControlStateValueOff;
std::u16string label16 = model->GetLabelAt(index);
std::u16string accessibility_label16 = model->GetAccessibilityLabelAt(index);
NSString* label = l10n_util::FixUpWindowsStyleLabel(label16);
item.title = label;
if (!accessibility_label16.empty()) {
NSString* accessibility_label =
base::SysUTF16ToNSString(accessibility_label16);
item.accessibilityLabel = accessibility_label;
}
std::u16string rawSecondaryLabel = model->GetSecondaryLabelAt(index);
if (!rawSecondaryLabel.empty()) {

View File

@@ -138,6 +138,10 @@ bool DevToolsManagerDelegate::HasBundledFrontendResources() {
return true;
}
bool DevToolsManagerDelegate::ShouldUseBundledFrontendResources() {
return true;
}
content::BrowserContext* DevToolsManagerDelegate::GetDefaultBrowserContext() {
return ElectronBrowserContext::GetDefaultBrowserContext();
}

View File

@@ -37,6 +37,7 @@ class DevToolsManagerDelegate : public content::DevToolsManagerDelegate {
bool new_window) override;
std::string GetDiscoveryPageHTML() override;
bool HasBundledFrontendResources() override;
bool ShouldUseBundledFrontendResources() override;
content::BrowserContext* GetDefaultBrowserContext() override;
};

View File

@@ -10,19 +10,15 @@
#include <vector>
#include "base/feature_list.h"
#include "base/i18n/rtl.h"
#include "shell/browser/api/electron_api_web_contents.h"
#include "shell/browser/linux/x11_util.h"
#include "shell/browser/native_window_features.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/window_delegate.h"
#include "ui/base/hit_test.h"
#include "ui/display/screen.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/geometry/skia_conversions.h"
#include "ui/linux/linux_ui.h"
#include "ui/ozone/public/ozone_platform.h"
#include "ui/platform_window/extensions/wayland_extension.h"
@@ -30,6 +26,8 @@
#include "ui/platform_window/platform_window_init_properties.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
#include "ui/views/window/frame_view_linux.h"
#include "ui/views/window/frame_view_utils_linux.h"
namespace electron {
@@ -50,6 +48,12 @@ bool ElectronDesktopWindowTreeHostLinux::SupportsClientFrameShadow() const {
void ElectronDesktopWindowTreeHostLinux::OnWidgetInitDone() {
views::DesktopWindowTreeHostLinux::OnWidgetInitDone();
// SetSupportsClientFrameShadow must happen after widget init when
// platform_window is available.
if (auto* fvl = native_window_view_->GetFrameViewLinux())
fvl->SetSupportsClientFrameShadow(SupportsClientFrameShadow());
UpdateFrameHints();
}
@@ -78,19 +82,22 @@ void ElectronDesktopWindowTreeHostLinux::Show(
DesktopWindowTreeHostLinux::SetWindowIcons(saved_window_icon_, {});
}
gfx::Insets ElectronDesktopWindowTreeHostLinux::GetRestoredFrameBorderInsets()
const {
if (auto* fvl = native_window_view_->GetFrameViewLinux())
return fvl->GetRestoredFrameBorderInsets();
auto* const layout = native_window_view_->GetLinuxFrameLayout();
return layout ? layout->RestoredFrameBorderInsets() : gfx::Insets();
}
gfx::Insets ElectronDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
ui::PlatformWindowState window_state) const {
// If we are not showing frame, the insets should be zero.
if (!IsShowingFrame(window_state)) {
if (!IsShowingFrame(window_state))
return gfx::Insets();
}
auto* const layout = native_window_view_->GetLinuxFrameLayout();
if (!layout)
return {};
gfx::Insets insets = layout->RestoredFrameBorderInsets();
return insets;
return GetRestoredFrameBorderInsets();
}
// Electron treats min/max constraints as the logical window size, but Chromium
@@ -101,11 +108,8 @@ std::optional<gfx::Size>
ElectronDesktopWindowTreeHostLinux::GetMinimumSizeForWindow() const {
auto min_size = views::DesktopWindowTreeHostLinux::GetMinimumSizeForWindow();
if (min_size.has_value()) {
auto* const layout = native_window_view_->GetLinuxFrameLayout();
if (layout) {
gfx::Insets insets = layout->RestoredFrameBorderInsets();
min_size->Enlarge(insets.width(), insets.height());
}
gfx::Insets insets = GetRestoredFrameBorderInsets();
min_size->Enlarge(insets.width(), insets.height());
}
return min_size;
}
@@ -114,15 +118,12 @@ std::optional<gfx::Size>
ElectronDesktopWindowTreeHostLinux::GetMaximumSizeForWindow() const {
auto max_size = views::DesktopWindowTreeHostLinux::GetMaximumSizeForWindow();
if (max_size.has_value()) {
auto* const layout = native_window_view_->GetLinuxFrameLayout();
if (layout) {
gfx::Insets insets = layout->RestoredFrameBorderInsets();
// 0 means no constraint, so don't inflate.
if (max_size->width() > 0)
max_size->set_width(max_size->width() + insets.width());
if (max_size->height() > 0)
max_size->set_height(max_size->height() + insets.height());
}
gfx::Insets insets = GetRestoredFrameBorderInsets();
// 0 means no constraint, so don't inflate.
if (max_size->width() > 0)
max_size->set_width(max_size->width() + insets.width());
if (max_size->height() > 0)
max_size->set_height(max_size->height() + insets.height());
}
return max_size;
}
@@ -151,15 +152,17 @@ void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged(
void ElectronDesktopWindowTreeHostLinux::OnWindowTiledStateChanged(
ui::WindowTiledEdges new_tiled_edges) {
if (auto* layout = native_window_view_->GetLinuxFrameLayout()) {
// GNOME on Ubuntu reports all edges as tiled
// even if the window is only half-tiled so do not trust individual edge
// values.
bool maximized = native_window_view_->IsMaximized();
bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
new_tiled_edges.bottom || new_tiled_edges.right;
layout->set_tiled(tiled && !maximized);
}
// GNOME on Ubuntu reports all edges as tiled even if the window is only
// half-tiled, so do not trust individual edge values.
bool maximized = native_window_view_->IsMaximized();
bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
new_tiled_edges.bottom || new_tiled_edges.right;
bool is_tiled = tiled && !maximized;
if (auto* fvl = native_window_view_->GetFrameViewLinux())
fvl->SetTiled(is_tiled);
else if (auto* layout = native_window_view_->GetLinuxFrameLayout())
layout->set_tiled(is_tiled);
UpdateFrameHints();
ScheduleRelayout();
if (GetWidget()->non_client_view()) {
@@ -213,95 +216,61 @@ void ElectronDesktopWindowTreeHostLinux::OnDeviceScaleFactorChanged() {
}
void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations)) {
auto* const layout = native_window_view_->GetLinuxFrameLayout();
if (!layout)
return;
ui::PlatformWindow* window = platform_window();
auto window_state = window->GetPlatformWindowState();
float scale = device_scale_factor();
const gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size();
if (SupportsClientFrameShadow()) {
auto insets = CalculateInsetsInDIP(window_state);
if (insets.IsEmpty()) {
window->SetInputRegion(std::nullopt);
} else {
gfx::Rect input_bounds(widget_size);
input_bounds.Inset(insets - layout->GetInputInsets());
input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
window->SetInputRegion(
std::optional<std::vector<gfx::Rect>>({input_bounds}));
}
if (native_window_view_->GetFrameViewLinux()) {
views::DesktopWindowTreeHostLinux::UpdateFrameHints();
// Clear the opaque region for translucent windows.
if (native_window_view_->IsTranslucent() &&
views::Widget::IsWindowCompositingSupported()) {
platform_window()->SetOpaqueRegion(std::vector<gfx::Rect>{});
}
if (ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported()) {
// Set the opaque region.
std::vector<gfx::Rect> opaque_region;
if (native_window_view_->IsTranslucent()) {
// Leave opaque_region empty.
} else if (IsShowingFrame(window_state)) {
// The opaque region is a list of rectangles that contain only fully
// opaque pixels of the window. We need to convert the clipping
// rounded-rect into this format.
SkRRect rrect = layout->GetRoundedWindowBounds();
gfx::RectF rectf(layout->GetWindowBounds());
rectf.Scale(scale);
// It is acceptable to omit some pixels that are opaque, but the region
// must not include any translucent pixels. Therefore, we must
// conservatively scale to the enclosed rectangle.
gfx::Rect rect = gfx::ToEnclosedRect(rectf);
// Create the initial region from the clipping rectangle without rounded
// corners.
SkRegion region(gfx::RectToSkIRect(rect));
// Now subtract out the small rectangles that cover the corners.
struct {
SkRRect::Corner corner;
bool left;
bool upper;
} kCorners[] = {
{SkRRect::kUpperLeft_Corner, true, true},
{SkRRect::kUpperRight_Corner, false, true},
{SkRRect::kLowerLeft_Corner, true, false},
{SkRRect::kLowerRight_Corner, false, false},
};
for (const auto& corner : kCorners) {
auto radii = rrect.radii(corner.corner);
auto rx = std::ceil(scale * radii.x());
auto ry = std::ceil(scale * radii.y());
auto corner_rect = SkIRect::MakeXYWH(
corner.left ? rect.x() : rect.right() - rx,
corner.upper ? rect.y() : rect.bottom() - ry, rx, ry);
region.op(corner_rect, SkRegion::kDifference_Op);
}
auto translucent_top_area_rect = SkIRect::MakeXYWH(
rect.x(), rect.y(), rect.width(),
std::ceil(layout->GetTranslucentTopAreaHeight() * scale -
rect.y()));
region.op(translucent_top_area_rect, SkRegion::kDifference_Op);
// Convert the region to a list of rectangles.
for (SkRegion::Iterator i(region); !i.done(); i.next()) {
opaque_region.push_back(gfx::SkIRectToRect(i.rect()));
}
} else {
// The entire window except for the translucent top is opaque.
gfx::Rect opaque_region_dip(widget_size);
gfx::Insets insets;
insets.set_top(layout->GetTranslucentTopAreaHeight());
opaque_region_dip.Inset(insets);
opaque_region.push_back(
gfx::ScaleToEnclosingRect(opaque_region_dip, scale));
}
window->SetOpaqueRegion(opaque_region);
}
SizeConstraintsChanged();
return;
}
auto* layout = native_window_view_->GetLinuxFrameLayout();
if (!layout)
return;
ui::PlatformWindow* window = platform_window();
auto window_state = window->GetPlatformWindowState();
float scale = device_scale_factor();
const gfx::Size widget_size = GetWidget()->GetWindowBoundsInScreen().size();
if (SupportsClientFrameShadow()) {
auto insets = CalculateInsetsInDIP(window_state);
if (insets.IsEmpty()) {
window->SetInputRegion(std::nullopt);
} else {
gfx::Rect input_bounds(widget_size);
input_bounds.Inset(insets - layout->GetInputInsets());
input_bounds = gfx::ScaleToEnclosingRect(input_bounds, scale);
window->SetInputRegion(
std::optional<std::vector<gfx::Rect>>({input_bounds}));
}
}
if (ui::OzonePlatform::GetInstance()->IsWindowCompositingSupported()) {
// Set the opaque region.
std::vector<gfx::Rect> opaque_region;
if (native_window_view_->IsTranslucent()) {
// Leave opaque_region empty.
} else if (IsShowingFrame(window_state)) {
opaque_region = views::GetRestoredOpaqueRegion(
layout->GetRoundedWindowBounds(), scale,
layout->GetTranslucentTopAreaHeight());
} else {
// The entire window except for the translucent top is opaque.
gfx::Rect opaque_region_dip(widget_size);
gfx::Insets insets;
insets.set_top(layout->GetTranslucentTopAreaHeight());
opaque_region_dip.Inset(insets);
opaque_region.push_back(
gfx::ScaleToEnclosingRect(opaque_region_dip, scale));
}
window->SetOpaqueRegion(opaque_region);
}
SizeConstraintsChanged();
}
void ElectronDesktopWindowTreeHostLinux::DispatchEvent(ui::Event* event) {

View File

@@ -16,7 +16,6 @@
#include "ui/gfx/image/image_skia.h"
#include "ui/linux/device_scale_factor_observer.h"
#include "ui/linux/linux_ui.h"
#include "ui/native_theme/native_theme_observer.h"
#include "ui/platform_window/platform_window.h"
#include "ui/views/widget/desktop_aura/desktop_window_tree_host_linux.h"
@@ -78,12 +77,13 @@ class ElectronDesktopWindowTreeHostLinux
bool IsShowingFrame(ui::PlatformWindowState window_state) const;
// Returns restored frame border insets regardless of current widget state.
gfx::Insets GetRestoredFrameBorderInsets() const;
gfx::ImageSkia saved_window_icon_;
raw_ptr<NativeWindowViews> native_window_view_; // weak ref
base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
theme_observation_{this};
base::ScopedObservation<ui::LinuxUi, ui::DeviceScaleFactorObserver>
scale_observation_{this};
ui::PlatformWindowState window_state_ = ui::PlatformWindowState::kUnknown;

View File

@@ -49,6 +49,12 @@ std::u16string ElectronMenuModel::GetCustomTypeAt(size_t index) {
return iter == std::end(customTypes_) ? std::u16string() : iter->second;
}
std::u16string ElectronMenuModel::GetAccessibilityLabelAt(size_t index) const {
if (delegate_)
return delegate_->GetAccessibilityLabelForCommandId(GetCommandIdAt(index));
return std::u16string();
}
void ElectronMenuModel::SetRole(size_t index, const std::u16string& role) {
int command_id = GetCommandIdAt(index);
roles_[command_id] = role;

View File

@@ -49,6 +49,9 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
virtual bool ShouldCommandIdWorkWhenHidden(int command_id) const = 0;
virtual std::u16string GetAccessibilityLabelForCommandId(
int command_id) const = 0;
#if BUILDFLAG(IS_MAC)
virtual bool GetSharingItemForCommandId(int command_id,
SharingItem* item) const = 0;
@@ -86,6 +89,7 @@ class ElectronMenuModel : public ui::SimpleMenuModel {
std::u16string GetToolTipAt(size_t index);
void SetCustomType(size_t index, const std::u16string& customType);
std::u16string GetCustomTypeAt(size_t index);
std::u16string GetAccessibilityLabelAt(size_t index) const;
void SetRole(size_t index, const std::u16string& role);
std::u16string GetRoleAt(size_t index);
std::u16string GetLabelAt(size_t index) const override;

View File

@@ -1,455 +0,0 @@
// Copyright (c) 2021 Ryan Gonzalez.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/views/client_frame_view_linux.h"
#include <algorithm>
#include "base/strings/utf_string_conversions.h"
#include "cc/paint/paint_filter.h"
#include "cc/paint/paint_flags.h"
#include "shell/browser/native_window_views.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "ui/base/hit_test.h"
#include "ui/base/l10n/l10n_util.h"
#include "ui/base/metadata/metadata_impl_macros.h"
#include "ui/base/models/image_model.h"
#include "ui/base/ui_base_types.h"
#include "ui/gfx/canvas.h"
#include "ui/gfx/font_list.h"
#include "ui/gfx/geometry/insets.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/text_constants.h"
#include "ui/gtk/gtk_compat.h" // nogncheck
#include "ui/gtk/gtk_util.h" // nogncheck
#include "ui/linux/linux_ui.h"
#include "ui/linux/nav_button_provider.h"
#include "ui/native_theme/native_theme.h"
#include "ui/strings/grit/ui_strings.h"
#include "ui/views/accessibility/view_accessibility.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/style/typography.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/frame_buttons.h"
#include "ui/views/window/window_button_order_provider.h"
namespace electron {
namespace {
ui::NavButtonProvider::ButtonState ButtonStateToNavButtonProviderState(
views::Button::ButtonState state) {
switch (state) {
case views::Button::STATE_NORMAL:
return ui::NavButtonProvider::ButtonState::kNormal;
case views::Button::STATE_HOVERED:
return ui::NavButtonProvider::ButtonState::kHovered;
case views::Button::STATE_PRESSED:
return ui::NavButtonProvider::ButtonState::kPressed;
case views::Button::STATE_DISABLED:
return ui::NavButtonProvider::ButtonState::kDisabled;
case views::Button::STATE_COUNT:
default:
NOTREACHED();
}
}
} // namespace
ClientFrameViewLinux::ClientFrameViewLinux(NativeWindowViews* window,
views::Widget* frame)
: FramelessView{window, frame},
theme_{ui::NativeTheme::GetInstanceForNativeUi()},
nav_button_provider_(
ui::LinuxUiTheme::GetForProfile(nullptr)->CreateNavButtonProvider(
ui::FrameType::kDefault)),
nav_buttons_{
NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kClose,
views::FrameButton::kClose, &views::Widget::Close,
IDS_APP_ACCNAME_CLOSE, HTCLOSE},
NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kMaximize,
views::FrameButton::kMaximize, &views::Widget::Maximize,
IDS_APP_ACCNAME_MAXIMIZE, HTMAXBUTTON},
NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kRestore,
views::FrameButton::kMaximize, &views::Widget::Restore,
IDS_APP_ACCNAME_RESTORE, HTMAXBUTTON},
NavButton{ui::NavButtonProvider::FrameButtonDisplayType::kMinimize,
views::FrameButton::kMinimize, &views::Widget::Minimize,
IDS_APP_ACCNAME_MINIMIZE, HTMINBUTTON},
},
trailing_frame_buttons_{views::FrameButton::kMinimize,
views::FrameButton::kMaximize,
views::FrameButton::kClose} {
for (auto& button : nav_buttons_) {
auto image_button = std::make_unique<views::ImageButton>();
image_button->SetImageVerticalAlignment(views::ImageButton::ALIGN_MIDDLE);
image_button->GetViewAccessibility().SetName(
l10n_util::GetStringUTF16(button.accessibility_id));
button.button = AddChildView(std::move(image_button));
}
auto title = std::make_unique<views::Label>();
title->SetSubpixelRenderingEnabled(false);
title->SetAutoColorReadabilityEnabled(false);
title->SetHorizontalAlignment(gfx::ALIGN_CENTER);
title->SetVerticalAlignment(gfx::ALIGN_MIDDLE);
title->SetTextStyle(views::style::STYLE_TAB_ACTIVE);
title_ = AddChildView(std::move(title));
native_theme_observer_.Observe(theme_);
if (auto* ui = ui::LinuxUi::instance()) {
ui->AddWindowButtonOrderObserver(this);
OnWindowButtonOrderingChange();
}
linux_frame_layout_ = std::make_unique<LinuxCSDNativeFrameLayout>(window);
// Unretained() is safe because the subscription is saved into an instance
// member and thus will be cancelled upon the instance's destruction.
paint_as_active_changed_subscription_ =
frame->RegisterPaintAsActiveChangedCallback(base::BindRepeating(
&ClientFrameViewLinux::PaintAsActiveChanged, base::Unretained(this)));
UpdateWindowTitle();
for (auto& button : nav_buttons_) {
// Unretained() is safe because the buttons are added as children to, and
// thus owned by, this view. Thus, the buttons themselves will be destroyed
// when this view is destroyed, and the frame's life must never outlive the
// view.
button.button->SetCallback(
base::BindRepeating(button.callback, base::Unretained(frame)));
}
UpdateThemeValues();
}
ClientFrameViewLinux::~ClientFrameViewLinux() {
if (auto* ui = ui::LinuxUi::instance())
ui->RemoveWindowButtonOrderObserver(this);
theme_->RemoveObserver(this);
}
gfx::Insets ClientFrameViewLinux::RestoredFrameBorderInsets() const {
return linux_frame_layout_->RestoredFrameBorderInsets();
}
LinuxFrameLayout* ClientFrameViewLinux::GetLinuxFrameLayout() const {
return linux_frame_layout_.get();
}
void ClientFrameViewLinux::OnNativeThemeUpdated(
ui::NativeTheme* observed_theme) {
UpdateThemeValues();
}
void ClientFrameViewLinux::OnWindowButtonOrderingChange() {
auto* provider = views::WindowButtonOrderProvider::GetInstance();
leading_frame_buttons_ = provider->leading_buttons();
trailing_frame_buttons_ = provider->trailing_buttons();
InvalidateLayout();
}
int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) {
return ResizingBorderHitTestImpl(
point, linux_frame_layout_->GetResizeBorderInsets());
}
gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {
gfx::Rect client_bounds = bounds();
if (!frame_->IsFullscreen()) {
client_bounds.Inset(linux_frame_layout_->FrameBorderInsets(false));
client_bounds.Inset(
gfx::Insets::TLBR(GetTitlebarBounds().height(), 0, 0, 0));
}
return client_bounds;
}
gfx::Rect ClientFrameViewLinux::GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const {
gfx::Insets insets = bounds().InsetsFrom(GetBoundsForClientView());
return gfx::Rect(std::max(0, client_bounds.x() - insets.left()),
std::max(0, client_bounds.y() - insets.top()),
client_bounds.width() + insets.width(),
client_bounds.height() + insets.height());
}
int ClientFrameViewLinux::NonClientHitTest(const gfx::Point& point) {
int component = ResizingBorderHitTest(point);
if (component != HTNOWHERE) {
return component;
}
for (auto& button : nav_buttons_) {
if (button.button->GetVisible() &&
button.button->GetMirroredBounds().Contains(point)) {
return button.hit_test_id;
}
}
if (GetTitlebarBounds().Contains(point)) {
return HTCAPTION;
}
return FramelessView::NonClientHitTest(point);
}
void ClientFrameViewLinux::GetWindowMask(const gfx::Size& size,
SkPath* window_mask) {
// Nothing to do here, as transparency is used for decorations, not masks.
}
void ClientFrameViewLinux::UpdateWindowTitle() {
title_->SetText(base::UTF8ToUTF16(window_->GetTitle()));
}
void ClientFrameViewLinux::SizeConstraintsChanged() {
InvalidateLayout();
}
void ClientFrameViewLinux::Layout(PassKey) {
LayoutSuperclass<FramelessView>(this);
if (frame_->IsFullscreen()) {
// Just hide everything and return.
for (NavButton& button : nav_buttons_) {
button.button->SetVisible(false);
}
title_->SetVisible(false);
return;
}
UpdateButtonImages();
LayoutButtons();
gfx::Rect title_bounds(GetTitlebarContentBounds());
title_bounds.Inset(theme_values_.title_padding);
title_->SetVisible(true);
title_->SetBoundsRect(title_bounds);
}
void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
if (frame_->IsFullscreen()) {
return;
}
if (frame_->IsMaximized()) {
// Some GTK themes (Breeze) still render shadow/border assets when
// maximized, and we don't need a border when maximized anyway. Chromium
// switches on this too: OpaqueBrowserFrameView::PaintMaximizedFrameBorder.
PaintMaximizedFrameBorder(canvas);
} else {
PaintRestoredFrameBorder(canvas);
}
}
void ClientFrameViewLinux::PaintRestoredFrameBorder(gfx::Canvas* canvas) {
if (auto* frame_provider = linux_frame_layout_->GetFrameProvider()) {
frame_provider->PaintWindowFrame(
canvas, GetLocalBounds(), GetTitlebarBounds().bottom(),
ShouldPaintAsActive(), linux_frame_layout_->GetInputInsets());
}
}
void ClientFrameViewLinux::PaintMaximizedFrameBorder(gfx::Canvas* canvas) {
ui::NativeTheme::FrameTopAreaExtraParams frame_top_area;
frame_top_area.use_custom_frame = true;
frame_top_area.is_active = ShouldPaintAsActive();
frame_top_area.default_background_color = SK_ColorTRANSPARENT;
ui::NativeTheme::ExtraParams params(frame_top_area);
GetNativeTheme()->Paint(
canvas->sk_canvas(), GetColorProvider(), ui::NativeTheme::kFrameTopArea,
ui::NativeTheme::kNormal,
gfx::Rect(0, 0, width(), GetTitlebarBounds().bottom()), params);
}
void ClientFrameViewLinux::PaintAsActiveChanged() {
UpdateThemeValues();
}
void ClientFrameViewLinux::UpdateThemeValues() {
gtk::GtkCssContext window_context =
gtk::AppendCssNodeToStyleContext({}, "window.background.csd");
gtk::GtkCssContext headerbar_context = gtk::AppendCssNodeToStyleContext(
window_context, "headerbar.default-decoration.titlebar");
gtk::GtkCssContext title_context =
gtk::AppendCssNodeToStyleContext(headerbar_context, "label.title");
// ShouldPaintAsActive asks the widget, so assume active if the widget is not
// set yet.
if (GetWidget() != nullptr && !ShouldPaintAsActive()) {
gtk_style_context_set_state(window_context, GTK_STATE_FLAG_BACKDROP);
gtk_style_context_set_state(headerbar_context, GTK_STATE_FLAG_BACKDROP);
gtk_style_context_set_state(title_context, GTK_STATE_FLAG_BACKDROP);
}
theme_values_.window_border_radius =
linux_frame_layout_->GetTopCornerRadiusDip();
gtk::GtkStyleContextGet(headerbar_context, "min-height",
&theme_values_.titlebar_min_height, nullptr);
theme_values_.titlebar_padding =
gtk::GtkStyleContextGetPadding(headerbar_context);
theme_values_.title_color = gtk::GtkStyleContextGetColor(title_context);
theme_values_.title_padding = gtk::GtkStyleContextGetPadding(title_context);
title_->SetEnabledColor(theme_values_.title_color);
InvalidateLayout();
SchedulePaint();
}
ui::NavButtonProvider::FrameButtonDisplayType
ClientFrameViewLinux::GetButtonTypeToSkip() const {
return frame_->IsMaximized()
? ui::NavButtonProvider::FrameButtonDisplayType::kMaximize
: ui::NavButtonProvider::FrameButtonDisplayType::kRestore;
}
void ClientFrameViewLinux::UpdateButtonImages() {
int top_area_height = theme_values_.titlebar_min_height +
theme_values_.titlebar_padding.height();
nav_button_provider_->RedrawImages(top_area_height, frame_->IsMaximized(),
ShouldPaintAsActive());
ui::NavButtonProvider::FrameButtonDisplayType skip_type =
GetButtonTypeToSkip();
for (NavButton& button : nav_buttons_) {
if (button.type == skip_type) {
continue;
}
for (size_t state_id = 0; state_id < views::Button::STATE_COUNT;
state_id++) {
views::Button::ButtonState state =
static_cast<views::Button::ButtonState>(state_id);
button.button->SetImageModel(
state, ui::ImageModel::FromImageSkia(nav_button_provider_->GetImage(
button.type, ButtonStateToNavButtonProviderState(state))));
}
}
}
void ClientFrameViewLinux::LayoutButtons() {
for (NavButton& button : nav_buttons_) {
button.button->SetVisible(false);
}
gfx::Rect remaining_content_bounds = GetTitlebarContentBounds();
LayoutButtonsOnSide(ButtonSide::kLeading, &remaining_content_bounds);
LayoutButtonsOnSide(ButtonSide::kTrailing, &remaining_content_bounds);
}
void ClientFrameViewLinux::LayoutButtonsOnSide(
ButtonSide side,
gfx::Rect* remaining_content_bounds) {
ui::NavButtonProvider::FrameButtonDisplayType skip_type =
GetButtonTypeToSkip();
std::vector<views::FrameButton> frame_buttons;
switch (side) {
case ButtonSide::kLeading:
frame_buttons = leading_frame_buttons_;
break;
case ButtonSide::kTrailing:
frame_buttons = trailing_frame_buttons_;
// We always lay buttons out going from the edge towards the center, but
// they are given to us as left-to-right, so reverse them.
std::ranges::reverse(frame_buttons);
break;
default:
NOTREACHED();
}
for (views::FrameButton frame_button : frame_buttons) {
auto button =
std::ranges::find_if(nav_buttons_, [&](const NavButton& test) {
return test.type != skip_type && test.frame_button == frame_button;
});
CHECK(button != nav_buttons_.end())
<< "Failed to find frame button: " << static_cast<int>(frame_button);
if (button->type == skip_type) {
continue;
}
button->button->SetVisible(true);
// CSS min-size/height/width is not enough to determine the actual size of
// the buttons, so we sample the rendered image. See Chromium's
// BrowserFrameViewLinuxNative::MaybeUpdateCachedFrameButtonImages.
int button_width =
nav_button_provider_
->GetImage(button->type,
ui::NavButtonProvider::ButtonState::kNormal)
.width();
int next_button_offset =
button_width + nav_button_provider_->GetInterNavButtonSpacing();
int x_position = 0;
gfx::Insets inset_after_placement;
switch (side) {
case ButtonSide::kLeading:
x_position = remaining_content_bounds->x();
inset_after_placement.set_left(next_button_offset);
break;
case ButtonSide::kTrailing:
x_position = remaining_content_bounds->right() - button_width;
inset_after_placement.set_right(next_button_offset);
break;
default:
NOTREACHED();
}
button->button->SetBounds(x_position, remaining_content_bounds->y(),
button_width, remaining_content_bounds->height());
remaining_content_bounds->Inset(inset_after_placement);
}
}
gfx::Rect ClientFrameViewLinux::GetTitlebarBounds() const {
if (frame_->IsFullscreen()) {
return {};
}
int font_height = gfx::FontList().GetHeight();
int titlebar_height =
std::max(font_height, theme_values_.titlebar_min_height) +
GetTitlebarContentInsets().height();
gfx::Insets decoration_insets = linux_frame_layout_->FrameBorderInsets(false);
// We add the inset height here, so the .Inset() that follows won't reduce it
// to be too small.
gfx::Rect titlebar(width(), titlebar_height + decoration_insets.height());
titlebar.Inset(decoration_insets);
return titlebar;
}
gfx::Insets ClientFrameViewLinux::GetTitlebarContentInsets() const {
return theme_values_.titlebar_padding +
nav_button_provider_->GetTopAreaSpacing();
}
gfx::Rect ClientFrameViewLinux::GetTitlebarContentBounds() const {
gfx::Rect titlebar(GetTitlebarBounds());
titlebar.Inset(GetTitlebarContentInsets());
return titlebar;
}
views::View* ClientFrameViewLinux::TargetForRect(views::View* root,
const gfx::Rect& rect) {
return views::FrameView::TargetForRect(root, rect);
}
BEGIN_METADATA(ClientFrameViewLinux) END_METADATA
} // namespace electron

View File

@@ -1,135 +0,0 @@
// Copyright (c) 2021 Ryan Gonzalez.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_CLIENT_FRAME_VIEW_LINUX_H_
#define ELECTRON_SHELL_BROWSER_UI_VIEWS_CLIENT_FRAME_VIEW_LINUX_H_
#include <array>
#include <memory>
#include <vector>
#include "base/memory/raw_ptr.h"
#include "base/memory/raw_ptr_exclusion.h"
#include "base/scoped_observation.h"
#include "shell/browser/ui/views/frameless_view.h"
#include "shell/browser/ui/views/linux_frame_layout.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/base/ui_base_types.h"
#include "ui/linux/linux_ui.h"
#include "ui/linux/nav_button_provider.h"
#include "ui/linux/window_button_order_observer.h"
#include "ui/native_theme/native_theme.h"
#include "ui/native_theme/native_theme_observer.h"
#include "ui/views/controls/button/image_button.h"
#include "ui/views/controls/label.h"
#include "ui/views/widget/widget.h"
#include "ui/views/window/frame_buttons.h"
namespace electron {
class NativeWindowViews;
class ClientFrameViewLinux : public FramelessView,
private ui::NativeThemeObserver,
private ui::WindowButtonOrderObserver {
METADATA_HEADER(ClientFrameViewLinux, FramelessView)
public:
ClientFrameViewLinux(NativeWindowViews* window, views::Widget* frame);
~ClientFrameViewLinux() override;
// FramelessView:
gfx::Insets RestoredFrameBorderInsets() const override;
LinuxFrameLayout* GetLinuxFrameLayout() const override;
protected:
// ui::NativeThemeObserver:
void OnNativeThemeUpdated(ui::NativeTheme* observed_theme) override;
// views::WindowButtonOrderObserver:
void OnWindowButtonOrderingChange() override;
// Overridden from FramelessView:
int ResizingBorderHitTest(const gfx::Point& point) override;
// Overridden from views::FrameView:
gfx::Rect GetBoundsForClientView() const override;
gfx::Rect GetWindowBoundsForClientBounds(
const gfx::Rect& client_bounds) const override;
int NonClientHitTest(const gfx::Point& point) override;
void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override;
void UpdateWindowTitle() override;
void SizeConstraintsChanged() override;
// Overridden from View:
void Layout(PassKey) override;
void OnPaint(gfx::Canvas* canvas) override;
// Overridden from views::ViewTargeterDelegate
views::View* TargetForRect(views::View* root, const gfx::Rect& rect) override;
private:
static constexpr int kNavButtonCount = 4;
struct NavButton {
ui::NavButtonProvider::FrameButtonDisplayType type;
views::FrameButton frame_button;
void (views::Widget::*callback)();
int accessibility_id;
int hit_test_id;
raw_ptr<views::ImageButton> button = {};
};
struct ThemeValues {
float window_border_radius;
int titlebar_min_height;
gfx::Insets titlebar_padding;
SkColor title_color;
gfx::Insets title_padding;
};
void PaintAsActiveChanged();
void PaintRestoredFrameBorder(gfx::Canvas* canvas);
void PaintMaximizedFrameBorder(gfx::Canvas* canvas);
void UpdateThemeValues();
enum class ButtonSide { kLeading, kTrailing };
ui::NavButtonProvider::FrameButtonDisplayType GetButtonTypeToSkip() const;
void UpdateButtonImages();
void LayoutButtons();
void LayoutButtonsOnSide(ButtonSide side,
gfx::Rect* remaining_content_bounds);
gfx::Rect GetTitlebarBounds() const;
gfx::Insets GetTitlebarContentInsets() const;
gfx::Rect GetTitlebarContentBounds() const;
std::unique_ptr<LinuxCSDNativeFrameLayout> linux_frame_layout_;
raw_ptr<ui::NativeTheme> theme_;
ThemeValues theme_values_;
raw_ptr<views::Label> title_;
std::unique_ptr<ui::NavButtonProvider> nav_button_provider_;
std::array<NavButton, kNavButtonCount> nav_buttons_;
std::vector<views::FrameButton> leading_frame_buttons_;
std::vector<views::FrameButton> trailing_frame_buttons_;
base::ScopedObservation<ui::NativeTheme, ui::NativeThemeObserver>
native_theme_observer_{this};
base::ScopedObservation<ui::LinuxUi, ui::WindowButtonOrderObserver>
window_button_order_observer_{this};
base::CallbackListSubscription paint_as_active_changed_subscription_;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_CLIENT_FRAME_VIEW_LINUX_H_

View File

@@ -0,0 +1,52 @@
// Copyright (c) 2026 Athul Iddya.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/ui/views/native_frame_view_linux.h"
#include <utility>
#include "shell/browser/native_window_views.h"
#include "ui/base/hit_test.h"
#include "ui/base/metadata/metadata_impl_macros.h"
namespace electron {
NativeFrameViewLinux::NativeFrameViewLinux(
NativeWindowViews* window,
views::Widget* widget,
std::unique_ptr<ui::NavButtonProvider> nav_button_provider,
views::NativeFrameViewLayoutLinux::FrameProviderGetter getter)
: views::NativeFrameViewLinux(widget,
std::move(nav_button_provider),
std::move(getter)),
window_(window) {
InitViews();
}
NativeFrameViewLinux::~NativeFrameViewLinux() = default;
int NativeFrameViewLinux::NonClientHitTest(const gfx::Point& point) {
int result = views::NativeFrameViewLinux::NonClientHitTest(point);
if (result != HTCLIENT)
return result;
int contents_hit_test = window_->NonClientHitTest(point);
if (contents_hit_test != HTNOWHERE)
return contents_hit_test;
return HTCLIENT;
}
gfx::Size NativeFrameViewLinux::GetMinimumSize() const {
return window_->GetMinimumSize();
}
gfx::Size NativeFrameViewLinux::GetMaximumSize() const {
return window_->GetMaximumSize();
}
BEGIN_METADATA(NativeFrameViewLinux)
END_METADATA
} // namespace electron

View File

@@ -0,0 +1,44 @@
// Copyright (c) 2026 Athul Iddya.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_LINUX_H_
#define ELECTRON_SHELL_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_LINUX_H_
#include <memory>
#include "base/memory/raw_ptr.h"
#include "ui/base/metadata/metadata_header_macros.h"
#include "ui/linux/nav_button_provider.h"
#include "ui/views/window/native_frame_view_layout_linux.h"
#include "ui/views/window/native_frame_view_linux.h"
namespace electron {
class NativeWindowViews;
// Extends views::NativeFrameViewLinux for Electron integration, such as
// app-defined draggable regions and frame sizing.
class NativeFrameViewLinux : public views::NativeFrameViewLinux {
METADATA_HEADER(NativeFrameViewLinux, views::NativeFrameViewLinux)
public:
NativeFrameViewLinux(
NativeWindowViews* window,
views::Widget* widget,
std::unique_ptr<ui::NavButtonProvider> nav_button_provider,
views::NativeFrameViewLayoutLinux::FrameProviderGetter getter);
~NativeFrameViewLinux() override;
// views::FrameViewLinux:
int NonClientHitTest(const gfx::Point& point) override;
gfx::Size GetMinimumSize() const override;
gfx::Size GetMaximumSize() const override;
private:
raw_ptr<NativeWindowViews> window_ = nullptr;
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_NATIVE_FRAME_VIEW_LINUX_H_

View File

@@ -0,0 +1,204 @@
// Copyright (c) 2026 Anthropic, PBC.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#include "shell/browser/webauthn/electron_authenticator_request_client_delegate.h"
#include <string>
#include <utility>
#include "base/base64url.h"
#include "base/containers/span.h"
#include "base/functional/bind.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/browser/web_contents.h"
#include "device/fido/authenticator_get_assertion_response.h"
#include "device/fido/public/public_key_credential_descriptor.h"
#include "device/fido/public/public_key_credential_user_entity.h"
#include "gin/arguments.h"
#include "gin/data_object_builder.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/javascript_environment.h"
#include "shell/common/gin_converters/callback_converter.h"
#include "shell/common/gin_converters/frame_converter.h"
#include "shell/common/gin_helper/event.h"
#include "shell/common/gin_helper/event_emitter_caller.h"
namespace electron {
namespace {
// WebAuthn's PublicKeyCredential.id is canonically URL-safe base64 with no
// padding, so encode credential IDs and user handles the same way to keep the
// event payload string-comparable to values returned by navigator.credentials.
std::string Base64UrlEncodeNoPad(base::span<const uint8_t> input) {
std::string out;
base::Base64UrlEncode(input, base::Base64UrlEncodePolicy::OMIT_PADDING, &out);
return out;
}
std::string CredentialIdFor(
const device::AuthenticatorGetAssertionResponse& response) {
if (response.credential) {
return Base64UrlEncodeNoPad(response.credential->id);
}
return {};
}
} // namespace
ElectronAuthenticatorRequestClientDelegate::
ElectronAuthenticatorRequestClientDelegate(
content::RenderFrameHost* render_frame_host)
: render_frame_host_id_(render_frame_host->GetGlobalId()) {}
ElectronAuthenticatorRequestClientDelegate::
~ElectronAuthenticatorRequestClientDelegate() = default;
void ElectronAuthenticatorRequestClientDelegate::SetRelyingPartyId(
const std::string& rp_id) {
relying_party_id_ = rp_id;
}
void ElectronAuthenticatorRequestClientDelegate::StartObserving(
device::FidoRequestHandlerBase* request_handler) {
request_handler_observation_.Observe(request_handler);
}
void ElectronAuthenticatorRequestClientDelegate::StopObserving(
device::FidoRequestHandlerBase* request_handler) {
request_handler_observation_.Reset();
}
void ElectronAuthenticatorRequestClientDelegate::RegisterActionCallbacks(
base::OnceClosure cancel_callback,
base::OnceClosure immediate_not_found_callback,
base::RepeatingClosure start_over_callback,
AccountPreselectedCallback account_preselected_callback,
PasswordSelectedCallback password_selected_callback,
device::FidoRequestHandlerBase::RequestCallback request_callback,
base::OnceClosure cancel_ui_timeout_callback,
base::RepeatingClosure bluetooth_adapter_power_on_callback,
base::RepeatingCallback<
void(device::FidoRequestHandlerBase::BlePermissionCallback)>
request_ble_permission_callback) {
cancel_callback_ = std::move(cancel_callback);
}
void ElectronAuthenticatorRequestClientDelegate::SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback) {
DCHECK(!responses.empty());
content::RenderFrameHost* rfh =
content::RenderFrameHost::FromID(render_frame_host_id_);
content::WebContents* web_contents =
rfh ? content::WebContents::FromRenderFrameHost(rfh) : nullptr;
gin::WeakCell<api::Session>* session =
web_contents
? api::Session::FromBrowserContext(web_contents->GetBrowserContext())
: nullptr;
pending_responses_ = std::move(responses);
select_account_callback_ = std::move(callback);
if (!session || !session->Get()) {
CancelPendingAccountSelection();
return;
}
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Array> accounts =
v8::Array::New(isolate, static_cast<int>(pending_responses_.size()));
for (size_t i = 0; i < pending_responses_.size(); ++i) {
const auto& response = pending_responses_[i];
gin::DataObjectBuilder account(isolate);
account.Set("credentialId", CredentialIdFor(response));
if (response.user_entity) {
account.Set("userHandle", Base64UrlEncodeNoPad(response.user_entity->id));
if (response.user_entity->name) {
account.Set("name", *response.user_entity->name);
}
if (response.user_entity->display_name) {
account.Set("displayName", *response.user_entity->display_name);
}
}
accounts
->CreateDataProperty(isolate->GetCurrentContext(),
static_cast<uint32_t>(i), account.Build())
.Check();
}
v8::Local<v8::Object> details = gin::DataObjectBuilder(isolate)
.Set("relyingPartyId", relying_party_id_)
.Set("accounts", accounts)
.Set("frame", rfh)
.Build();
v8::Local<v8::Object> session_wrapper;
if (!session->Get()->GetWrapper(isolate).ToLocal(&session_wrapper)) {
CancelPendingAccountSelection();
return;
}
v8::Local<v8::Object> event_object = gin_helper::internal::Event::New(isolate)
->GetWrapper(isolate)
.ToLocalChecked();
v8::Local<v8::Value> emit_result = gin_helper::EmitEvent(
isolate, session_wrapper, "select-webauthn-account", event_object,
details,
base::BindRepeating(
&ElectronAuthenticatorRequestClientDelegate::OnAccountSelected,
weak_factory_.GetWeakPtr()));
// EventEmitter.prototype.emit() returns true iff there was at least one
// listener. With no listener there is no way for the app to choose an
// account, so cancel rather than silently picking one.
bool had_listener = false;
if (!gin::ConvertFromV8(isolate, emit_result, &had_listener) ||
!had_listener) {
CancelPendingAccountSelection();
}
}
void ElectronAuthenticatorRequestClientDelegate::
CancelPendingAccountSelection() {
pending_responses_.clear();
select_account_callback_.Reset();
if (cancel_callback_) {
std::move(cancel_callback_).Run();
}
}
void ElectronAuthenticatorRequestClientDelegate::OnAccountSelected(
gin::Arguments* args) {
if (!select_account_callback_) {
return;
}
std::string credential_id;
if (!args->GetNext(&credential_id) || credential_id.empty()) {
CancelPendingAccountSelection();
return;
}
for (auto& response : pending_responses_) {
if (CredentialIdFor(response) == credential_id) {
auto selected = std::move(response);
pending_responses_.clear();
std::move(select_account_callback_).Run(std::move(selected));
return;
}
}
// Unknown credentialId: cancel the pending request rather than leaving it
// hanging. Matches the no-args branch above so the listener has a single,
// consistent failure mode whether it cancels deliberately or by mistake.
CancelPendingAccountSelection();
}
} // namespace electron

View File

@@ -0,0 +1,81 @@
// Copyright (c) 2026 Anthropic, PBC.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_BROWSER_WEBAUTHN_ELECTRON_AUTHENTICATOR_REQUEST_CLIENT_DELEGATE_H_
#define ELECTRON_SHELL_BROWSER_WEBAUTHN_ELECTRON_AUTHENTICATOR_REQUEST_CLIENT_DELEGATE_H_
#include <vector>
#include "base/memory/weak_ptr.h"
#include "base/scoped_observation.h"
#include "content/public/browser/authenticator_request_client_delegate.h"
#include "content/public/browser/global_routing_id.h"
namespace content {
class RenderFrameHost;
}
namespace gin {
class Arguments;
}
namespace electron {
class ElectronAuthenticatorRequestClientDelegate
: public content::AuthenticatorRequestClientDelegate {
public:
explicit ElectronAuthenticatorRequestClientDelegate(
content::RenderFrameHost* render_frame_host);
~ElectronAuthenticatorRequestClientDelegate() override;
// disable copy
ElectronAuthenticatorRequestClientDelegate(
const ElectronAuthenticatorRequestClientDelegate&) = delete;
ElectronAuthenticatorRequestClientDelegate& operator=(
const ElectronAuthenticatorRequestClientDelegate&) = delete;
// content::AuthenticatorRequestClientDelegate:
void SetRelyingPartyId(const std::string& rp_id) override;
void StartObserving(device::FidoRequestHandlerBase* request_handler) override;
void StopObserving(device::FidoRequestHandlerBase* request_handler) override;
void RegisterActionCallbacks(
base::OnceClosure cancel_callback,
base::OnceClosure immediate_not_found_callback,
base::RepeatingClosure start_over_callback,
AccountPreselectedCallback account_preselected_callback,
PasswordSelectedCallback password_selected_callback,
device::FidoRequestHandlerBase::RequestCallback request_callback,
base::OnceClosure cancel_ui_timeout_callback,
base::RepeatingClosure bluetooth_adapter_power_on_callback,
base::RepeatingCallback<
void(device::FidoRequestHandlerBase::BlePermissionCallback)>
request_ble_permission_callback) override;
void SelectAccount(
std::vector<device::AuthenticatorGetAssertionResponse> responses,
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
callback) override;
private:
void OnAccountSelected(gin::Arguments* args);
void CancelPendingAccountSelection();
const content::GlobalRenderFrameHostId render_frame_host_id_;
std::string relying_party_id_;
base::OnceClosure cancel_callback_;
base::ScopedObservation<device::FidoRequestHandlerBase,
device::FidoRequestHandlerBase::Observer>
request_handler_observation_{this};
std::vector<device::AuthenticatorGetAssertionResponse> pending_responses_;
base::OnceCallback<void(device::AuthenticatorGetAssertionResponse)>
select_account_callback_;
base::WeakPtrFactory<ElectronAuthenticatorRequestClientDelegate>
weak_factory_{this};
};
} // namespace electron
#endif // ELECTRON_SHELL_BROWSER_WEBAUTHN_ELECTRON_AUTHENTICATOR_REQUEST_CLIENT_DELEGATE_H_

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