mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
57 Commits
cherry-pic
...
v41.1.1
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78896775d9 | ||
|
|
40eb41656a | ||
|
|
5a69e80cac | ||
|
|
90decd4eaf | ||
|
|
ba551d265c | ||
|
|
24784ed024 | ||
|
|
f49f6b1a29 | ||
|
|
c63e0d8b96 | ||
|
|
33a81b40c2 | ||
|
|
eb49ed962d | ||
|
|
7e36ac67ce | ||
|
|
cbae32aac6 | ||
|
|
880b1e08e7 | ||
|
|
aedea576da | ||
|
|
707541d9b2 | ||
|
|
3dcb641a99 | ||
|
|
878a763344 | ||
|
|
6a8d187105 | ||
|
|
29622930a0 | ||
|
|
8b9e721047 | ||
|
|
43bb93908c | ||
|
|
b0055e0500 | ||
|
|
9a7381a328 | ||
|
|
af3e0fca24 | ||
|
|
99d879b52e | ||
|
|
3d8105ae7f | ||
|
|
aba01d38dc | ||
|
|
a0f01336a3 | ||
|
|
4a98b4e27e | ||
|
|
44bc2c8cef | ||
|
|
4e9e7335bc | ||
|
|
cd88382756 | ||
|
|
105c5591d0 | ||
|
|
90b3a2341d | ||
|
|
21f9474f4f | ||
|
|
c3e397ed2d | ||
|
|
d84dca2818 | ||
|
|
bb20d0c352 | ||
|
|
c66fc559b2 | ||
|
|
76f34911f2 | ||
|
|
5d381dd27e | ||
|
|
42d7f2783b | ||
|
|
61b4c6b93e | ||
|
|
b9ca21156b | ||
|
|
23960241f9 | ||
|
|
6d2986302c | ||
|
|
01b99cd9a9 | ||
|
|
a8f64f684f | ||
|
|
ca1b77d9b7 | ||
|
|
3678edfa37 | ||
|
|
cb4d31ae61 | ||
|
|
616a63bc73 | ||
|
|
e78e2ca996 | ||
|
|
cea004c31e | ||
|
|
a14f661c58 | ||
|
|
64354677bf | ||
|
|
15dd5dc396 |
7
.github/actions/build-electron/action.yml
vendored
7
.github/actions/build-electron/action.yml
vendored
@@ -125,6 +125,9 @@ runs:
|
||||
fi
|
||||
sed $SEDOPTION '/.*builtins-pgo/d' out/Default/mksnapshot_args
|
||||
sed $SEDOPTION '/--turbo-profiling-input/d' out/Default/mksnapshot_args
|
||||
sed $SEDOPTION '/--reorder-builtins/d' out/Default/mksnapshot_args
|
||||
sed $SEDOPTION '/--warn-about-builtin-profile-data/d' out/Default/mksnapshot_args
|
||||
sed $SEDOPTION '/--abort-on-bad-builtin-profile-data/d' out/Default/mksnapshot_args
|
||||
|
||||
if [ "${{ inputs.target-platform }}" = "win" ]; then
|
||||
cd out/Default
|
||||
@@ -271,12 +274,12 @@ runs:
|
||||
run: ./src/electron/script/actions/move-artifacts.sh
|
||||
- name: Upload Generated Artifacts ${{ inputs.step-suffix }}
|
||||
if: always() && !cancelled()
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7.0.0
|
||||
with:
|
||||
name: generated_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./generated_artifacts_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}
|
||||
- name: Upload Src Artifacts ${{ inputs.step-suffix }}
|
||||
uses: actions/upload-artifact@65462800fd760344b1a7b4382951275a0abb4808
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7.0.0
|
||||
with:
|
||||
name: src_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./src_artifacts_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}
|
||||
|
||||
2
.github/actions/checkout/action.yml
vendored
2
.github/actions/checkout/action.yml
vendored
@@ -43,7 +43,7 @@ runs:
|
||||
curl --unix-socket /var/run/sas/sas.sock --fail "http://foo/$CACHE_FILE?platform=${{ inputs.target-platform }}&getAccountName=true" > sas-token
|
||||
- name: Save SAS Key
|
||||
if: ${{ inputs.generate-sas-token == 'true' }}
|
||||
uses: actions/cache/save@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: sas-token
|
||||
key: sas-key-${{ inputs.target-platform }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
|
||||
@@ -7,7 +7,7 @@ runs:
|
||||
shell: bash
|
||||
id: yarn-cache-dir-path
|
||||
run: echo "dir=$(node src/electron/script/yarn.js config get cacheFolder)" >> $GITHUB_OUTPUT
|
||||
- uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
id: yarn-cache
|
||||
with:
|
||||
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
|
||||
|
||||
@@ -8,14 +8,14 @@ runs:
|
||||
steps:
|
||||
- name: Obtain SAS Key
|
||||
continue-on-error: true
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: sas-token
|
||||
key: sas-key-${{ inputs.target-platform }}-${{ github.run_number }}-1
|
||||
enableCrossOsArchive: true
|
||||
- name: Obtain SAS Key
|
||||
continue-on-error: true
|
||||
uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3
|
||||
uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
|
||||
with:
|
||||
path: sas-token
|
||||
key: sas-key-${{ inputs.target-platform }}-${{ github.run_number }}-${{ github.run_attempt }}
|
||||
@@ -24,7 +24,7 @@ runs:
|
||||
# The cache will always exist here as a result of the checkout job
|
||||
# Either it was uploaded to Azure in the checkout job for this commit
|
||||
# or it was uploaded in the checkout job for a previous commit.
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
@@ -101,7 +101,7 @@ runs:
|
||||
|
||||
- name: Move Src Cache (Windows)
|
||||
if: ${{ inputs.target-platform == 'win' }}
|
||||
uses: nick-fields/retry@7152eba30c6575329ac0576536151aca5a72780e # v3.0.0
|
||||
uses: nick-fields/retry@ad984534de44a9489a53aefd81eb77f87c70dc60 # v4.0.0
|
||||
with:
|
||||
timeout_minutes: 30
|
||||
max_attempts: 3
|
||||
|
||||
8
.github/workflows/apply-patches.yml
vendored
8
.github/workflows/apply-patches.yml
vendored
@@ -71,3 +71,11 @@ jobs:
|
||||
uses: ./src/electron/.github/actions/checkout
|
||||
with:
|
||||
target-platform: linux
|
||||
- name: Upload Patch Conflict Fix
|
||||
if: ${{ failure() }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: update-patches
|
||||
path: patches/update-patches.patch
|
||||
if-no-files-found: ignore
|
||||
archive: false
|
||||
|
||||
27
.github/workflows/build.yml
vendored
27
.github/workflows/build.yml
vendored
@@ -431,3 +431,30 @@ jobs:
|
||||
- name: GitHub Actions Jobs Done
|
||||
run: |
|
||||
echo "All GitHub Actions Jobs are done"
|
||||
|
||||
check-signed-commits:
|
||||
name: Check signed commits in green PR
|
||||
needs: gha-done
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
@@ -209,6 +209,7 @@ jobs:
|
||||
|
||||
- name: Run Electron Tests
|
||||
shell: bash
|
||||
timeout-minutes: 40
|
||||
env:
|
||||
MOCHA_REPORTER: mocha-multi-reporters
|
||||
MOCHA_MULTI_REPORTERS: mocha-junit-reporter, tap
|
||||
@@ -259,6 +260,19 @@ jobs:
|
||||
|
||||
fi
|
||||
fi
|
||||
- name: Take screenshot on timeout or cancellation
|
||||
if: ${{ inputs.target-platform != 'linux' && (cancelled() || failure()) }}
|
||||
shell: bash
|
||||
run: |
|
||||
screenshot_dir="src/electron/spec/artifacts"
|
||||
mkdir -p "$screenshot_dir"
|
||||
screenshot_file="$screenshot_dir/screenshot-timeout-$(date +%Y%m%d%H%M%S).png"
|
||||
if [ "${{ inputs.target-platform }}" = "macos" ]; then
|
||||
screencapture -x "$screenshot_file" || true
|
||||
elif [ "${{ inputs.target-platform }}" = "win" ]; then
|
||||
powershell -command "Add-Type -AssemblyName System.Windows.Forms; \$screen = [System.Windows.Forms.Screen]::PrimaryScreen.Bounds; \$bitmap = New-Object System.Drawing.Bitmap(\$screen.Width, \$screen.Height); \$graphics = [System.Drawing.Graphics]::FromImage(\$bitmap); \$graphics.CopyFromScreen(\$screen.Location, [System.Drawing.Point]::Empty, \$screen.Size); \$bitmap.Save('$screenshot_file')" || true
|
||||
fi
|
||||
|
||||
- name: Upload Test results to Datadog
|
||||
env:
|
||||
DD_ENV: ci
|
||||
@@ -274,8 +288,8 @@ jobs:
|
||||
fi
|
||||
if: always() && !cancelled()
|
||||
- name: Upload Test Artifacts
|
||||
if: always() && !cancelled()
|
||||
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f
|
||||
if: always()
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7.0.0
|
||||
with:
|
||||
name: test_artifacts_${{ env.ARTIFACT_KEY }}_${{ matrix.shard }}
|
||||
path: src/electron/spec/artifacts
|
||||
|
||||
35
.github/workflows/pull-request-opened-synchronized.yml
vendored
Normal file
35
.github/workflows/pull-request-opened-synchronized.yml
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
name: Pull Request Opened/Synchronized
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, synchronize]
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-signed-commits:
|
||||
name: Check signed commits in PR
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
|
||||
- name: Add needs-signed-commits label
|
||||
if: ${{ failure() }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --add-label needs-signed-commits
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -42,6 +42,7 @@ spec/.hash
|
||||
|
||||
# Generated native addon files
|
||||
/spec/fixtures/native-addon/echo/build/
|
||||
/spec/fixtures/native-addon/dialog-helper/build/
|
||||
|
||||
# If someone runs tsc this is where stuff will end up
|
||||
ts-gen
|
||||
|
||||
@@ -9,4 +9,8 @@ npmMinimalAgeGate: 10080
|
||||
npmPreapprovedPackages:
|
||||
- "@electron/*"
|
||||
|
||||
httpProxy: "${HTTP_PROXY:-}"
|
||||
|
||||
httpsProxy: "${HTTPS_PROXY:-}"
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.12.0.cjs
|
||||
|
||||
16
CLAUDE.md
16
CLAUDE.md
@@ -127,6 +127,22 @@ patches/{target}/*.patch → [e sync --3] → target repo commits
|
||||
2. Create a git commit
|
||||
3. Run `e patches <target>` to export
|
||||
|
||||
**Fixing patch conflicts on an existing PR:**
|
||||
|
||||
If asked to fix a patch conflict on a branch that already has an open PR, check the PR's failed **Apply Patches** CI run for an `update-patches` artifact before running `e sync` locally. CI has already performed the 3-way merge and exported the resolved patch diff — applying it is much faster than a full local sync.
|
||||
|
||||
```bash
|
||||
# Find the failed Apply Patches run for the PR and download the artifact
|
||||
gh run list --repo electron/electron --branch <pr-branch> --workflow "Apply Patches" --limit 1
|
||||
gh run download <run-id> --repo electron/electron --name update-patches
|
||||
|
||||
# Apply the CI-generated fix, then push
|
||||
git am update-patches.patch
|
||||
git push
|
||||
```
|
||||
|
||||
If no artifact exists (e.g. the 3-way merge itself failed), fall back to `e sync --3` and resolve manually.
|
||||
|
||||
## Testing
|
||||
|
||||
**Test location:** `spec/` directory
|
||||
|
||||
2
DEPS
2
DEPS
@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
|
||||
|
||||
vars = {
|
||||
'chromium_version':
|
||||
'146.0.7680.65',
|
||||
'146.0.7680.166',
|
||||
'node_version':
|
||||
'v24.14.0',
|
||||
'nan_version':
|
||||
|
||||
@@ -51,9 +51,6 @@ is_cfi = false
|
||||
use_qt5 = false
|
||||
use_qt6 = false
|
||||
|
||||
# Disables the builtins PGO for V8
|
||||
v8_builtins_profiling_log_file = ""
|
||||
|
||||
# https://chromium.googlesource.com/chromium/src/+/main/docs/dangling_ptr.md
|
||||
# TODO(vertedinde): hunt down dangling pointers on Linux
|
||||
enable_dangling_raw_ptr_checks = false
|
||||
|
||||
@@ -245,6 +245,10 @@ static_library("chrome") {
|
||||
"//chrome/browser/ui/views/dark_mode_manager_linux.cc",
|
||||
"//chrome/browser/ui/views/dark_mode_manager_linux.h",
|
||||
]
|
||||
sources += [
|
||||
"//chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.cc",
|
||||
"//chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h",
|
||||
]
|
||||
public_deps += [ "//components/dbus" ]
|
||||
}
|
||||
|
||||
|
||||
@@ -256,7 +256,7 @@ async function startRepl () {
|
||||
if (option.file && !option.webdriver) {
|
||||
const file = option.file;
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const protocol = url.parse(file).protocol;
|
||||
const protocol = URL.canParse(file) ? new URL(file).protocol : null;
|
||||
const extension = path.extname(file);
|
||||
if (protocol === 'http:' || protocol === 'https:' || protocol === 'file:' || protocol === 'chrome:') {
|
||||
await loadApplicationByURL(file);
|
||||
|
||||
@@ -84,3 +84,7 @@ Currently, Windows high contrast is the only system setting that triggers forced
|
||||
### `nativeTheme.prefersReducedTransparency` _Readonly_
|
||||
|
||||
A `boolean` that indicates whether the user has chosen via system accessibility settings to reduce transparency at the OS level.
|
||||
|
||||
### `nativeTheme.shouldDifferentiateWithoutColor` _macOS_ _Readonly_
|
||||
|
||||
A `boolean` that indicates whether the user prefers UI that differentiates items using something other than color alone (e.g. shapes or labels). This maps to [NSWorkspace.accessibilityDisplayShouldDifferentiateWithoutColor](https://developer.apple.com/documentation/appkit/nsworkspace/accessibilitydisplayshoulddifferentiatewithoutcolor).
|
||||
|
||||
@@ -42,11 +42,15 @@ Returns `boolean` - Whether or not desktop notifications are supported on the cu
|
||||
* `timeoutType` string (optional) _Linux_ _Windows_ - The timeout duration of the notification. Can be 'default' or 'never'.
|
||||
* `replyPlaceholder` string (optional) _macOS_ - The placeholder to write in the inline reply input field.
|
||||
* `sound` string (optional) _macOS_ - The name of the sound file to play when the notification is shown.
|
||||
* `urgency` string (optional) _Linux_ - The urgency level of the notification. Can be 'normal', 'critical', or 'low'.
|
||||
* `urgency` string (optional) _Linux_ _Windows_ - The urgency level of the notification. Can be 'normal', 'critical', or 'low'.
|
||||
* `actions` [NotificationAction[]](structures/notification-action.md) (optional) _macOS_ - Actions to add to the notification. Please read the available actions and limitations in the `NotificationAction` documentation.
|
||||
* `closeButtonText` string (optional) _macOS_ - A custom title for the close button of an alert. An empty string will cause the default localized text to be used.
|
||||
* `toastXml` string (optional) _Windows_ - A custom description of the Notification on Windows superseding all properties above. Provides full customization of design and behavior of the notification.
|
||||
|
||||
> [!NOTE]
|
||||
> On Windows, `urgency` type 'critical' sorts the notification higher in Action Center (above default priority notifications), but does not prevent auto-dismissal. To prevent auto-dismissal, you should also set
|
||||
> `timeoutType` to 'never'.
|
||||
|
||||
### Instance Events
|
||||
|
||||
Objects created with `new Notification` emit the following events:
|
||||
|
||||
@@ -56,6 +56,15 @@ app.whenReady().then(() => {
|
||||
})
|
||||
```
|
||||
|
||||
## Protocol names
|
||||
|
||||
[RFC 3986](https://www.rfc-editor.org/rfc/rfc3986#section-3.1) defines what a valid
|
||||
protocol name is:
|
||||
|
||||
> Scheme names consist of a sequence of characters beginning with a letter and followed
|
||||
> by any combination of letters, digits, plus ("+"), period ("."), or hyphen ("-").
|
||||
> Although schemes are case-insensitive, the canonical form is lowercase […].
|
||||
|
||||
## Methods
|
||||
|
||||
The `protocol` module has the following methods:
|
||||
|
||||
@@ -1485,6 +1485,11 @@ mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||
const browserView = new BrowserView(options)
|
||||
mainWindow.addBrowserView(browserView)
|
||||
browserView.setBounds({ x: 0, y: 0, width: 640, height: 480 })
|
||||
// For `background-tab` disposition (e.g., when middle-clicking or ctrl/cmd-clicking a link),
|
||||
// `options.webContents` is undefined because its creation can be deferred. So load the URL manually.
|
||||
if (details.disposition === 'background-tab') {
|
||||
browserView.webContents.loadURL(details.url)
|
||||
}
|
||||
return browserView.webContents
|
||||
}
|
||||
}
|
||||
@@ -2235,6 +2240,16 @@ Returns `string` - The identifier of a WebContents stream. This identifier can b
|
||||
with `navigator.mediaDevices.getUserMedia` using a `chromeMediaSource` of `tab`.
|
||||
The identifier is restricted to the web contents that it is registered to and is only valid for 10 seconds.
|
||||
|
||||
#### `contents.getOrCreateDevToolsTargetId()`
|
||||
|
||||
Returns `string` - The Chrome DevTools Protocol
|
||||
[TargetID](https://chromedevtools.github.io/devtools-protocol/tot/Target/#type-TargetID)
|
||||
associated with this WebContents. This is the reverse of
|
||||
[`webContents.fromDevToolsTargetId()`](#webcontentsfromdevtoolstargetidtargetid).
|
||||
|
||||
> [!NOTE]
|
||||
> This method creates a new DevTools agent for this WebContents if one does not already exist.
|
||||
|
||||
#### `contents.getOSProcessId()`
|
||||
|
||||
Returns `Integer` - The operating system `pid` of the associated renderer
|
||||
|
||||
@@ -21,24 +21,33 @@
|
||||
|
||||
### Step 1: Fork
|
||||
|
||||
Fork the project [on GitHub](https://github.com/electron/electron) and clone your fork
|
||||
locally.
|
||||
|
||||
```sh
|
||||
$ git clone git@github.com:username/electron.git
|
||||
$ cd electron
|
||||
$ git remote add upstream https://github.com/electron/electron.git
|
||||
$ git fetch upstream
|
||||
```
|
||||
Fork Electron's [GitHub repository](https://github.com/electron/electron).
|
||||
|
||||
### Step 2: Build
|
||||
|
||||
Build steps and dependencies differ slightly depending on your operating system.
|
||||
See these detailed guides on building Electron locally:
|
||||
We recommend using [`@electron/build-tools`](https://github.com/electron/build-tools) to build
|
||||
Electron itself.
|
||||
|
||||
* [Building on macOS](build-instructions-macos.md)
|
||||
* [Building on Linux](build-instructions-linux.md)
|
||||
* [Building on Windows](build-instructions-windows.md)
|
||||
```sh
|
||||
# Install build-tools package globally:
|
||||
npm install -g @electron/build-tools
|
||||
# Run the init script where you want to clone the project and point it to your fork:
|
||||
e init --fork my-org/electron --bootstrap testing
|
||||
```
|
||||
|
||||
This will create a new `electron` folder in your working directory and initialize the project.
|
||||
Once the build completes, navigate to `electron/src/electron`, where your fork is actually cloned.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Your Electron project has a complex folder structure with nested repositories.
|
||||
> See the [Build Instructions](./build-instructions-gn.md) docs for detailed Build Tools
|
||||
> usage instructions (e.g. how to sync dependencies or how to recompile the binary)
|
||||
> and platform-specific notices.
|
||||
|
||||
There, you should have two `remote` URLs in git:
|
||||
|
||||
* `origin` will point to `electron/electron`
|
||||
* `fork` will point to your fork (`my-org/electron`)
|
||||
|
||||
Once you've built the project locally, you're ready to start making changes!
|
||||
|
||||
@@ -48,7 +57,7 @@ To keep your development environment organized, create local branches to
|
||||
hold your work. These should be branched directly off of the `main` branch.
|
||||
|
||||
```sh
|
||||
$ git checkout -b my-branch -t upstream/main
|
||||
git checkout -b my-branch
|
||||
```
|
||||
|
||||
## Making Changes
|
||||
@@ -60,7 +69,7 @@ changes to either the C/C++ code in the `shell/` folder,
|
||||
the JavaScript code in the `lib/` folder, the documentation in `docs/api/`
|
||||
or tests in the `spec/` folder.
|
||||
|
||||
Please be sure to run `npm run lint` from time to time on any code changes
|
||||
Please be sure to run `yarn lint` from time to time on any code changes
|
||||
to ensure that they follow the project's code style.
|
||||
|
||||
See [coding style](coding-style.md) for
|
||||
@@ -75,8 +84,8 @@ across multiple commits. There is no limit to the number of commits in a
|
||||
pull request.
|
||||
|
||||
```sh
|
||||
$ git add my/changed/files
|
||||
$ git commit
|
||||
git add my/changed/files
|
||||
git commit
|
||||
```
|
||||
|
||||
Note that multiple commits get squashed when they are landed.
|
||||
@@ -138,8 +147,8 @@ Once you have committed your changes, it is a good idea to use `git rebase`
|
||||
(not `git merge`) to synchronize your work with the main repository.
|
||||
|
||||
```sh
|
||||
$ git fetch upstream
|
||||
$ git rebase upstream/main
|
||||
git fetch origin
|
||||
git rebase origin/main
|
||||
```
|
||||
|
||||
This ensures that your working branch has the latest changes from `electron/electron`
|
||||
@@ -156,7 +165,7 @@ Before submitting your changes in a pull request, always run the full
|
||||
test suite. To run the tests:
|
||||
|
||||
```sh
|
||||
$ npm run test
|
||||
yarn test
|
||||
```
|
||||
|
||||
Make sure the linter does not report any issues and that all tests pass.
|
||||
@@ -165,7 +174,7 @@ Please do not submit patches that fail either check.
|
||||
If you are updating tests and want to run a single spec to check it:
|
||||
|
||||
```sh
|
||||
$ npm run test -match=menu
|
||||
yarn test -match=menu
|
||||
```
|
||||
|
||||
The above would only run spec modules matching `menu`, which is useful for
|
||||
@@ -179,7 +188,7 @@ begin the process of opening a pull request by pushing your working branch
|
||||
to your fork on GitHub.
|
||||
|
||||
```sh
|
||||
$ git push origin my-branch
|
||||
git push fork my-branch
|
||||
```
|
||||
|
||||
### Step 9: Opening the Pull Request
|
||||
@@ -203,9 +212,9 @@ branch, add a new commit with those changes, and push those to your fork.
|
||||
GitHub will automatically update the pull request.
|
||||
|
||||
```sh
|
||||
$ git add my/changed/files
|
||||
$ git commit
|
||||
$ git push origin my-branch
|
||||
git add my/changed/files
|
||||
git commit
|
||||
git push fork my-branch
|
||||
```
|
||||
|
||||
There are a number of more advanced mechanisms for managing commits using
|
||||
@@ -213,8 +222,8 @@ There are a number of more advanced mechanisms for managing commits using
|
||||
|
||||
Feel free to post a comment in the pull request to ping reviewers if you are
|
||||
awaiting an answer on something. If you encounter words or acronyms that
|
||||
seem unfamiliar, refer to this
|
||||
[glossary](https://sites.google.com/a/chromium.org/dev/glossary).
|
||||
seem unfamiliar, refer to the
|
||||
[Chromium glossary](https://sites.google.com/a/chromium.org/dev/glossary).
|
||||
|
||||
#### Approval and Request Changes Workflow
|
||||
|
||||
|
||||
@@ -12,6 +12,10 @@ To create a frameless window, set the [`BaseWindowContructorOptions`][] `frame`
|
||||
|
||||
```
|
||||
|
||||
On Wayland (Linux), frameless windows have GTK drop shadows and extended
|
||||
resize boundaries by default. To create a fully frameless window with no
|
||||
decorations, set `hasShadow: false` in the window constructor options.
|
||||
|
||||
## Transparent windows
|
||||
|
||||

|
||||
|
||||
@@ -146,13 +146,15 @@ The extra privileges granted to the `file://` protocol by this fuse are incomple
|
||||
The `wasmTrapHandlers` fuse controls whether V8 will use signal handlers to trap Out of Bounds memory
|
||||
access from WebAssembly. The feature works by surrounding the WebAssembly memory with large guard regions
|
||||
and then installing a signal handler that traps attempt to access memory in the guard region. The feature
|
||||
is only supported on the following 64-bit systems.
|
||||
is only supported on the following 64-bit systems:
|
||||
|
||||
Linux. MacOS, Windows - x86_64
|
||||
Linux, MacOS - aarch64
|
||||
* Linux, macOS, Windows - x86_64
|
||||
* Linux, macOS - aarch64
|
||||
|
||||
```text
|
||||
| Guard Pages | WASM heap | Guard Pages |
|
||||
|-----8GB-----| |-----8GB-----|
|
||||
```
|
||||
|
||||
When the fuse is disabled V8 will use explicit bound checks in the generated WebAssembly code to ensure
|
||||
memory safety. However, this method has some downsides
|
||||
|
||||
@@ -50,7 +50,7 @@ sections.
|
||||
|
||||
In the main process, set an IPC listener on the `set-title` channel with the `ipcMain.on` API:
|
||||
|
||||
```js {6-10,22} title='main.js (Main Process)'
|
||||
```js {7-11,23} title='main.js (Main Process)'
|
||||
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
@@ -782,8 +782,7 @@ WebContents.prototype._init = function () {
|
||||
const originCounts = new Map<string, number>();
|
||||
const openDialogs = new Set<AbortController>();
|
||||
this.on('-run-dialog', async (info, callback) => {
|
||||
const originUrl = new URL(info.frame.url);
|
||||
const origin = originUrl.protocol === 'file:' ? originUrl.href : originUrl.origin;
|
||||
const origin = info.frame.origin === 'file://' ? info.frame.url : info.frame.origin;
|
||||
if ((originCounts.get(origin) ?? 0) < 0) return callback(false, '');
|
||||
|
||||
const prefs = this.getLastWebPreferences();
|
||||
|
||||
@@ -17,11 +17,6 @@ export type WindowOpenArgs = {
|
||||
features: string,
|
||||
}
|
||||
|
||||
const frameNamesToWindow = new Map<string, WebContents>();
|
||||
const registerFrameNameToGuestWindow = (name: string, webContents: WebContents) => frameNamesToWindow.set(name, webContents);
|
||||
const unregisterFrameName = (name: string) => frameNamesToWindow.delete(name);
|
||||
const getGuestWebContentsByFrameName = (name: string) => frameNamesToWindow.get(name);
|
||||
|
||||
/**
|
||||
* `openGuestWindow` is called to create and setup event handling for the new
|
||||
* window.
|
||||
@@ -47,20 +42,6 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
...overrideBrowserWindowOptions
|
||||
};
|
||||
|
||||
// To spec, subsequent window.open calls with the same frame name (`target` in
|
||||
// spec parlance) will reuse the previous window.
|
||||
// https://html.spec.whatwg.org/multipage/window-object.html#apis-for-creating-and-navigating-browsing-contexts-by-name
|
||||
const existingWebContents = getGuestWebContentsByFrameName(frameName);
|
||||
if (existingWebContents) {
|
||||
if (existingWebContents.isDestroyed()) {
|
||||
// FIXME(t57ser): The webContents is destroyed for some reason, unregister the frame name
|
||||
unregisterFrameName(frameName);
|
||||
} else {
|
||||
existingWebContents.loadURL(url);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (createWindow) {
|
||||
const webContents = createWindow({
|
||||
webContents: guest,
|
||||
@@ -72,7 +53,7 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
throw new Error('Invalid webContents. Created window should be connected to webContents passed with options object.');
|
||||
}
|
||||
|
||||
handleWindowLifecycleEvents({ embedder, frameName, guest, outlivesOpener });
|
||||
handleWindowLifecycleEvents({ embedder, guest, outlivesOpener });
|
||||
}
|
||||
|
||||
return;
|
||||
@@ -96,7 +77,7 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
});
|
||||
}
|
||||
|
||||
handleWindowLifecycleEvents({ embedder, frameName, guest: window.webContents, outlivesOpener });
|
||||
handleWindowLifecycleEvents({ embedder, guest: window.webContents, outlivesOpener });
|
||||
|
||||
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
|
||||
}
|
||||
@@ -107,10 +88,9 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
||||
* too is the guest destroyed; this is Electron convention and isn't based in
|
||||
* browser behavior.
|
||||
*/
|
||||
const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outlivesOpener }: {
|
||||
const handleWindowLifecycleEvents = function ({ embedder, guest, outlivesOpener }: {
|
||||
embedder: WebContents,
|
||||
guest: WebContents,
|
||||
frameName: string,
|
||||
outlivesOpener: boolean
|
||||
}) {
|
||||
const closedByEmbedder = function () {
|
||||
@@ -128,13 +108,6 @@ const handleWindowLifecycleEvents = function ({ embedder, guest, frameName, outl
|
||||
embedder.once('current-render-view-deleted' as any, closedByEmbedder);
|
||||
}
|
||||
guest.once('destroyed', closedByUser);
|
||||
|
||||
if (frameName) {
|
||||
registerFrameNameToGuestWindow(frameName, guest);
|
||||
guest.once('destroyed', function () {
|
||||
unregisterFrameName(frameName);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
// Security options that child windows will always inherit from parent windows
|
||||
|
||||
@@ -227,10 +227,9 @@ function validateHeader (name: any, value: any): void {
|
||||
}
|
||||
|
||||
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const options: any = typeof optionsIn === 'string' ? url.parse(optionsIn) : { ...optionsIn };
|
||||
const options: any = typeof optionsIn === 'string' ? new URL(optionsIn) : { ...optionsIn };
|
||||
|
||||
let urlStr: string = options.url;
|
||||
let urlStr: string = options.url || options.href;
|
||||
|
||||
if (!urlStr) {
|
||||
const urlObj: url.UrlObject = {};
|
||||
@@ -260,8 +259,8 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
|
||||
// an invalid request.
|
||||
throw new TypeError('Request path contains unescaped characters');
|
||||
}
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
const pathObj = url.parse(options.path || '/');
|
||||
|
||||
const pathObj = new URL(options.path || '/', 'http://localhost');
|
||||
urlObj.pathname = pathObj.pathname;
|
||||
urlObj.search = pathObj.search;
|
||||
urlObj.hash = pathObj.hash;
|
||||
|
||||
@@ -1232,6 +1232,8 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
|
||||
// has filesystem caching.
|
||||
overrideAPI(fs, 'copyFile');
|
||||
overrideAPISync(fs, 'copyFileSync');
|
||||
overrideAPI(fs, 'cp');
|
||||
overrideAPISync(fs, 'cpSync');
|
||||
|
||||
overrideAPI(fs, 'open');
|
||||
overrideAPISync(process, 'dlopen', 1);
|
||||
|
||||
@@ -146,3 +146,5 @@ fix_os_crypt_async_cookie_encryption.patch
|
||||
fix_update_dbus_signal_signature_for_xdg_globalshortcuts_portal.patch
|
||||
fix_set_correct_app_id_on_linux.patch
|
||||
fix_pass_trigger_for_global_shortcuts_on_wayland.patch
|
||||
feat_plumb_node_integration_in_worker_through_workersettings.patch
|
||||
fix_fire_menu_popup_start_for_dynamically_created_aria_menus.patch
|
||||
|
||||
@@ -33,10 +33,10 @@ index 4a742db71f62f9ac891ceeb0604ca0b99d1d89c1..2c5af6482e2b6905552a05b16d3df0a4
|
||||
"//base",
|
||||
"//build:branding_buildflags",
|
||||
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
|
||||
index 2fc3a991d89093ff9139eb09d74123197155caff..0862aa96c2a7b496338ac0593f84fcfa21f25572 100644
|
||||
index a2a14349d40ce34831ab063cd5eb55cd5085c814..1a861ff7867f19935178c8368a9a720230fee026 100644
|
||||
--- a/chrome/browser/BUILD.gn
|
||||
+++ b/chrome/browser/BUILD.gn
|
||||
@@ -4749,7 +4749,7 @@ static_library("browser") {
|
||||
@@ -4751,7 +4751,7 @@ static_library("browser") {
|
||||
]
|
||||
}
|
||||
|
||||
@@ -46,10 +46,10 @@ index 2fc3a991d89093ff9139eb09d74123197155caff..0862aa96c2a7b496338ac0593f84fcfa
|
||||
# than here in :chrome_dll.
|
||||
deps += [ "//chrome:packed_resources_integrity_header" ]
|
||||
diff --git a/chrome/test/BUILD.gn b/chrome/test/BUILD.gn
|
||||
index 7d5a246787bc3cc3bcb883aa78121d3d3f124780..b5de35620bc636d5e1d0d5770d898f564843bcef 100644
|
||||
index 40ea51f97470e2b86f8d2d373ea99a2a71ad185e..db6a2291ce77d89c8e28a1435336fd939e436906 100644
|
||||
--- a/chrome/test/BUILD.gn
|
||||
+++ b/chrome/test/BUILD.gn
|
||||
@@ -7728,9 +7728,12 @@ test("unit_tests") {
|
||||
@@ -7731,9 +7731,12 @@ test("unit_tests") {
|
||||
"//chrome/notification_helper",
|
||||
]
|
||||
|
||||
@@ -63,7 +63,7 @@ index 7d5a246787bc3cc3bcb883aa78121d3d3f124780..b5de35620bc636d5e1d0d5770d898f56
|
||||
"//chrome//services/util_win:unit_tests",
|
||||
"//chrome/app:chrome_dll_resources",
|
||||
"//chrome/app:win_unit_tests",
|
||||
@@ -8698,6 +8701,10 @@ test("unit_tests") {
|
||||
@@ -8703,6 +8706,10 @@ test("unit_tests") {
|
||||
"../browser/performance_manager/policies/background_tab_loading_policy_unittest.cc",
|
||||
]
|
||||
|
||||
@@ -74,7 +74,7 @@ index 7d5a246787bc3cc3bcb883aa78121d3d3f124780..b5de35620bc636d5e1d0d5770d898f56
|
||||
sources += [
|
||||
# The importer code is not used on Android.
|
||||
"../common/importer/firefox_importer_utils_unittest.cc",
|
||||
@@ -8755,7 +8762,6 @@ test("unit_tests") {
|
||||
@@ -8760,7 +8767,6 @@ test("unit_tests") {
|
||||
# TODO(crbug.com/417513088): Maybe merge with the non-android `deps` declaration above?
|
||||
deps += [
|
||||
"../browser/screen_ai:screen_ai_install_state",
|
||||
|
||||
@@ -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 46368e70af175d8d0ab0fb5a36d258e48270371e..8d7be769a6c76650ae999338578215dcd324c199 100644
|
||||
index 2d8a70f5fc0f6c2dc2a7587b7bc2e43dbcee8f0e..a87bd09d7a12c5f003488792843cd1807ee1e30f 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -9990,6 +9990,7 @@ void RenderFrameHostImpl::CreateNewWindow(
|
||||
@@ -9997,6 +9997,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,
|
||||
|
||||
@@ -65,7 +65,7 @@ index 2748dd196fe1f56357348a204e24f0b8a28b97dd..5800dd00b47c657d9e6766f3fc5a3065
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
bool EscapeVirtualization(const base::FilePath& user_data_dir);
|
||||
diff --git a/chrome/browser/process_singleton_posix.cc b/chrome/browser/process_singleton_posix.cc
|
||||
index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0aa3183a0b 100644
|
||||
index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..144788ceadea85c9d1fae12d1ba4dbc1fc7cd699 100644
|
||||
--- a/chrome/browser/process_singleton_posix.cc
|
||||
+++ b/chrome/browser/process_singleton_posix.cc
|
||||
@@ -619,6 +619,7 @@ class ProcessSingleton::LinuxWatcher
|
||||
@@ -106,22 +106,41 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
const size_t kMinMessageLength = kStartToken.length() + 4;
|
||||
if (bytes_read_ < kMinMessageLength) {
|
||||
buf_[bytes_read_] = 0;
|
||||
@@ -745,10 +751,26 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
||||
@@ -745,10 +751,45 @@ void ProcessSingleton::LinuxWatcher::SocketReader::
|
||||
tokens.erase(tokens.begin());
|
||||
tokens.erase(tokens.begin());
|
||||
|
||||
+ size_t num_args;
|
||||
+ base::StringToSizeT(tokens[0], &num_args);
|
||||
+ std::vector<std::string> command_line(tokens.begin() + 1, tokens.begin() + 1 + num_args);
|
||||
+ if (!base::StringToSizeT(tokens[0], &num_args) ||
|
||||
+ num_args > tokens.size() - 1) {
|
||||
+ LOG(ERROR) << "Invalid num_args in socket message";
|
||||
+ CleanupAndDeleteSelf();
|
||||
+ return;
|
||||
+ }
|
||||
+ std::vector<std::string> command_line(tokens.begin() + 1,
|
||||
+ tokens.begin() + 1 + num_args);
|
||||
+
|
||||
+ std::vector<uint8_t> additional_data;
|
||||
+ if (tokens.size() >= 3 + num_args) {
|
||||
+ // After consuming [num_args, argv...], two more tokens are needed for
|
||||
+ // additional data: [size, payload]. Subtract to avoid overflow when
|
||||
+ // num_args is large.
|
||||
+ if (tokens.size() - 1 - num_args >= 2) {
|
||||
+ size_t additional_data_size;
|
||||
+ base::StringToSizeT(tokens[1 + num_args], &additional_data_size);
|
||||
+ if (!base::StringToSizeT(tokens[1 + num_args], &additional_data_size)) {
|
||||
+ LOG(ERROR) << "Invalid additional_data_size in socket message";
|
||||
+ CleanupAndDeleteSelf();
|
||||
+ return;
|
||||
+ }
|
||||
+ std::string remaining_args = base::JoinString(
|
||||
+ base::span(tokens).subspan(2 + num_args),
|
||||
+ std::string(1, kTokenDelimiter));
|
||||
+ const auto adspan = base::as_byte_span(remaining_args).first(additional_data_size);
|
||||
+ if (additional_data_size > remaining_args.size()) {
|
||||
+ LOG(ERROR) << "additional_data_size exceeds payload length";
|
||||
+ CleanupAndDeleteSelf();
|
||||
+ return;
|
||||
+ }
|
||||
+ const auto adspan =
|
||||
+ base::as_byte_span(remaining_args).first(additional_data_size);
|
||||
+ additional_data.assign(adspan.begin(), adspan.end());
|
||||
+ }
|
||||
+
|
||||
@@ -134,7 +153,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
fd_watch_controller_.reset();
|
||||
|
||||
// LinuxWatcher::HandleMessage() is in charge of destroying this SocketReader
|
||||
@@ -777,8 +799,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
|
||||
@@ -777,8 +818,10 @@ void ProcessSingleton::LinuxWatcher::SocketReader::FinishWithACK(
|
||||
//
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const base::FilePath& user_data_dir,
|
||||
@@ -145,7 +164,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
current_pid_(base::GetCurrentProcId()) {
|
||||
socket_path_ = user_data_dir.Append(chrome::kSingletonSocketFilename);
|
||||
lock_path_ = user_data_dir.Append(chrome::kSingletonLockFilename);
|
||||
@@ -899,7 +923,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
@@ -899,7 +942,8 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
sizeof(socket_timeout));
|
||||
|
||||
// Found another process, prepare our command line
|
||||
@@ -155,7 +174,7 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
std::string to_send(kStartToken);
|
||||
to_send.push_back(kTokenDelimiter);
|
||||
|
||||
@@ -909,11 +934,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
@@ -909,11 +953,21 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcessWithTimeout(
|
||||
to_send.append(current_dir.value());
|
||||
|
||||
const std::vector<std::string>& argv = cmd_line.argv();
|
||||
@@ -178,10 +197,18 @@ index 73aa4cb9652870b0bff4684d7c72ae7dbd852db8..b55c942a8ccb326e4898172a7b4f6c0a
|
||||
if (!WriteToSocket(socket.fd(), to_send)) {
|
||||
// Try to kill the other process, because it might have been dead.
|
||||
diff --git a/chrome/browser/process_singleton_win.cc b/chrome/browser/process_singleton_win.cc
|
||||
index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f947e6bfc 100644
|
||||
index ae659d84a5ae2f2e87ce288477506575f8d86839..274887d62ff8d008bb86815a11205fcaa5f2c2ff 100644
|
||||
--- a/chrome/browser/process_singleton_win.cc
|
||||
+++ b/chrome/browser/process_singleton_win.cc
|
||||
@@ -81,10 +81,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <shellapi.h>
|
||||
#include <stddef.h>
|
||||
|
||||
+#include "base/base64.h"
|
||||
#include "base/base_paths.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
@@ -81,10 +82,12 @@ BOOL CALLBACK BrowserWindowEnumeration(HWND window, LPARAM param) {
|
||||
|
||||
bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
base::CommandLine* parsed_command_line,
|
||||
@@ -196,7 +223,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
static const int min_message_size = 7;
|
||||
if (cds->cbData < min_message_size * sizeof(wchar_t) ||
|
||||
cds->cbData % sizeof(wchar_t) != 0) {
|
||||
@@ -134,6 +136,23 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
@@ -134,6 +137,25 @@ bool ParseCommandLine(const COPYDATASTRUCT* cds,
|
||||
const std::wstring cmd_line =
|
||||
msg.substr(second_null + 1, third_null - second_null);
|
||||
*parsed_command_line = base::CommandLine::FromString(cmd_line);
|
||||
@@ -209,18 +236,20 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
+ return true;
|
||||
+ }
|
||||
+
|
||||
+ // Get the actual additional data.
|
||||
+ const std::wstring additional_data =
|
||||
+ msg.substr(third_null + 1, fourth_null - third_null);
|
||||
+ base::span<const uint8_t> additional_data_bytes =
|
||||
+ base::as_byte_span(additional_data);
|
||||
+ *parsed_additional_data = std::vector<uint8_t>(
|
||||
+ additional_data_bytes.begin(), additional_data_bytes.end());
|
||||
+ // Get the actual additional data. It is base64-encoded so it can
|
||||
+ // safely traverse the null-delimited wchar_t buffer.
|
||||
+ const std::wstring encoded_w =
|
||||
+ msg.substr(third_null + 1, fourth_null - third_null - 1);
|
||||
+ std::string encoded = base::WideToASCII(encoded_w);
|
||||
+ std::optional<std::vector<uint8_t>> decoded = base::Base64Decode(encoded);
|
||||
+ if (decoded) {
|
||||
+ *parsed_additional_data = std::move(*decoded);
|
||||
+ }
|
||||
+
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
@@ -155,13 +174,14 @@ bool ProcessLaunchNotification(
|
||||
@@ -155,13 +177,14 @@ bool ProcessLaunchNotification(
|
||||
|
||||
base::CommandLine parsed_command_line(base::CommandLine::NO_PROGRAM);
|
||||
base::FilePath current_directory;
|
||||
@@ -238,7 +267,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -265,9 +285,11 @@ bool ProcessSingleton::EscapeVirtualization(
|
||||
@@ -265,9 +288,11 @@ bool ProcessSingleton::EscapeVirtualization(
|
||||
ProcessSingleton::ProcessSingleton(
|
||||
const std::string& program_name,
|
||||
const base::FilePath& user_data_dir,
|
||||
@@ -250,7 +279,7 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
program_name_(program_name),
|
||||
is_app_sandboxed_(is_app_sandboxed),
|
||||
is_virtualized_(false),
|
||||
@@ -294,7 +316,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
||||
@@ -294,7 +319,7 @@ ProcessSingleton::NotifyResult ProcessSingleton::NotifyOtherProcess() {
|
||||
return PROCESS_NONE;
|
||||
}
|
||||
|
||||
@@ -260,10 +289,18 @@ index ae659d84a5ae2f2e87ce288477506575f8d86839..d93c7e8487ab1a2bbb5f56f2ca44868f
|
||||
return PROCESS_NOTIFIED;
|
||||
case NotifyChromeResult::kFailed:
|
||||
diff --git a/chrome/browser/win/chrome_process_finder.cc b/chrome/browser/win/chrome_process_finder.cc
|
||||
index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3e5b73942 100644
|
||||
index 594f3bc08a4385c177fb488123cef79448e94850..28e5a18a19718b2e748ada6882341413a1ab0705 100644
|
||||
--- a/chrome/browser/win/chrome_process_finder.cc
|
||||
+++ b/chrome/browser/win/chrome_process_finder.cc
|
||||
@@ -39,7 +39,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
|
||||
@@ -11,6 +11,7 @@
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
||||
+#include "base/base64.h"
|
||||
#include "base/check.h"
|
||||
#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
@@ -39,7 +40,9 @@ HWND FindRunningChromeWindow(const base::FilePath& user_data_dir) {
|
||||
return base::win::MessageWindow::FindWindow(user_data_dir.value());
|
||||
}
|
||||
|
||||
@@ -274,7 +311,7 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
|
||||
TRACE_EVENT0("startup", "AttemptToNotifyRunningChrome");
|
||||
|
||||
DCHECK(remote_window);
|
||||
@@ -70,12 +72,24 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
||||
@@ -70,12 +73,22 @@ NotifyChromeResult AttemptToNotifyRunningChrome(HWND remote_window) {
|
||||
new_command_line.AppendSwitch(switches::kSourceAppId);
|
||||
}
|
||||
// Send the command line to the remote chrome window.
|
||||
@@ -286,14 +323,12 @@ index 594f3bc08a4385c177fb488123cef79448e94850..5a1dde19a4bc2bf728eba4c738f831c3
|
||||
std::wstring_view{L"\0", 1}, new_command_line.GetCommandLineString(),
|
||||
std::wstring_view{L"\0", 1}});
|
||||
|
||||
+ size_t additional_data_size = additional_data.size_bytes();
|
||||
+ if (additional_data_size) {
|
||||
+ size_t padded_size = additional_data_size / sizeof(wchar_t);
|
||||
+ if (additional_data_size % sizeof(wchar_t) != 0) {
|
||||
+ padded_size++;
|
||||
+ }
|
||||
+ to_send.append(reinterpret_cast<const wchar_t*>(additional_data.data()),
|
||||
+ padded_size);
|
||||
+ if (!additional_data.empty()) {
|
||||
+ // Base64-encode so the payload survives the null-delimited wchar_t
|
||||
+ // framing; raw serialized bytes can contain 0x0000 sequences which
|
||||
+ // would otherwise terminate the field early.
|
||||
+ std::string encoded = base::Base64Encode(additional_data);
|
||||
+ to_send.append(base::ASCIIToWide(encoded));
|
||||
+ to_send.append(L"\0", 1); // Null separator.
|
||||
+ }
|
||||
+
|
||||
|
||||
@@ -313,7 +313,7 @@ index 18f283e625101318ee14b50e6e765dfd1c9a1a44..44a3a55974c9e4b9e715574075f25661
|
||||
|
||||
auto DrawAsSinglePath = [&]() {
|
||||
diff --git a/third_party/blink/renderer/platform/runtime_enabled_features.json5 b/third_party/blink/renderer/platform/runtime_enabled_features.json5
|
||||
index 70a7e2a5203d3cdddbad7eecca28d65945522fed..35751435ebe8205a5c9d73bed0422ccbe61ab8b4 100644
|
||||
index d5fe1468676285540909ee078d5b88a9bd8e197c..427c77b17d3e130c43a69d79d330bf5c4759f4cc 100644
|
||||
--- a/third_party/blink/renderer/platform/runtime_enabled_features.json5
|
||||
+++ b/third_party/blink/renderer/platform/runtime_enabled_features.json5
|
||||
@@ -214,6 +214,10 @@
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Samuel Attard <sattard@anthropic.com>
|
||||
Date: Sat, 7 Mar 2026 23:07:30 -0800
|
||||
Subject: feat: plumb node_integration_in_worker through WorkerSettings
|
||||
|
||||
Copy the node_integration_in_worker flag from the initiating frame's
|
||||
WebPreferences into WorkerSettings at dedicated worker creation time,
|
||||
so the value is readable per-worker on the worker thread rather than
|
||||
relying on a process-wide command line switch. The value is also
|
||||
propagated to nested workers via WorkerSettings::Copy.
|
||||
|
||||
diff --git a/third_party/blink/renderer/core/workers/dedicated_worker.cc b/third_party/blink/renderer/core/workers/dedicated_worker.cc
|
||||
index 8a558e1a1f84c3dbed07143680c9c088084051b4..8895e76170cd7e79a93477cd826dcd84fa102d81 100644
|
||||
--- a/third_party/blink/renderer/core/workers/dedicated_worker.cc
|
||||
+++ b/third_party/blink/renderer/core/workers/dedicated_worker.cc
|
||||
@@ -37,6 +37,7 @@
|
||||
#include "third_party/blink/renderer/core/frame/local_frame_client.h"
|
||||
#include "third_party/blink/renderer/core/frame/web_frame_widget_impl.h"
|
||||
#include "third_party/blink/renderer/core/frame/web_local_frame_impl.h"
|
||||
+#include "third_party/blink/renderer/core/exported/web_view_impl.h"
|
||||
#include "third_party/blink/renderer/core/inspector/inspector_trace_events.h"
|
||||
#include "third_party/blink/renderer/core/inspector/main_thread_debugger.h"
|
||||
#include "third_party/blink/renderer/core/loader/document_loader.h"
|
||||
@@ -557,6 +558,12 @@ DedicatedWorker::CreateGlobalScopeCreationParams(
|
||||
auto* frame = window->GetFrame();
|
||||
parent_devtools_token = frame->GetDevToolsFrameToken();
|
||||
settings = std::make_unique<WorkerSettings>(frame->GetSettings());
|
||||
+ if (auto* web_local_frame = WebLocalFrameImpl::FromFrame(frame)) {
|
||||
+ if (auto* web_view = web_local_frame->ViewImpl()) {
|
||||
+ settings->SetNodeIntegrationInWorker(
|
||||
+ web_view->GetWebPreferences().node_integration_in_worker);
|
||||
+ }
|
||||
+ }
|
||||
agent_group_scheduler_compositor_task_runner =
|
||||
execution_context->GetScheduler()
|
||||
->ToFrameScheduler()
|
||||
diff --git a/third_party/blink/renderer/core/workers/worker_settings.cc b/third_party/blink/renderer/core/workers/worker_settings.cc
|
||||
index 45680c5f6ea0c7e89ccf43eb88f8a11e3318c02e..3fa3af62f4e7ba8186441c5e3184b1c04fe32d12 100644
|
||||
--- a/third_party/blink/renderer/core/workers/worker_settings.cc
|
||||
+++ b/third_party/blink/renderer/core/workers/worker_settings.cc
|
||||
@@ -40,6 +40,8 @@ std::unique_ptr<WorkerSettings> WorkerSettings::Copy(
|
||||
old_settings->strictly_block_blockable_mixed_content_;
|
||||
new_settings->generic_font_family_settings_ =
|
||||
old_settings->generic_font_family_settings_;
|
||||
+ new_settings->node_integration_in_worker_ =
|
||||
+ old_settings->node_integration_in_worker_;
|
||||
return new_settings;
|
||||
}
|
||||
|
||||
diff --git a/third_party/blink/renderer/core/workers/worker_settings.h b/third_party/blink/renderer/core/workers/worker_settings.h
|
||||
index 45c60dd2c44b05fdd279f759069383479823c7f2..33a2a0337efb9a46293e11d0d09b3fc182ab9618 100644
|
||||
--- a/third_party/blink/renderer/core/workers/worker_settings.h
|
||||
+++ b/third_party/blink/renderer/core/workers/worker_settings.h
|
||||
@@ -43,6 +43,11 @@ class CORE_EXPORT WorkerSettings {
|
||||
return generic_font_family_settings_;
|
||||
}
|
||||
|
||||
+ bool NodeIntegrationInWorker() const { return node_integration_in_worker_; }
|
||||
+ void SetNodeIntegrationInWorker(bool value) {
|
||||
+ node_integration_in_worker_ = value;
|
||||
+ }
|
||||
+
|
||||
private:
|
||||
void CopyFlagValuesFromSettings(Settings*);
|
||||
|
||||
@@ -54,6 +59,7 @@ class CORE_EXPORT WorkerSettings {
|
||||
bool strict_mixed_content_checking_ = false;
|
||||
bool allow_running_of_insecure_content_ = false;
|
||||
bool strictly_block_blockable_mixed_content_ = false;
|
||||
+ bool node_integration_in_worker_ = false;
|
||||
|
||||
GenericFontFamilySettings generic_font_family_settings_;
|
||||
};
|
||||
@@ -28,7 +28,7 @@ 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 5b79df01e0a5ee81919ebed7d689e430fe7fe305..b11808a69483f4cbcc56d90cc6161984df90c1e4 100644
|
||||
index 7d101d40116bf743f940f32ba4c9b507aa9a235b..2aa1584fd451fb15ec6084fb0c19724e6c63e0e3 100644
|
||||
--- a/content/browser/renderer_host/navigation_request.cc
|
||||
+++ b/content/browser/renderer_host/navigation_request.cc
|
||||
@@ -11666,6 +11666,11 @@ url::Origin NavigationRequest::GetOriginForURLLoaderFactoryUnchecked() {
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Keeley Hammond <khammond@slack-corp.com>
|
||||
Date: Thu, 19 Mar 2026 00:34:37 -0700
|
||||
Subject: fix: fire MENU_POPUP_START for dynamically created ARIA menus
|
||||
|
||||
When an ARIA menu element is dynamically created (e.g. via appendChild)
|
||||
rather than being shown by toggling visibility, the AXMenuOpened event
|
||||
was not fired. The OnIgnoredChanged path handles the visibility toggle
|
||||
case, but OnAtomicUpdateFinished did not fire MENU_POPUP_START for
|
||||
newly created menu nodes.
|
||||
|
||||
Previous attempts to fix this (crbug.com/1254875) were reverted because
|
||||
they fired the event too eagerly in OnNodeCreated (before the tree was
|
||||
fully formed) and without filtering, causing regressions with screen
|
||||
readers on pages that misused role="menu".
|
||||
|
||||
This fix addresses both issues:
|
||||
1. Fires MENU_POPUP_START in OnAtomicUpdateFinished (after the tree
|
||||
update is complete) rather than in OnNodeCreated.
|
||||
2. Only fires if the menu has at least one menuitem child, filtering
|
||||
out false positives from misused role="menu" elements.
|
||||
|
||||
MENU_POPUP_END for deleted menus is already handled by
|
||||
AXTreeManager::OnNodeWillBeDeleted, which fires the event directly
|
||||
on the menu node before destruction.
|
||||
|
||||
The change is behind the DynamicMenuPopupEvents feature flag, disabled
|
||||
by default, to allow stabilization before enabling by default. Enable
|
||||
with --enable-features=DynamicMenuPopupEvents.
|
||||
|
||||
This patch can be removed when a CL containing the fix is accepted
|
||||
into Chromium.
|
||||
|
||||
Bug: 40794596
|
||||
|
||||
diff --git a/ui/accessibility/ax_event_generator.cc b/ui/accessibility/ax_event_generator.cc
|
||||
index 5e0d7a48b4a039db67b5cc6b7e86103739702b40..517fb5e9904f3907de177e172c76328910bb7333 100644
|
||||
--- a/ui/accessibility/ax_event_generator.cc
|
||||
+++ b/ui/accessibility/ax_event_generator.cc
|
||||
@@ -4,6 +4,7 @@
|
||||
|
||||
#include "ui/accessibility/ax_event_generator.h"
|
||||
|
||||
+#include "base/feature_list.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "ui/accessibility/ax_enums.mojom.h"
|
||||
#include "ui/accessibility/ax_event.h"
|
||||
@@ -12,6 +13,12 @@
|
||||
|
||||
namespace ui {
|
||||
|
||||
+// Feature flag for firing MENU_POPUP_START for dynamically created ARIA menus.
|
||||
+// Disabled by default to allow stabilization before enabling globally.
|
||||
+BASE_FEATURE(kDynamicMenuPopupEvents,
|
||||
+ "DynamicMenuPopupEvents",
|
||||
+ base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
+
|
||||
namespace {
|
||||
|
||||
bool HasEvent(const std::set<AXEventGenerator::EventParams>& node_events,
|
||||
@@ -914,12 +921,31 @@ void AXEventGenerator::OnAtomicUpdateFinished(
|
||||
/*new_value*/ true);
|
||||
}
|
||||
|
||||
- if (IsAlert(change.node->GetRole()))
|
||||
+ if (IsAlert(change.node->GetRole())) {
|
||||
AddEvent(change.node, Event::ALERT);
|
||||
- else if (change.node->data().IsActiveLiveRegionRoot())
|
||||
+ } else if (change.node->data().IsActiveLiveRegionRoot()) {
|
||||
AddEvent(change.node, Event::LIVE_REGION_CREATED);
|
||||
- else if (change.node->data().IsContainedInActiveLiveRegion())
|
||||
+ } else if (change.node->data().IsContainedInActiveLiveRegion()) {
|
||||
FireLiveRegionEvents(change.node, /* is_removal */ false);
|
||||
+ }
|
||||
+
|
||||
+ // Fire MENU_POPUP_START when a menu is dynamically created (e.g. via
|
||||
+ // appendChild). The OnIgnoredChanged path handles menus that already exist
|
||||
+ // in the DOM and are shown/hidden. This handles the case where the menu
|
||||
+ // element itself is created on the fly.
|
||||
+ // Only fire if the menu has at least one menuitem child, to avoid false
|
||||
+ // positives from elements that misuse role="menu".
|
||||
+ if (base::FeatureList::IsEnabled(kDynamicMenuPopupEvents) &&
|
||||
+ change.node->GetRole() == ax::mojom::Role::kMenu &&
|
||||
+ !change.node->IsInvisibleOrIgnored()) {
|
||||
+ for (auto iter = change.node->UnignoredChildrenBegin();
|
||||
+ iter != change.node->UnignoredChildrenEnd(); ++iter) {
|
||||
+ if (IsMenuItem(iter->GetRole())) {
|
||||
+ AddEvent(change.node, Event::MENU_POPUP_START);
|
||||
+ break;
|
||||
+ }
|
||||
+ }
|
||||
+ }
|
||||
}
|
||||
|
||||
FireActiveDescendantEvents();
|
||||
@@ -17,7 +17,7 @@ Revert "Reland "Port net::CookieCryptoDelegate to os_crypt async""
|
||||
This reverts commit f01b115c7e21a09cc762f65bf7fd9c6ea9d9d0f8.
|
||||
|
||||
diff --git a/chrome/browser/BUILD.gn b/chrome/browser/BUILD.gn
|
||||
index 0862aa96c2a7b496338ac0593f84fcfa21f25572..aed5a316bd3d97df715f779273ae4c283cd29c92 100644
|
||||
index 1a861ff7867f19935178c8368a9a720230fee026..b1ca947122f4ea715be18a0fd4e75b30fffc5a3c 100644
|
||||
--- a/chrome/browser/BUILD.gn
|
||||
+++ b/chrome/browser/BUILD.gn
|
||||
@@ -714,6 +714,8 @@ static_library("browser") {
|
||||
|
||||
@@ -1189,7 +1189,7 @@ index a1068589ad844518038ee7bc15a3de9bc5cba525..1ff781c49f086ec8015c7d3c44567dbe
|
||||
|
||||
} // namespace content
|
||||
diff --git a/content/test/BUILD.gn b/content/test/BUILD.gn
|
||||
index 8575983261c7b57fc85097edb94a8e6f306974f9..aae50b6830450baf27f2834a8187540d7ff6eb35 100644
|
||||
index d368b2481156bb79c6e74c8b09a828eb2fa2d44c..07cbf495717714d71d977a8820e08050c3062526 100644
|
||||
--- a/content/test/BUILD.gn
|
||||
+++ b/content/test/BUILD.gn
|
||||
@@ -700,6 +700,7 @@ static_library("test_support") {
|
||||
@@ -1217,7 +1217,7 @@ index 8575983261c7b57fc85097edb94a8e6f306974f9..aae50b6830450baf27f2834a8187540d
|
||||
]
|
||||
|
||||
if (!(is_chromeos && target_cpu == "arm64" && current_cpu == "arm")) {
|
||||
@@ -3411,6 +3415,7 @@ test("content_unittests") {
|
||||
@@ -3412,6 +3416,7 @@ test("content_unittests") {
|
||||
"//ui/shell_dialogs",
|
||||
"//ui/webui:test_support",
|
||||
"//url",
|
||||
|
||||
@@ -10,10 +10,10 @@ on Windows. We should refactor our code so that this patch isn't
|
||||
necessary.
|
||||
|
||||
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
|
||||
index eecdbe8a279ef1a7d9aed4f5496e871d54092e0f..16ff3ffbe8300534cef76f857284ef92ad0a88f6 100644
|
||||
index d17637a54208450504d071a3f10c20668cfbe76d..f3ffc975d794f356d9a83837fd977e758b726501 100644
|
||||
--- a/testing/variations/fieldtrial_testing_config.json
|
||||
+++ b/testing/variations/fieldtrial_testing_config.json
|
||||
@@ -27059,6 +27059,21 @@
|
||||
@@ -27095,6 +27095,21 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
|
||||
@@ -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 8d7be769a6c76650ae999338578215dcd324c199..3e8021289c00ec6b15457b17173dfed386eac2fe 100644
|
||||
index a87bd09d7a12c5f003488792843cd1807ee1e30f..b38240fd422163f09bfb8d4b40213a1940a72acd 100644
|
||||
--- a/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
+++ b/content/browser/renderer_host/render_frame_host_impl.cc
|
||||
@@ -9097,6 +9097,17 @@ void RenderFrameHostImpl::EnterFullscreen(
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
chore_expose_ui_to_allow_electron_to_set_dock_side.patch
|
||||
fix_prefer_browser_runtime_over_node_in_hostruntime_detection.patch
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Shelley Vohr <shelley.vohr@gmail.com>
|
||||
Date: Thu, 12 Mar 2026 17:03:29 +0100
|
||||
Subject: fix: prefer browser runtime over node in HostRuntime detection
|
||||
|
||||
In Electron, the `process` global is available in renderer processes,
|
||||
including the DevTools renderer. This causes the IS_NODE check to pass,
|
||||
leading DevTools to attempt importing the Node.js platform runtime
|
||||
(which uses `node:worker_threads`). However, DevTools Web Workers
|
||||
running under the `devtools://` protocol don't have access to Node.js
|
||||
built-in modules, resulting in a failed dynamic import.
|
||||
|
||||
Fix by checking IS_BROWSER first, since DevTools always runs in a
|
||||
browser-like environment. The Node.js runtime is only needed when
|
||||
DevTools runs under pure Node.js (e.g., CLI tooling or testing).
|
||||
|
||||
diff --git a/front_end/core/platform/HostRuntime.ts b/front_end/core/platform/HostRuntime.ts
|
||||
index 91adba7c966a9c4c0e5315d2cfee07f8f622b731..16822b8d4ea74a4ffd6870e5e95948d75918f5d2 100644
|
||||
--- a/front_end/core/platform/HostRuntime.ts
|
||||
+++ b/front_end/core/platform/HostRuntime.ts
|
||||
@@ -14,12 +14,12 @@ export const IS_BROWSER =
|
||||
typeof window !== 'undefined' || (typeof self !== 'undefined' && typeof self.postMessage === 'function');
|
||||
|
||||
export const HOST_RUNTIME = await (async(): Promise<Api.HostRuntime.HostRuntime> => {
|
||||
- if (IS_NODE) {
|
||||
- return (await import('./node/node.js')).HostRuntime.HOST_RUNTIME;
|
||||
- }
|
||||
if (IS_BROWSER) {
|
||||
return (await import('./browser/browser.js')).HostRuntime.HOST_RUNTIME;
|
||||
}
|
||||
+ if (IS_NODE) {
|
||||
+ return (await import('./node/node.js')).HostRuntime.HOST_RUNTIME;
|
||||
+ }
|
||||
|
||||
throw new Error('Unknown runtime!');
|
||||
})();
|
||||
@@ -17,7 +17,7 @@ Upstreams:
|
||||
- https://github.com/nodejs/node/pull/39136
|
||||
|
||||
diff --git a/deps/ncrypto/ncrypto.cc b/deps/ncrypto/ncrypto.cc
|
||||
index 461819ce0fa732048e4365c40a86ef55d984c35f..fa55c980a9c4f373723a867fd41276d67b0b9413 100644
|
||||
index 461819ce0fa732048e4365c40a86ef55d984c35f..f1c85e94cf526d0255f47c003664680d26413ec3 100644
|
||||
--- a/deps/ncrypto/ncrypto.cc
|
||||
+++ b/deps/ncrypto/ncrypto.cc
|
||||
@@ -11,6 +11,7 @@
|
||||
@@ -28,38 +28,6 @@ index 461819ce0fa732048e4365c40a86ef55d984c35f..fa55c980a9c4f373723a867fd41276d6
|
||||
#if OPENSSL_VERSION_MAJOR >= 3
|
||||
#include <openssl/core_names.h>
|
||||
#include <openssl/params.h>
|
||||
@@ -1130,7 +1131,9 @@ int64_t X509View::getValidToTime() const {
|
||||
return tp;
|
||||
#else
|
||||
struct tm tp;
|
||||
- ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp);
|
||||
+#ifndef OPENSSL_IS_BORINGSSL
|
||||
+ ASN1_TIME_to_tm(X509_get0_notAfter(cert_), &tp);
|
||||
+#endif
|
||||
return PortableTimeGM(&tp);
|
||||
#endif
|
||||
}
|
||||
@@ -1142,7 +1145,9 @@ int64_t X509View::getValidFromTime() const {
|
||||
return tp;
|
||||
#else
|
||||
struct tm tp;
|
||||
+#ifndef OPENSSL_IS_BORINGSSL
|
||||
ASN1_TIME_to_tm(X509_get0_notBefore(cert_), &tp);
|
||||
+#endif
|
||||
return PortableTimeGM(&tp);
|
||||
#endif
|
||||
}
|
||||
@@ -2886,10 +2891,6 @@ std::optional<uint32_t> SSLPointer::verifyPeerCertificate() const {
|
||||
const char* SSLPointer::getClientHelloAlpn() const {
|
||||
if (ssl_ == nullptr) return {};
|
||||
#ifndef OPENSSL_IS_BORINGSSL
|
||||
- const unsigned char* buf;
|
||||
- size_t len;
|
||||
- size_t rem;
|
||||
-
|
||||
if (!SSL_client_hello_get0_ext(
|
||||
get(),
|
||||
TLSEXT_TYPE_application_layer_protocol_negotiation,
|
||||
@@ -3090,9 +3091,11 @@ const Cipher Cipher::AES_256_GCM = Cipher::FromNid(NID_aes_256_gcm);
|
||||
const Cipher Cipher::AES_128_KW = Cipher::FromNid(NID_id_aes128_wrap);
|
||||
const Cipher Cipher::AES_192_KW = Cipher::FromNid(NID_id_aes192_wrap);
|
||||
|
||||
@@ -9,4 +9,4 @@ refactor_use_non-deprecated_nskeyedarchiver_apis.patch
|
||||
chore_turn_off_launchapplicationaturl_deprecation_errors_in_squirrel.patch
|
||||
fix_crash_when_process_to_extract_zip_cannot_be_launched.patch
|
||||
use_uttype_class_instead_of_deprecated_uttypeconformsto.patch
|
||||
fix_clean_up_old_staged_updates_before_downloading_new_update.patch
|
||||
fix_clean_up_orphaned_staged_updates_before_downloading_new_update.patch
|
||||
|
||||
@@ -1,64 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Andy Locascio <loc@anthropic.com>
|
||||
Date: Tue, 6 Jan 2026 08:23:03 -0800
|
||||
Subject: fix: clean up old staged updates before downloading new update
|
||||
|
||||
When checkForUpdates() is called while an update is already staged,
|
||||
Squirrel creates a new temporary directory for the download without
|
||||
cleaning up the old one. This can lead to significant disk usage if
|
||||
the app keeps checking for updates without restarting.
|
||||
|
||||
This change adds a force parameter to pruneUpdateDirectories that
|
||||
bypasses the AwaitingRelaunch state check. This is called before
|
||||
creating a new temp directory, ensuring old staged updates are
|
||||
cleaned up when a new download starts.
|
||||
|
||||
diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m
|
||||
index d156616e81e6f25a3bded30e6216b8fc311f31bc..6cd4346bf43b191147aff819cb93387e71275a46 100644
|
||||
--- a/Squirrel/SQRLUpdater.m
|
||||
+++ b/Squirrel/SQRLUpdater.m
|
||||
@@ -543,11 +543,17 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL
|
||||
#pragma mark File Management
|
||||
|
||||
- (RACSignal *)uniqueTemporaryDirectoryForUpdate {
|
||||
- return [[[RACSignal
|
||||
+ // Clean up any old staged update directories before creating a new one.
|
||||
+ // This prevents disk usage from growing when checkForUpdates() is called
|
||||
+ // multiple times without the app restarting.
|
||||
+ return [[[[[self
|
||||
+ pruneUpdateDirectoriesWithForce:YES]
|
||||
+ ignoreValues]
|
||||
+ concat:[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
- }]
|
||||
+ }]]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
|
||||
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
|
||||
@@ -643,7 +649,7 @@ - (BOOL)isRunningOnReadOnlyVolume {
|
||||
|
||||
- (RACSignal *)performHousekeeping {
|
||||
return [[RACSignal
|
||||
- merge:@[ [self pruneUpdateDirectories], [self truncateLogs] ]]
|
||||
+ merge:@[ [self pruneUpdateDirectoriesWithForce:NO], [self truncateLogs] ]]
|
||||
catch:^(NSError *error) {
|
||||
NSLog(@"Error doing housekeeping: %@", error);
|
||||
return [RACSignal empty];
|
||||
@@ -658,11 +664,12 @@ - (RACSignal *)performHousekeeping {
|
||||
///
|
||||
/// Sends each removed directory then completes, or errors, on an unspecified
|
||||
/// thread.
|
||||
-- (RACSignal *)pruneUpdateDirectories {
|
||||
+- (RACSignal *)pruneUpdateDirectoriesWithForce:(BOOL)force {
|
||||
return [[[RACSignal
|
||||
defer:^{
|
||||
- // If we already have updates downloaded we don't wanna prune them.
|
||||
- if (self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
|
||||
+ // If we already have updates downloaded we don't wanna prune them,
|
||||
+ // unless force is YES (used when starting a new download).
|
||||
+ if (!force && self.state == SQRLUpdaterStateAwaitingRelaunch) return [RACSignal empty];
|
||||
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
@@ -0,0 +1,130 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Andy Locascio <loc@anthropic.com>
|
||||
Date: Tue, 6 Jan 2026 08:23:03 -0800
|
||||
Subject: fix: clean up orphaned staged updates before downloading new update
|
||||
|
||||
When checkForUpdates() is called while an update is already staged,
|
||||
Squirrel creates a new temporary directory for the download without
|
||||
cleaning up the old one. This can lead to significant disk usage if
|
||||
the app keeps checking for updates without restarting.
|
||||
|
||||
This change adds a pruneOrphanedUpdateDirectories step before creating
|
||||
a new temp directory. Unlike a blanket prune, this reads the current
|
||||
ShipItState.plist and preserves the directory it references, deleting
|
||||
only truly orphaned update directories. This keeps the on-disk
|
||||
footprint bounded (at most 2 dirs) while ensuring quitAndInstall
|
||||
remains safe to call even when a new check is in progress.
|
||||
|
||||
Refs https://github.com/electron/electron/issues/50200
|
||||
|
||||
diff --git a/Squirrel/SQRLUpdater.m b/Squirrel/SQRLUpdater.m
|
||||
index d156616e81e6f25a3bded30e6216b8fc311f31bc..41856e5754228d33982db72f97f2ff241615a357 100644
|
||||
--- a/Squirrel/SQRLUpdater.m
|
||||
+++ b/Squirrel/SQRLUpdater.m
|
||||
@@ -543,11 +543,19 @@ - (RACSignal *)downloadBundleForUpdate:(SQRLUpdate *)update intoDirectory:(NSURL
|
||||
#pragma mark File Management
|
||||
|
||||
- (RACSignal *)uniqueTemporaryDirectoryForUpdate {
|
||||
- return [[[RACSignal
|
||||
+ // Clean up any orphaned update directories before creating a new one.
|
||||
+ // This prevents disk usage from growing when checkForUpdates() is called
|
||||
+ // multiple times without the app restarting. The currently staged update
|
||||
+ // (referenced by ShipItState.plist) is always preserved so quitAndInstall
|
||||
+ // remains safe to call while a new check is in progress.
|
||||
+ return [[[[[self
|
||||
+ pruneOrphanedUpdateDirectories]
|
||||
+ ignoreValues]
|
||||
+ concat:[RACSignal
|
||||
defer:^{
|
||||
SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
return [directoryManager storageURL];
|
||||
- }]
|
||||
+ }]]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
NSURL *updateDirectoryTemplate = [storageURL URLByAppendingPathComponent:[SQRLUpdaterUniqueTemporaryDirectoryPrefix stringByAppendingString:@"XXXXXXX"]];
|
||||
char *updateDirectoryCString = strdup(updateDirectoryTemplate.path.fileSystemRepresentation);
|
||||
@@ -668,25 +676,68 @@ - (RACSignal *)pruneUpdateDirectories {
|
||||
return [directoryManager storageURL];
|
||||
}]
|
||||
flattenMap:^(NSURL *storageURL) {
|
||||
- NSFileManager *manager = [[NSFileManager alloc] init];
|
||||
- NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
|
||||
- NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
|
||||
- return YES;
|
||||
- }];
|
||||
+ return [self removeUpdateDirectoriesInStorageURL:storageURL excludingURL:nil];
|
||||
+ }]
|
||||
+ setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
|
||||
+}
|
||||
|
||||
- return [[enumerator.rac_sequence.signal
|
||||
- filter:^(NSURL *enumeratedURL) {
|
||||
- NSString *name = enumeratedURL.lastPathComponent;
|
||||
- return [name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix];
|
||||
- }]
|
||||
- doNext:^(NSURL *directoryURL) {
|
||||
- NSError *error = nil;
|
||||
- if (![manager removeItemAtURL:directoryURL error:&error]) {
|
||||
- NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
|
||||
- }
|
||||
+/// Lazily removes orphaned temporary directories upon subscription, always
|
||||
+/// preserving the directory currently referenced by ShipItState.plist so that
|
||||
+/// quitAndInstall remains safe to call mid-check.
|
||||
+///
|
||||
+/// Safe to call in any state. Sends each removed directory then completes on
|
||||
+/// an unspecified thread. Errors reading the staged request are swallowed
|
||||
+/// (treated as "nothing staged").
|
||||
+- (RACSignal *)pruneOrphanedUpdateDirectories {
|
||||
+ return [[[[[SQRLShipItRequest
|
||||
+ readUsingURL:self.shipItStateURL]
|
||||
+ map:^(SQRLShipItRequest *request) {
|
||||
+ // The request holds the URL to the staged .app bundle; its parent
|
||||
+ // is the update.XXXXXXX directory we must preserve.
|
||||
+ return [request.updateBundleURL URLByDeletingLastPathComponent];
|
||||
+ }]
|
||||
+ catch:^(NSError *error) {
|
||||
+ // No staged request (or unreadable) — nothing to preserve.
|
||||
+ return [RACSignal return:nil];
|
||||
+ }]
|
||||
+ flattenMap:^(NSURL *stagedDirectoryURL) {
|
||||
+ SQRLDirectoryManager *directoryManager = [[SQRLDirectoryManager alloc] initWithApplicationIdentifier:SQRLShipItLauncher.shipItJobLabel];
|
||||
+ return [[directoryManager storageURL]
|
||||
+ flattenMap:^(NSURL *storageURL) {
|
||||
+ return [self removeUpdateDirectoriesInStorageURL:storageURL excludingURL:stagedDirectoryURL];
|
||||
}];
|
||||
}]
|
||||
- setNameWithFormat:@"%@ -prunedUpdateDirectories", self];
|
||||
+ setNameWithFormat:@"%@ -pruneOrphanedUpdateDirectories", self];
|
||||
+}
|
||||
+
|
||||
+/// Shared enumerate-and-delete logic for update temp directories.
|
||||
+///
|
||||
+/// storageURL - The Squirrel storage root to enumerate. Must not be nil.
|
||||
+/// excludedURL - Directory to skip (compared by standardized path). May be nil.
|
||||
+- (RACSignal *)removeUpdateDirectoriesInStorageURL:(NSURL *)storageURL excludingURL:(NSURL *)excludedURL {
|
||||
+ NSParameterAssert(storageURL != nil);
|
||||
+
|
||||
+ NSFileManager *manager = [[NSFileManager alloc] init];
|
||||
+ NSDirectoryEnumerator *enumerator = [manager enumeratorAtURL:storageURL includingPropertiesForKeys:nil options:NSDirectoryEnumerationSkipsSubdirectoryDescendants errorHandler:^(NSURL *URL, NSError *error) {
|
||||
+ NSLog(@"Error enumerating item %@ within directory %@: %@", URL, storageURL, error);
|
||||
+ return YES;
|
||||
+ }];
|
||||
+
|
||||
+ NSString *excludedPath = excludedURL.URLByStandardizingPath.path;
|
||||
+
|
||||
+ return [[enumerator.rac_sequence.signal
|
||||
+ filter:^(NSURL *enumeratedURL) {
|
||||
+ NSString *name = enumeratedURL.lastPathComponent;
|
||||
+ if (![name hasPrefix:SQRLUpdaterUniqueTemporaryDirectoryPrefix]) return NO;
|
||||
+ if (excludedPath != nil && [enumeratedURL.URLByStandardizingPath.path isEqualToString:excludedPath]) return NO;
|
||||
+ return YES;
|
||||
+ }]
|
||||
+ doNext:^(NSURL *directoryURL) {
|
||||
+ NSError *error = nil;
|
||||
+ if (![manager removeItemAtURL:directoryURL error:&error]) {
|
||||
+ NSLog(@"Error removing old update directory at %@: %@", directoryURL, error.sqrl_verboseDescription);
|
||||
+ }
|
||||
+ }];
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
chore_allow_customizing_microtask_policy_per_context.patch
|
||||
build_warn_instead_of_abort_on_builtin_pgo_profile_mismatch.patch
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Sam Attard <sattard@anthropic.com>
|
||||
Date: Sun, 22 Mar 2026 10:51:26 +0000
|
||||
Subject: build: warn instead of abort on builtin PGO profile mismatch
|
||||
|
||||
Electron sets v8_enable_javascript_promise_hooks = true to support
|
||||
Node.js async_hooks (see node/src/env.cc SetPromiseHooks usage:
|
||||
https://github.com/nodejs/node/blob/abff716eaccd0c4f4949d1315cb057a45979649d/src/env.cc#L223-L236).
|
||||
This flag adds conditional branches to builtins-microtask-queue-gen.cc
|
||||
and promise-misc.tq, changing the control-flow graph hash of several
|
||||
Promise/async builtins. This invalidates V8's pre-generated PGO profile
|
||||
for those builtins (built with Chrome defaults where the flag is off).
|
||||
|
||||
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 15de2179a0e5ce50d5c659a9d15a920c50124c3e..9fb3a69450bdcab42c2571e8b1f57c4f3c283d9a 100644
|
||||
--- a/BUILD.gn
|
||||
+++ b/BUILD.gn
|
||||
@@ -2764,9 +2764,11 @@ template("run_mksnapshot") {
|
||||
"--turbo-profiling-input",
|
||||
rebase_path(v8_builtins_profiling_log_file, root_build_dir),
|
||||
|
||||
- # Replace this with --warn-about-builtin-profile-data to see the full
|
||||
- # list of builtins with incompatible profiles.
|
||||
- "--abort-on-bad-builtin-profile-data",
|
||||
+ # Electron: Use warn instead of abort so that builtins whose control
|
||||
+ # flow is changed by Electron's build flags (e.g. RunMicrotasks via
|
||||
+ # v8_enable_javascript_promise_hooks) are skipped rather than failing
|
||||
+ # the build. All other builtins still receive PGO.
|
||||
+ "--warn-about-builtin-profile-data",
|
||||
]
|
||||
|
||||
if (!v8_enable_builtins_profiling && v8_enable_builtins_reordering) {
|
||||
@@ -32,7 +32,8 @@ async function main () {
|
||||
}));
|
||||
const hitRate = stats.CacheHit / (stats.Remote + stats.CacheHit + stats.LocalFallback);
|
||||
|
||||
console.log(`Effective cache hit rate: ${(hitRate * 100).toFixed(2)}%`);
|
||||
const messagePrefix = process.env.GITHUB_ACTIONS ? '::notice title=Build Stats::' : '';
|
||||
console.log(`${messagePrefix}Effective cache hit rate: ${(hitRate * 100).toFixed(2)}%`);
|
||||
|
||||
if (uploadStats) {
|
||||
if (!process.env.DD_API_KEY) {
|
||||
|
||||
@@ -14,6 +14,7 @@ const args = minimist(process.argv.slice(2), {
|
||||
|
||||
const BASE = path.resolve(__dirname, '../..');
|
||||
|
||||
const ROOT_PACKAGE_JSON = path.resolve(BASE, 'package.json');
|
||||
const NODE_DIR = path.resolve(BASE, 'third_party', 'electron_node');
|
||||
const JUNIT_DIR = args.jUnitDir ? path.resolve(args.jUnitDir) : null;
|
||||
const TAP_FILE_NAME = 'test.tap';
|
||||
@@ -38,6 +39,18 @@ const defaultOptions = [
|
||||
'-J'
|
||||
];
|
||||
|
||||
// The root package.json is ESM, which breaks the test runner.
|
||||
// Temporarily change it to CommonJS while running the tests, then
|
||||
// change it back when done.
|
||||
const resetPackageJson = ({ useESM }) => {
|
||||
// This won't always exist in CI.
|
||||
if (!fs.existsSync(ROOT_PACKAGE_JSON)) { return; }
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(ROOT_PACKAGE_JSON, 'utf-8'));
|
||||
packageJson.type = useESM ? 'module' : 'commonjs';
|
||||
fs.writeFileSync(ROOT_PACKAGE_JSON, JSON.stringify(packageJson, null, 2) + '\n');
|
||||
};
|
||||
|
||||
const getCustomOptions = () => {
|
||||
let customOptions = ['tools/test.py'];
|
||||
|
||||
@@ -79,6 +92,8 @@ async function main () {
|
||||
|
||||
const options = args.default ? defaultOptions : getCustomOptions();
|
||||
|
||||
resetPackageJson({ useESM: false });
|
||||
|
||||
const testChild = cp.spawn('python3', options, {
|
||||
env: {
|
||||
...process.env,
|
||||
@@ -88,7 +103,10 @@ async function main () {
|
||||
cwd: NODE_DIR,
|
||||
stdio: 'inherit'
|
||||
});
|
||||
|
||||
testChild.on('exit', (testCode) => {
|
||||
resetPackageJson({ useESM: true });
|
||||
|
||||
if (JUNIT_DIR) {
|
||||
fs.mkdirSync(JUNIT_DIR);
|
||||
const converterStream = require('tap-xunit')();
|
||||
|
||||
@@ -317,6 +317,12 @@ void BaseWindow::OnWindowSheetEnd() {
|
||||
Emit("sheet-end");
|
||||
}
|
||||
|
||||
void BaseWindow::OnWindowIsKeyChanged(bool is_key) {
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
window()->SetActive(is_key);
|
||||
#endif
|
||||
}
|
||||
|
||||
void BaseWindow::OnWindowEnterHtmlFullScreen() {
|
||||
Emit("enter-html-full-screen");
|
||||
}
|
||||
|
||||
@@ -85,6 +85,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
|
||||
void OnWindowRotateGesture(float rotation) override;
|
||||
void OnWindowSheetBegin() override;
|
||||
void OnWindowSheetEnd() override;
|
||||
void OnWindowIsKeyChanged(bool is_key) override;
|
||||
void OnWindowEnterFullScreen() override;
|
||||
void OnWindowLeaveFullScreen() override;
|
||||
void OnWindowEnterHtmlFullScreen() override;
|
||||
|
||||
@@ -280,16 +280,22 @@ v8::Local<v8::Value> BrowserWindow::GetWebContents(v8::Isolate* isolate) {
|
||||
}
|
||||
|
||||
void BrowserWindow::OnWindowShow() {
|
||||
if (!web_contents_shown_) {
|
||||
web_contents()->WasShown();
|
||||
web_contents_shown_ = true;
|
||||
}
|
||||
BaseWindow::OnWindowShow();
|
||||
}
|
||||
|
||||
void BrowserWindow::OnWindowHide() {
|
||||
web_contents()->WasOccluded();
|
||||
web_contents_shown_ = false;
|
||||
BaseWindow::OnWindowHide();
|
||||
}
|
||||
|
||||
void BrowserWindow::Show() {
|
||||
web_contents()->WasShown();
|
||||
web_contents_shown_ = true;
|
||||
BaseWindow::Show();
|
||||
}
|
||||
|
||||
@@ -298,6 +304,7 @@ void BrowserWindow::ShowInactive() {
|
||||
if (IsModal())
|
||||
return;
|
||||
web_contents()->WasShown();
|
||||
web_contents_shown_ = true;
|
||||
BaseWindow::ShowInactive();
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ class BrowserWindow : public BaseWindow,
|
||||
// Helpers.
|
||||
|
||||
v8::Global<v8::Value> web_contents_;
|
||||
bool web_contents_shown_ = false;
|
||||
v8::Global<v8::Value> web_contents_view_;
|
||||
base::WeakPtr<api::WebContents> api_web_contents_;
|
||||
|
||||
|
||||
@@ -151,7 +151,10 @@ void OnTraceBufferUsageAvailable(
|
||||
gin_helper::Promise<gin_helper::Dictionary> promise,
|
||||
float percent_full,
|
||||
size_t approximate_count) {
|
||||
auto dict = gin_helper::Dictionary::CreateEmpty(promise.isolate());
|
||||
v8::Isolate* isolate = promise.isolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
||||
dict.Set("percentage", percent_full);
|
||||
dict.Set("value", approximate_count);
|
||||
|
||||
|
||||
@@ -147,7 +147,12 @@ gin::ObjectTemplateBuilder NativeTheme::GetObjectTemplateBuilder(
|
||||
&NativeTheme::ShouldUseInvertedColorScheme)
|
||||
.SetProperty("inForcedColorsMode", &NativeTheme::InForcedColorsMode)
|
||||
.SetProperty("prefersReducedTransparency",
|
||||
&NativeTheme::GetPrefersReducedTransparency);
|
||||
&NativeTheme::GetPrefersReducedTransparency)
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
.SetProperty("shouldDifferentiateWithoutColor",
|
||||
&NativeTheme::ShouldDifferentiateWithoutColor)
|
||||
#endif
|
||||
;
|
||||
}
|
||||
|
||||
const char* NativeTheme::GetTypeName() {
|
||||
|
||||
@@ -56,6 +56,9 @@ class NativeTheme final : public gin_helper::DeprecatedWrappable<NativeTheme>,
|
||||
bool ShouldUseInvertedColorScheme();
|
||||
bool InForcedColorsMode();
|
||||
bool GetPrefersReducedTransparency();
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
bool ShouldDifferentiateWithoutColor();
|
||||
#endif
|
||||
|
||||
// ui::NativeThemeObserver:
|
||||
void OnNativeThemeUpdated(ui::NativeTheme* theme) override;
|
||||
|
||||
@@ -26,4 +26,9 @@ void NativeTheme::UpdateMacOSAppearanceForOverrideValue(
|
||||
[[NSApplication sharedApplication] setAppearance:new_appearance];
|
||||
}
|
||||
|
||||
bool NativeTheme::ShouldDifferentiateWithoutColor() {
|
||||
return [[NSWorkspace sharedWorkspace]
|
||||
accessibilityDisplayShouldDifferentiateWithoutColor];
|
||||
}
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -259,7 +259,7 @@ void UtilityProcessWrapper::OnServiceProcessLaunch(
|
||||
EmitWithoutEvent("spawn");
|
||||
}
|
||||
|
||||
void UtilityProcessWrapper::HandleTermination(uint64_t exit_code) {
|
||||
void UtilityProcessWrapper::HandleTermination(uint32_t exit_code) {
|
||||
// HandleTermination is called from multiple callsites,
|
||||
// we need to ensure we only process it for the first callsite.
|
||||
if (terminated_)
|
||||
@@ -327,7 +327,7 @@ void UtilityProcessWrapper::CloseConnectorPort() {
|
||||
}
|
||||
}
|
||||
|
||||
void UtilityProcessWrapper::Shutdown(uint64_t exit_code) {
|
||||
void UtilityProcessWrapper::Shutdown(uint32_t exit_code) {
|
||||
node_service_remote_.reset();
|
||||
HandleTermination(exit_code);
|
||||
}
|
||||
|
||||
@@ -57,7 +57,7 @@ class UtilityProcessWrapper final
|
||||
static gin_helper::Handle<UtilityProcessWrapper> Create(gin::Arguments* args);
|
||||
static raw_ptr<UtilityProcessWrapper> FromProcessId(base::ProcessId pid);
|
||||
|
||||
void Shutdown(uint64_t exit_code);
|
||||
void Shutdown(uint32_t exit_code);
|
||||
|
||||
// gin_helper::Wrappable
|
||||
static gin::DeprecatedWrapperInfo kWrapperInfo;
|
||||
@@ -77,7 +77,7 @@ class UtilityProcessWrapper final
|
||||
void OnServiceProcessLaunch(const base::Process& process);
|
||||
void CloseConnectorPort();
|
||||
|
||||
void HandleTermination(uint64_t exit_code);
|
||||
void HandleTermination(uint32_t exit_code);
|
||||
|
||||
void PostMessage(gin::Arguments* args);
|
||||
bool Kill();
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
#include "content/public/browser/context_menu_params.h"
|
||||
#include "content/public/browser/desktop_media_id.h"
|
||||
#include "content/public/browser/desktop_streams_registry.h"
|
||||
#include "content/public/browser/devtools_agent_host.h"
|
||||
#include "content/public/browser/download_request_utils.h"
|
||||
#include "content/public/browser/favicon_status.h"
|
||||
#include "content/public/browser/file_select_listener.h"
|
||||
@@ -1311,10 +1312,11 @@ void WebContents::MaybeOverrideCreateParamsForNewWindow(
|
||||
dict.Get(options::kOffscreen, &is_offscreen) && is_offscreen);
|
||||
|
||||
if (is_offscreen) {
|
||||
// Use a no-op callback here. The real OnPaint callback will be bound
|
||||
// to the child WebContents in AddNewContents via SetCallback().
|
||||
auto* view = new OffScreenWebContentsView(
|
||||
false, offscreen_use_shared_texture_,
|
||||
offscreen_shared_texture_pixel_format_,
|
||||
base::BindRepeating(&WebContents::OnPaint, base::Unretained(this)));
|
||||
offscreen_shared_texture_pixel_format_, base::DoNothing());
|
||||
create_params->view = view;
|
||||
create_params->delegate_view = view;
|
||||
}
|
||||
@@ -1342,6 +1344,15 @@ content::WebContents* WebContents::AddNewContents(
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
auto api_web_contents = CreateAndTake(isolate, std::move(new_contents), type);
|
||||
|
||||
// Rebind the paint callback to the child WebContents. The
|
||||
// OffScreenWebContentsView was initially created with the parent's OnPaint
|
||||
// in MaybeOverrideCreateParamsForNewWindow, but the paint data
|
||||
// belongs to the child.
|
||||
if (auto* osr_view = api_web_contents->GetOffScreenWebContentsView()) {
|
||||
osr_view->SetCallback(base::BindRepeating(&WebContents::OnPaint,
|
||||
api_web_contents->GetWeakPtr()));
|
||||
}
|
||||
|
||||
// We call RenderFrameCreated here as at this point the empty "about:blank"
|
||||
// render frame has already been created. If the window never navigates again
|
||||
// RenderFrameCreated won't be called and certain prefs like
|
||||
@@ -2791,6 +2802,11 @@ std::string WebContents::GetMediaSourceID(
|
||||
return id;
|
||||
}
|
||||
|
||||
std::string WebContents::GetOrCreateDevToolsTargetId() {
|
||||
auto agent_host = content::DevToolsAgentHost::GetOrCreateFor(web_contents());
|
||||
return agent_host->GetId();
|
||||
}
|
||||
|
||||
bool WebContents::IsCrashed() const {
|
||||
return web_contents()->IsCrashed();
|
||||
}
|
||||
@@ -4657,6 +4673,8 @@ void WebContents::FillObjectTemplate(v8::Isolate* isolate,
|
||||
&WebContents::SetWebRTCIPHandlingPolicy)
|
||||
.SetMethod("setWebRTCUDPPortRange", &WebContents::SetWebRTCUDPPortRange)
|
||||
.SetMethod("getMediaSourceId", &WebContents::GetMediaSourceID)
|
||||
.SetMethod("getOrCreateDevToolsTargetId",
|
||||
&WebContents::GetOrCreateDevToolsTargetId)
|
||||
.SetMethod("getWebRTCIPHandlingPolicy",
|
||||
&WebContents::GetWebRTCIPHandlingPolicy)
|
||||
.SetMethod("getWebRTCUDPPortRange", &WebContents::GetWebRTCUDPPortRange)
|
||||
|
||||
@@ -230,6 +230,7 @@ class WebContents final : public ExclusiveAccessContext,
|
||||
v8::Local<v8::Value> GetWebRTCUDPPortRange(v8::Isolate* isolate) const;
|
||||
void SetWebRTCUDPPortRange(gin::Arguments* args);
|
||||
std::string GetMediaSourceID(content::WebContents* request_web_contents);
|
||||
std::string GetOrCreateDevToolsTargetId();
|
||||
bool IsCrashed() const;
|
||||
void ForcefullyCrashRenderer();
|
||||
void SetUserAgent(const std::string& user_agent);
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
#include <utility>
|
||||
|
||||
#include "base/files/file_util.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/path_service.h"
|
||||
#include "base/strings/string_util.h"
|
||||
#include "base/task/single_thread_task_runner.h"
|
||||
#include "base/threading/thread_restrictions.h"
|
||||
#include "chrome/common/chrome_paths.h"
|
||||
@@ -71,6 +73,29 @@ Browser* Browser::Get() {
|
||||
return ElectronBrowserMainParts::Get()->browser();
|
||||
}
|
||||
|
||||
// static
|
||||
bool Browser::IsValidProtocolScheme(const std::string& scheme) {
|
||||
// RFC 3986 Section 3.1:
|
||||
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||
if (scheme.empty()) {
|
||||
LOG(ERROR) << "Protocol scheme must not be empty";
|
||||
return false;
|
||||
}
|
||||
if (!base::IsAsciiAlpha(scheme[0])) {
|
||||
LOG(ERROR) << "Protocol scheme must start with an ASCII letter";
|
||||
return false;
|
||||
}
|
||||
for (size_t i = 1; i < scheme.size(); ++i) {
|
||||
const char c = scheme[i];
|
||||
if (!base::IsAsciiAlpha(c) && !base::IsAsciiDigit(c) && c != '+' &&
|
||||
c != '-' && c != '.') {
|
||||
LOG(ERROR) << "Protocol scheme contains invalid character: '" << c << "'";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_LINUX)
|
||||
void Browser::Focus(gin::Arguments* args) {
|
||||
// Focus on the first visible window.
|
||||
|
||||
@@ -134,6 +134,10 @@ class Browser : private WindowListObserver {
|
||||
void SetAppUserModelID(const std::wstring& name);
|
||||
#endif
|
||||
|
||||
// Validate that a protocol scheme conforms to RFC 3986:
|
||||
// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
|
||||
static bool IsValidProtocolScheme(const std::string& scheme);
|
||||
|
||||
// Remove the default protocol handler registry key
|
||||
bool RemoveAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args);
|
||||
|
||||
@@ -103,16 +103,19 @@ void Browser::ClearRecentDocuments() {}
|
||||
|
||||
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
return SetDefaultWebClient(protocol);
|
||||
}
|
||||
|
||||
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
auto env = base::Environment::Create();
|
||||
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
auto env = base::Environment::Create();
|
||||
|
||||
std::vector<std::string> argv = {kXdgSettings, "check",
|
||||
kXdgSettingsDefaultSchemeHandler, protocol};
|
||||
if (std::optional<std::string> desktop_name = env->GetVar("CHROME_DESKTOP")) {
|
||||
|
||||
@@ -233,7 +233,7 @@ bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
|
||||
|
||||
bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];
|
||||
@@ -249,7 +249,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
|
||||
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
NSString* identifier = [base::apple::MainBundle() bundleIdentifier];
|
||||
|
||||
@@ -429,7 +429,7 @@ bool Browser::SetUserTasks(const std::vector<UserTask>& tasks) {
|
||||
|
||||
bool Browser::RemoveAsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
// Main Registry Key
|
||||
@@ -508,7 +508,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
// Software\Classes", which is inherited by "HKEY_CLASSES_ROOT"
|
||||
// anyway, and can be written by unprivileged users.
|
||||
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
std::wstring exe;
|
||||
@@ -538,7 +538,7 @@ bool Browser::SetAsDefaultProtocolClient(const std::string& protocol,
|
||||
|
||||
bool Browser::IsDefaultProtocolClient(const std::string& protocol,
|
||||
gin::Arguments* args) {
|
||||
if (protocol.empty())
|
||||
if (!IsValidProtocolScheme(protocol))
|
||||
return false;
|
||||
|
||||
std::wstring exe;
|
||||
|
||||
@@ -127,6 +127,10 @@
|
||||
#include "shell/common/plugin_info.h"
|
||||
#endif // BUILDFLAG(ENABLE_PLUGINS)
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
#include "components/printing/common/print_dialog_linux_factory.h"
|
||||
#endif
|
||||
|
||||
namespace electron {
|
||||
|
||||
namespace {
|
||||
@@ -415,6 +419,10 @@ void ElectronBrowserMainParts::ToolkitInitialized() {
|
||||
|
||||
ui::LinuxUi::SetInstance(linux_ui);
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
print_dialog_factory_ = std::make_unique<printing::PrintDialogLinuxFactory>();
|
||||
#endif
|
||||
|
||||
// Cursor theme changes are tracked by LinuxUI (via a CursorThemeManager
|
||||
// implementation). Start observing them once it's initialized.
|
||||
ui::CursorFactory::GetInstance()->ObserveThemeChanges();
|
||||
|
||||
@@ -14,8 +14,13 @@
|
||||
#include "content/public/browser/browser_main_parts.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "mojo/public/cpp/bindings/remote.h"
|
||||
#include "printing/buildflags/buildflags.h"
|
||||
#include "services/device/public/mojom/geolocation_control.mojom.h"
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
#include "printing/printing_context_linux.h"
|
||||
#endif
|
||||
|
||||
class BrowserProcessImpl;
|
||||
class IconManager;
|
||||
|
||||
@@ -179,6 +184,11 @@ class ElectronBrowserMainParts : public content::BrowserMainParts {
|
||||
std::unique_ptr<display::ScopedNativeScreen> screen_;
|
||||
#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PRINTING)
|
||||
std::unique_ptr<printing::PrintingContextLinux::PrintDialogFactory>
|
||||
print_dialog_factory_;
|
||||
#endif
|
||||
|
||||
static ElectronBrowserMainParts* self_;
|
||||
};
|
||||
|
||||
|
||||
@@ -87,13 +87,9 @@ HidChooserController::HidChooserController(
|
||||
exclusion_filters_(std::move(exclusion_filters)),
|
||||
callback_(std::move(callback)),
|
||||
initiator_document_(render_frame_host->GetWeakDocumentPtr()),
|
||||
origin_(content::WebContents::FromRenderFrameHost(render_frame_host)
|
||||
->GetPrimaryMainFrame()
|
||||
->GetLastCommittedOrigin()),
|
||||
origin_(render_frame_host->GetLastCommittedOrigin()),
|
||||
hid_delegate_(hid_delegate),
|
||||
render_frame_host_id_(render_frame_host->GetGlobalId()) {
|
||||
// The use above of GetMainFrame is safe as content::HidService instances are
|
||||
// not created for fenced frames.
|
||||
DCHECK(!render_frame_host->IsNestedWithinFencedFrame());
|
||||
|
||||
chooser_context_ = HidChooserContextFactory::GetForBrowserContext(
|
||||
|
||||
@@ -170,6 +170,12 @@ class NativeWindowMac : public NativeWindow,
|
||||
void NotifyWindowDidFailToEnterFullScreen();
|
||||
void NotifyWindowWillLeaveFullScreen();
|
||||
|
||||
// Hide/show traffic light buttons around miniaturize/deminiaturize to
|
||||
// prevent them from flashing at the default position during the restore
|
||||
// animation when a custom trafficLightPosition is configured.
|
||||
void HideTrafficLights();
|
||||
void RestoreTrafficLights();
|
||||
|
||||
// Cleanup observers when window is getting closed. Note that the destructor
|
||||
// can be called much later after window gets closed, so we should not do
|
||||
// cleanup in destructor.
|
||||
|
||||
@@ -1533,6 +1533,18 @@ void NativeWindowMac::RedrawTrafficLights() {
|
||||
[buttons_proxy_ redraw];
|
||||
}
|
||||
|
||||
void NativeWindowMac::HideTrafficLights() {
|
||||
if (buttons_proxy_)
|
||||
[buttons_proxy_ setVisible:NO];
|
||||
}
|
||||
|
||||
void NativeWindowMac::RestoreTrafficLights() {
|
||||
if (buttons_proxy_ && window_button_visibility_.value_or(true)) {
|
||||
[buttons_proxy_ redraw];
|
||||
[buttons_proxy_ setVisible:YES];
|
||||
}
|
||||
}
|
||||
|
||||
// In simpleFullScreen mode, update the frame for new bounds.
|
||||
void NativeWindowMac::UpdateFrame() {
|
||||
NSWindow* window = GetNativeWindow().GetNativeNSWindow();
|
||||
|
||||
@@ -1017,17 +1017,13 @@ void NativeWindowViews::MoveTop() {
|
||||
|
||||
bool NativeWindowViews::CanResize() const {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
return resizable_ && thick_frame_;
|
||||
return has_frame() ? resizable_ && thick_frame_ : resizable_;
|
||||
#else
|
||||
return resizable_;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool NativeWindowViews::IsResizable() const {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (has_frame())
|
||||
return ::GetWindowLong(GetAcceleratedWidget(), GWL_STYLE) & WS_THICKFRAME;
|
||||
#endif
|
||||
return CanResize();
|
||||
}
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ struct NotificationOptions {
|
||||
std::u16string timeout_type;
|
||||
std::u16string reply_placeholder;
|
||||
std::u16string sound;
|
||||
std::u16string urgency; // Linux
|
||||
std::u16string urgency; // Linux/Windows
|
||||
std::vector<NotificationAction> actions;
|
||||
std::u16string close_button_text;
|
||||
std::u16string toast_xml;
|
||||
|
||||
@@ -72,7 +72,8 @@ std::wstring NotificationPresenterWin::SaveIconToFilesystem(
|
||||
|
||||
std::string filename;
|
||||
if (origin.is_valid()) {
|
||||
filename = base::SHA1HashString(origin.spec()) + ".png";
|
||||
const auto hash = base::SHA1HashString(origin.spec());
|
||||
filename = base::HexEncode(hash) + ".png";
|
||||
} else {
|
||||
const int64_t now_usec = base::Time::Now().since_origin().InMicroseconds();
|
||||
filename = base::NumberToString(now_usec) + ".png";
|
||||
|
||||
@@ -96,6 +96,21 @@ std::wstring GetExecutablePath() {
|
||||
return std::wstring(path, len);
|
||||
}
|
||||
|
||||
// Installers sometimes put the running app in a versioned subfolder and ship a
|
||||
// stub with the same filename one directory up. Point the Start Menu shortcut
|
||||
// at the stub when it exists so toast activation and updates keep a stable
|
||||
// launch path.
|
||||
std::wstring GetShortcutTargetPath(const std::wstring& exe_path) {
|
||||
if (exe_path.empty())
|
||||
return L"";
|
||||
base::FilePath exe_fp(exe_path);
|
||||
base::FilePath stub_candidate =
|
||||
exe_fp.DirName().DirName().Append(exe_fp.BaseName());
|
||||
if (base::PathExists(stub_candidate))
|
||||
return stub_candidate.value();
|
||||
return exe_path;
|
||||
}
|
||||
|
||||
void EnsureCLSIDRegistry() {
|
||||
std::wstring exe = GetExecutablePath();
|
||||
if (exe.empty())
|
||||
@@ -116,7 +131,10 @@ void EnsureCLSIDRegistry() {
|
||||
server_key.WriteValue(nullptr, exe.c_str());
|
||||
}
|
||||
|
||||
bool ExistingShortcutValid(const base::FilePath& lnk_path, PCWSTR aumid) {
|
||||
bool ExistingShortcutValid(const base::FilePath& lnk_path,
|
||||
PCWSTR aumid,
|
||||
const std::wstring& expected_target_path,
|
||||
const std::wstring& expected_working_dir) {
|
||||
if (!base::PathExists(lnk_path))
|
||||
return false;
|
||||
Microsoft::WRL::ComPtr<IShellLink> existing;
|
||||
@@ -128,6 +146,31 @@ bool ExistingShortcutValid(const base::FilePath& lnk_path, PCWSTR aumid) {
|
||||
FAILED(pf->Load(lnk_path.value().c_str(), STGM_READ))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// After an auto-update the .lnk may still have the correct AUMID/CLSID but
|
||||
// point at an old install path; treat that as invalid so we rewrite it.
|
||||
wchar_t target_path[MAX_PATH];
|
||||
if (FAILED(existing->GetPath(target_path, MAX_PATH, nullptr, SLGP_RAWPATH)))
|
||||
return false;
|
||||
if (base::FilePath::CompareIgnoreCase(
|
||||
base::FilePath(expected_target_path).value(),
|
||||
base::FilePath(target_path).value()) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t work_dir[MAX_PATH];
|
||||
work_dir[0] = L'\0';
|
||||
if (FAILED(existing->GetWorkingDirectory(work_dir, MAX_PATH)))
|
||||
return false;
|
||||
base::FilePath expected_cwd =
|
||||
base::FilePath(expected_working_dir).NormalizePathSeparators();
|
||||
base::FilePath actual_cwd =
|
||||
base::FilePath(work_dir).NormalizePathSeparators();
|
||||
if (base::FilePath::CompareIgnoreCase(expected_cwd.value(),
|
||||
actual_cwd.value()) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Microsoft::WRL::ComPtr<IPropertyStore> store;
|
||||
if (FAILED(existing.As(&store)))
|
||||
return false;
|
||||
@@ -157,6 +200,7 @@ void EnsureShortcut() {
|
||||
std::wstring exe = GetExecutablePath();
|
||||
if (exe.empty())
|
||||
return;
|
||||
std::wstring shortcut_target = GetShortcutTargetPath(exe);
|
||||
|
||||
PWSTR programs_path = nullptr;
|
||||
if (FAILED(
|
||||
@@ -195,18 +239,20 @@ void EnsureShortcut() {
|
||||
}
|
||||
}
|
||||
|
||||
if (ExistingShortcutValid(lnk_path, aumid))
|
||||
const std::wstring expected_working_dir =
|
||||
base::FilePath(exe).DirName().value();
|
||||
if (ExistingShortcutValid(lnk_path, aumid, shortcut_target,
|
||||
expected_working_dir))
|
||||
return;
|
||||
|
||||
Microsoft::WRL::ComPtr<IShellLink> shell_link;
|
||||
if (FAILED(CoCreateInstance(CLSID_ShellLink, nullptr, CLSCTX_INPROC_SERVER,
|
||||
IID_PPV_ARGS(&shell_link))))
|
||||
return;
|
||||
shell_link->SetPath(exe.c_str());
|
||||
shell_link->SetPath(shortcut_target.c_str());
|
||||
shell_link->SetArguments(L"");
|
||||
shell_link->SetDescription(product_name.c_str());
|
||||
shell_link->SetWorkingDirectory(
|
||||
base::FilePath(exe).DirName().value().c_str());
|
||||
shell_link->SetWorkingDirectory(expected_working_dir.c_str());
|
||||
|
||||
Microsoft::WRL::ComPtr<IPropertyStore> prop_store;
|
||||
if (SUCCEEDED(shell_link.As(&prop_store))) {
|
||||
|
||||
@@ -280,8 +280,9 @@ void WindowsToastNotification::CreateToastNotificationOnBackgroundThread(
|
||||
// Continue to create the toast notification
|
||||
ComPtr<ABI::Windows::UI::Notifications::IToastNotification>
|
||||
toast_notification;
|
||||
if (!CreateToastNotification(toast_xml, notification_id, weak_notification,
|
||||
ui_task_runner, &toast_notification)) {
|
||||
if (!CreateToastNotification(toast_xml, options, notification_id,
|
||||
weak_notification, ui_task_runner,
|
||||
&toast_notification)) {
|
||||
return; // Error already posted to UI thread
|
||||
}
|
||||
|
||||
@@ -349,6 +350,7 @@ bool WindowsToastNotification::CreateToastXmlDocument(
|
||||
// returns the created notification via out parameter.
|
||||
bool WindowsToastNotification::CreateToastNotification(
|
||||
ComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocument> toast_xml,
|
||||
const NotificationOptions& options,
|
||||
const std::string& notification_id,
|
||||
base::WeakPtr<Notification> weak_notification,
|
||||
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
|
||||
@@ -416,6 +418,27 @@ bool WindowsToastNotification::CreateToastNotification(
|
||||
return false;
|
||||
}
|
||||
|
||||
ComPtr<winui::Notifications::IToastNotification4> toast4;
|
||||
hr = (*toast_notification)->QueryInterface(IID_PPV_ARGS(&toast4));
|
||||
if (SUCCEEDED(hr)) {
|
||||
winui::Notifications::ToastNotificationPriority priority =
|
||||
winui::Notifications::ToastNotificationPriority::
|
||||
ToastNotificationPriority_Default;
|
||||
if (options.urgency == u"critical") {
|
||||
priority = winui::Notifications::ToastNotificationPriority::
|
||||
ToastNotificationPriority_High;
|
||||
}
|
||||
|
||||
hr = toast4->put_Priority(priority);
|
||||
if (FAILED(hr)) {
|
||||
std::string err = base::StrCat({"WinAPI: Setting priority failed, ERROR ",
|
||||
FailureResultToString(hr)});
|
||||
DebugLog(err);
|
||||
PostNotificationFailedToUIThread(weak_notification, err, ui_task_runner);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@ class WindowsToastNotification : public Notification {
|
||||
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner);
|
||||
static bool CreateToastNotification(
|
||||
ComPtr<ABI::Windows::Data::Xml::Dom::IXmlDocument> toast_xml,
|
||||
const NotificationOptions& options,
|
||||
const std::string& notification_id,
|
||||
base::WeakPtr<Notification> weak_notification,
|
||||
scoped_refptr<base::SingleThreadTaskRunner> ui_task_runner,
|
||||
|
||||
@@ -45,6 +45,10 @@ void OffScreenWebContentsView::SetWebContents(
|
||||
view->InstallTransparency();
|
||||
}
|
||||
|
||||
void OffScreenWebContentsView::SetCallback(const OnPaintCallback& callback) {
|
||||
callback_ = callback;
|
||||
}
|
||||
|
||||
void OffScreenWebContentsView::SetNativeWindow(NativeWindow* window) {
|
||||
if (native_window_)
|
||||
native_window_->RemoveObserver(this);
|
||||
|
||||
@@ -43,6 +43,7 @@ class OffScreenWebContentsView : public content::WebContentsView,
|
||||
|
||||
void SetWebContents(content::WebContents*);
|
||||
void SetNativeWindow(NativeWindow* window);
|
||||
void SetCallback(const OnPaintCallback& callback);
|
||||
|
||||
// NativeWindowObserver:
|
||||
void OnWindowResize() override;
|
||||
|
||||
@@ -59,6 +59,8 @@ gfx::Size GetDefaultPrinterDPI(const std::u16string& device_name) {
|
||||
GtkPrintSettings* print_settings = gtk_print_settings_new();
|
||||
int dpi = gtk_print_settings_get_resolution(print_settings);
|
||||
g_object_unref(print_settings);
|
||||
if (dpi <= 0)
|
||||
dpi = printing::kDefaultPdfDpi;
|
||||
return {dpi, dpi};
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -52,25 +52,21 @@ bool ElectronSerialDelegate::CanRequestPortPermission(
|
||||
auto* permission_helper =
|
||||
WebContentsPermissionHelper::FromWebContents(web_contents);
|
||||
return permission_helper->CheckSerialAccessPermission(
|
||||
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin());
|
||||
frame->GetLastCommittedOrigin());
|
||||
}
|
||||
|
||||
bool ElectronSerialDelegate::HasPortPermission(
|
||||
content::RenderFrameHost* frame,
|
||||
const device::mojom::SerialPortInfo& port) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
return GetChooserContext(frame)->HasPortPermission(
|
||||
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), port,
|
||||
frame);
|
||||
frame->GetLastCommittedOrigin(), port, frame);
|
||||
}
|
||||
|
||||
void ElectronSerialDelegate::RevokePortPermissionWebInitiated(
|
||||
content::RenderFrameHost* frame,
|
||||
const base::UnguessableToken& token) {
|
||||
auto* web_contents = content::WebContents::FromRenderFrameHost(frame);
|
||||
return GetChooserContext(frame)->RevokePortPermissionWebInitiated(
|
||||
web_contents->GetPrimaryMainFrame()->GetLastCommittedOrigin(), token,
|
||||
frame);
|
||||
frame->GetLastCommittedOrigin(), token, frame);
|
||||
}
|
||||
|
||||
const device::mojom::SerialPortInfo* ElectronSerialDelegate::GetPortInfo(
|
||||
|
||||
@@ -125,7 +125,7 @@ SerialChooserController::SerialChooserController(
|
||||
std::move(allowed_bluetooth_service_class_ids)),
|
||||
callback_(std::move(callback)),
|
||||
initiator_document_(render_frame_host->GetWeakDocumentPtr()) {
|
||||
origin_ = web_contents_->GetPrimaryMainFrame()->GetLastCommittedOrigin();
|
||||
origin_ = render_frame_host->GetLastCommittedOrigin();
|
||||
|
||||
chooser_context_ = SerialChooserContextFactory::GetForBrowserContext(
|
||||
web_contents_->GetBrowserContext())
|
||||
|
||||
@@ -87,8 +87,8 @@ MouseDownImpl g_nsnextstepframe_mousedown;
|
||||
(electron::NativeWindowMac*)[(id)self.window shell];
|
||||
if (shell && !shell->has_frame())
|
||||
[self cr_mouseDownOnFrameView:event];
|
||||
g_nsthemeframe_mousedown(self, @selector(mouseDown:), event);
|
||||
}
|
||||
g_nsthemeframe_mousedown(self, @selector(mouseDown:), event);
|
||||
}
|
||||
|
||||
- (void)swiz_nsnextstepframe_mouseDown:(NSEvent*)event {
|
||||
@@ -98,8 +98,8 @@ MouseDownImpl g_nsnextstepframe_mousedown;
|
||||
if (shell && !shell->has_frame()) {
|
||||
[self cr_mouseDownOnFrameView:event];
|
||||
}
|
||||
g_nsnextstepframe_mousedown(self, @selector(mouseDown:), event);
|
||||
}
|
||||
g_nsnextstepframe_mousedown(self, @selector(mouseDown:), event);
|
||||
}
|
||||
|
||||
- (void)swiz_nsview_swipeWithEvent:(NSEvent*)event {
|
||||
|
||||
@@ -256,6 +256,10 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
shell_->SetWindowLevel(NSNormalWindowLevel);
|
||||
shell_->UpdateWindowOriginalFrame();
|
||||
shell_->DetachChildren();
|
||||
// Hide the traffic light buttons container before miniaturize so that
|
||||
// when the window is restored, macOS does not render the buttons at
|
||||
// their default position during the deminiaturize animation.
|
||||
shell_->HideTrafficLights();
|
||||
}
|
||||
|
||||
- (void)windowDidMiniaturize:(NSNotification*)notification {
|
||||
@@ -273,6 +277,10 @@ using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle;
|
||||
shell_->set_wants_to_be_visible(true);
|
||||
shell_->AttachChildren();
|
||||
shell_->SetWindowLevel(level_);
|
||||
// Reposition traffic light buttons and make them visible again.
|
||||
// They were hidden in windowWillMiniaturize to prevent a flash at
|
||||
// the default (0,0) position during the restore animation.
|
||||
shell_->RestoreTrafficLights();
|
||||
shell_->NotifyWindowRestore();
|
||||
}
|
||||
|
||||
|
||||
@@ -243,8 +243,8 @@ void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
// 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->GetRoundedWindowContentBounds();
|
||||
gfx::RectF rectf(layout->GetWindowContentBounds());
|
||||
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
|
||||
|
||||
@@ -158,7 +158,7 @@ DialogResult ShowTaskDialogWstr(gfx::AcceleratedWidget parent,
|
||||
config.hInstance = GetModuleHandle(nullptr);
|
||||
config.dwFlags = flags;
|
||||
|
||||
if (parent) {
|
||||
if (parent && ::IsWindowEnabled(parent)) {
|
||||
config.hwndParent = parent;
|
||||
config.dwFlags |= TDF_POSITION_RELATIVE_TO_WINDOW;
|
||||
}
|
||||
|
||||
@@ -112,7 +112,7 @@ ClientFrameViewLinux::~ClientFrameViewLinux() {
|
||||
void ClientFrameViewLinux::Init(NativeWindowViews* window,
|
||||
views::Widget* frame) {
|
||||
FramelessView::Init(window, frame);
|
||||
linux_frame_layout_ = std::make_unique<LinuxCSDFrameLayout>(window);
|
||||
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.
|
||||
@@ -156,7 +156,8 @@ void ClientFrameViewLinux::OnWindowButtonOrderingChange() {
|
||||
}
|
||||
|
||||
int ClientFrameViewLinux::ResizingBorderHitTest(const gfx::Point& point) {
|
||||
return ResizingBorderHitTestImpl(point, RestoredFrameBorderInsets());
|
||||
return ResizingBorderHitTestImpl(
|
||||
point, linux_frame_layout_->GetResizeBorderInsets());
|
||||
}
|
||||
|
||||
gfx::Rect ClientFrameViewLinux::GetBoundsForClientView() const {
|
||||
@@ -235,8 +236,11 @@ void ClientFrameViewLinux::Layout(PassKey) {
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::OnPaint(gfx::Canvas* canvas) {
|
||||
linux_frame_layout_->PaintWindowFrame(
|
||||
canvas, GetLocalBounds(), GetTitlebarBounds(), ShouldPaintAsActive());
|
||||
if (auto* frame_provider = linux_frame_layout_->GetFrameProvider()) {
|
||||
frame_provider->PaintWindowFrame(
|
||||
canvas, GetLocalBounds(), GetTitlebarBounds().bottom(),
|
||||
ShouldPaintAsActive(), linux_frame_layout_->GetInputInsets());
|
||||
}
|
||||
}
|
||||
|
||||
void ClientFrameViewLinux::PaintAsActiveChanged() {
|
||||
@@ -267,7 +271,7 @@ void ClientFrameViewLinux::UpdateThemeValues() {
|
||||
}
|
||||
|
||||
theme_values_.window_border_radius =
|
||||
linux_frame_layout_->GetFrameProvider()->GetTopCornerRadiusDip();
|
||||
linux_frame_layout_->GetTopCornerRadiusDip();
|
||||
|
||||
gtk::GtkStyleContextGet(headerbar_context, "min-height",
|
||||
&theme_values_.titlebar_min_height, nullptr);
|
||||
|
||||
@@ -112,7 +112,7 @@ class ClientFrameViewLinux : public FramelessView,
|
||||
gfx::Insets GetTitlebarContentInsets() const;
|
||||
gfx::Rect GetTitlebarContentBounds() const;
|
||||
|
||||
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
|
||||
std::unique_ptr<LinuxCSDNativeFrameLayout> linux_frame_layout_;
|
||||
|
||||
raw_ptr<ui::NativeTheme> theme_;
|
||||
ThemeValues theme_values_;
|
||||
|
||||
@@ -4,14 +4,20 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
#include "shell/browser/ui/views/linux_frame_layout.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h" // nogncheck
|
||||
#include "shell/browser/linux/x11_util.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "third_party/skia/include/core/SkRRect.h"
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/gfx/geometry/skia_conversions.h"
|
||||
#include "ui/linux/linux_ui.h"
|
||||
#include "ui/native_theme/native_theme.h"
|
||||
#include "ui/linux/window_frame_provider.h"
|
||||
#include "ui/views/layout/layout_provider.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
|
||||
namespace electron {
|
||||
@@ -21,151 +27,174 @@ namespace {
|
||||
constexpr int kResizeBorder = 10;
|
||||
// This should match FramelessView's inside resize band.
|
||||
constexpr int kResizeInsideBoundsSize = 5;
|
||||
// These should match Chromium's restored frame edge thickness.
|
||||
constexpr gfx::Insets kDefaultCustomFrameBorder = gfx::Insets::TLBR(2, 1, 1, 1);
|
||||
|
||||
bool CheckClientFrameShadowSupport(NativeWindowViews* window) {
|
||||
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
|
||||
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
|
||||
window->GetAcceleratedWidget()));
|
||||
return tree_host && tree_host->SupportsClientFrameShadow();
|
||||
}
|
||||
} // namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<LinuxFrameLayout> LinuxFrameLayout::Create(
|
||||
NativeWindowViews* window,
|
||||
bool wants_shadow) {
|
||||
bool wants_shadow,
|
||||
CSDStyle csd_style) {
|
||||
if (x11_util::IsX11() || window->IsTranslucent() || !wants_shadow) {
|
||||
return std::make_unique<LinuxUndecoratedFrameLayout>(window);
|
||||
return std::make_unique<LinuxFrameLayout>(window);
|
||||
} else if (csd_style == CSDStyle::kCustom) {
|
||||
return std::make_unique<LinuxCSDCustomFrameLayout>(window);
|
||||
} else {
|
||||
return std::make_unique<LinuxCSDFrameLayout>(window);
|
||||
return std::make_unique<LinuxCSDNativeFrameLayout>(window);
|
||||
}
|
||||
}
|
||||
|
||||
LinuxCSDFrameLayout::LinuxCSDFrameLayout(NativeWindowViews* window)
|
||||
: window_(window) {
|
||||
host_supports_client_frame_shadow_ = SupportsClientFrameShadow();
|
||||
gfx::Insets LinuxFrameLayout::GetResizeBorderInsets() const {
|
||||
gfx::Insets insets = RestoredFrameBorderInsets();
|
||||
return insets.IsEmpty() ? GetInputInsets() : insets;
|
||||
}
|
||||
|
||||
bool LinuxCSDFrameLayout::tiled() const {
|
||||
return tiled_;
|
||||
}
|
||||
|
||||
void LinuxCSDFrameLayout::set_tiled(bool tiled) {
|
||||
tiled_ = tiled;
|
||||
}
|
||||
|
||||
gfx::Insets LinuxCSDFrameLayout::RestoredFrameBorderInsets() const {
|
||||
gfx::Insets insets = GetFrameProvider()->GetFrameThicknessDip();
|
||||
const gfx::Insets input = GetInputInsets();
|
||||
|
||||
auto expand_if_visible = [](int side_thickness, int min_band) {
|
||||
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
|
||||
};
|
||||
|
||||
gfx::Insets merged;
|
||||
merged.set_top(expand_if_visible(insets.top(), input.top()));
|
||||
merged.set_left(expand_if_visible(insets.left(), input.left()));
|
||||
merged.set_bottom(expand_if_visible(insets.bottom(), input.bottom()));
|
||||
merged.set_right(expand_if_visible(insets.right(), input.right()));
|
||||
|
||||
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
|
||||
merged.bottom(), merged.left())
|
||||
: merged;
|
||||
}
|
||||
|
||||
gfx::Insets LinuxCSDFrameLayout::GetInputInsets() const {
|
||||
bool showing_shadow = host_supports_client_frame_shadow_ &&
|
||||
!window_->IsMaximized() && !window_->IsFullscreen();
|
||||
return gfx::Insets(showing_shadow ? kResizeBorder : 0);
|
||||
}
|
||||
|
||||
bool LinuxCSDFrameLayout::SupportsClientFrameShadow() const {
|
||||
auto* tree_host = static_cast<ElectronDesktopWindowTreeHostLinux*>(
|
||||
ElectronDesktopWindowTreeHostLinux::GetHostForWidget(
|
||||
window_->GetAcceleratedWidget()));
|
||||
return tree_host->SupportsClientFrameShadow();
|
||||
}
|
||||
|
||||
void LinuxCSDFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) {
|
||||
GetFrameProvider()->PaintWindowFrame(
|
||||
canvas, local_bounds, titlebar_bounds.bottom(), active, GetInputInsets());
|
||||
}
|
||||
|
||||
gfx::Rect LinuxCSDFrameLayout::GetWindowContentBounds() const {
|
||||
gfx::Rect content_bounds = window_->widget()->GetWindowBoundsInScreen();
|
||||
content_bounds.Inset(RestoredFrameBorderInsets());
|
||||
return content_bounds;
|
||||
}
|
||||
|
||||
SkRRect LinuxCSDFrameLayout::GetRoundedWindowContentBounds() const {
|
||||
SkRect rect = gfx::RectToSkRect(GetWindowContentBounds());
|
||||
SkRRect LinuxFrameLayout::GetRoundedWindowBounds() const {
|
||||
SkRect rect = gfx::RectToSkRect(GetWindowBounds());
|
||||
SkRRect rrect;
|
||||
|
||||
if (!window_->IsMaximized()) {
|
||||
float radius = GetFrameProvider()->GetTopCornerRadiusDip();
|
||||
float radius = GetTopCornerRadiusDip();
|
||||
if (radius > 0) {
|
||||
SkPoint round_point{radius, radius};
|
||||
SkPoint radii[] = {round_point, round_point, {}, {}};
|
||||
rrect.setRectRadii(rect, radii);
|
||||
} else {
|
||||
rrect.setRect(rect);
|
||||
}
|
||||
|
||||
return rrect;
|
||||
}
|
||||
|
||||
int LinuxCSDFrameLayout::GetTranslucentTopAreaHeight() const {
|
||||
// Base implementation is suitable for X11/views without shadows
|
||||
LinuxFrameLayout::LinuxFrameLayout(NativeWindowViews* window)
|
||||
: window_(window) {
|
||||
host_supports_client_frame_shadow_ = false;
|
||||
}
|
||||
|
||||
LinuxFrameLayout::~LinuxFrameLayout() = default;
|
||||
|
||||
gfx::Insets LinuxFrameLayout::RestoredFrameBorderInsets() const {
|
||||
return gfx::Insets();
|
||||
}
|
||||
|
||||
gfx::Insets LinuxFrameLayout::GetInputInsets() const {
|
||||
return gfx::Insets(kResizeInsideBoundsSize);
|
||||
}
|
||||
|
||||
bool LinuxFrameLayout::IsShowingShadow() const {
|
||||
return host_supports_client_frame_shadow_ && !window_->IsMaximized() &&
|
||||
!window_->IsFullscreen();
|
||||
}
|
||||
|
||||
bool LinuxFrameLayout::SupportsClientFrameShadow() const {
|
||||
return host_supports_client_frame_shadow_;
|
||||
}
|
||||
|
||||
bool LinuxFrameLayout::tiled() const {
|
||||
return tiled_;
|
||||
}
|
||||
|
||||
void LinuxFrameLayout::set_tiled(bool tiled) {
|
||||
tiled_ = tiled;
|
||||
}
|
||||
|
||||
gfx::Rect LinuxFrameLayout::GetWindowBounds() const {
|
||||
gfx::Rect bounds = window_->widget()->GetWindowBoundsInScreen();
|
||||
bounds.Inset(RestoredFrameBorderInsets());
|
||||
return bounds;
|
||||
}
|
||||
|
||||
float LinuxFrameLayout::GetTopCornerRadiusDip() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ui::WindowFrameProvider* LinuxCSDFrameLayout::GetFrameProvider() const {
|
||||
int LinuxFrameLayout::GetTranslucentTopAreaHeight() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
gfx::Insets LinuxFrameLayout::NormalizeBorderInsets(
|
||||
const gfx::Insets& frame_insets,
|
||||
const gfx::Insets& input_insets) const {
|
||||
auto expand_if_visible = [](int side_thickness, int min_band) {
|
||||
return side_thickness > 0 ? std::max(side_thickness, min_band) : 0;
|
||||
};
|
||||
|
||||
// Ensure hit testing for resize targets works
|
||||
// even if borders/shadows are absent on some edges.
|
||||
gfx::Insets merged;
|
||||
merged.set_top(expand_if_visible(frame_insets.top(), input_insets.top()));
|
||||
merged.set_left(expand_if_visible(frame_insets.left(), input_insets.left()));
|
||||
merged.set_bottom(
|
||||
expand_if_visible(frame_insets.bottom(), input_insets.bottom()));
|
||||
merged.set_right(
|
||||
expand_if_visible(frame_insets.right(), input_insets.right()));
|
||||
|
||||
return base::i18n::IsRTL() ? gfx::Insets::TLBR(merged.top(), merged.right(),
|
||||
merged.bottom(), merged.left())
|
||||
: merged;
|
||||
}
|
||||
|
||||
// Used for a native-like frame with a FrameProvider
|
||||
LinuxCSDNativeFrameLayout::LinuxCSDNativeFrameLayout(NativeWindowViews* window)
|
||||
: LinuxFrameLayout(window) {
|
||||
host_supports_client_frame_shadow_ = CheckClientFrameShadowSupport(window);
|
||||
}
|
||||
|
||||
LinuxCSDNativeFrameLayout::~LinuxCSDNativeFrameLayout() = default;
|
||||
|
||||
gfx::Insets LinuxCSDNativeFrameLayout::RestoredFrameBorderInsets() const {
|
||||
const gfx::Insets input_insets = GetInputInsets();
|
||||
const gfx::Insets frame_insets = GetFrameProvider()->GetFrameThicknessDip();
|
||||
return NormalizeBorderInsets(frame_insets, input_insets);
|
||||
}
|
||||
|
||||
gfx::Insets LinuxCSDNativeFrameLayout::GetInputInsets() const {
|
||||
return gfx::Insets(IsShowingShadow() ? kResizeBorder : 0);
|
||||
}
|
||||
|
||||
float LinuxCSDNativeFrameLayout::GetTopCornerRadiusDip() const {
|
||||
return window_->IsMaximized() ? 0
|
||||
: GetFrameProvider()->GetTopCornerRadiusDip();
|
||||
}
|
||||
|
||||
ui::WindowFrameProvider* LinuxCSDNativeFrameLayout::GetFrameProvider() const {
|
||||
return ui::LinuxUiTheme::GetForProfile(nullptr)->GetWindowFrameProvider(
|
||||
!host_supports_client_frame_shadow_, tiled(), window_->IsMaximized());
|
||||
}
|
||||
|
||||
LinuxUndecoratedFrameLayout::LinuxUndecoratedFrameLayout(
|
||||
NativeWindowViews* window)
|
||||
: window_(window) {}
|
||||
|
||||
gfx::Insets LinuxUndecoratedFrameLayout::RestoredFrameBorderInsets() const {
|
||||
return gfx::Insets();
|
||||
// Used for Chromium-like custom CSD
|
||||
LinuxCSDCustomFrameLayout::LinuxCSDCustomFrameLayout(NativeWindowViews* window)
|
||||
: LinuxFrameLayout(window) {
|
||||
host_supports_client_frame_shadow_ = CheckClientFrameShadowSupport(window);
|
||||
}
|
||||
|
||||
gfx::Insets LinuxUndecoratedFrameLayout::GetInputInsets() const {
|
||||
return gfx::Insets(kResizeInsideBoundsSize);
|
||||
LinuxCSDCustomFrameLayout::~LinuxCSDCustomFrameLayout() = default;
|
||||
|
||||
gfx::Insets LinuxCSDCustomFrameLayout::RestoredFrameBorderInsets() const {
|
||||
const gfx::Insets input_insets = GetInputInsets();
|
||||
const bool showing_shadow = IsShowingShadow();
|
||||
const auto shadow_values = (showing_shadow && !tiled())
|
||||
? GetFrameShadowValuesLinux(/*active=*/true)
|
||||
: gfx::ShadowValues();
|
||||
const gfx::Insets frame_insets = GetRestoredFrameBorderInsetsLinux(
|
||||
showing_shadow, kDefaultCustomFrameBorder, shadow_values, input_insets);
|
||||
return NormalizeBorderInsets(frame_insets, input_insets);
|
||||
}
|
||||
|
||||
bool LinuxUndecoratedFrameLayout::SupportsClientFrameShadow() const {
|
||||
return false;
|
||||
gfx::Insets LinuxCSDCustomFrameLayout::GetInputInsets() const {
|
||||
return gfx::Insets(IsShowingShadow() ? kResizeBorder : 0);
|
||||
}
|
||||
|
||||
bool LinuxUndecoratedFrameLayout::tiled() const {
|
||||
return tiled_;
|
||||
}
|
||||
|
||||
void LinuxUndecoratedFrameLayout::set_tiled(bool tiled) {
|
||||
tiled_ = tiled;
|
||||
}
|
||||
|
||||
void LinuxUndecoratedFrameLayout::PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) {
|
||||
// No-op
|
||||
}
|
||||
|
||||
gfx::Rect LinuxUndecoratedFrameLayout::GetWindowContentBounds() const {
|
||||
// With no transparent insets, widget bounds and logical bounds match.
|
||||
return window_->widget()->GetWindowBoundsInScreen();
|
||||
}
|
||||
|
||||
SkRRect LinuxUndecoratedFrameLayout::GetRoundedWindowContentBounds() const {
|
||||
SkRRect rrect;
|
||||
rrect.setRect(gfx::RectToSkRect(GetWindowContentBounds()));
|
||||
return rrect;
|
||||
}
|
||||
|
||||
int LinuxUndecoratedFrameLayout::GetTranslucentTopAreaHeight() const {
|
||||
return 0;
|
||||
}
|
||||
|
||||
ui::WindowFrameProvider* LinuxUndecoratedFrameLayout::GetFrameProvider() const {
|
||||
return nullptr;
|
||||
gfx::ShadowValues GetFrameShadowValuesLinux(bool active) {
|
||||
const int elevation = views::LayoutProvider::Get()->GetShadowElevationMetric(
|
||||
active ? views::Emphasis::kMaximum : views::Emphasis::kMedium);
|
||||
return gfx::ShadowValue::MakeMdShadowValues(elevation);
|
||||
}
|
||||
|
||||
} // namespace electron
|
||||
|
||||
@@ -8,110 +8,96 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "shell/browser/linux/x11_util.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/electron_desktop_window_tree_host_linux.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "third_party/skia/include/core/SkRRect.h"
|
||||
#include "ui/base/ozone_buildflags.h"
|
||||
#include "ui/gfx/canvas.h"
|
||||
#include "ui/gfx/geometry/insets.h"
|
||||
#include "ui/linux/linux_ui.h"
|
||||
#include "ui/gfx/shadow_value.h"
|
||||
#include "ui/linux/window_frame_provider.h"
|
||||
|
||||
namespace gfx {
|
||||
class Insets;
|
||||
class Rect;
|
||||
} // namespace gfx
|
||||
|
||||
namespace electron {
|
||||
|
||||
class NativeWindowViews;
|
||||
|
||||
// Shared helper for CSD layout and frame painting on Linux (shadows, resize
|
||||
// regions, titlebars, etc.). Also helps views determine insets and perform
|
||||
// bounds conversions between widget and logical coordinates.
|
||||
// Shared helper for CSD layout on Linux (shadows, resize regions, titlebars,
|
||||
// etc.). Also helps views determine insets and perform bounds conversions
|
||||
// between widget and logical coordinates.
|
||||
//
|
||||
// The base class is concrete and suitable as-is for the undecorated case (X11,
|
||||
// translucent windows, or windows without shadows). CSD subclasses override
|
||||
// the methods that differ.
|
||||
class LinuxFrameLayout {
|
||||
public:
|
||||
virtual ~LinuxFrameLayout() = default;
|
||||
enum class CSDStyle {
|
||||
kNativeFrame,
|
||||
kCustom,
|
||||
};
|
||||
|
||||
explicit LinuxFrameLayout(NativeWindowViews* window);
|
||||
virtual ~LinuxFrameLayout();
|
||||
|
||||
static std::unique_ptr<LinuxFrameLayout> Create(NativeWindowViews* window,
|
||||
bool wants_shadow);
|
||||
bool wants_shadow,
|
||||
CSDStyle csd_style);
|
||||
|
||||
// Insets from the transparent widget border to the opaque part of the window
|
||||
virtual gfx::Insets RestoredFrameBorderInsets() const = 0;
|
||||
// Insets for parts of the surface that should be counted for user input
|
||||
virtual gfx::Insets GetInputInsets() const = 0;
|
||||
// Insets from the transparent widget border to the opaque part of the window.
|
||||
virtual gfx::Insets RestoredFrameBorderInsets() const;
|
||||
// Insets for parts of the surface that should be counted for user input.
|
||||
virtual gfx::Insets GetInputInsets() const;
|
||||
// Insets to use for non-client resize hit-testing.
|
||||
gfx::Insets GetResizeBorderInsets() const;
|
||||
|
||||
virtual bool SupportsClientFrameShadow() const = 0;
|
||||
bool IsShowingShadow() const;
|
||||
bool SupportsClientFrameShadow() const;
|
||||
|
||||
virtual bool tiled() const = 0;
|
||||
virtual void set_tiled(bool tiled) = 0;
|
||||
bool tiled() const;
|
||||
void set_tiled(bool tiled);
|
||||
|
||||
virtual void PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) = 0;
|
||||
// The logical bounds of the window interior.
|
||||
gfx::Rect GetWindowBounds() const;
|
||||
// The logical window bounds as a rounded rect with corner radii applied.
|
||||
SkRRect GetRoundedWindowBounds() const;
|
||||
// The corner radius of the top corners of the window, in DIPs.
|
||||
virtual float GetTopCornerRadiusDip() const;
|
||||
|
||||
// The logical bounds of the window
|
||||
virtual gfx::Rect GetWindowContentBounds() const = 0;
|
||||
// The logical bounds as a rounded rect with corner radii applied
|
||||
virtual SkRRect GetRoundedWindowContentBounds() const = 0;
|
||||
int GetTranslucentTopAreaHeight() const;
|
||||
|
||||
virtual int GetTranslucentTopAreaHeight() const = 0;
|
||||
protected:
|
||||
gfx::Insets NormalizeBorderInsets(const gfx::Insets& frame_insets,
|
||||
const gfx::Insets& input_insets) const;
|
||||
|
||||
virtual ui::WindowFrameProvider* GetFrameProvider() const = 0;
|
||||
};
|
||||
|
||||
// Client-side decoration (CSD) Linux frame layout implementation.
|
||||
class LinuxCSDFrameLayout : public LinuxFrameLayout {
|
||||
public:
|
||||
explicit LinuxCSDFrameLayout(NativeWindowViews* window);
|
||||
~LinuxCSDFrameLayout() override = default;
|
||||
|
||||
gfx::Insets RestoredFrameBorderInsets() const override;
|
||||
gfx::Insets GetInputInsets() const override;
|
||||
bool SupportsClientFrameShadow() const override;
|
||||
bool tiled() const override;
|
||||
void set_tiled(bool tiled) override;
|
||||
void PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) override;
|
||||
gfx::Rect GetWindowContentBounds() const override;
|
||||
SkRRect GetRoundedWindowContentBounds() const override;
|
||||
int GetTranslucentTopAreaHeight() const override;
|
||||
ui::WindowFrameProvider* GetFrameProvider() const override;
|
||||
|
||||
private:
|
||||
raw_ptr<NativeWindowViews> window_;
|
||||
bool tiled_ = false;
|
||||
bool host_supports_client_frame_shadow_ = false;
|
||||
};
|
||||
|
||||
// No-decoration Linux frame layout implementation.
|
||||
//
|
||||
// Intended for cases where we do not allocate a transparent inset area around
|
||||
// the window (e.g. X11 / server-side decorations, or when insets are disabled).
|
||||
// All inset math returns 0 and frame painting is skipped.
|
||||
class LinuxUndecoratedFrameLayout : public LinuxFrameLayout {
|
||||
// CSD strategy that uses the GTK window frame provider for metrics.
|
||||
class LinuxCSDNativeFrameLayout : public LinuxFrameLayout {
|
||||
public:
|
||||
explicit LinuxUndecoratedFrameLayout(NativeWindowViews* window);
|
||||
~LinuxUndecoratedFrameLayout() override = default;
|
||||
explicit LinuxCSDNativeFrameLayout(NativeWindowViews* window);
|
||||
~LinuxCSDNativeFrameLayout() override;
|
||||
|
||||
gfx::Insets RestoredFrameBorderInsets() const override;
|
||||
gfx::Insets GetInputInsets() const override;
|
||||
bool SupportsClientFrameShadow() const override;
|
||||
bool tiled() const override;
|
||||
void set_tiled(bool tiled) override;
|
||||
void PaintWindowFrame(gfx::Canvas* canvas,
|
||||
gfx::Rect local_bounds,
|
||||
gfx::Rect titlebar_bounds,
|
||||
bool active) override;
|
||||
gfx::Rect GetWindowContentBounds() const override;
|
||||
SkRRect GetRoundedWindowContentBounds() const override;
|
||||
int GetTranslucentTopAreaHeight() const override;
|
||||
ui::WindowFrameProvider* GetFrameProvider() const override;
|
||||
|
||||
private:
|
||||
raw_ptr<NativeWindowViews> window_;
|
||||
bool tiled_ = false;
|
||||
float GetTopCornerRadiusDip() const override;
|
||||
ui::WindowFrameProvider* GetFrameProvider() const;
|
||||
};
|
||||
|
||||
// CSD strategy that uses custom metrics, similar to those used in Chromium.
|
||||
class LinuxCSDCustomFrameLayout : public LinuxFrameLayout {
|
||||
public:
|
||||
explicit LinuxCSDCustomFrameLayout(NativeWindowViews* window);
|
||||
~LinuxCSDCustomFrameLayout() override;
|
||||
|
||||
gfx::Insets RestoredFrameBorderInsets() const override;
|
||||
gfx::Insets GetInputInsets() const override;
|
||||
};
|
||||
|
||||
gfx::ShadowValues GetFrameShadowValuesLinux(bool active);
|
||||
|
||||
} // namespace electron
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_UI_VIEWS_LINUX_FRAME_LAYOUT_H_
|
||||
|
||||
@@ -5,22 +5,24 @@
|
||||
#include "shell/browser/ui/views/opaque_frame_view.h"
|
||||
|
||||
#include "base/containers/adapters.h"
|
||||
#include "base/i18n/rtl.h"
|
||||
#include "chrome/browser/ui/views/frame/browser_frame_view_paint_utils_linux.h" // nogncheck
|
||||
#include "chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h" // nogncheck
|
||||
#include "chrome/grit/generated_resources.h"
|
||||
#include "components/strings/grit/components_strings.h"
|
||||
#include "shell/browser/native_window_views.h"
|
||||
#include "shell/browser/ui/views/caption_button_placeholder_container.h"
|
||||
#include "third_party/skia/include/core/SkRRect.h"
|
||||
#include "ui/base/hit_test.h"
|
||||
#include "ui/base/l10n/l10n_util.h"
|
||||
#include "ui/base/metadata/metadata_impl_macros.h"
|
||||
#include "ui/compositor/layer.h"
|
||||
#include "ui/gfx/font_list.h"
|
||||
#include "ui/linux/linux_ui.h"
|
||||
#include "ui/gfx/geometry/insets_f.h"
|
||||
#include "ui/gfx/geometry/skia_conversions.h"
|
||||
#include "ui/views/accessibility/view_accessibility.h"
|
||||
#include "ui/views/background.h"
|
||||
#include "ui/views/widget/widget.h"
|
||||
#include "ui/views/widget/widget_delegate.h"
|
||||
#include "ui/views/window/frame_background.h"
|
||||
#include "ui/views/window/frame_caption_button.h"
|
||||
#include "ui/views/window/vector_icons/vector_icons.h"
|
||||
|
||||
@@ -55,12 +57,14 @@ const int kCaptionButtonBottomPadding = 3;
|
||||
// The content edge images have a shadow built into them.
|
||||
const int OpaqueFrameView::kContentEdgeShadowThickness = 2;
|
||||
|
||||
OpaqueFrameView::OpaqueFrameView() = default;
|
||||
OpaqueFrameView::OpaqueFrameView()
|
||||
: frame_background_(std::make_unique<views::FrameBackground>()) {}
|
||||
OpaqueFrameView::~OpaqueFrameView() = default;
|
||||
|
||||
void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
|
||||
FramelessView::Init(window, frame);
|
||||
linux_frame_layout_ = LinuxFrameLayout::Create(window, window->HasShadow());
|
||||
linux_frame_layout_ = LinuxFrameLayout::Create(
|
||||
window, window->HasShadow(), LinuxFrameLayout::CSDStyle::kCustom);
|
||||
|
||||
// Unretained() is safe because the subscription is saved into an instance
|
||||
// member and thus will be cancelled upon the instance's destruction.
|
||||
@@ -98,9 +102,8 @@ void OpaqueFrameView::Init(NativeWindowViews* window, views::Widget* frame) {
|
||||
}
|
||||
|
||||
int OpaqueFrameView::ResizingBorderHitTest(const gfx::Point& point) {
|
||||
auto insets = RestoredFrameBorderInsets();
|
||||
return ResizingBorderHitTestImpl(
|
||||
point, insets.IsEmpty() ? linux_frame_layout_->GetInputInsets() : insets);
|
||||
point, linux_frame_layout_->GetResizeBorderInsets());
|
||||
}
|
||||
|
||||
void OpaqueFrameView::InvalidateCaptionButtons() {
|
||||
@@ -200,14 +203,31 @@ void OpaqueFrameView::OnPaint(gfx::Canvas* canvas) {
|
||||
if (frame()->IsFullscreen())
|
||||
return;
|
||||
|
||||
// Titlebar height must be at least the frame border insets to avoid
|
||||
// a negative height calculation in the GTK frame provider. We add 1 to
|
||||
// ensure it's always positive even when insets are 0.
|
||||
int top_area_height = RestoredFrameBorderInsets().top() + 1;
|
||||
const bool active = ShouldPaintAsActive();
|
||||
const gfx::Insets border = RestoredFrameBorderInsets();
|
||||
const bool showing_shadow = linux_frame_layout_->IsShowingShadow();
|
||||
gfx::RectF bounds_dip(GetLocalBounds());
|
||||
if (showing_shadow) {
|
||||
bounds_dip.Inset(gfx::InsetsF(border));
|
||||
}
|
||||
|
||||
linux_frame_layout_->PaintWindowFrame(
|
||||
canvas, GetLocalBounds(), gfx::Rect(0, 0, width(), top_area_height),
|
||||
ShouldPaintAsActive());
|
||||
// TODO: support roundedCorners.
|
||||
float radius_dip = 0;
|
||||
SkVector radii[4]{{radius_dip, radius_dip}, {radius_dip, radius_dip}, {}, {}};
|
||||
SkRRect clip;
|
||||
clip.setRectRadii(gfx::RectFToSkRect(bounds_dip), radii);
|
||||
|
||||
frame_background_->set_frame_color(GetFrameColor());
|
||||
frame_background_->set_use_custom_frame(true);
|
||||
frame_background_->set_is_active(active);
|
||||
frame_background_->set_top_area_height(GetTopAreaHeight());
|
||||
|
||||
const bool draw_shadow = showing_shadow && !linux_frame_layout_->tiled();
|
||||
auto shadow_values =
|
||||
draw_shadow ? GetFrameShadowValuesLinux(active) : gfx::ShadowValues();
|
||||
::PaintRestoredFrameBorderLinux(*canvas, *this, frame_background_.get(), clip,
|
||||
showing_shadow, active, border, shadow_values,
|
||||
linux_frame_layout_->tiled());
|
||||
|
||||
if (!window()->IsWindowControlsOverlayEnabled())
|
||||
return;
|
||||
|
||||
@@ -20,6 +20,10 @@
|
||||
|
||||
class CaptionButtonPlaceholderContainer;
|
||||
|
||||
namespace views {
|
||||
class FrameBackground;
|
||||
}
|
||||
|
||||
namespace electron {
|
||||
|
||||
class NativeWindowViews;
|
||||
@@ -166,6 +170,7 @@ class OpaqueFrameView : public FramelessView {
|
||||
bool is_leading_button) const;
|
||||
|
||||
std::unique_ptr<LinuxFrameLayout> linux_frame_layout_;
|
||||
std::unique_ptr<views::FrameBackground> frame_background_;
|
||||
|
||||
// Window controls.
|
||||
raw_ptr<views::Button> minimize_button_;
|
||||
|
||||
@@ -43,7 +43,7 @@ UsbChooserController::UsbChooserController(
|
||||
: WebContentsObserver(web_contents),
|
||||
options_(std::move(options)),
|
||||
callback_(std::move(callback)),
|
||||
origin_(render_frame_host->GetMainFrame()->GetLastCommittedOrigin()),
|
||||
origin_(render_frame_host->GetLastCommittedOrigin()),
|
||||
usb_delegate_(usb_delegate),
|
||||
render_frame_host_id_(render_frame_host->GetGlobalId()) {
|
||||
chooser_context_ = UsbChooserContextFactory::GetForBrowserContext(
|
||||
|
||||
@@ -219,7 +219,7 @@ void WebContentsPermissionHelper::RequestPermission(
|
||||
base::DictValue details) {
|
||||
auto* permission_manager = static_cast<ElectronPermissionManager*>(
|
||||
web_contents_->GetBrowserContext()->GetPermissionControllerDelegate());
|
||||
auto origin = web_contents_->GetLastCommittedURL();
|
||||
auto origin = requesting_frame->GetLastCommittedOrigin().GetURL();
|
||||
permission_manager->RequestPermissionWithDetails(
|
||||
content::PermissionDescriptorUtil::
|
||||
CreatePermissionDescriptorForPermissionType(permission),
|
||||
|
||||
@@ -343,9 +343,6 @@ void WebContentsPreferences::AppendCommandLineSwitches(
|
||||
command_line->AppendSwitchASCII(::switches::kDisableBlinkFeatures,
|
||||
*disable_blink_features_);
|
||||
|
||||
if (node_integration_in_worker_)
|
||||
command_line->AppendSwitch(switches::kNodeIntegrationInWorker);
|
||||
|
||||
// We are appending args to a webContents so let's save the current state
|
||||
// of our preferences object so that during the lifetime of the WebContents
|
||||
// we can fetch the options used to initially configure the WebContents
|
||||
|
||||
@@ -266,7 +266,11 @@ gfx::Image Clipboard::ReadImage(gin::Arguments* const args) {
|
||||
[](std::optional<gfx::Image>* image, base::RepeatingClosure cb,
|
||||
const std::vector<uint8_t>& result) {
|
||||
SkBitmap bitmap = gfx::PNGCodec::Decode(result);
|
||||
image->emplace(gfx::Image::CreateFrom1xBitmap(bitmap));
|
||||
if (bitmap.isNull()) {
|
||||
image->emplace();
|
||||
} else {
|
||||
image->emplace(gfx::Image::CreateFrom1xBitmap(bitmap));
|
||||
}
|
||||
std::move(cb).Run();
|
||||
},
|
||||
&image, std::move(callback)));
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
#ifndef ELECTRON_SHELL_COMMON_GIN_CONVERTERS_FILE_PATH_CONVERTER_H_
|
||||
#define ELECTRON_SHELL_COMMON_GIN_CONVERTERS_FILE_PATH_CONVERTER_H_
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "base/files/file_path.h"
|
||||
#include "gin/converter.h"
|
||||
#include "shell/common/gin_converters/std_converter.h"
|
||||
@@ -30,6 +32,11 @@ struct Converter<base::FilePath> {
|
||||
|
||||
base::FilePath::StringType path;
|
||||
if (Converter<base::FilePath::StringType>::FromV8(isolate, val, &path)) {
|
||||
bool has_control_chars = std::any_of(
|
||||
path.begin(), path.end(),
|
||||
[](base::FilePath::CharType c) { return c >= 0 && c < 0x20; });
|
||||
if (has_control_chars)
|
||||
return false;
|
||||
*out = base::FilePath(path);
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -607,6 +607,9 @@ bool Converter<scoped_refptr<network::ResourceRequestBody>>::FromV8(
|
||||
const std::string* file = dict.FindString("filePath");
|
||||
if (!file)
|
||||
return false;
|
||||
if (std::any_of(file->begin(), file->end(),
|
||||
[](char c) { return c >= 0 && c < 0x20; }))
|
||||
return false;
|
||||
double modification_time =
|
||||
dict.FindDouble("modificationTime").value_or(0.0);
|
||||
int offset = dict.FindInt("offset").value_or(0);
|
||||
|
||||
@@ -153,9 +153,12 @@ v8::Local<v8::Value> Converter<electron::OffscreenSharedTextureValue>::ToV8(
|
||||
root.Set("textureInfo", ConvertToV8(isolate, dict));
|
||||
auto root_local = ConvertToV8(isolate, root);
|
||||
|
||||
// Create a persistent reference of the object, so that we can check the
|
||||
// monitor again when GC collects this object.
|
||||
auto* tex_persistent = monitor->CreatePersistent(isolate, root_local);
|
||||
// Create a weak persistent that tracks the release function rather than the
|
||||
// texture object. The release function holds a raw pointer to |monitor| via
|
||||
// its v8::External data, so |monitor| must outlive it. Since the texture
|
||||
// keeps |release| alive via its property, this also covers the case where
|
||||
// the texture itself is leaked without calling release().
|
||||
auto* tex_persistent = monitor->CreatePersistent(isolate, releaser);
|
||||
tex_persistent->SetWeak(
|
||||
monitor,
|
||||
[](const v8::WeakCallbackInfo<OffscreenReleaseHolderMonitor>& data) {
|
||||
|
||||
@@ -277,10 +277,6 @@ inline constexpr base::cstring_view kAppPath = "app-path";
|
||||
// The command line switch versions of the options.
|
||||
inline constexpr base::cstring_view kScrollBounce = "scroll-bounce";
|
||||
|
||||
// Command switch passed to renderer process to control nodeIntegration.
|
||||
inline constexpr base::cstring_view kNodeIntegrationInWorker =
|
||||
"node-integration-in-worker";
|
||||
|
||||
// Widevine options
|
||||
// Path to Widevine CDM binaries.
|
||||
inline constexpr base::cstring_view kWidevineCdmPath = "widevine-cdm-path";
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user