mirror of
https://github.com/electron/electron.git
synced 2026-02-19 03:14:51 -05:00
Compare commits
1 Commits
refactor/r
...
ensure_boo
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0be6d43f4c |
6
.github/actions/build-electron/action.yml
vendored
6
.github/actions/build-electron/action.yml
vendored
@@ -38,9 +38,6 @@ runs:
|
||||
run: |
|
||||
GN_APPENDED_ARGS="$GN_EXTRA_ARGS target_cpu=\"x64\" v8_snapshot_toolchain=\"//build/toolchain/mac:clang_x64\""
|
||||
echo "GN_EXTRA_ARGS=$GN_APPENDED_ARGS" >> $GITHUB_ENV
|
||||
- name: Add Clang problem matcher
|
||||
shell: bash
|
||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/clang.json"
|
||||
- name: Build Electron ${{ inputs.step-suffix }}
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -202,9 +199,6 @@ runs:
|
||||
e build --target electron:libcxx_headers_zip -j $NUMBER_OF_NINJA_PROCESSES
|
||||
e build --target electron:libcxxabi_headers_zip -j $NUMBER_OF_NINJA_PROCESSES
|
||||
e build --target electron:libcxx_objects_zip -j $NUMBER_OF_NINJA_PROCESSES
|
||||
- name: Remove Clang problem matcher
|
||||
shell: bash
|
||||
run: echo "::remove-matcher owner=clang::"
|
||||
- name: Generate TypeScript Definitions ${{ inputs.step-suffix }}
|
||||
if: ${{ inputs.is-release == 'true' }}
|
||||
shell: bash
|
||||
|
||||
18
.github/actions/checkout/action.yml
vendored
18
.github/actions/checkout/action.yml
vendored
@@ -20,8 +20,8 @@ runs:
|
||||
echo "GIT_CACHE_PATH=$(pwd)/git-cache" >> $GITHUB_ENV
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Set Chromium Git Helper
|
||||
uses: ./src/electron/.github/actions/set-chromium-git-helper
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Install Build Tools
|
||||
uses: ./src/electron/.github/actions/install-build-tools
|
||||
- name: Generate DEPS Hash
|
||||
@@ -80,9 +80,6 @@ runs:
|
||||
else
|
||||
echo "The cross mount cache has $freespace_human free space - continuing"
|
||||
fi
|
||||
- name: Add patch conflict problem matcher
|
||||
shell: bash
|
||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/patch-conflict.json"
|
||||
- name: Gclient Sync
|
||||
if: steps.check-cache.outputs.cache_exists == 'false'
|
||||
shell: bash
|
||||
@@ -131,16 +128,7 @@ runs:
|
||||
echo "No changes to patches detected"
|
||||
fi
|
||||
fi
|
||||
- name: Remove patch conflict problem matcher
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::remove-matcher owner=merge-conflict::"
|
||||
echo "::remove-matcher owner=patch-conflict::"
|
||||
- name: Upload patches stats
|
||||
if: ${{ inputs.target-platform == 'linux' && github.ref == 'refs/heads/main' }}
|
||||
shell: bash
|
||||
run: |
|
||||
npx node src/electron/script/patches-stats.mjs --upload-stats || true
|
||||
|
||||
# delete all .git directories under src/ except for
|
||||
# third_party/angle/ and third_party/dawn/ because of build time generation of files
|
||||
# gen/angle/commit.h depends on third_party/angle/.git/HEAD
|
||||
|
||||
12
.github/actions/install-build-tools/action.yml
vendored
12
.github/actions/install-build-tools/action.yml
vendored
@@ -15,16 +15,12 @@ runs:
|
||||
fi
|
||||
export BUILD_TOOLS_SHA=6e8526315ea3b4828882497e532b8340e64e053c
|
||||
npm i -g @electron/build-tools
|
||||
# Update depot_tools to ensure python
|
||||
e d update_depot_tools
|
||||
e d ensure_bootstrap
|
||||
e auto-update disable
|
||||
# Disable further updates of depot_tools
|
||||
e d auto-update disable
|
||||
if [ "$(expr substr $(uname -s) 1 10)" == "MSYS_NT-10" ]; then
|
||||
e d cipd.bat --version
|
||||
cp "C:\Python311\python.exe" "C:\Python311\python3.exe"
|
||||
echo "C:\Users\ContainerAdministrator\.electron_build_tools\third_party\depot_tools" >> $GITHUB_PATH
|
||||
else
|
||||
echo "$HOME/.electron_build_tools/third_party/depot_tools" >> $GITHUB_PATH
|
||||
echo "$HOME/.electron_build_tools/third_party/depot_tools/python-bin" >> $GITHUB_PATH
|
||||
fi
|
||||
fi
|
||||
echo "$HOME/.electron_build_tools/third_party/depot_tools" >> $GITHUB_PATH
|
||||
echo "$HOME/.electron_build_tools/third_party/depot_tools/python-bin" >> $GITHUB_PATH
|
||||
|
||||
58
.github/actions/set-chromium-cookie/action.yml
vendored
Normal file
58
.github/actions/set-chromium-cookie/action.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: 'Set Chromium Git Cookie'
|
||||
description: 'Sets an authenticated cookie from Chromium to allow for a higher request limit'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Set the git cookie from chromium.googlesource.com (Unix)
|
||||
if: ${{ runner.os != 'Windows' }}
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -z "${{ env.CHROMIUM_GIT_COOKIE }}" ]]; then
|
||||
echo "CHROMIUM_GIT_COOKIE is not set - cannot authenticate."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
eval 'set +o history' 2>/dev/null || setopt HIST_IGNORE_SPACE 2>/dev/null
|
||||
touch ~/.gitcookies
|
||||
chmod 0600 ~/.gitcookies
|
||||
|
||||
git config --global http.cookiefile ~/.gitcookies
|
||||
|
||||
tr , \\t <<\__END__ >>~/.gitcookies
|
||||
${{ env.CHROMIUM_GIT_COOKIE }}
|
||||
__END__
|
||||
eval 'set -o history' 2>/dev/null || unsetopt HIST_IGNORE_SPACE 2>/dev/null
|
||||
|
||||
RESPONSE=$(curl -s -b ~/.gitcookies https://chromium-review.googlesource.com/a/accounts/self)
|
||||
if [[ $RESPONSE == ")]}'"* ]]; then
|
||||
# Extract account email for verification
|
||||
EMAIL=$(echo "$RESPONSE" | tail -c +5 | jq -r '.email // "No email found"')
|
||||
echo "Cookie authentication successful - authenticated as: $EMAIL"
|
||||
else
|
||||
echo "Cookie authentication failed - ensure CHROMIUM_GIT_COOKIE is set correctly"
|
||||
echo $RESPONSE
|
||||
fi
|
||||
- name: Set the git cookie from chromium.googlesource.com (Windows)
|
||||
if: ${{ runner.os == 'Windows' }}
|
||||
shell: cmd
|
||||
run: |
|
||||
if "%CHROMIUM_GIT_COOKIE_WINDOWS_STRING%"=="" (
|
||||
echo CHROMIUM_GIT_COOKIE_WINDOWS_STRING is not set - cannot authenticate.
|
||||
exit /b 0
|
||||
)
|
||||
|
||||
git config --global http.cookiefile "%USERPROFILE%\.gitcookies"
|
||||
powershell -noprofile -nologo -command Write-Output "${{ env.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }}" >>"%USERPROFILE%\.gitcookies"
|
||||
|
||||
curl -s -b "%USERPROFILE%\.gitcookies" https://chromium-review.googlesource.com/a/accounts/self > response.txt
|
||||
|
||||
findstr /B /C:")]}'" response.txt > nul
|
||||
if %ERRORLEVEL% EQU 0 (
|
||||
echo Cookie authentication successful
|
||||
powershell -NoProfile -Command "& {$content = Get-Content -Raw response.txt; $content = $content.Substring(4); try { $json = ConvertFrom-Json $content; if($json.email) { Write-Host 'Authenticated as:' $json.email } else { Write-Host 'No email found in response' } } catch { Write-Host 'Error parsing JSON:' $_ }}"
|
||||
) else (
|
||||
echo Cookie authentication failed - ensure CHROMIUM_GIT_COOKIE_WINDOWS_STRING is set correctly
|
||||
type response.txt
|
||||
)
|
||||
|
||||
del response.txt
|
||||
@@ -1,41 +0,0 @@
|
||||
name: 'Set Chromium Git Helper'
|
||||
description: 'Sets Chromium Git Helper to allow for a higher request limit'
|
||||
runs:
|
||||
using: "composite"
|
||||
steps:
|
||||
- name: Save the chromium git credentials to a file
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ -z "${{ env.CHROMIUM_GIT_AUTH }}" ]]; then
|
||||
echo "CHROMIUM_GIT_AUTH is not set - cannot authenticate."
|
||||
exit 0
|
||||
fi
|
||||
if [[ "${{ runner.os }}" != "Windows" ]]; then
|
||||
cd $HOME
|
||||
fi
|
||||
echo "${{ env.CHROMIUM_GIT_AUTH }}" > .chromium_git_auth
|
||||
|
||||
- name: Set the chromium git helper to use auth from a file
|
||||
shell: bash
|
||||
run: |
|
||||
if [[ "${{ runner.os }}" == "Windows" ]]; then
|
||||
if [[ ! -f "/c/actions-runner/_work/electron/electron/.chromium_git_auth" ]]; then
|
||||
echo "File /c/actions-runner/_work/electron/electron/.chromium_git_auth does not exist - cannot authenticate."
|
||||
exit 0
|
||||
fi
|
||||
else
|
||||
if [[ ! -f "$HOME/.chromium_git_auth" ]]; then
|
||||
echo "File $HOME/.chromium_git_auth does not exist - cannot authenticate."
|
||||
exit 0
|
||||
fi
|
||||
fi
|
||||
if [[ -z "${{ env.CHROMIUM_GIT_USER }}" ]]; then
|
||||
echo "CHROMIUM_GIT_USER is not set - cannot authenticate."
|
||||
exit 0
|
||||
fi
|
||||
git config --global credential.https://chromium.googlesource.com.username "${{ env.CHROMIUM_GIT_USER }}"
|
||||
if [[ "${{ runner.os }}" == "Windows" ]]; then
|
||||
git config --global credential.https://chromium.googlesource.com.helper '!f() { test "$1" = get && echo "password=$(cat /c/actions-runner/_work/electron/electron/.chromium_git_auth)"; }; f'
|
||||
else
|
||||
git config --global credential.https://chromium.googlesource.com.helper '!f() { test "$1" = get && echo "password=$(cat $HOME/.chromium_git_auth)"; }; f'
|
||||
fi
|
||||
18
.github/problem-matchers/clang.json
vendored
18
.github/problem-matchers/clang.json
vendored
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "clang",
|
||||
"fromPath": "src/out/Default/args.gn",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^(.+)[(:](\\d+)[:,](\\d+)\\)?:\\s+(warning|error):\\s+(.*)$",
|
||||
"file": 1,
|
||||
"line": 2,
|
||||
"column": 3,
|
||||
"severity": 4,
|
||||
"message": 5
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
22
.github/problem-matchers/eslint-stylish.json
vendored
22
.github/problem-matchers/eslint-stylish.json
vendored
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "eslint-stylish",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^\\s*([^\\s].*)$",
|
||||
"file": 1
|
||||
},
|
||||
{
|
||||
"regexp": "^\\s+(\\d+):(\\d+)\\s+(error|warning|info)\\s+(.*)\\s\\s+(.*)$",
|
||||
"line": 1,
|
||||
"column": 2,
|
||||
"severity": 3,
|
||||
"message": 4,
|
||||
"code": 5,
|
||||
"loop": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
24
.github/problem-matchers/patch-conflict.json
vendored
24
.github/problem-matchers/patch-conflict.json
vendored
@@ -1,24 +0,0 @@
|
||||
{
|
||||
"problemMatcher": [
|
||||
{
|
||||
"owner": "merge-conflict",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^CONFLICT\\s\\(\\S+\\): (Merge conflict in \\S+)$",
|
||||
"message": 1
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"owner": "patch-conflict",
|
||||
"pattern": [
|
||||
{
|
||||
"regexp": "^error: (patch failed: (\\S+):(\\d+))$",
|
||||
"message": 1,
|
||||
"file": 2,
|
||||
"line": 3
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
137
.github/workflows/audit-branch-ci.yml
vendored
137
.github/workflows/audit-branch-ci.yml
vendored
@@ -1,137 +0,0 @@
|
||||
name: Audit CI on Branches
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
# Run every 2 hours
|
||||
- cron: '0 */2 * * *'
|
||||
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
audit_branch_ci:
|
||||
name: Audit CI on Branches
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- run: npm install @actions/cache @electron/fiddle-core
|
||||
- uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
script: |
|
||||
const cache = require('@actions/cache');
|
||||
const { ElectronVersions } = require('@electron/fiddle-core');
|
||||
|
||||
const runsWithErrors = [];
|
||||
|
||||
// Only want the most recent workflow run that wasn't skipped or cancelled
|
||||
const isValidWorkflowRun = (run) => !['skipped', 'cancelled'].includes(run.conclusion);
|
||||
|
||||
const versions = await ElectronVersions.create(undefined, { ignoreCache: true });
|
||||
const branches = versions.supportedMajors.map((branch) => `${branch}-x-y`);
|
||||
|
||||
for (const branch of ["main", ...branches]) {
|
||||
const latestCheckRuns = new Map();
|
||||
const allCheckRuns = await github.paginate(github.rest.checks.listForRef, {
|
||||
owner: "electron",
|
||||
repo: "electron",
|
||||
ref: branch,
|
||||
status: 'completed',
|
||||
});
|
||||
|
||||
// Sort the check runs by completed_at so that multiple check runs on the
|
||||
// same ref (like a scheduled workflow) only looks at the most recent one
|
||||
for (const checkRun of allCheckRuns.filter(
|
||||
(run) => !['skipped', 'cancelled'].includes(run.conclusion),
|
||||
).sort((a, b) => new Date(b.completed_at) - new Date(a.completed_at))) {
|
||||
if (!latestCheckRuns.has(checkRun.name)) {
|
||||
latestCheckRuns.set(checkRun.name, checkRun);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for runs which had error annotations
|
||||
for (const checkRun of Array.from(latestCheckRuns.values())) {
|
||||
const annotations = (await github.rest.checks.listAnnotations({
|
||||
owner: "electron",
|
||||
repo: "electron",
|
||||
check_run_id: checkRun.id,
|
||||
})).data ?? [];
|
||||
console.log(checkRun);
|
||||
console.log(annotations);
|
||||
|
||||
if (
|
||||
annotations.find(
|
||||
({ annotation_level, message }) =>
|
||||
annotation_level === 'failure' && !message.startsWith("Process completed with exit code")
|
||||
)
|
||||
) {
|
||||
checkRun.hasErrorAnnotations = true;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if this is a known failure from a previous audit run
|
||||
const cacheKey = `check-run-error-annotations-${checkRun.id}`;
|
||||
const cacheHit =
|
||||
(await cache.restoreCache(['/dev/null'], cacheKey, undefined, {
|
||||
lookupOnly: true,
|
||||
})) !== undefined;
|
||||
|
||||
if (cacheHit) {
|
||||
checkRun.isStale = true;
|
||||
}
|
||||
|
||||
checkRun.branch = branch;
|
||||
runsWithErrors.push(checkRun);
|
||||
|
||||
// Create a cache entry (only the name matters) to keep track of
|
||||
// failures we've seen from previous runs to mark them as stale
|
||||
if (!cacheHit) {
|
||||
await cache.saveCache(['/dev/null'], cacheKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (runsWithErrors.length > 0) {
|
||||
core.summary.addHeading('⚠️ Runs with Errors');
|
||||
core.summary.addTable([
|
||||
[
|
||||
{ data: 'Branch', header: true },
|
||||
{ data: 'Workflow Run', header: true },
|
||||
{ data: 'Status', header: true },
|
||||
],
|
||||
...runsWithErrors
|
||||
.sort(
|
||||
(a, b) =>
|
||||
a.branch.localeCompare(b.branch) ||
|
||||
a.name.localeCompare(b.name),
|
||||
)
|
||||
.map((run) => [
|
||||
run.branch,
|
||||
`<a href="${run.html_url}">${run.name}</a>`,
|
||||
run.isStale
|
||||
? '📅 Stale'
|
||||
: run.hasErrorAnnotations
|
||||
? '⚠️ Errors'
|
||||
: '✅ Succeeded',
|
||||
]),
|
||||
]);
|
||||
|
||||
// Set this as failed so it's easy to scan runs to find failures
|
||||
if (runsWithErrors.find((run) => !run.isStale)) {
|
||||
process.exitCode = 1;
|
||||
}
|
||||
} else {
|
||||
core.summary.addRaw('🎉 No runs with errors');
|
||||
}
|
||||
|
||||
await core.summary.write();
|
||||
- name: Send Slack message if errors
|
||||
if: failure()
|
||||
uses: slackapi/slack-github-action@b0fa283ad8fea605de13dc3f449259339835fc52 # v2.1.0
|
||||
with:
|
||||
payload: |
|
||||
link: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
webhook: ${{ secrets.CI_ERRORS_SLACK_WEBHOOK_URL }}
|
||||
webhook-type: webhook-trigger
|
||||
13
.github/workflows/build.yml
vendored
13
.github/workflows/build.yml
vendored
@@ -100,8 +100,7 @@ jobs:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /var/run/sas:/var/run/sas
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
outputs:
|
||||
build-image-sha: ${{ needs.setup.outputs.build-image-sha }}
|
||||
@@ -129,9 +128,7 @@ jobs:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /var/run/sas:/var/run/sas
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
DD_API_KEY: ${{ secrets.DD_API_KEY }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
PATCH_UP_APP_CREDS: ${{ secrets.PATCH_UP_APP_CREDS }}
|
||||
outputs:
|
||||
@@ -145,8 +142,6 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Checkout & Sync & Save
|
||||
uses: ./src/electron/.github/actions/checkout
|
||||
with:
|
||||
target-platform: linux
|
||||
|
||||
checkout-windows:
|
||||
needs: setup
|
||||
@@ -159,8 +154,8 @@ jobs:
|
||||
- /mnt/win-cache:/mnt/win-cache
|
||||
- /var/run/sas:/var/run/sas
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
CHROMIUM_GIT_COOKIE_WINDOWS_STRING: ${{ secrets.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }}
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_win=True'
|
||||
TARGET_OS: 'win'
|
||||
ELECTRON_DEPOT_TOOLS_WIN_TOOLCHAIN: '1'
|
||||
|
||||
3
.github/workflows/linux-publish.yml
vendored
3
.github/workflows/linux-publish.yml
vendored
@@ -27,8 +27,7 @@ jobs:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /var/run/sas:/var/run/sas
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||
steps:
|
||||
- name: Checkout Electron
|
||||
|
||||
3
.github/workflows/macos-publish.yml
vendored
3
.github/workflows/macos-publish.yml
vendored
@@ -28,8 +28,7 @@ jobs:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /var/run/sas:/var/run/sas
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_mac=True --custom-var=host_os=mac'
|
||||
steps:
|
||||
- name: Checkout Electron
|
||||
|
||||
10
.github/workflows/pipeline-electron-lint.yml
vendored
10
.github/workflows/pipeline-electron-lint.yml
vendored
@@ -13,8 +13,7 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref_protected != true }}
|
||||
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
@@ -31,8 +30,8 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Set Chromium Git Helper
|
||||
uses: ./src/electron/.github/actions/set-chromium-git-helper
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Setup third_party Depot Tools
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -62,9 +61,6 @@ jobs:
|
||||
curl -sL "https://chromium.googlesource.com/chromium/src/+/${chromium_revision}/buildtools/DEPS?format=TEXT" | base64 -d > src/buildtools/DEPS
|
||||
|
||||
gclient sync --spec="solutions=[{'name':'src/buildtools','url':None,'deps_file':'DEPS','custom_vars':{'process_deps':True},'managed':False}]"
|
||||
- name: Add ESLint problem matcher
|
||||
shell: bash
|
||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/eslint-stylish.json"
|
||||
- name: Run Lint
|
||||
shell: bash
|
||||
run: |
|
||||
|
||||
@@ -65,8 +65,8 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref_protected != true }}
|
||||
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
CHROMIUM_GIT_COOKIE_WINDOWS_STRING: ${{ secrets.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }}
|
||||
ELECTRON_ARTIFACTS_BLOB_STORAGE: ${{ secrets.ELECTRON_ARTIFACTS_BLOB_STORAGE }}
|
||||
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
|
||||
SUDOWOODO_EXCHANGE_URL: ${{ secrets.SUDOWOODO_EXCHANGE_URL }}
|
||||
@@ -127,8 +127,8 @@ jobs:
|
||||
GN_EXTRA_ARGS='is_asan=true'
|
||||
fi
|
||||
echo "GN_EXTRA_ARGS=$GN_EXTRA_ARGS" >> $GITHUB_ENV
|
||||
- name: Set Chromium Git Helper
|
||||
uses: ./src/electron/.github/actions/set-chromium-git-helper
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Install Build Tools
|
||||
uses: ./src/electron/.github/actions/install-build-tools
|
||||
- name: Generate DEPS Hash
|
||||
|
||||
@@ -66,8 +66,8 @@ jobs:
|
||||
- name: Check disk space after freeing up space
|
||||
if: ${{ inputs.target-platform == 'macos' }}
|
||||
run: df -h
|
||||
- name: Set Chromium Git Helper
|
||||
uses: ./src/electron/.github/actions/set-chromium-git-helper
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Install Build Tools
|
||||
uses: ./src/electron/.github/actions/install-build-tools
|
||||
- name: Enable windows toolchain
|
||||
|
||||
@@ -36,8 +36,8 @@ permissions:
|
||||
pull-requests: read
|
||||
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
CHROMIUM_GIT_COOKIE_WINDOWS_STRING: ${{ secrets.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }}
|
||||
ELECTRON_OUT_DIR: Default
|
||||
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
|
||||
|
||||
@@ -126,8 +126,8 @@ jobs:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Set Chromium Git Helper
|
||||
uses: ./src/electron/.github/actions/set-chromium-git-helper
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Get Depot Tools
|
||||
timeout-minutes: 5
|
||||
run: |
|
||||
|
||||
@@ -31,8 +31,7 @@ concurrency:
|
||||
cancel-in-progress: ${{ github.ref_protected != true }}
|
||||
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||
ELECTRON_OUT_DIR: Default
|
||||
ELECTRON_RBE_JWT: ${{ secrets.ELECTRON_RBE_JWT }}
|
||||
|
||||
@@ -52,8 +51,8 @@ jobs:
|
||||
path: src/electron
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set Chromium Git Helper
|
||||
uses: ./src/electron/.github/actions/set-chromium-git-helper
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Install Build Tools
|
||||
uses: ./src/electron/.github/actions/install-build-tools
|
||||
- name: Init Build Tools
|
||||
@@ -106,8 +105,8 @@ jobs:
|
||||
path: src/electron
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Set Chromium Git Helper
|
||||
uses: ./src/electron/.github/actions/set-chromium-git-helper
|
||||
- name: Set Chromium Git Cookie
|
||||
uses: ./src/electron/.github/actions/set-chromium-cookie
|
||||
- name: Install Build Tools
|
||||
uses: ./src/electron/.github/actions/install-build-tools
|
||||
- name: Init Build Tools
|
||||
|
||||
2
.github/workflows/scorecards.yml
vendored
2
.github/workflows/scorecards.yml
vendored
@@ -50,6 +50,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@ff0a06e83cb2de871e5a09832bc6a81e7276941f # v3.28.18
|
||||
uses: github/codeql-action/upload-sarif@45775bd8235c68ba998cffa5171334d58593da47 # v3.28.15
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
3
.github/workflows/windows-publish.yml
vendored
3
.github/workflows/windows-publish.yml
vendored
@@ -28,8 +28,7 @@ jobs:
|
||||
- /mnt/win-cache:/mnt/win-cache
|
||||
- /var/run/sas:/var/run/sas
|
||||
env:
|
||||
CHROMIUM_GIT_AUTH: ${{ secrets.CHROMIUM_GIT_AUTH }}
|
||||
CHROMIUM_GIT_USER: ${{ secrets.CHROMIUM_GIT_USER }}
|
||||
CHROMIUM_GIT_COOKIE_WINDOWS_STRING: ${{ secrets.CHROMIUM_GIT_COOKIE_WINDOWS_STRING }}
|
||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_win=True'
|
||||
TARGET_OS: 'win'
|
||||
ELECTRON_DEPOT_TOOLS_WIN_TOOLCHAIN: '1'
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
"plugins": [
|
||||
"markdown",
|
||||
"unicorn"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["*.md", "**/*.md"],
|
||||
"processor": "markdown/markdown"
|
||||
}
|
||||
],
|
||||
"rules": {
|
||||
"@typescript-eslint/no-unused-vars": "off",
|
||||
"import/order": ["error", {
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
},
|
||||
"newlines-between": "always",
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "{electron,electron/**}",
|
||||
"group": "builtin",
|
||||
"position": "before"
|
||||
}
|
||||
],
|
||||
"pathGroupsExcludedImportTypes": []
|
||||
}],
|
||||
"n/no-callback-literal": "off",
|
||||
"no-undef": "off",
|
||||
"no-unused-expressions": "off",
|
||||
"no-unused-vars": "off",
|
||||
"unicorn/prefer-node-protocol": "error"
|
||||
}
|
||||
}
|
||||
@@ -113,7 +113,6 @@ These individual tutorials expand on topics discussed in the guide above.
|
||||
* [dialog](api/dialog.md)
|
||||
* [globalShortcut](api/global-shortcut.md)
|
||||
* [inAppPurchase](api/in-app-purchase.md)
|
||||
* [ImageView](api/image-view.md)
|
||||
* [ipcMain](api/ipc-main.md)
|
||||
* [Menu](api/menu.md)
|
||||
* [MenuItem](api/menu-item.md)
|
||||
|
||||
@@ -9,7 +9,6 @@ closed:
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
app.quit()
|
||||
})
|
||||
@@ -1024,7 +1023,6 @@ starts:
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
let myWindow = null
|
||||
|
||||
const additionalData = { myKey: 'myValue' }
|
||||
@@ -1228,8 +1226,6 @@ For `infoType` equal to `complete`:
|
||||
For `infoType` equal to `basic`:
|
||||
Promise is fulfilled with `Object` containing fewer attributes than when requested with `complete`. Here's an example of basic response:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
{
|
||||
auxAttributes:
|
||||
@@ -1343,7 +1339,6 @@ latest version.
|
||||
|
||||
``` js
|
||||
const { app } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
const appFolder = path.dirname(process.execPath)
|
||||
@@ -1418,7 +1413,6 @@ Returns `Function` - This function **must** be called once you have finished acc
|
||||
|
||||
```js
|
||||
const { app, dialog } = require('electron')
|
||||
|
||||
const fs = require('node:fs')
|
||||
|
||||
let filepath
|
||||
|
||||
@@ -294,7 +294,6 @@ e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`.
|
||||
|
||||
```js
|
||||
const { BaseWindow } = require('electron')
|
||||
|
||||
const win = new BaseWindow()
|
||||
win.on('app-command', (e, cmd) => {
|
||||
// Navigate the window back when the user hits their mouse back button
|
||||
@@ -503,7 +502,6 @@ A `boolean` property that determines whether the window is excluded from the app
|
||||
|
||||
```js @ts-expect-error=[12]
|
||||
const { Menu, BaseWindow } = require('electron')
|
||||
|
||||
const win = new BaseWindow({ height: 600, width: 600 })
|
||||
|
||||
const template = [
|
||||
@@ -729,7 +727,6 @@ Resizes and moves the window to the supplied bounds. Any properties that are not
|
||||
|
||||
```js
|
||||
const { BaseWindow } = require('electron')
|
||||
|
||||
const win = new BaseWindow()
|
||||
|
||||
// set all bounds properties
|
||||
@@ -990,7 +987,6 @@ a HTML-rendered toolbar. For example:
|
||||
|
||||
```js
|
||||
const { BaseWindow } = require('electron')
|
||||
|
||||
const win = new BaseWindow()
|
||||
|
||||
const toolbarRect = document.getElementById('toolbar').getBoundingClientRect()
|
||||
@@ -1350,10 +1346,6 @@ On Windows it calls SetWindowDisplayAffinity with `WDA_EXCLUDEFROMCAPTURE`.
|
||||
For Windows 10 version 2004 and up the window will be removed from capture entirely,
|
||||
older Windows versions behave as if `WDA_MONITOR` is applied capturing a black window.
|
||||
|
||||
#### `win.isContentProtected()` _macOS_ _Windows_
|
||||
|
||||
Returns `boolean` - whether or not content protection is currently enabled.
|
||||
|
||||
#### `win.setFocusable(focusable)` _macOS_ _Windows_
|
||||
|
||||
* `focusable` boolean
|
||||
|
||||
@@ -40,7 +40,6 @@ the window after this event will have no visual flash:
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({ show: false })
|
||||
win.once('ready-to-show', () => {
|
||||
win.show()
|
||||
@@ -374,7 +373,6 @@ e.g. `APPCOMMAND_BROWSER_BACKWARD` is emitted as `browser-backward`.
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
win.on('app-command', (e, cmd) => {
|
||||
// Navigate the window back when the user hits their mouse back button
|
||||
@@ -822,7 +820,6 @@ Resizes and moves the window to the supplied bounds. Any properties that are not
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
// set all bounds properties
|
||||
@@ -1083,7 +1080,6 @@ a HTML-rendered toolbar. For example:
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
const toolbarRect = document.getElementById('toolbar').getBoundingClientRect()
|
||||
@@ -1236,10 +1232,9 @@ method:
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
const url = require('node:url').format({
|
||||
const url = require('url').format({
|
||||
protocol: 'file',
|
||||
slashes: true,
|
||||
pathname: require('node:path').join(__dirname, 'index.html')
|
||||
@@ -1253,7 +1248,6 @@ the following:
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.loadURL('http://localhost:8000/post', {
|
||||
@@ -1534,10 +1528,6 @@ On Windows it calls SetWindowDisplayAffinity with `WDA_EXCLUDEFROMCAPTURE`.
|
||||
For Windows 10 version 2004 and up the window will be removed from capture entirely,
|
||||
older Windows versions behave as if `WDA_MONITOR` is applied capturing a black window.
|
||||
|
||||
#### `win.isContentProtected()` _macOS_ _Windows_
|
||||
|
||||
Returns `boolean` - whether or not content protection is currently enabled.
|
||||
|
||||
#### `win.setFocusable(focusable)` _macOS_ _Windows_
|
||||
|
||||
* `focusable` boolean
|
||||
|
||||
@@ -60,10 +60,6 @@ following properties:
|
||||
`strict-origin-when-cross-origin`.
|
||||
* `cache` string (optional) - can be `default`, `no-store`, `reload`,
|
||||
`no-cache`, `force-cache` or `only-if-cached`.
|
||||
* `priority` string (optional) - can be `throttled`, `idle`, `lowest`,
|
||||
`low`, `medium`, or `highest`. Defaults to `idle`.
|
||||
* `priorityIncremental` boolean (optional) - the incremental loading flag as part
|
||||
of HTTP extensible priorities (RFC 9218). Default is `true`.
|
||||
|
||||
`options` properties such as `protocol`, `host`, `hostname`, `port` and `path`
|
||||
strictly follow the Node.js model as described in the
|
||||
|
||||
@@ -8,7 +8,6 @@ is emitted:
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
app.commandLine.appendSwitch('remote-debugging-port', '8315')
|
||||
app.commandLine.appendSwitch('host-rules', 'MAP * 127.0.0.1')
|
||||
|
||||
@@ -189,7 +188,6 @@ For example:
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
app.commandLine.appendSwitch('proxy-bypass-list', '<local>;*.google.com;*foo.com;1.2.3.4:5678')
|
||||
```
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ The following example shows how to check if the `--disable-gpu` flag is set.
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
app.commandLine.hasSwitch('disable-gpu')
|
||||
```
|
||||
|
||||
|
||||
@@ -189,9 +189,7 @@ Be very cautious about which globals and APIs you expose to untrusted remote con
|
||||
|
||||
```js
|
||||
const { contextBridge } = require('electron')
|
||||
|
||||
const crypto = require('node:crypto')
|
||||
|
||||
contextBridge.exposeInMainWorld('nodeCrypto', {
|
||||
sha256sum (data) {
|
||||
const hash = crypto.createHash('sha256')
|
||||
|
||||
@@ -10,7 +10,6 @@ runtime that allows interacting with pages and instrumenting them.
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
try {
|
||||
|
||||
@@ -8,7 +8,6 @@ An example of showing a dialog to select multiple files:
|
||||
|
||||
```js
|
||||
const { dialog } = require('electron')
|
||||
|
||||
console.log(dialog.showOpenDialog({ properties: ['openFile', 'multiSelections'] }))
|
||||
```
|
||||
|
||||
@@ -53,8 +52,6 @@ The `window` argument allows the dialog to attach itself to a parent window, mak
|
||||
The `filters` specifies an array of file types that can be displayed or
|
||||
selected when you want to limit the user to a specific type. For example:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
{
|
||||
filters: [
|
||||
@@ -129,8 +126,6 @@ The `window` argument allows the dialog to attach itself to a parent window, mak
|
||||
The `filters` specifies an array of file types that can be displayed or
|
||||
selected when you want to limit the user to a specific type. For example:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
{
|
||||
filters: [
|
||||
|
||||
@@ -9,7 +9,6 @@ The following example shows how to bounce your icon on the dock.
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
app.dock?.bounce()
|
||||
```
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@ control the download item.
|
||||
```js
|
||||
// In the main process.
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
win.webContents.session.on('will-download', (event, item, webContents) => {
|
||||
// Set the save path, making Electron not to prompt a save dialog.
|
||||
|
||||
@@ -77,7 +77,6 @@ extension to be loaded.
|
||||
|
||||
```js
|
||||
const { app, session } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
# ImageView
|
||||
|
||||
> A View that displays an image.
|
||||
|
||||
Process: [Main](../glossary.md#main-process)
|
||||
|
||||
This module cannot be used until the `ready` event of the `app`
|
||||
module is emitted.
|
||||
|
||||
Useful for showing splash screens that will be swapped for `WebContentsView`s
|
||||
when the content finishes loading.
|
||||
|
||||
Note that `ImageView` is experimental and may be changed or removed in the future.
|
||||
|
||||
```js
|
||||
const { BaseWindow, ImageView, nativeImage, WebContentsView } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
const win = new BaseWindow({ width: 800, height: 600 })
|
||||
|
||||
// Create a "splash screen" image to display while the WebContentsView loads
|
||||
const splashView = new ImageView()
|
||||
const splashImage = nativeImage.createFromPath(path.join(__dirname, 'loading.png'))
|
||||
splashView.setImage(splashImage)
|
||||
win.setContentView(splashView)
|
||||
|
||||
const webContentsView = new WebContentsView()
|
||||
webContentsView.webContents.once('did-finish-load', () => {
|
||||
// Now that the WebContentsView has loaded, swap out the "splash screen" ImageView
|
||||
win.setContentView(webContentsView)
|
||||
})
|
||||
webContentsView.webContents.loadURL('https://electronjs.org')
|
||||
```
|
||||
|
||||
## Class: ImageView extends `View`
|
||||
|
||||
> A View that displays an image.
|
||||
|
||||
Process: [Main](../glossary.md#main-process)
|
||||
|
||||
`ImageView` inherits from [`View`](view.md).
|
||||
|
||||
`ImageView` is an [EventEmitter][event-emitter].
|
||||
|
||||
### `new ImageView()` _Experimental_
|
||||
|
||||
Creates an ImageView.
|
||||
|
||||
### Instance Methods
|
||||
|
||||
The following methods are available on instances of the `ImageView` class, in
|
||||
addition to those inherited from [View](view.md):
|
||||
|
||||
#### `image.setImage(image)` _Experimental_
|
||||
|
||||
* `image` NativeImage
|
||||
|
||||
Sets the image for this `ImageView`. Note that only image formats supported by
|
||||
`NativeImage` can be used with an `ImageView`.
|
||||
|
||||
[event-emitter]: https://nodejs.org/api/events.html#events_class_eventemitter
|
||||
@@ -16,14 +16,8 @@ See [`Menu`](menu.md) for examples.
|
||||
* `event` [KeyboardEvent](structures/keyboard-event.md)
|
||||
* `role` string (optional) - Can be `undo`, `redo`, `cut`, `copy`, `paste`, `pasteAndMatchStyle`, `delete`, `selectAll`, `reload`, `forceReload`, `toggleDevTools`, `resetZoom`, `zoomIn`, `zoomOut`, `toggleSpellChecker`, `togglefullscreen`, `window`, `minimize`, `close`, `help`, `about`, `services`, `hide`, `hideOthers`, `unhide`, `quit`, `showSubstitutions`, `toggleSmartQuotes`, `toggleSmartDashes`, `toggleTextReplacement`, `startSpeaking`, `stopSpeaking`, `zoom`, `front`, `appMenu`, `fileMenu`, `editMenu`, `viewMenu`, `shareMenu`, `recentDocuments`, `toggleTabBar`, `selectNextTab`, `selectPreviousTab`, `showAllTabs`, `mergeAllWindows`, `clearRecentDocuments`, `moveTabToNewWindow` or `windowMenu` - Define the action of the menu item, when specified the
|
||||
`click` property will be ignored. See [roles](#roles).
|
||||
* `type` string (optional)
|
||||
* `normal`
|
||||
* `separator`
|
||||
* `submenu`
|
||||
* `checkbox`
|
||||
* `radio`
|
||||
* `header` - Only available on macOS 14 and up.
|
||||
* `palette` - Only available on macOS 14 and up.
|
||||
* `type` string (optional) - Can be `normal`, `separator`, `submenu`, `checkbox` or
|
||||
`radio`.
|
||||
* `label` string (optional)
|
||||
* `sublabel` string (optional) _macOS_ - Available in macOS >= 14.4
|
||||
* `toolTip` string (optional) _macOS_ - Hover text for this menu item.
|
||||
@@ -164,10 +158,7 @@ item's submenu, if present.
|
||||
|
||||
#### `menuItem.type`
|
||||
|
||||
A `string` indicating the type of the item. Can be `normal`, `separator`, `submenu`, `checkbox`, `radio`, `header` or `palette`.
|
||||
|
||||
> [!NOTE]
|
||||
> `header` and `palette` are only available on macOS 14 and up.
|
||||
A `string` indicating the type of the item. Can be `normal`, `separator`, `submenu`, `checkbox` or `radio`.
|
||||
|
||||
#### `menuItem.role`
|
||||
|
||||
|
||||
@@ -15,12 +15,9 @@ Process: [Main](../glossary.md#main-process)
|
||||
|
||||
Example:
|
||||
|
||||
<!-- eslint-disable import/order -->
|
||||
|
||||
```js
|
||||
// Main process
|
||||
const { BrowserWindow, MessageChannelMain } = require('electron')
|
||||
|
||||
const w = new BrowserWindow()
|
||||
const { port1, port2 } = new MessageChannelMain()
|
||||
w.webContents.postMessage('port', null, [port2])
|
||||
@@ -28,7 +25,6 @@ port1.postMessage({ some: 'message' })
|
||||
|
||||
// Renderer process
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
ipcRenderer.on('port', (e) => {
|
||||
// e.ports is a list of ports sent along with this message
|
||||
e.ports[0].onmessage = (messageEvent) => {
|
||||
|
||||
@@ -86,7 +86,6 @@ images/
|
||||
|
||||
```js title='Main Process'
|
||||
const { Tray } = require('electron')
|
||||
|
||||
const appTray = new Tray('/Users/somebody/images/icon.png')
|
||||
```
|
||||
|
||||
|
||||
@@ -28,7 +28,6 @@ Example usage:
|
||||
|
||||
```js
|
||||
const { app } = require('electron')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const { net } = require('electron')
|
||||
const request = net.request('https://github.com')
|
||||
|
||||
@@ -9,7 +9,6 @@ An example of implementing a protocol that has the same effect as the
|
||||
|
||||
```js
|
||||
const { app, protocol, net } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
const url = require('node:url')
|
||||
|
||||
@@ -39,9 +38,8 @@ to register it to that session explicitly.
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow, net, protocol, session } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
const url = require('node:url')
|
||||
const url = require('url')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const partition = 'persist:example'
|
||||
@@ -78,7 +76,6 @@ Policy:
|
||||
|
||||
```js
|
||||
const { protocol } = require('electron')
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{ scheme: 'foo', privileges: { bypassCSP: true } }
|
||||
])
|
||||
@@ -131,9 +128,8 @@ Example:
|
||||
|
||||
```js
|
||||
const { app, net, protocol } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
const { pathToFileURL } = require('node:url')
|
||||
const { pathToFileURL } = require('url')
|
||||
|
||||
protocol.registerSchemesAsPrivileged([
|
||||
{
|
||||
@@ -334,8 +330,7 @@ Example:
|
||||
|
||||
```js
|
||||
const { protocol } = require('electron')
|
||||
|
||||
const { PassThrough } = require('node:stream')
|
||||
const { PassThrough } = require('stream')
|
||||
|
||||
function createStream (text) {
|
||||
const rv = new PassThrough() // PassThrough is also a Readable stream
|
||||
|
||||
@@ -109,7 +109,6 @@ Starts the service worker or does nothing if already running.
|
||||
|
||||
```js
|
||||
const { app, session } = require('electron')
|
||||
|
||||
const { serviceWorkers } = session.defaultSession
|
||||
|
||||
// Collect service workers scopes
|
||||
|
||||
@@ -79,7 +79,6 @@ You can create a `Session` object in the `session` module:
|
||||
|
||||
```js
|
||||
const { session } = require('electron')
|
||||
|
||||
const ses = session.fromPartition('persist:name')
|
||||
console.log(ses.getUserAgent())
|
||||
```
|
||||
@@ -101,9 +100,8 @@ Emitted when Electron is about to download `item` in `webContents`.
|
||||
Calling `event.preventDefault()` will cancel the download and `item` will not be
|
||||
available from next tick of the process.
|
||||
|
||||
```js @ts-expect-error=[5]
|
||||
```js @ts-expect-error=[4]
|
||||
const { session } = require('electron')
|
||||
|
||||
session.defaultSession.on('will-download', (event, item, webContents) => {
|
||||
event.preventDefault()
|
||||
require('got')(item.getURL()).then((response) => {
|
||||
@@ -854,7 +852,6 @@ verify proc.
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.webContents.session.setCertificateVerifyProc((request, callback) => {
|
||||
@@ -906,7 +903,6 @@ Most web APIs do a permission check and then make a permission request if the ch
|
||||
|
||||
```js
|
||||
const { session } = require('electron')
|
||||
|
||||
session.fromPartition('some-partition').setPermissionRequestHandler((webContents, permission, callback) => {
|
||||
if (webContents.getURL() === 'some-host' && permission === 'notifications') {
|
||||
return callback(false) // denied.
|
||||
@@ -956,9 +952,7 @@ To clear the handler, call `setPermissionCheckHandler(null)`.
|
||||
|
||||
```js
|
||||
const { session } = require('electron')
|
||||
|
||||
const url = require('node:url')
|
||||
|
||||
const url = require('url')
|
||||
session.fromPartition('some-partition').setPermissionCheckHandler((webContents, permission, requestingOrigin) => {
|
||||
if (new URL(requestingOrigin).hostname === 'some-host' && permission === 'notifications') {
|
||||
return true // granted
|
||||
@@ -1195,7 +1189,6 @@ automatically. To clear the handler, call `setBluetoothPairingHandler(null)`.
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow, session } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
function createWindow () {
|
||||
@@ -1525,7 +1518,6 @@ extension to be loaded.
|
||||
|
||||
```js
|
||||
const { app, session } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
@@ -1667,7 +1659,6 @@ A [`Protocol`](protocol.md) object for this session.
|
||||
|
||||
```js
|
||||
const { app, session } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
|
||||
@@ -12,8 +12,6 @@ The number represented by `status` means different things on different platforms
|
||||
Below is an example of some of the additional options that may be set which
|
||||
may be different on each platform.
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
{
|
||||
name: 'Austin_4th_Floor_Printer___C02XK13BJHD4',
|
||||
|
||||
@@ -26,8 +26,6 @@
|
||||
|
||||
An example TraceConfig that roughly matches what Chrome DevTools records:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
{
|
||||
recording_mode: 'record-until-full',
|
||||
|
||||
@@ -6,7 +6,6 @@ Process: [Main](../glossary.md#main-process), [Utility](../glossary.md#utility-p
|
||||
|
||||
```js
|
||||
const { systemPreferences } = require('electron')
|
||||
|
||||
console.log(systemPreferences.getEffectiveAppearance())
|
||||
```
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ module is emitted.
|
||||
|
||||
```js
|
||||
const { BaseWindow, View } = require('electron')
|
||||
|
||||
const win = new BaseWindow()
|
||||
const view = new View()
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ module is emitted.
|
||||
|
||||
```js
|
||||
const { BaseWindow, WebContentsView } = require('electron')
|
||||
|
||||
const win = new BaseWindow({ width: 800, height: 400 })
|
||||
|
||||
const view1 = new WebContentsView()
|
||||
@@ -53,7 +52,6 @@ Use this to interact with the `WebContents`, for instance to load a URL.
|
||||
|
||||
```js
|
||||
const { WebContentsView } = require('electron')
|
||||
|
||||
const view = new WebContentsView()
|
||||
view.webContents.loadURL('https://electronjs.org/')
|
||||
```
|
||||
|
||||
@@ -55,7 +55,6 @@ These methods can be accessed from the `webContents` module:
|
||||
|
||||
```js
|
||||
const { webContents } = require('electron')
|
||||
|
||||
console.log(webContents)
|
||||
```
|
||||
|
||||
@@ -447,7 +446,6 @@ and allow the page to be unloaded.
|
||||
|
||||
```js
|
||||
const { BrowserWindow, dialog } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({ width: 800, height: 600 })
|
||||
win.webContents.on('will-prevent-unload', (event) => {
|
||||
const choice = dialog.showMessageBoxSync(win, {
|
||||
@@ -1105,7 +1103,6 @@ Returns `string` - The URL of the current web page.
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({ width: 800, height: 600 })
|
||||
win.loadURL('https://github.com').then(() => {
|
||||
const currentURL = win.webContents.getURL()
|
||||
@@ -1775,10 +1772,9 @@ An example of `webContents.printToPDF`:
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
const fs = require('node:fs')
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
const os = require('node:os')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const win = new BrowserWindow()
|
||||
@@ -1810,7 +1806,6 @@ creation:
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
win.webContents.on('devtools-opened', () => {
|
||||
win.webContents.addWorkSpace(__dirname)
|
||||
@@ -1877,7 +1872,6 @@ An example of showing devtools in a `<webview>` tag:
|
||||
```js
|
||||
// Main process
|
||||
const { ipcMain, webContents } = require('electron')
|
||||
|
||||
ipcMain.on('open-devtools', (event, targetContentsId, devtoolsContentsId) => {
|
||||
const target = webContents.fromId(targetContentsId)
|
||||
const devtools = webContents.fromId(devtoolsContentsId)
|
||||
@@ -2137,7 +2131,6 @@ Returns `Promise<void>` - resolves if the page is saved.
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.loadURL('https://github.com')
|
||||
|
||||
@@ -99,11 +99,9 @@ with an array of misspelt words when complete.
|
||||
|
||||
An example of using [node-spellchecker][spellchecker] as provider:
|
||||
|
||||
```js @ts-expect-error=[3,8]
|
||||
```js @ts-expect-error=[2,6]
|
||||
const { webFrame } = require('electron')
|
||||
|
||||
const spellChecker = require('spellchecker')
|
||||
|
||||
webFrame.setSpellCheckProvider('en-US', {
|
||||
spellCheck (words, callback) {
|
||||
setTimeout(() => {
|
||||
@@ -214,14 +212,11 @@ caches.
|
||||
|
||||
```js
|
||||
const { webFrame } = require('electron')
|
||||
|
||||
console.log(webFrame.getResourceUsage())
|
||||
```
|
||||
|
||||
This will generate:
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js
|
||||
{
|
||||
images: {
|
||||
|
||||
@@ -22,6 +22,5 @@ const oldPath = document.querySelector('input').files[0].path
|
||||
|
||||
// After
|
||||
const { webUtils } = require('electron')
|
||||
|
||||
const newPath = webUtils.getPathForFile(document.querySelector('input').files[0])
|
||||
```
|
||||
|
||||
@@ -987,7 +987,6 @@ webview.send('ping')
|
||||
```js
|
||||
// In guest page.
|
||||
const { ipcRenderer } = require('electron')
|
||||
|
||||
ipcRenderer.on('ping', () => {
|
||||
ipcRenderer.sendToHost('pong')
|
||||
})
|
||||
|
||||
@@ -141,7 +141,7 @@ On Linux, the required portal version for file dialogs has been reverted
|
||||
to 3 from 4. Using the `defaultPath` option of the Dialog API is not
|
||||
supported when using portal file chooser dialogs unless the portal
|
||||
backend is version 4 or higher. The `--xdg-portal-required-version`
|
||||
[command-line switch](api/command-line-switches.md#--xdg-portal-required-versionversion)
|
||||
[command-line switch](/api/command-line-switches.md#--xdg-portal-required-versionversion)
|
||||
can be used to force a required version for your application.
|
||||
See [#44426](https://github.com/electron/electron/pull/44426) for more details.
|
||||
|
||||
|
||||
@@ -165,10 +165,8 @@ An example of the contents of this file can be found [here](https://github.com/e
|
||||
|
||||
Add your module to the module list found at `"lib/browser/api/module-list.ts"` like so:
|
||||
|
||||
<!-- eslint-disable semi -->
|
||||
|
||||
```ts title='lib/browser/api/module-list.ts' @ts-nocheck
|
||||
export const browserModuleList: ElectronInternal.ModuleEntry[] = [
|
||||
{ name: 'apiName', loader: () => require('./api-name') }
|
||||
{ name: 'apiName', loader: () => require('./api-name') },
|
||||
];
|
||||
```
|
||||
|
||||
@@ -81,11 +81,6 @@ $ git commit
|
||||
|
||||
Note that multiple commits get squashed when they are landed.
|
||||
|
||||
#### Commit signing
|
||||
|
||||
The `electron/electron` repo enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification/signing-commits) for all incoming PRs.
|
||||
To sign your 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).
|
||||
|
||||
#### Commit message guidelines
|
||||
|
||||
A good commit message should describe what changed and why. The Electron project
|
||||
|
||||
@@ -67,7 +67,6 @@ code from this:
|
||||
|
||||
```js
|
||||
const { app, Tray } = require('electron')
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const tray = new Tray('/path/to/icon.png')
|
||||
tray.setTitle('hello world')
|
||||
@@ -78,7 +77,6 @@ to this:
|
||||
|
||||
```js
|
||||
const { app, Tray } = require('electron')
|
||||
|
||||
let tray = null
|
||||
app.whenReady().then(() => {
|
||||
tray = new Tray('/path/to/icon.png')
|
||||
@@ -97,7 +95,6 @@ To solve this, you can turn off node integration in Electron:
|
||||
```js
|
||||
// In the main process.
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({
|
||||
webPreferences: {
|
||||
nodeIntegration: false
|
||||
@@ -146,7 +143,6 @@ To achieve this goal, set the background in the constructor for [BrowserWindow][
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({
|
||||
backgroundColor: '#fff'
|
||||
})
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
"rules": {
|
||||
"import/order": "off"
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,6 @@ Read a file in the ASAR archive:
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
fs.readFileSync('/path/to/example.asar/file.txt')
|
||||
```
|
||||
|
||||
@@ -50,7 +49,6 @@ List all files under the root of the archive:
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
fs.readdirSync('/path/to/example.asar')
|
||||
```
|
||||
|
||||
@@ -64,7 +62,6 @@ You can also display a web page in an ASAR archive with `BrowserWindow`:
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
win.loadURL('file:///path/to/example.asar/static/index.html')
|
||||
@@ -94,7 +91,6 @@ content of an ASAR archive as a file. For this purpose you can use the built-in
|
||||
|
||||
```js
|
||||
const originalFs = require('original-fs')
|
||||
|
||||
originalFs.readFileSync('/path/to/example.asar')
|
||||
```
|
||||
|
||||
@@ -103,7 +99,6 @@ the `fs` module:
|
||||
|
||||
```js
|
||||
const fs = require('node:fs')
|
||||
|
||||
process.noAsar = true
|
||||
fs.readFileSync('/path/to/example.asar')
|
||||
```
|
||||
|
||||
@@ -95,7 +95,6 @@ or to retrieve other Electron process information:
|
||||
```js @ts-nocheck
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { browser, expect } from '@wdio/globals'
|
||||
|
||||
const packageJson = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'package.json'), { encoding: 'utf-8' }))
|
||||
@@ -166,7 +165,6 @@ ChromeDriver and where to find the binary of your Electron app:
|
||||
|
||||
```js title='test.js' @ts-expect-error=[1]
|
||||
const webdriver = require('selenium-webdriver')
|
||||
|
||||
const driver = new webdriver.Builder()
|
||||
// The "9515" is the port opened by ChromeDriver.
|
||||
.usingServer('http://localhost:9515')
|
||||
@@ -319,9 +317,8 @@ To create a custom driver, we'll use Node.js' [`child_process`](https://nodejs.o
|
||||
The test suite will spawn the Electron process, then establish a simple messaging protocol:
|
||||
|
||||
```js title='testDriver.js' @ts-nocheck
|
||||
const electronPath = require('electron')
|
||||
|
||||
const childProcess = require('node:child_process')
|
||||
const electronPath = require('electron')
|
||||
|
||||
// spawn the process
|
||||
const env = { /* ... */ }
|
||||
@@ -439,10 +436,8 @@ framework of your choosing. The following example uses
|
||||
or Mocha would work as well:
|
||||
|
||||
```js title='test.js' @ts-nocheck
|
||||
const electronPath = require('electron')
|
||||
|
||||
const test = require('ava')
|
||||
|
||||
const electronPath = require('electron')
|
||||
const { TestDriver } = require('./testDriver')
|
||||
|
||||
const app = new TestDriver({
|
||||
|
||||
@@ -76,7 +76,6 @@ use the native UI to control the window.
|
||||
|
||||
```js
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({ titleBarStyle: 'customButtonsOnHover' })
|
||||
```
|
||||
|
||||
@@ -90,7 +89,6 @@ by a fixed amount.
|
||||
|
||||
```js title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({ titleBarStyle: 'hiddenInset' })
|
||||
```
|
||||
|
||||
@@ -100,7 +98,6 @@ constructor.
|
||||
|
||||
```js title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({
|
||||
titleBarStyle: 'hidden',
|
||||
trafficLightPosition: { x: 10, y: 10 }
|
||||
@@ -115,7 +112,6 @@ on the value of its boolean parameter.
|
||||
|
||||
```js title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
// hides the traffic lights
|
||||
win.setWindowButtonVisibility(false)
|
||||
@@ -153,7 +149,6 @@ default to the standard system height:
|
||||
|
||||
```js title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow({
|
||||
titleBarStyle: 'hidden',
|
||||
titleBarOverlay: {
|
||||
|
||||
@@ -62,7 +62,6 @@ API:
|
||||
|
||||
```js title='main.js'
|
||||
const { BrowserWindow } = require('electron')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
win.setIgnoreMouseEvents(true)
|
||||
```
|
||||
@@ -76,7 +75,6 @@ allowing events such as `mouseleave` to be emitted:
|
||||
|
||||
```js title='main.js'
|
||||
const { BrowserWindow, ipcMain } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
const win = new BrowserWindow({
|
||||
|
||||
@@ -135,7 +135,6 @@ Finally, the `main.js` file represents the main process and contains the actual
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow, ipcMain, nativeTheme } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
const createWindow = () => {
|
||||
|
||||
@@ -35,16 +35,15 @@ Using the [React Developer Tools][react-devtools] as an example:
|
||||
|
||||
```js
|
||||
const { app, session } = require('electron')
|
||||
|
||||
const os = require('node:os')
|
||||
const path = require('node:path')
|
||||
|
||||
const os = require('node:os')
|
||||
|
||||
// on macOS
|
||||
const reactDevToolsPath = path.join(
|
||||
os.homedir(),
|
||||
'/Library/Application Support/Google/Chrome/Default/Extensions/fmkadmapgofadopljbjfkapdkoienihi/4.9.0_0'
|
||||
)
|
||||
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
await session.defaultSession.loadExtension(reactDevToolsPath)
|
||||
})
|
||||
|
||||
@@ -85,8 +85,6 @@ The `@babel/plugin-transform-modules-commonjs` plugin will transform
|
||||
ESM imports down to `require` calls. The exact syntax will depend on the
|
||||
[`importInterop` setting](https://babeljs.io/docs/babel-plugin-transform-modules-commonjs#importinterop).
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js @nolint @ts-nocheck title='@babel/plugin-transform-modules-commonjs'
|
||||
import foo from "foo";
|
||||
import { bar } from "bar";
|
||||
|
||||
@@ -37,7 +37,6 @@ Here is an example that shows how to use In-App Purchases in Electron. You'll ha
|
||||
```js
|
||||
// Main process
|
||||
const { inAppPurchase } = require('electron')
|
||||
|
||||
const PRODUCT_IDS = ['id1', 'id2']
|
||||
|
||||
// Listen for transactions as soon as possible.
|
||||
|
||||
@@ -52,7 +52,6 @@ In the main process, set an IPC listener on the `set-title` channel with the `ip
|
||||
|
||||
```js {6-10,22} title='main.js (Main Process)'
|
||||
const { app, BrowserWindow, ipcMain } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
// ...
|
||||
@@ -184,7 +183,6 @@ provided to the renderer process. Please refer to
|
||||
|
||||
```js {6-13,25} title='main.js (Main Process)'
|
||||
const { app, BrowserWindow, dialog, ipcMain } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
// ...
|
||||
@@ -336,7 +334,6 @@ response.
|
||||
|
||||
```js title='main.js (Main Process)'
|
||||
const { ipcMain } = require('electron')
|
||||
|
||||
ipcMain.on('synchronous-message', (event, arg) => {
|
||||
console.log(arg) // prints "ping" in the Node console
|
||||
event.returnValue = 'pong'
|
||||
@@ -381,7 +378,6 @@ target renderer.
|
||||
|
||||
```js {11-26} title='main.js (Main Process)'
|
||||
const { app, BrowserWindow, Menu, ipcMain } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
function createWindow () {
|
||||
@@ -416,8 +412,6 @@ function createWindow () {
|
||||
For the purposes of the tutorial, it's important to note that the `click` handler
|
||||
sends a message (either `1` or `-1`) to the renderer process through the `update-counter` channel.
|
||||
|
||||
<!-- eslint-skip -->
|
||||
|
||||
```js @ts-type={mainWindow:Electron.BrowserWindow}
|
||||
click: () => mainWindow.webContents.send('update-counter', -1)
|
||||
```
|
||||
|
||||
@@ -27,7 +27,6 @@ control our application lifecycle and create a native browser window.
|
||||
|
||||
```js
|
||||
const { app, BrowserWindow, shell } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
```
|
||||
|
||||
|
||||
@@ -303,7 +303,6 @@ without having to step through the isolated world.
|
||||
|
||||
```js title='main.js (Main Process)'
|
||||
const { BrowserWindow, app, MessageChannelMain } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
app.whenReady().then(async () => {
|
||||
|
||||
@@ -1075,7 +1075,7 @@ NODE_API_MODULE(objc_addon, Init)
|
||||
You're so close! We now have working Objective-C and thread-safe ways to expose methods and events to JavaScript. In this final step, let's create a JavaScript wrapper in `js/index.js` to provide a more friendly API:
|
||||
|
||||
```js title='js/index.js' @ts-expect-error=[10]
|
||||
const EventEmitter = require('node:events')
|
||||
const EventEmitter = require('events')
|
||||
|
||||
class ObjcMacosAddon extends EventEmitter {
|
||||
constructor () {
|
||||
|
||||
@@ -269,10 +269,8 @@ Let's break down this code:
|
||||
|
||||
Now, let's create a JavaScript wrapper to make the addon easier to use. Create `js/index.js`:
|
||||
|
||||
<!-- eslint-disable import/newline-after-import,import/order -->
|
||||
|
||||
```js title='js/index.js' @ts-expect-error=[5]
|
||||
const EventEmitter = require('node:events')
|
||||
const EventEmitter = require('events')
|
||||
|
||||
// Load the native addon using the 'bindings' module
|
||||
// This will look for the compiled .node file in various places
|
||||
@@ -364,7 +362,6 @@ To use this addon in an Electron application, you would:
|
||||
```js @ts-expect-error=[2]
|
||||
// In your main process
|
||||
const myAddon = require('my-native-addon')
|
||||
|
||||
console.log(myAddon.helloWorld('Electron'))
|
||||
```
|
||||
|
||||
|
||||
@@ -74,6 +74,7 @@ if (navigationHistory.canGoToOffset(2)) {
|
||||
A common flow is that you want to restore the history of a webContents - for instance to implement an "undo close tab" feature. To do so, you can call `navigationHistory.restore({ index, entries })`. This will restore the webContent's navigation history and the webContents location in said history, meaning that `goBack()` and `goForward()` navigate you through the stack as expected.
|
||||
|
||||
```js @ts-type={navigationHistory:Electron.NavigationHistory}
|
||||
|
||||
const firstWindow = new BrowserWindow()
|
||||
|
||||
// Later, you want a second window to have the same history and navigation position
|
||||
|
||||
@@ -175,7 +175,6 @@ you might write code that eagerly loads dependencies:
|
||||
|
||||
```js title='parser.js' @ts-expect-error=[2]
|
||||
const fs = require('node:fs')
|
||||
|
||||
const fooParser = require('foo-parser')
|
||||
|
||||
class Parser {
|
||||
|
||||
@@ -242,8 +242,8 @@ These aliases have no impact on runtime, but can be used for typechecking
|
||||
and autocomplete.
|
||||
|
||||
```js title="Usage example"
|
||||
const { shell } = require('electron/common')
|
||||
const { app } = require('electron/main')
|
||||
const { shell } = require('electron/common')
|
||||
```
|
||||
|
||||
[window-mdn]: https://developer.mozilla.org/en-US/docs/Web/API/Window
|
||||
|
||||
@@ -281,8 +281,7 @@ security-conscious developers might want to assume the very opposite.
|
||||
|
||||
```js title='main.js (Main Process)'
|
||||
const { session } = require('electron')
|
||||
|
||||
const { URL } = require('node:url')
|
||||
const { URL } = require('url')
|
||||
|
||||
session
|
||||
.fromPartition('some-partition')
|
||||
@@ -612,10 +611,9 @@ sometimes be fooled - a `startsWith('https://example.com')` test would let
|
||||
`https://example.com.attacker.com` through.
|
||||
|
||||
```js title='main.js (Main Process)'
|
||||
const { URL } = require('url')
|
||||
const { app } = require('electron')
|
||||
|
||||
const { URL } = require('node:url')
|
||||
|
||||
app.on('web-contents-created', (event, contents) => {
|
||||
contents.on('will-navigate', (event, navigationUrl) => {
|
||||
const parsedUrl = new URL(navigationUrl)
|
||||
@@ -692,14 +690,12 @@ leveraged to execute arbitrary commands.
|
||||
```js title='main.js (Main Process)' @ts-type={USER_CONTROLLED_DATA_HERE:string}
|
||||
// Bad
|
||||
const { shell } = require('electron')
|
||||
|
||||
shell.openExternal(USER_CONTROLLED_DATA_HERE)
|
||||
```
|
||||
|
||||
```js title='main.js (Main Process)'
|
||||
// Good
|
||||
const { shell } = require('electron')
|
||||
|
||||
shell.openExternal('https://example.com/index.html')
|
||||
```
|
||||
|
||||
|
||||
@@ -83,7 +83,6 @@ To attach this script to your renderer process, pass its path to the
|
||||
|
||||
```js {2,8-10} title="main.js"
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
const createWindow = () => {
|
||||
@@ -205,7 +204,6 @@ you send out the `invoke` call from the renderer.
|
||||
|
||||
```js {1,15} title="main.js"
|
||||
const { app, BrowserWindow, ipcMain } = require('electron/main')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
const createWindow = () => {
|
||||
|
||||
@@ -126,7 +126,6 @@ following lines:
|
||||
|
||||
```js
|
||||
const { BrowserWindow, nativeImage } = require('electron')
|
||||
|
||||
const path = require('node:path')
|
||||
|
||||
const win = new BrowserWindow()
|
||||
|
||||
@@ -25,7 +25,6 @@ auto_filenames = {
|
||||
"docs/api/extensions-api.md",
|
||||
"docs/api/extensions.md",
|
||||
"docs/api/global-shortcut.md",
|
||||
"docs/api/image-view.md",
|
||||
"docs/api/in-app-purchase.md",
|
||||
"docs/api/incoming-message.md",
|
||||
"docs/api/ipc-main-service-worker.md",
|
||||
|
||||
@@ -71,7 +71,7 @@ const MenuItem = function (this: any, options: any) {
|
||||
};
|
||||
};
|
||||
|
||||
MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio', 'header', 'palette'];
|
||||
MenuItem.types = ['normal', 'separator', 'submenu', 'checkbox', 'radio'];
|
||||
|
||||
MenuItem.prototype.getDefaultRoleAccelerator = function () {
|
||||
return roles.getDefaultAccelerator(this.role);
|
||||
|
||||
@@ -143,9 +143,6 @@ Menu.prototype.insert = function (pos, item) {
|
||||
if (item.toolTip) this.setToolTip(pos, item.toolTip);
|
||||
if (item.icon) this.setIcon(pos, item.icon);
|
||||
if (item.role) this.setRole(pos, item.role);
|
||||
if (item.type === 'palette' || item.type === 'header') {
|
||||
this.setCustomType(pos, item.type);
|
||||
}
|
||||
|
||||
// Make menu accessible to items.
|
||||
item.overrideReadOnlyProperty('menu', this);
|
||||
@@ -267,11 +264,9 @@ function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]
|
||||
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
|
||||
const types = {
|
||||
normal: () => this.insertItem(pos, item.commandId, item.label),
|
||||
header: () => this.insertItem(pos, item.commandId, item.label),
|
||||
checkbox: () => this.insertCheckItem(pos, item.commandId, item.label),
|
||||
separator: () => this.insertSeparator(pos),
|
||||
submenu: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
|
||||
palette: () => this.insertSubMenu(pos, item.commandId, item.label, item.submenu),
|
||||
radio: () => {
|
||||
// Grouping radio menu items
|
||||
item.overrideReadOnlyProperty('groupId', generateGroupId(this.items, pos));
|
||||
|
||||
@@ -288,12 +288,8 @@ function parseOptions (optionsIn: ClientRequestConstructorOptions | string): Nod
|
||||
origin: options.origin,
|
||||
referrerPolicy: options.referrerPolicy,
|
||||
cache: options.cache,
|
||||
allowNonHttpProtocols: Object.hasOwn(options, kAllowNonHttpProtocols),
|
||||
priority: options.priority
|
||||
allowNonHttpProtocols: Object.hasOwn(options, kAllowNonHttpProtocols)
|
||||
};
|
||||
if ('priorityIncremental' in options) {
|
||||
urlLoaderOptions.priorityIncremental = options.priorityIncremental;
|
||||
}
|
||||
const headers: Record<string, string | string[]> = options.headers || {};
|
||||
for (const [name, value] of Object.entries(headers)) {
|
||||
validateHeader(name, value);
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
"@electron/docs-parser": "^2.0.0",
|
||||
"@electron/fiddle-core": "^1.3.4",
|
||||
"@electron/github-app-auth": "^2.2.1",
|
||||
"@electron/lint-roller": "^3.1.1",
|
||||
"@electron/lint-roller": "^3.0.0",
|
||||
"@electron/typescript-definitions": "^9.1.2",
|
||||
"@octokit/rest": "^20.0.2",
|
||||
"@primer/octicons": "^10.0.0",
|
||||
@@ -29,11 +29,11 @@
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.30.0",
|
||||
"eslint-plugin-markdown": "^5.1.0",
|
||||
"eslint-plugin-mocha": "^10.5.0",
|
||||
"eslint-plugin-n": "^16.6.2",
|
||||
"eslint-plugin-node": "^11.1.0",
|
||||
"eslint-plugin-promise": "^6.6.0",
|
||||
"eslint-plugin-standard": "^5.0.0",
|
||||
"eslint-plugin-unicorn": "^55.0.0",
|
||||
"events": "^3.2.0",
|
||||
"folder-hash": "^2.1.1",
|
||||
@@ -48,6 +48,7 @@
|
||||
"remark-cli": "^12.0.1",
|
||||
"remark-preset-lint-markdown-style-guide": "^4.0.0",
|
||||
"semver": "^7.6.3",
|
||||
"shx": "^0.3.4",
|
||||
"stream-json": "^1.8.0",
|
||||
"tap-xunit": "^2.4.1",
|
||||
"temp": "^0.9.4",
|
||||
@@ -81,7 +82,7 @@
|
||||
"lint:api-history": "lint-roller-markdown-api-history --root \"./docs/api/\" --schema \"./docs/api-history.schema.json\" --breaking-changes-file \"./docs/breaking-changes.md\" --check-placement --check-strings \"*.md\"",
|
||||
"create-api-json": "node script/create-api-json.mjs",
|
||||
"create-typescript-definitions": "npm run create-api-json && electron-typescript-definitions --api=electron-api.json && node spec/ts-smoke/runner.js",
|
||||
"gn-typescript-definitions": "npm run create-typescript-definitions && node script/cp.mjs electron.d.ts",
|
||||
"gn-typescript-definitions": "npm run create-typescript-definitions && shx cp electron.d.ts",
|
||||
"pre-flight": "pre-flight",
|
||||
"gn-check": "node ./script/gn-check.js",
|
||||
"gn-format": "python3 script/run-gn-format.py",
|
||||
|
||||
@@ -7,4 +7,3 @@ fix_abort_installation_attempt_at_the_final_mile_if_the_app_is.patch
|
||||
feat_add_ability_to_prevent_version_downgrades.patch
|
||||
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
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Niklas Wenzel <dev@nikwen.de>
|
||||
Date: Tue, 27 May 2025 02:03:54 +0200
|
||||
Subject: fix: crash when process to extract zip cannot be launched
|
||||
|
||||
Fixes https://github.com/electron/electron/issues/47270
|
||||
|
||||
diff --git a/Squirrel/SQRLZipArchiver.m b/Squirrel/SQRLZipArchiver.m
|
||||
index 68f5dac8e553638f41306956df9d38eeda18f8f2..a9cd676df63e19edf9e20473d27b85591c7cb49e 100644
|
||||
--- a/Squirrel/SQRLZipArchiver.m
|
||||
+++ b/Squirrel/SQRLZipArchiver.m
|
||||
@@ -153,7 +153,17 @@ - (RACSignal *)launchWithArguments:(NSArray *)arguments {
|
||||
setNameWithFormat:@"-launchWithArguments: %@", arguments];
|
||||
|
||||
self.dittoTask.arguments = arguments;
|
||||
- [self.dittoTask launch];
|
||||
+
|
||||
+ NSError *launchError = nil;
|
||||
+
|
||||
+ if (![self.dittoTask launchAndReturnError:&launchError]) {
|
||||
+ NSMutableDictionary *userInfo = [NSMutableDictionary dictionary];
|
||||
+ userInfo[NSLocalizedDescriptionKey] = launchError.localizedDescription;
|
||||
+
|
||||
+ NSLog(@"Starting ditto task failed with error: %@", launchError.localizedDescription);
|
||||
+
|
||||
+ return [RACSignal error:[NSError errorWithDomain:SQRLZipArchiverErrorDomain code:SQRLZipArchiverShellTaskFailed userInfo:userInfo]];
|
||||
+ }
|
||||
|
||||
return signal;
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
import * as fs from 'node:fs/promises';
|
||||
|
||||
const source = process.argv[2];
|
||||
const destination = process.argv[3];
|
||||
|
||||
await fs.cp(source, destination);
|
||||
@@ -1,5 +1,7 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { getCodeBlocks } = require('@electron/lint-roller/dist/lib/markdown');
|
||||
|
||||
const { GitProcess } = require('dugite');
|
||||
const { ESLint } = require('eslint');
|
||||
const minimist = require('minimist');
|
||||
@@ -73,24 +75,6 @@ function spawnAndCheckExitCode (cmd, args, opts) {
|
||||
}
|
||||
}
|
||||
|
||||
async function runEslint (eslint, filenames, { fix, verbose }) {
|
||||
const formatter = await eslint.loadFormatter();
|
||||
let successCount = 0;
|
||||
const results = await eslint.lintFiles(filenames);
|
||||
for (const result of results) {
|
||||
successCount += result.errorCount === 0 ? 1 : 0;
|
||||
if (verbose && result.errorCount === 0 && result.warningCount === 0) {
|
||||
console.log(`${result.filePath}: no errors or warnings`);
|
||||
}
|
||||
}
|
||||
console.log(formatter.format(results));
|
||||
if (fix) {
|
||||
await ESLint.outputFixes(results);
|
||||
}
|
||||
|
||||
return successCount === filenames.length;
|
||||
}
|
||||
|
||||
function cpplint (args) {
|
||||
args.unshift(`--root=${SOURCE_ROOT}`);
|
||||
const cmd = IS_WINDOWS ? 'cpplint.bat' : 'cpplint.py';
|
||||
@@ -164,8 +148,20 @@ const LINTERS = [{
|
||||
fix: opts.fix,
|
||||
resolvePluginsRelativeTo: ELECTRON_ROOT
|
||||
});
|
||||
const clean = await runEslint(eslint, filenames, { fix: opts.fix, verbose: opts.verbose });
|
||||
if (!clean) {
|
||||
const formatter = await eslint.loadFormatter();
|
||||
let successCount = 0;
|
||||
const results = await eslint.lintFiles(filenames);
|
||||
for (const result of results) {
|
||||
successCount += result.errorCount === 0 ? 1 : 0;
|
||||
if (opts.verbose && result.errorCount === 0 && result.warningCount === 0) {
|
||||
console.log(`${result.filePath}: no errors or warnings`);
|
||||
}
|
||||
}
|
||||
console.log(formatter.format(results));
|
||||
if (opts.fix) {
|
||||
await ESLint.outputFixes(results);
|
||||
}
|
||||
if (successCount !== filenames.length) {
|
||||
console.error('Linting had errors');
|
||||
process.exit(1);
|
||||
}
|
||||
@@ -285,7 +281,6 @@ const LINTERS = [{
|
||||
ignoreRoots: ['.git', 'node_modules', 'spec/node_modules'],
|
||||
test: filename => filename.endsWith('.md'),
|
||||
run: async (opts, filenames) => {
|
||||
const { getCodeBlocks } = await import('@electron/lint-roller/dist/lib/markdown.js');
|
||||
let errors = false;
|
||||
|
||||
// Run markdownlint on all Markdown files
|
||||
@@ -369,26 +364,6 @@ const LINTERS = [{
|
||||
}
|
||||
}
|
||||
|
||||
const eslint = new ESLint({
|
||||
// Do not use the lint cache on CI builds
|
||||
cache: !process.env.CI,
|
||||
cacheLocation: `node_modules/.eslintcache.${crypto.createHash('md5').update(fs.readFileSync(__filename)).digest('hex')}`,
|
||||
fix: opts.fix,
|
||||
overrideConfigFile: path.join(ELECTRON_ROOT, 'docs', '.eslintrc.json'),
|
||||
resolvePluginsRelativeTo: ELECTRON_ROOT
|
||||
});
|
||||
const clean = await runEslint(
|
||||
eslint,
|
||||
docs.filter(
|
||||
// TODO(dsanders11): Once we move to newer ESLint and the flat config,
|
||||
// switch to using `ignorePatterns` and `warnIgnore: false` instead of
|
||||
// explicitly filtering out this file that we don't want to lint
|
||||
(filename) => !filename.endsWith('docs/breaking-changes.md')
|
||||
),
|
||||
{ fix: opts.fix, verbose: opts.verbose }
|
||||
);
|
||||
errors ||= !clean;
|
||||
|
||||
if (errors) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
import * as fs from 'node:fs/promises';
|
||||
import * as path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
async function main () {
|
||||
const { values: { 'upload-stats': uploadStats } } = parseArgs({
|
||||
options: {
|
||||
'upload-stats': {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const config = JSON.parse(await fs.readFile(path.join(import.meta.dirname, '..', 'patches', 'config.json'), 'utf-8'));
|
||||
|
||||
let patchCount = 0;
|
||||
let patchesLineCount = 0;
|
||||
|
||||
const root = path.join(import.meta.dirname, '..', '..', '..');
|
||||
|
||||
for (const target of config) {
|
||||
const patchDirPath = path.join(root, target.patch_dir);
|
||||
const patches = await fs.readFile(path.join(patchDirPath, '.patches'), 'utf-8');
|
||||
|
||||
for (const patch of patches.trim().split('\n')) {
|
||||
patchCount++;
|
||||
const contents = await fs.readFile(path.join(patchDirPath, patch), 'utf-8');
|
||||
patchesLineCount += Array.from(contents.matchAll(/\n/g)).length;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`Total patches: ${patchCount}`);
|
||||
console.log(`Total lines in patches: ${patchesLineCount}`);
|
||||
|
||||
if (uploadStats) {
|
||||
if (!process.env.DD_API_KEY) {
|
||||
throw new Error('DD_API_KEY is not set');
|
||||
}
|
||||
|
||||
const timestamp = Math.round(new Date().getTime() / 1000);
|
||||
|
||||
await fetch('https://api.datadoghq.com/api/v2/series', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'DD-API-KEY': process.env.DD_API_KEY
|
||||
},
|
||||
body: JSON.stringify({
|
||||
series: [
|
||||
{
|
||||
metric: 'electron.patches.count',
|
||||
points: [
|
||||
{
|
||||
timestamp,
|
||||
value: patchCount
|
||||
}
|
||||
],
|
||||
type: 1 // COUNT
|
||||
},
|
||||
{
|
||||
metric: 'electron.patches.lineCount',
|
||||
points: [
|
||||
{
|
||||
timestamp,
|
||||
value: patchesLineCount
|
||||
}
|
||||
],
|
||||
type: 1 // COUNT
|
||||
}
|
||||
]
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if ((await fs.realpath(process.argv[1])) === fileURLToPath(import.meta.url)) {
|
||||
main()
|
||||
.then(() => {
|
||||
process.exit(0);
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(`ERROR: ${err.message}`);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
@@ -1204,7 +1204,7 @@ void BaseWindow::BuildPrototype(v8::Isolate* isolate,
|
||||
.SetMethod("isDocumentEdited", &BaseWindow::IsDocumentEdited)
|
||||
.SetMethod("setIgnoreMouseEvents", &BaseWindow::SetIgnoreMouseEvents)
|
||||
.SetMethod("setContentProtection", &BaseWindow::SetContentProtection)
|
||||
.SetMethod("isContentProtected", &BaseWindow::IsContentProtected)
|
||||
.SetMethod("_isContentProtected", &BaseWindow::IsContentProtected)
|
||||
.SetMethod("setFocusable", &BaseWindow::SetFocusable)
|
||||
.SetMethod("isFocusable", &BaseWindow::IsFocusable)
|
||||
.SetMethod("setMenu", &BaseWindow::SetMenu)
|
||||
|
||||
@@ -213,10 +213,6 @@ void Menu::SetRole(int index, const std::u16string& role) {
|
||||
model_->SetRole(index, role);
|
||||
}
|
||||
|
||||
void Menu::SetCustomType(int index, const std::u16string& customType) {
|
||||
model_->SetCustomType(index, customType);
|
||||
}
|
||||
|
||||
void Menu::Clear() {
|
||||
model_->Clear();
|
||||
}
|
||||
@@ -290,7 +286,6 @@ void Menu::FillObjectTemplate(v8::Isolate* isolate,
|
||||
.SetMethod("setSublabel", &Menu::SetSublabel)
|
||||
.SetMethod("setToolTip", &Menu::SetToolTip)
|
||||
.SetMethod("setRole", &Menu::SetRole)
|
||||
.SetMethod("setCustomType", &Menu::SetCustomType)
|
||||
.SetMethod("clear", &Menu::Clear)
|
||||
.SetMethod("getIndexOfCommandId", &Menu::GetIndexOfCommandId)
|
||||
.SetMethod("getItemCount", &Menu::GetItemCount)
|
||||
|
||||
@@ -116,7 +116,6 @@ class Menu : public gin::Wrappable<Menu>,
|
||||
void SetSublabel(int index, const std::u16string& sublabel);
|
||||
void SetToolTip(int index, const std::u16string& toolTip);
|
||||
void SetRole(int index, const std::u16string& role);
|
||||
void SetCustomType(int index, const std::u16string& customType);
|
||||
void Clear();
|
||||
int GetIndexOfCommandId(int command_id) const;
|
||||
int GetItemCount() const;
|
||||
|
||||
@@ -27,7 +27,7 @@ void MenuViews::PopupAt(BaseWindow* window,
|
||||
int positioning_item,
|
||||
ui::mojom::MenuSourceType source_type,
|
||||
base::OnceClosure callback) {
|
||||
const NativeWindow* native_window = window->window();
|
||||
auto* native_window = static_cast<NativeWindowViews*>(window->window());
|
||||
if (!native_window)
|
||||
return;
|
||||
|
||||
|
||||
@@ -34,6 +34,17 @@
|
||||
selector:@selector(onScreenUnlocked:)
|
||||
name:@"com.apple.screenIsUnlocked"
|
||||
object:nil];
|
||||
// A notification that the workspace posts before the machine goes to sleep.
|
||||
[distributed_center addObserver:self
|
||||
selector:@selector(isSuspending:)
|
||||
name:NSWorkspaceWillSleepNotification
|
||||
object:nil];
|
||||
// A notification that the workspace posts when the machine wakes from
|
||||
// sleep.
|
||||
[distributed_center addObserver:self
|
||||
selector:@selector(isResuming:)
|
||||
name:NSWorkspaceDidWakeNotification
|
||||
object:nil];
|
||||
|
||||
NSNotificationCenter* shared_center =
|
||||
[[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
@@ -62,6 +73,18 @@
|
||||
self->emitters.push_back(monitor_);
|
||||
}
|
||||
|
||||
- (void)isSuspending:(NSNotification*)notify {
|
||||
for (auto* emitter : self->emitters) {
|
||||
emitter->Emit("suspend");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)isResuming:(NSNotification*)notify {
|
||||
for (auto* emitter : self->emitters) {
|
||||
emitter->Emit("resume");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)onScreenLocked:(NSNotification*)notification {
|
||||
for (auto* emitter : self->emitters) {
|
||||
emitter->Emit("lock-screen");
|
||||
|
||||
@@ -88,6 +88,18 @@ LRESULT CALLBACK PowerMonitor::WndProc(HWND hwnd,
|
||||
base::Unretained(this)));
|
||||
}
|
||||
}
|
||||
} else if (message == WM_POWERBROADCAST) {
|
||||
if (wparam == PBT_APMRESUMEAUTOMATIC) {
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce([](PowerMonitor* pm) { pm->Emit("resume"); },
|
||||
base::Unretained(this)));
|
||||
} else if (wparam == PBT_APMSUSPEND) {
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce([](PowerMonitor* pm) { pm->Emit("suspend"); },
|
||||
base::Unretained(this)));
|
||||
}
|
||||
}
|
||||
return ::DefWindowProc(hwnd, message, wparam, lparam);
|
||||
}
|
||||
|
||||
@@ -2007,8 +2007,13 @@ void WebContents::ReadyToCommitNavigation(
|
||||
// Don't focus content in an inactive window.
|
||||
if (!owner_window())
|
||||
return;
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
if (!owner_window()->IsActive())
|
||||
return;
|
||||
#else
|
||||
if (!owner_window()->widget()->IsActive())
|
||||
return;
|
||||
#endif
|
||||
// Don't focus content after subframe navigations.
|
||||
if (!navigation_handle->IsInMainFrame())
|
||||
return;
|
||||
@@ -2147,11 +2152,8 @@ void WebContents::DevToolsOpened() {
|
||||
// Inherit owner window in devtools when it doesn't have one.
|
||||
auto* devtools = inspectable_web_contents_->GetDevToolsWebContents();
|
||||
bool has_window = devtools->GetUserData(NativeWindowRelay::UserDataKey());
|
||||
if (owner_window_ && !has_window) {
|
||||
DCHECK(!owner_window_.WasInvalidated());
|
||||
DCHECK_EQ(handle->owner_window(), nullptr);
|
||||
if (owner_window() && !has_window)
|
||||
handle->SetOwnerWindow(devtools, owner_window());
|
||||
}
|
||||
|
||||
Emit("devtools-opened");
|
||||
}
|
||||
|
||||
@@ -8,14 +8,12 @@
|
||||
#include <utility>
|
||||
|
||||
#include "base/auto_reset.h"
|
||||
#include "base/mac/mac_util.h"
|
||||
#include "base/observer_list.h"
|
||||
#include "base/strings/sys_string_conversions.h"
|
||||
#include "content/public/browser/browser_accessibility_state.h"
|
||||
#include "content/public/browser/native_event_processor_mac.h"
|
||||
#include "content/public/browser/native_event_processor_observer_mac.h"
|
||||
#include "content/public/browser/scoped_accessibility_mode.h"
|
||||
#include "content/public/common/content_features.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/mac/dict_util.h"
|
||||
#import "shell/browser/mac/electron_application_delegate.h"
|
||||
@@ -33,19 +31,12 @@ inline void dispatch_sync_main(dispatch_block_t block) {
|
||||
} // namespace
|
||||
|
||||
@interface AtomApplication () <NativeEventProcessor> {
|
||||
int _AXEnhancedUserInterfaceRequests;
|
||||
BOOL _voiceOverEnabled;
|
||||
BOOL _sonomaAccessibilityRefinementsAreActive;
|
||||
base::ObserverList<content::NativeEventProcessorObserver>::Unchecked
|
||||
observers_;
|
||||
}
|
||||
// Enables/disables screen reader support on changes to VoiceOver status.
|
||||
- (void)voiceOverStateChanged:(BOOL)voiceOverEnabled;
|
||||
@end
|
||||
|
||||
@implementation AtomApplication {
|
||||
std::unique_ptr<content::ScopedAccessibilityMode>
|
||||
_scoped_accessibility_mode_voiceover;
|
||||
std::unique_ptr<content::ScopedAccessibilityMode>
|
||||
_scoped_accessibility_mode_general;
|
||||
}
|
||||
@@ -54,37 +45,6 @@ inline void dispatch_sync_main(dispatch_block_t block) {
|
||||
return (AtomApplication*)[super sharedApplication];
|
||||
}
|
||||
|
||||
- (void)finishLaunching {
|
||||
[super finishLaunching];
|
||||
|
||||
_sonomaAccessibilityRefinementsAreActive =
|
||||
base::mac::MacOSVersion() >= 14'00'00 &&
|
||||
base::FeatureList::IsEnabled(
|
||||
features::kSonomaAccessibilityActivationRefinements);
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString*)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary*)change
|
||||
context:(void*)context {
|
||||
if ([keyPath isEqualToString:@"voiceOverEnabled"] &&
|
||||
context == content::BrowserAccessibilityState::GetInstance()) {
|
||||
NSNumber* newValueNumber = [change objectForKey:NSKeyValueChangeNewKey];
|
||||
DCHECK([newValueNumber isKindOfClass:[NSNumber class]]);
|
||||
|
||||
if ([newValueNumber isKindOfClass:[NSNumber class]]) {
|
||||
[self voiceOverStateChanged:[newValueNumber boolValue]];
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
[super observeValueForKeyPath:keyPath
|
||||
ofObject:object
|
||||
change:change
|
||||
context:context];
|
||||
}
|
||||
|
||||
- (void)willPowerOff:(NSNotification*)notify {
|
||||
userStoppedShutdown_ = shouldShutdown_ && !shouldShutdown_.Run();
|
||||
}
|
||||
@@ -254,7 +214,14 @@ inline void dispatch_sync_main(dispatch_block_t block) {
|
||||
- (void)accessibilitySetValue:(id)value forAttribute:(NSString*)attribute {
|
||||
bool is_manual_ax = [attribute isEqualToString:@"AXManualAccessibility"];
|
||||
if ([attribute isEqualToString:@"AXEnhancedUserInterface"] || is_manual_ax) {
|
||||
[self enableScreenReaderCompleteModeAfterDelay:[value boolValue]];
|
||||
if (![value boolValue]) {
|
||||
_scoped_accessibility_mode_general.reset();
|
||||
} else if (!_scoped_accessibility_mode_general) {
|
||||
_scoped_accessibility_mode_general =
|
||||
content::BrowserAccessibilityState::GetInstance()
|
||||
->CreateScopedModeForProcess(ui::kAXModeComplete);
|
||||
}
|
||||
|
||||
electron::Browser::Get()->OnAccessibilitySupportChanged();
|
||||
|
||||
// Don't call the superclass function for AXManualAccessibility,
|
||||
@@ -274,11 +241,8 @@ inline void dispatch_sync_main(dispatch_block_t block) {
|
||||
// recommends turning on a11y when an AT accesses the 'accessibilityRole'
|
||||
// property. This function is accessed frequently, so we only change the
|
||||
// accessibility state when accessibility is already disabled.
|
||||
if (!_scoped_accessibility_mode_general &&
|
||||
!_scoped_accessibility_mode_voiceover) {
|
||||
ui::AXMode target_mode = _sonomaAccessibilityRefinementsAreActive
|
||||
? ui::AXMode::kNativeAPIs
|
||||
: ui::kAXModeBasic;
|
||||
if (!_scoped_accessibility_mode_general) {
|
||||
ui::AXMode target_mode = ui::kAXModeBasic;
|
||||
_scoped_accessibility_mode_general =
|
||||
content::BrowserAccessibilityState::GetInstance()
|
||||
->CreateScopedModeForProcess(target_mode |
|
||||
@@ -288,82 +252,6 @@ inline void dispatch_sync_main(dispatch_block_t block) {
|
||||
return [super accessibilityRole];
|
||||
}
|
||||
|
||||
- (void)enableScreenReaderCompleteMode:(BOOL)enable {
|
||||
if (enable) {
|
||||
if (!_scoped_accessibility_mode_voiceover) {
|
||||
_scoped_accessibility_mode_voiceover =
|
||||
content::BrowserAccessibilityState::GetInstance()
|
||||
->CreateScopedModeForProcess(ui::kAXModeComplete |
|
||||
ui::AXMode::kFromPlatform |
|
||||
ui::AXMode::kScreenReader);
|
||||
}
|
||||
} else {
|
||||
_scoped_accessibility_mode_voiceover.reset();
|
||||
}
|
||||
}
|
||||
|
||||
// We need to call enableScreenReaderCompleteMode:YES from performSelector:...
|
||||
// but there's no way to supply a BOOL as a parameter, so we have this
|
||||
// explicit enable... helper method.
|
||||
- (void)enableScreenReaderCompleteMode {
|
||||
_AXEnhancedUserInterfaceRequests = 0;
|
||||
[self enableScreenReaderCompleteMode:YES];
|
||||
}
|
||||
|
||||
- (void)voiceOverStateChanged:(BOOL)voiceOverEnabled {
|
||||
_voiceOverEnabled = voiceOverEnabled;
|
||||
|
||||
[self enableScreenReaderCompleteMode:voiceOverEnabled];
|
||||
}
|
||||
|
||||
// Enables or disables screen reader support for non-VoiceOver assistive
|
||||
// technology (AT), possibly after a delay.
|
||||
//
|
||||
// Now that we directly monitor VoiceOver status, we no longer watch for
|
||||
// changes to AXEnhancedUserInterface for that signal from VO. However, other
|
||||
// AT can set a value for AXEnhancedUserInterface, so we can't ignore it.
|
||||
// Unfortunately, as of macOS Sonoma, we sometimes see spurious changes to
|
||||
// AXEnhancedUserInterface (quick on and off). We debounce by waiting for these
|
||||
// changes to settle down before updating the screen reader state.
|
||||
- (void)enableScreenReaderCompleteModeAfterDelay:(BOOL)enable {
|
||||
// If VoiceOver is already explicitly enabled, ignore requests from other AT.
|
||||
if (_voiceOverEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If this is a request to disable screen reader support, and we haven't seen
|
||||
// a corresponding enable request, go ahead and disable.
|
||||
if (!enable && _AXEnhancedUserInterfaceRequests == 0) {
|
||||
[self enableScreenReaderCompleteMode:NO];
|
||||
return;
|
||||
}
|
||||
|
||||
// Use a counter to track requests for changes to the screen reader state.
|
||||
if (enable) {
|
||||
_AXEnhancedUserInterfaceRequests++;
|
||||
} else {
|
||||
_AXEnhancedUserInterfaceRequests--;
|
||||
}
|
||||
|
||||
DCHECK_GE(_AXEnhancedUserInterfaceRequests, 0);
|
||||
|
||||
// _AXEnhancedUserInterfaceRequests > 0 means we want to enable screen
|
||||
// reader support, but we'll delay that action until there are no more state
|
||||
// change requests within a two-second window. Cancel any pending
|
||||
// performSelector:..., and schedule a new one to restart the countdown.
|
||||
[NSObject cancelPreviousPerformRequestsWithTarget:self
|
||||
selector:@selector
|
||||
(enableScreenReaderCompleteMode)
|
||||
object:nil];
|
||||
|
||||
if (_AXEnhancedUserInterfaceRequests > 0) {
|
||||
const float kTwoSecondDelay = 2.0;
|
||||
[self performSelector:@selector(enableScreenReaderCompleteMode)
|
||||
withObject:nil
|
||||
afterDelay:kTwoSecondDelay];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)orderFrontStandardAboutPanel:(id)sender {
|
||||
electron::Browser::Get()->ShowAboutPanel();
|
||||
}
|
||||
|
||||
@@ -75,12 +75,10 @@ namespace electron {
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
gfx::Size GetExpandedWindowSize(const NativeWindow* window,
|
||||
bool transparent,
|
||||
gfx::Size size) {
|
||||
gfx::Size GetExpandedWindowSize(const NativeWindow* window, gfx::Size size) {
|
||||
if (!base::FeatureList::IsEnabled(
|
||||
views::features::kEnableTransparentHwndEnlargement) ||
|
||||
!transparent) {
|
||||
!window->transparent()) {
|
||||
return size;
|
||||
}
|
||||
|
||||
@@ -95,43 +93,51 @@ gfx::Size GetExpandedWindowSize(const NativeWindow* window,
|
||||
}
|
||||
#endif
|
||||
|
||||
[[nodiscard]] bool GetHasTitlebarOverlay(const gin_helper::Dictionary& opts) {
|
||||
if (bool flag; opts.Get(options::ktitleBarOverlay, &flag))
|
||||
return flag;
|
||||
gin_helper::Dictionary dict;
|
||||
return opts.Get(options::ktitleBarOverlay, &dict);
|
||||
}
|
||||
|
||||
[[nodiscard]] std::optional<int> GetTitlebarOverlayHeight(
|
||||
const gin_helper::Dictionary& opts) {
|
||||
if (gin_helper::Dictionary dict; opts.Get(options::ktitleBarOverlay, &dict)) {
|
||||
if (int height; dict.Get(options::kOverlayHeight, &height))
|
||||
return height;
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NativeWindow::NativeWindow(const gin_helper::Dictionary& options,
|
||||
NativeWindow* parent)
|
||||
: title_bar_style_{options.ValueOrDefault(options::kTitleBarStyle,
|
||||
TitleBarStyle::kNormal)},
|
||||
transparent_{options.ValueOrDefault(options::kTransparent, false)},
|
||||
enable_larger_than_screen_{
|
||||
options.ValueOrDefault(options::kEnableLargerThanScreen, false)},
|
||||
is_modal_{parent != nullptr && options.ValueOrDefault("modal", false)},
|
||||
has_frame_{options.ValueOrDefault(options::kFrame, true) &&
|
||||
title_bar_style_ == TitleBarStyle::kNormal},
|
||||
titlebar_overlay_{GetHasTitlebarOverlay(options)},
|
||||
titlebar_overlay_height_{GetTitlebarOverlayHeight(options).value_or(0)},
|
||||
parent_{parent} {
|
||||
: widget_(std::make_unique<views::Widget>()), parent_(parent) {
|
||||
options.Get(options::kFrame, &has_frame_);
|
||||
options.Get(options::kTransparent, &transparent_);
|
||||
options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_);
|
||||
options.Get(options::kTitleBarStyle, &title_bar_style_);
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
options.Get(options::kBackgroundMaterial, &background_material_);
|
||||
#elif BUILDFLAG(IS_MAC)
|
||||
options.Get(options::kVibrancyType, &vibrancy_);
|
||||
#endif
|
||||
|
||||
v8::Local<v8::Value> titlebar_overlay;
|
||||
if (options.Get(options::ktitleBarOverlay, &titlebar_overlay)) {
|
||||
if (titlebar_overlay->IsBoolean()) {
|
||||
options.Get(options::ktitleBarOverlay, &titlebar_overlay_);
|
||||
} else if (titlebar_overlay->IsObject()) {
|
||||
titlebar_overlay_ = true;
|
||||
|
||||
auto titlebar_overlay_dict =
|
||||
gin_helper::Dictionary::CreateEmpty(options.isolate());
|
||||
options.Get(options::ktitleBarOverlay, &titlebar_overlay_dict);
|
||||
int height;
|
||||
if (titlebar_overlay_dict.Get(options::kOverlayHeight, &height))
|
||||
titlebar_overlay_height_ = height;
|
||||
}
|
||||
}
|
||||
|
||||
if (parent)
|
||||
options.Get("modal", &is_modal_);
|
||||
|
||||
#if defined(USE_OZONE)
|
||||
// Ozone X11 likes to prefer custom frames, but we don't need them unless
|
||||
// on Wayland.
|
||||
if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations) &&
|
||||
!ui::OzonePlatform::GetInstance()
|
||||
->GetPlatformRuntimeProperties()
|
||||
.supports_server_side_window_decorations) {
|
||||
has_client_frame_ = true;
|
||||
}
|
||||
#endif
|
||||
|
||||
WindowList::AddWindow(this);
|
||||
}
|
||||
|
||||
@@ -411,15 +417,14 @@ gfx::Size NativeWindow::GetContentMinimumSize() const {
|
||||
}
|
||||
|
||||
gfx::Size NativeWindow::GetContentMaximumSize() const {
|
||||
const auto size_constraints = GetContentSizeConstraints();
|
||||
gfx::Size maximum_size = size_constraints.GetMaximumSize();
|
||||
|
||||
gfx::Size maximum_size = GetContentSizeConstraints().GetMaximumSize();
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
if (size_constraints.HasMaximumSize())
|
||||
maximum_size = GetExpandedWindowSize(this, transparent(), maximum_size);
|
||||
#endif
|
||||
|
||||
return GetContentSizeConstraints().HasMaximumSize()
|
||||
? GetExpandedWindowSize(this, maximum_size)
|
||||
: maximum_size;
|
||||
#else
|
||||
return maximum_size;
|
||||
#endif
|
||||
}
|
||||
|
||||
void NativeWindow::SetSheetOffset(const double offsetX, const double offsetY) {
|
||||
@@ -832,22 +837,6 @@ bool NativeWindow::IsTranslucent() const {
|
||||
return false;
|
||||
}
|
||||
|
||||
// static
|
||||
bool NativeWindow::PlatformHasClientFrame() {
|
||||
#if defined(USE_OZONE)
|
||||
// Ozone X11 likes to prefer custom frames,
|
||||
// but we don't need them unless on Wayland.
|
||||
static const bool has_client_frame =
|
||||
base::FeatureList::IsEnabled(features::kWaylandWindowDecorations) &&
|
||||
!ui::OzonePlatform::GetInstance()
|
||||
->GetPlatformRuntimeProperties()
|
||||
.supports_server_side_window_decorations;
|
||||
return has_client_frame;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
// static
|
||||
void NativeWindowRelay::CreateForWebContents(
|
||||
content::WebContents* web_contents,
|
||||
|
||||
@@ -8,11 +8,11 @@
|
||||
#include <list>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <queue>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/queue.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/weak_ptr.h"
|
||||
#include "base/observer_list.h"
|
||||
@@ -157,10 +157,10 @@ class NativeWindow : public base::SupportsUserData,
|
||||
virtual ui::ZOrderLevel GetZOrderLevel() const = 0;
|
||||
virtual void Center() = 0;
|
||||
virtual void Invalidate() = 0;
|
||||
[[nodiscard]] virtual bool IsActive() const = 0;
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
virtual std::string GetAlwaysOnTopLevel() const = 0;
|
||||
virtual void SetActive(bool is_key) = 0;
|
||||
virtual bool IsActive() const = 0;
|
||||
virtual void RemoveChildFromParentWindow() = 0;
|
||||
virtual void RemoveChildWindow(NativeWindow* child) = 0;
|
||||
virtual void AttachChildren() = 0;
|
||||
@@ -374,18 +374,14 @@ class NativeWindow : public base::SupportsUserData,
|
||||
views::Widget* widget() const { return widget_.get(); }
|
||||
views::View* content_view() const { return content_view_; }
|
||||
|
||||
enum class TitleBarStyle : uint8_t {
|
||||
enum class TitleBarStyle {
|
||||
kNormal,
|
||||
kHidden,
|
||||
kHiddenInset,
|
||||
kCustomButtonsOnHover,
|
||||
};
|
||||
|
||||
[[nodiscard]] TitleBarStyle title_bar_style() const {
|
||||
return title_bar_style_;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_titlebar_overlay() const { return titlebar_overlay_; }
|
||||
TitleBarStyle title_bar_style() const { return title_bar_style_; }
|
||||
|
||||
bool IsWindowControlsOverlayEnabled() const {
|
||||
bool valid_titlebar_style = title_bar_style() == TitleBarStyle::kHidden
|
||||
@@ -394,16 +390,19 @@ class NativeWindow : public base::SupportsUserData,
|
||||
title_bar_style() == TitleBarStyle::kHiddenInset
|
||||
#endif
|
||||
;
|
||||
return valid_titlebar_style && has_titlebar_overlay();
|
||||
return valid_titlebar_style && titlebar_overlay_;
|
||||
}
|
||||
|
||||
int titlebar_overlay_height() const { return titlebar_overlay_height_; }
|
||||
|
||||
[[nodiscard]] bool has_frame() const { return has_frame_; }
|
||||
bool has_frame() const { return has_frame_; }
|
||||
|
||||
bool has_client_frame() const { return has_client_frame_; }
|
||||
bool transparent() const { return transparent_; }
|
||||
bool enable_larger_than_screen() const { return enable_larger_than_screen_; }
|
||||
|
||||
NativeWindow* parent() const { return parent_; }
|
||||
|
||||
[[nodiscard]] bool is_modal() const { return is_modal_; }
|
||||
bool is_modal() const { return is_modal_; }
|
||||
|
||||
[[nodiscard]] constexpr int32_t window_id() const { return window_id_; }
|
||||
|
||||
@@ -429,21 +428,15 @@ class NativeWindow : public base::SupportsUserData,
|
||||
void UpdateBackgroundThrottlingState();
|
||||
|
||||
protected:
|
||||
NativeWindow(const gin_helper::Dictionary& options, NativeWindow* parent);
|
||||
|
||||
void set_titlebar_overlay_height(int height) {
|
||||
titlebar_overlay_height_ = height;
|
||||
}
|
||||
|
||||
[[nodiscard]] bool has_client_frame() const { return has_client_frame_; }
|
||||
constexpr void set_has_frame(const bool val) { has_frame_ = val; }
|
||||
|
||||
[[nodiscard]] bool transparent() const { return transparent_; }
|
||||
[[nodiscard]] constexpr bool is_closed() const { return is_closed_; }
|
||||
|
||||
[[nodiscard]] bool is_closed() const { return is_closed_; }
|
||||
|
||||
[[nodiscard]] bool enable_larger_than_screen() const {
|
||||
return enable_larger_than_screen_;
|
||||
}
|
||||
NativeWindow(const gin_helper::Dictionary& options, NativeWindow* parent);
|
||||
|
||||
virtual void OnTitleChanged() {}
|
||||
|
||||
@@ -465,6 +458,12 @@ class NativeWindow : public base::SupportsUserData,
|
||||
static inline constexpr base::cstring_view kNativeWindowKey =
|
||||
"__ELECTRON_NATIVE_WINDOW__";
|
||||
|
||||
// The boolean parsing of the "titleBarOverlay" option
|
||||
bool titlebar_overlay_ = false;
|
||||
|
||||
// The "titleBarStyle" option.
|
||||
TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal;
|
||||
|
||||
// Minimum and maximum size.
|
||||
std::optional<extensions::SizeConstraints> size_constraints_;
|
||||
// Same as above but stored as content size, we are storing 2 types of size
|
||||
@@ -472,39 +471,18 @@ class NativeWindow : public base::SupportsUserData,
|
||||
// on HiDPI displays on some environments.
|
||||
std::optional<extensions::SizeConstraints> content_size_constraints_;
|
||||
|
||||
std::queue<bool> pending_transitions_;
|
||||
FullScreenTransitionType fullscreen_transition_type_ =
|
||||
FullScreenTransitionType::kNone;
|
||||
|
||||
std::list<NativeWindow*> child_windows_;
|
||||
|
||||
private:
|
||||
static bool PlatformHasClientFrame();
|
||||
|
||||
std::unique_ptr<views::Widget> widget_ = std::make_unique<views::Widget>();
|
||||
std::unique_ptr<views::Widget> widget_;
|
||||
|
||||
static inline int32_t next_id_ = 0;
|
||||
const int32_t window_id_ = ++next_id_;
|
||||
|
||||
// The "titleBarStyle" option.
|
||||
const TitleBarStyle title_bar_style_;
|
||||
|
||||
// Whether window has standard frame, but it's drawn by Electron (the client
|
||||
// application) instead of the OS. Currently only has meaning on Linux for
|
||||
// Wayland hosts.
|
||||
const bool has_client_frame_ = PlatformHasClientFrame();
|
||||
|
||||
// Whether window is transparent.
|
||||
const bool transparent_;
|
||||
|
||||
// Whether window can be resized larger than screen.
|
||||
const bool enable_larger_than_screen_;
|
||||
|
||||
// Is this a modal window.
|
||||
const bool is_modal_;
|
||||
|
||||
// Whether window has standard frame.
|
||||
const bool has_frame_;
|
||||
|
||||
// The boolean parsing of the "titleBarOverlay" option
|
||||
const bool titlebar_overlay_ = false;
|
||||
|
||||
// The content view, weak ref.
|
||||
raw_ptr<views::View> content_view_ = nullptr;
|
||||
|
||||
@@ -512,6 +490,20 @@ class NativeWindow : public base::SupportsUserData,
|
||||
// "titleBarOverlay"
|
||||
int titlebar_overlay_height_ = 0;
|
||||
|
||||
// Whether window has standard frame.
|
||||
bool has_frame_ = true;
|
||||
|
||||
// Whether window has standard frame, but it's drawn by Electron (the client
|
||||
// application) instead of the OS. Currently only has meaning on Linux for
|
||||
// Wayland hosts.
|
||||
bool has_client_frame_ = false;
|
||||
|
||||
// Whether window is transparent.
|
||||
bool transparent_ = false;
|
||||
|
||||
// Whether window can be resized larger than screen.
|
||||
bool enable_larger_than_screen_ = false;
|
||||
|
||||
// The windows has been closed.
|
||||
bool is_closed_ = false;
|
||||
|
||||
@@ -528,6 +520,9 @@ class NativeWindow : public base::SupportsUserData,
|
||||
// The parent window, it is guaranteed to be valid during this window's life.
|
||||
raw_ptr<NativeWindow> parent_ = nullptr;
|
||||
|
||||
// Is this a modal window.
|
||||
bool is_modal_ = false;
|
||||
|
||||
bool is_transitioning_fullscreen_ = false;
|
||||
|
||||
std::list<DraggableRegionProvider*> draggable_region_providers_;
|
||||
@@ -543,11 +538,6 @@ class NativeWindow : public base::SupportsUserData,
|
||||
|
||||
gfx::Rect overlay_rect_;
|
||||
|
||||
base::queue<bool> pending_transitions_;
|
||||
|
||||
FullScreenTransitionType fullscreen_transition_type_ =
|
||||
FullScreenTransitionType::kNone;
|
||||
|
||||
base::WeakPtrFactory<NativeWindow> weak_factory_{this};
|
||||
};
|
||||
|
||||
|
||||
@@ -151,6 +151,10 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
|
||||
const bool paint_when_initially_hidden =
|
||||
options.ValueOrDefault(options::kPaintWhenInitiallyHidden, true);
|
||||
|
||||
// The window without titlebar is treated the same with frameless window.
|
||||
if (title_bar_style_ != TitleBarStyle::kNormal)
|
||||
set_has_frame(false);
|
||||
|
||||
NSUInteger styleMask = NSWindowStyleMaskTitled;
|
||||
|
||||
// The NSWindowStyleMaskFullSizeContentView style removes rounded corners
|
||||
@@ -248,20 +252,20 @@ NativeWindowMac::NativeWindowMac(const gin_helper::Dictionary& options,
|
||||
// https://github.com/electron/electron/issues/517.
|
||||
[window_ setOpaque:NO];
|
||||
// Show window buttons if titleBarStyle is not "normal".
|
||||
if (title_bar_style() == TitleBarStyle::kNormal) {
|
||||
if (title_bar_style_ == TitleBarStyle::kNormal) {
|
||||
InternalSetWindowButtonVisibility(false);
|
||||
} else {
|
||||
buttons_proxy_ = [[WindowButtonsProxy alloc] initWithWindow:window_];
|
||||
[buttons_proxy_ setHeight:titlebar_overlay_height()];
|
||||
if (traffic_light_position_) {
|
||||
[buttons_proxy_ setMargin:*traffic_light_position_];
|
||||
} else if (title_bar_style() == TitleBarStyle::kHiddenInset) {
|
||||
} else if (title_bar_style_ == TitleBarStyle::kHiddenInset) {
|
||||
// For macOS >= 11, while this value does not match official macOS apps
|
||||
// like Safari or Notes, it matches titleBarStyle's old implementation
|
||||
// before Electron <= 12.
|
||||
[buttons_proxy_ setMargin:gfx::Point(12, 11)];
|
||||
}
|
||||
if (title_bar_style() == TitleBarStyle::kCustomButtonsOnHover) {
|
||||
if (title_bar_style_ == TitleBarStyle::kCustomButtonsOnHover) {
|
||||
[buttons_proxy_ setShowOnHover:YES];
|
||||
} else {
|
||||
// customButtonsOnHover does not show buttons initially.
|
||||
@@ -1049,7 +1053,7 @@ void NativeWindowMac::SetSimpleFullScreen(bool simple_fullscreen) {
|
||||
if (has_frame())
|
||||
visibility = true;
|
||||
else
|
||||
visibility = title_bar_style() != TitleBarStyle::kNormal;
|
||||
visibility = title_bar_style_ != TitleBarStyle::kNormal;
|
||||
InternalSetWindowButtonVisibility(visibility);
|
||||
}
|
||||
|
||||
@@ -1477,7 +1481,7 @@ void NativeWindowMac::SetWindowButtonVisibility(bool visible) {
|
||||
[buttons_proxy_ setVisible:visible];
|
||||
}
|
||||
|
||||
if (title_bar_style() != TitleBarStyle::kCustomButtonsOnHover)
|
||||
if (title_bar_style_ != TitleBarStyle::kCustomButtonsOnHover)
|
||||
InternalSetWindowButtonVisibility(visible);
|
||||
|
||||
NotifyLayoutWindowControlsOverlay();
|
||||
@@ -1827,7 +1831,7 @@ void NativeWindowMac::SetForwardMouseMessages(bool forward) {
|
||||
}
|
||||
|
||||
std::optional<gfx::Rect> NativeWindowMac::GetWindowControlsOverlayRect() {
|
||||
if (!has_titlebar_overlay())
|
||||
if (!titlebar_overlay_)
|
||||
return std::nullopt;
|
||||
|
||||
// On macOS, when in fullscreen mode, window controls (the menu bar, title
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "base/containers/fixed_flat_set.h"
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/memory/raw_ref.h"
|
||||
#include "base/numerics/ranges.h"
|
||||
#include "base/strings/utf_string_conversions.h"
|
||||
@@ -241,6 +241,10 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
|
||||
}
|
||||
}
|
||||
|
||||
// |hidden| is the only non-default titleBarStyle valid on Windows and Linux.
|
||||
if (title_bar_style_ == TitleBarStyle::kHidden)
|
||||
set_has_frame(false);
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// If the taskbar is re-created after we start up, we have to rebuild all of
|
||||
// our buttons.
|
||||
@@ -286,7 +290,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
|
||||
if (parent)
|
||||
params.parent = parent->GetNativeWindow();
|
||||
|
||||
params.native_widget = new ElectronDesktopNativeWidgetAura{this, widget()};
|
||||
params.native_widget = new ElectronDesktopNativeWidgetAura(this);
|
||||
#elif BUILDFLAG(IS_LINUX)
|
||||
std::string name = Browser::Get()->GetName();
|
||||
// Set WM_WINDOW_ROLE.
|
||||
@@ -300,7 +304,7 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options,
|
||||
auto* native_widget = new views::DesktopNativeWidgetAura(widget());
|
||||
params.native_widget = native_widget;
|
||||
params.desktop_window_tree_host =
|
||||
new ElectronDesktopWindowTreeHostLinux{this, widget(), native_widget};
|
||||
new ElectronDesktopWindowTreeHostLinux(this, native_widget);
|
||||
#endif
|
||||
|
||||
widget()->Init(std::move(params));
|
||||
@@ -1157,9 +1161,9 @@ void NativeWindowViews::SetAlwaysOnTop(ui::ZOrderLevel z_order,
|
||||
if (z_order != ui::ZOrderLevel::kNormal) {
|
||||
// On macOS the window is placed behind the Dock for the following levels.
|
||||
// Re-use the same names on Windows to make it easier for the user.
|
||||
static constexpr auto levels = base::MakeFixedFlatSet<std::string_view>(
|
||||
{"floating", "torn-off-menu", "modal-panel", "main-menu", "status"});
|
||||
behind_task_bar_ = levels.contains(level);
|
||||
static const std::vector<std::string> levels = {
|
||||
"floating", "torn-off-menu", "modal-panel", "main-menu", "status"};
|
||||
behind_task_bar_ = base::Contains(levels, level);
|
||||
}
|
||||
#endif
|
||||
MoveBehindTaskBarIfNeeded();
|
||||
@@ -1198,11 +1202,6 @@ void NativeWindowViews::Invalidate() {
|
||||
widget()->SchedulePaintInRect(gfx::Rect(GetBounds().size()));
|
||||
}
|
||||
|
||||
bool NativeWindowViews::IsActive() const {
|
||||
views::Widget* const widget = this->widget();
|
||||
return widget && widget->IsActive();
|
||||
}
|
||||
|
||||
void NativeWindowViews::FlashFrame(bool flash) {
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
// The Chromium's implementation has a bug stopping flash.
|
||||
@@ -1846,19 +1845,6 @@ NativeWindowViews::CreateNonClientFrameView(views::Widget* widget) {
|
||||
#endif
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
electron::ClientFrameViewLinux* NativeWindowViews::GetClientFrameViewLinux() {
|
||||
// Check to make sure this window's non-client frame view is a
|
||||
// ClientFrameViewLinux. If either has_frame() or has_client_frame()
|
||||
// are false, it will be an OpaqueFrameView or NativeFrameView instead.
|
||||
// See NativeWindowViews::CreateNonClientFrameView.
|
||||
if (!has_frame() || !has_client_frame())
|
||||
return {};
|
||||
return static_cast<ClientFrameViewLinux*>(
|
||||
widget()->non_client_view()->frame_view());
|
||||
}
|
||||
#endif
|
||||
|
||||
void NativeWindowViews::OnWidgetMove() {
|
||||
NotifyWindowMove();
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class Arguments;
|
||||
namespace electron {
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
class ClientFrameViewLinux;
|
||||
class GlobalMenuBarX11;
|
||||
#endif
|
||||
|
||||
@@ -104,7 +103,6 @@ class NativeWindowViews : public NativeWindow,
|
||||
ui::ZOrderLevel GetZOrderLevel() const override;
|
||||
void Center() override;
|
||||
void Invalidate() override;
|
||||
[[nodiscard]] bool IsActive() const override;
|
||||
void FlashFrame(bool flash) override;
|
||||
void SetSkipTaskbar(bool skip) override;
|
||||
void SetExcludedFromShownWindowsMenu(bool excluded) override {}
|
||||
@@ -183,12 +181,6 @@ class NativeWindowViews : public NativeWindow,
|
||||
SkColor overlay_button_color() const { return overlay_button_color_; }
|
||||
SkColor overlay_symbol_color() const { return overlay_symbol_color_; }
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
// returns the ClientFrameViewLinux iff that is our NonClientFrameView type,
|
||||
// nullptr otherwise.
|
||||
ClientFrameViewLinux* GetClientFrameViewLinux();
|
||||
#endif
|
||||
|
||||
private:
|
||||
void set_overlay_button_color(SkColor color) {
|
||||
overlay_button_color_ = color;
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
#ifndef ELECTRON_SHELL_BROWSER_NET_RESOLVE_PROXY_HELPER_H_
|
||||
#define ELECTRON_SHELL_BROWSER_NET_RESOLVE_PROXY_HELPER_H_
|
||||
|
||||
#include <deque>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
#include "base/containers/circular_deque.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/memory/ref_counted.h"
|
||||
#include "mojo/public/cpp/bindings/receiver.h"
|
||||
@@ -66,7 +66,7 @@ class ResolveProxyHelper
|
||||
// Self-reference. Owned as long as there's an outstanding proxy lookup.
|
||||
scoped_refptr<ResolveProxyHelper> owned_self_;
|
||||
|
||||
base::circular_deque<PendingRequest> pending_requests_;
|
||||
std::deque<PendingRequest> pending_requests_;
|
||||
// Receiver for the currently in-progress request, if any.
|
||||
mojo::Receiver<network::mojom::ProxyLookupClient> receiver_{this};
|
||||
|
||||
|
||||
@@ -330,17 +330,6 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||
}
|
||||
}
|
||||
|
||||
std::u16string role = model->GetRoleAt(index);
|
||||
electron::ElectronMenuModel::ItemType type = model->GetTypeAt(index);
|
||||
std::u16string customType = model->GetCustomTypeAt(index);
|
||||
|
||||
// The sectionHeaderWithTitle menu item is only available in macOS 14.0+.
|
||||
if (@available(macOS 14, *)) {
|
||||
if (customType == u"header") {
|
||||
item = [NSMenuItem sectionHeaderWithTitle:label];
|
||||
}
|
||||
}
|
||||
|
||||
// If the menu item has an icon, set it.
|
||||
ui::ImageModel icon = model->GetIconAt(index);
|
||||
if (icon.IsImage())
|
||||
@@ -349,6 +338,9 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||
std::u16string toolTip = model->GetToolTipAt(index);
|
||||
[item setToolTip:base::SysUTF16ToNSString(toolTip)];
|
||||
|
||||
std::u16string role = model->GetRoleAt(index);
|
||||
electron::ElectronMenuModel::ItemType type = model->GetTypeAt(index);
|
||||
|
||||
if (role == u"services") {
|
||||
std::u16string title = u"Services";
|
||||
NSString* sub_label = l10n_util::FixUpWindowsStyleLabel(title);
|
||||
@@ -380,14 +372,6 @@ NSArray* ConvertSharingItemToNS(const SharingItem& item) {
|
||||
NSMenu* submenu = MenuHasVisibleItems(submenuModel)
|
||||
? [self menuFromModel:submenuModel]
|
||||
: MakeEmptySubmenu();
|
||||
|
||||
// NSMenuPresentationStylePalette is only available in macOS 14.0+.
|
||||
if (@available(macOS 14, *)) {
|
||||
if (customType == u"palette") {
|
||||
submenu.presentationStyle = NSMenuPresentationStylePalette;
|
||||
}
|
||||
}
|
||||
|
||||
[submenu setTitle:[item title]];
|
||||
[item setSubmenu:submenu];
|
||||
|
||||
|
||||
@@ -33,10 +33,10 @@ namespace electron {
|
||||
|
||||
ElectronDesktopWindowTreeHostLinux::ElectronDesktopWindowTreeHostLinux(
|
||||
NativeWindowViews* native_window_view,
|
||||
views::Widget* widget,
|
||||
views::DesktopNativeWidgetAura* desktop_native_widget_aura)
|
||||
: views::DesktopWindowTreeHostLinux{widget, desktop_native_widget_aura},
|
||||
native_window_view_{native_window_view} {}
|
||||
: views::DesktopWindowTreeHostLinux(native_window_view->widget(),
|
||||
desktop_native_widget_aura),
|
||||
native_window_view_(native_window_view) {}
|
||||
|
||||
ElectronDesktopWindowTreeHostLinux::~ElectronDesktopWindowTreeHostLinux() =
|
||||
default;
|
||||
@@ -64,9 +64,13 @@ gfx::Insets ElectronDesktopWindowTreeHostLinux::CalculateInsetsInDIP(
|
||||
return gfx::Insets();
|
||||
}
|
||||
|
||||
auto* const view = native_window_view_->GetClientFrameViewLinux();
|
||||
if (!view)
|
||||
return {};
|
||||
if (!native_window_view_->has_frame() ||
|
||||
!native_window_view_->has_client_frame()) {
|
||||
return gfx::Insets();
|
||||
}
|
||||
|
||||
auto* view = static_cast<ClientFrameViewLinux*>(
|
||||
native_window_view_->widget()->non_client_view()->frame_view());
|
||||
|
||||
gfx::Insets insets = view->RestoredMirroredFrameBorderInsets();
|
||||
if (base::i18n::IsRTL())
|
||||
@@ -98,12 +102,19 @@ void ElectronDesktopWindowTreeHostLinux::OnWindowStateChanged(
|
||||
|
||||
void ElectronDesktopWindowTreeHostLinux::OnWindowTiledStateChanged(
|
||||
ui::WindowTiledEdges new_tiled_edges) {
|
||||
if (auto* const view = native_window_view_->GetClientFrameViewLinux()) {
|
||||
// CreateNonClientFrameView creates `ClientFrameViewLinux` only when both
|
||||
// frame and client_frame booleans are set, otherwise it is a different type
|
||||
// of view.
|
||||
if (native_window_view_->has_frame() &&
|
||||
native_window_view_->has_client_frame()) {
|
||||
ClientFrameViewLinux* frame = static_cast<ClientFrameViewLinux*>(
|
||||
native_window_view_->widget()->non_client_view()->frame_view());
|
||||
|
||||
bool maximized = new_tiled_edges.top && new_tiled_edges.left &&
|
||||
new_tiled_edges.bottom && new_tiled_edges.right;
|
||||
bool tiled = new_tiled_edges.top || new_tiled_edges.left ||
|
||||
new_tiled_edges.bottom || new_tiled_edges.right;
|
||||
view->set_tiled(tiled && !maximized);
|
||||
frame->set_tiled(tiled && !maximized);
|
||||
}
|
||||
UpdateFrameHints();
|
||||
}
|
||||
@@ -155,13 +166,15 @@ void ElectronDesktopWindowTreeHostLinux::OnDeviceScaleFactorChanged() {
|
||||
|
||||
void ElectronDesktopWindowTreeHostLinux::UpdateFrameHints() {
|
||||
if (base::FeatureList::IsEnabled(features::kWaylandWindowDecorations)) {
|
||||
auto* const view = native_window_view_->GetClientFrameViewLinux();
|
||||
if (!view)
|
||||
if (!native_window_view_->has_frame() ||
|
||||
!native_window_view_->has_client_frame())
|
||||
return;
|
||||
|
||||
ui::PlatformWindow* window = platform_window();
|
||||
auto window_state = window->GetPlatformWindowState();
|
||||
float scale = device_scale_factor();
|
||||
auto* view = static_cast<ClientFrameViewLinux*>(
|
||||
native_window_view_->widget()->non_client_view()->frame_view());
|
||||
const gfx::Size widget_size =
|
||||
view->GetWidget()->GetWindowBoundsInScreen().size();
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user