mirror of
https://github.com/electron/electron.git
synced 2026-04-10 03:01:51 -04:00
Compare commits
163 Commits
42-x-y
...
remove-dec
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f7a20e3c9 | ||
|
|
7245c6a3f0 | ||
|
|
b484b0bde9 | ||
|
|
6c8a910232 | ||
|
|
cc3d4f5f58 | ||
|
|
b711ce7b04 | ||
|
|
adf9a6e303 | ||
|
|
6744293e96 | ||
|
|
0d3342debf | ||
|
|
157cdac4b9 | ||
|
|
4dfada86ce | ||
|
|
df81a1d4ac | ||
|
|
c3e3958668 | ||
|
|
afd5fb4a60 | ||
|
|
8679522922 | ||
|
|
0828de3ccd | ||
|
|
6b5a4ff66c | ||
|
|
ca28023d4d | ||
|
|
e60441ad60 | ||
|
|
a189425373 | ||
|
|
7eccea1315 | ||
|
|
2e74ad2c68 | ||
|
|
9ba299afff | ||
|
|
6df2228ea0 | ||
|
|
a29674e4cf | ||
|
|
81dd0f42e1 | ||
|
|
6aaf490aa5 | ||
|
|
b8f25c4ced | ||
|
|
9fafc81e88 | ||
|
|
4d05010945 | ||
|
|
c3189e9886 | ||
|
|
983ebdd6de | ||
|
|
b9c08ef9c2 | ||
|
|
9f3cc9122c | ||
|
|
e66e4ca02c | ||
|
|
6ed3198ba8 | ||
|
|
903e65e048 | ||
|
|
fef2fd2941 | ||
|
|
4d8fd31e5f | ||
|
|
96486a4102 | ||
|
|
3f8238b92c | ||
|
|
64c5440eec | ||
|
|
30cf60a935 | ||
|
|
ec30e4cdae | ||
|
|
40033db422 | ||
|
|
c3d441cf7d | ||
|
|
14583d22e6 | ||
|
|
68bfe49120 | ||
|
|
2c6332a7d6 | ||
|
|
ddc1bd9553 | ||
|
|
12109371d3 | ||
|
|
69891d04bf | ||
|
|
188813e206 | ||
|
|
8b768b8211 | ||
|
|
82b97ddf5b | ||
|
|
16f408a502 | ||
|
|
246aa63910 | ||
|
|
230f02faf2 | ||
|
|
1362d7b94d | ||
|
|
877fe479b5 | ||
|
|
f41438ff73 | ||
|
|
c6e201c965 | ||
|
|
156a4e610c | ||
|
|
81f8fc1880 | ||
|
|
343d6e5f3f | ||
|
|
e7080835f1 | ||
|
|
7c1a6f7e95 | ||
|
|
22ac2b13fb | ||
|
|
a8acb96608 | ||
|
|
97773bf50c | ||
|
|
1e0846749b | ||
|
|
8cd766ff53 | ||
|
|
e5b20a11d2 | ||
|
|
e0bd4ffc39 | ||
|
|
bbbcae1a12 | ||
|
|
3e1666be08 | ||
|
|
a06b49aca1 | ||
|
|
d318893aa0 | ||
|
|
f133e2f775 | ||
|
|
b44b9ba316 | ||
|
|
d5e4429724 | ||
|
|
8f11366f50 | ||
|
|
0dabcfdec4 | ||
|
|
b4460a05da | ||
|
|
0a1ea1f028 | ||
|
|
b41ec6586a | ||
|
|
4eff8f20f2 | ||
|
|
8cb61e8b9b | ||
|
|
b9731b89dc | ||
|
|
d64e1146dd | ||
|
|
ae6b219545 | ||
|
|
c44d60cfe4 | ||
|
|
9928c7d828 | ||
|
|
f5bc6f7949 | ||
|
|
a839fb94aa | ||
|
|
2e2c56adde | ||
|
|
678adeaf7c | ||
|
|
1d14694dec | ||
|
|
a48f03fb8d | ||
|
|
f6b43cb0ef | ||
|
|
7451d560ba | ||
|
|
27edd6e21c | ||
|
|
ec3a18d438 | ||
|
|
02d4101ca3 | ||
|
|
fdaba4c6b0 | ||
|
|
542ff828ab | ||
|
|
4371a4dceb | ||
|
|
60f4b07723 | ||
|
|
f282bec8ef | ||
|
|
cef388de3d | ||
|
|
1828690467 | ||
|
|
f4c4cd14ac | ||
|
|
3db3996102 | ||
|
|
dbcf0fb5f0 | ||
|
|
29750dda08 | ||
|
|
6df6ec5f09 | ||
|
|
882a6b2cf9 | ||
|
|
b8fa540fd3 | ||
|
|
dee8f5a0ff | ||
|
|
32f8e2ce45 | ||
|
|
4e6324e00b | ||
|
|
7f21d31498 | ||
|
|
639d3b99b7 | ||
|
|
0c7bde54d4 | ||
|
|
8a0c20431c | ||
|
|
72797d7b42 | ||
|
|
9ccc752a43 | ||
|
|
6993eb3c78 | ||
|
|
d9649f9e16 | ||
|
|
5b2b9cdeff | ||
|
|
e31a95b15f | ||
|
|
1ad832a4c1 | ||
|
|
8e077a09f3 | ||
|
|
95f0d8156b | ||
|
|
b881f86c8f | ||
|
|
5959ecc3ee | ||
|
|
a6a44692dc | ||
|
|
12ea28c23e | ||
|
|
ade684dc35 | ||
|
|
4ec6923898 | ||
|
|
e86cd9da96 | ||
|
|
d6db1a27af | ||
|
|
76331f0564 | ||
|
|
7cb6a737a9 | ||
|
|
3659b97563 | ||
|
|
7d72eb809e | ||
|
|
8ba0ae7fa8 | ||
|
|
36350d78d0 | ||
|
|
9b80324d7f | ||
|
|
a549c56faa | ||
|
|
958278c273 | ||
|
|
b7e9bbed0c | ||
|
|
eec3fe967e | ||
|
|
01714757e3 | ||
|
|
ffad67222d | ||
|
|
078586fab0 | ||
|
|
a561dd97a6 | ||
|
|
b9cbcde600 | ||
|
|
36b0709942 | ||
|
|
cf84efbbb9 | ||
|
|
58cd1aba10 | ||
|
|
26a3a8679a | ||
|
|
a1e4c260ea |
@@ -35,7 +35,7 @@
|
||||
"ms-vscode.cpptools",
|
||||
"mutantdino.resourcemonitor",
|
||||
"dsanders11.vscode-electron-build-tools",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"oxc.oxc-vscode",
|
||||
"shakram02.bash-beautify",
|
||||
"marshallofsound.gnls-electron"
|
||||
],
|
||||
|
||||
@@ -1,79 +0,0 @@
|
||||
{
|
||||
"root": true,
|
||||
"extends": "standard",
|
||||
"parser": "@typescript-eslint/parser",
|
||||
"plugins": ["@typescript-eslint"],
|
||||
"env": {
|
||||
"browser": true
|
||||
},
|
||||
"rules": {
|
||||
"semi": ["error", "always"],
|
||||
"no-var": "error",
|
||||
"no-unused-vars": "off",
|
||||
"guard-for-in": "error",
|
||||
"@typescript-eslint/no-unused-vars": ["error", {
|
||||
"vars": "all",
|
||||
"args": "after-used",
|
||||
"ignoreRestSiblings": true
|
||||
}],
|
||||
"prefer-const": ["error", {
|
||||
"destructuring": "all"
|
||||
}],
|
||||
"n/no-callback-literal": "off",
|
||||
"import/newline-after-import": "error",
|
||||
"import/order": ["error", {
|
||||
"alphabetize": {
|
||||
"order": "asc"
|
||||
},
|
||||
"newlines-between": "always",
|
||||
"pathGroups": [
|
||||
{
|
||||
"pattern": "@electron/internal/**",
|
||||
"group": "external",
|
||||
"position": "before"
|
||||
},
|
||||
{
|
||||
"pattern": "@electron/**",
|
||||
"group": "external",
|
||||
"position": "before"
|
||||
},
|
||||
{
|
||||
"pattern": "{electron,electron/**}",
|
||||
"group": "external",
|
||||
"position": "before"
|
||||
}
|
||||
],
|
||||
"pathGroupsExcludedImportTypes": [],
|
||||
"distinctGroup": true,
|
||||
"groups": [
|
||||
"external",
|
||||
"builtin",
|
||||
["sibling", "parent"],
|
||||
"index",
|
||||
"type"
|
||||
]
|
||||
}]
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 6,
|
||||
"sourceType": "module"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.ts",
|
||||
"rules": {
|
||||
"no-undef": "off",
|
||||
"no-redeclare": "off",
|
||||
"@typescript-eslint/no-redeclare": ["error"],
|
||||
"no-use-before-define": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": "*.d.ts",
|
||||
"rules": {
|
||||
"no-useless-constructor": "off",
|
||||
"@typescript-eslint/no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@@ -19,6 +19,7 @@ DEPS @electron/wg-upgrades
|
||||
/lib/renderer/security-warnings.ts @electron/wg-security
|
||||
|
||||
# Infra WG
|
||||
/.claude/ @electron/wg-infra
|
||||
/.github/actions/ @electron/wg-infra
|
||||
/.github/workflows/*-publish.yml @electron/wg-infra
|
||||
/.github/workflows/build.yml @electron/wg-infra
|
||||
|
||||
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -5,6 +5,8 @@ Thank you for your Pull Request. Please provide a description above and review
|
||||
the requirements below.
|
||||
|
||||
Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
|
||||
|
||||
NOTE: PRS submitted without this template will be automatically closed.
|
||||
-->
|
||||
|
||||
#### Checklist
|
||||
|
||||
45
.github/actions/build-electron/action.yml
vendored
45
.github/actions/build-electron/action.yml
vendored
@@ -47,6 +47,16 @@ runs:
|
||||
- name: Add Clang problem matcher
|
||||
shell: bash
|
||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/clang.json"
|
||||
- name: Download previous object checksums
|
||||
shell: bash
|
||||
if: ${{ (github.event_name == 'push' || github.event_name == 'pull_request') && inputs.is-asan != 'true' }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
||||
ARTIFACT_NAME: object-checksums.${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json
|
||||
SEARCH_BRANCH: ${{ case(github.event_name == 'push', github.ref_name, github.event.pull_request.base.ref) }}
|
||||
REPO: ${{ github.repository }}
|
||||
OUTPUT_PATH: src/previous-object-checksums.json
|
||||
run: node src/electron/.github/actions/build-electron/download-previous-object-checksums.mjs
|
||||
- name: Build Electron ${{ inputs.step-suffix }}
|
||||
if: ${{ inputs.target-platform != 'win' }}
|
||||
shell: bash
|
||||
@@ -72,12 +82,17 @@ runs:
|
||||
cp out/Default/.ninja_log out/electron_ninja_log
|
||||
node electron/script/check-symlinks.js
|
||||
|
||||
# Upload build stats to Datadog
|
||||
if ! [ -z $DD_API_KEY ]; then
|
||||
npx node electron/script/build-stats.mjs out/Default/siso.INFO --upload-stats || true
|
||||
# Build stats and object checksums
|
||||
BUILD_STATS_ARGS="out/Default/siso.INFO --out-dir out/Default --output-object-checksums object-checksums.${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json"
|
||||
if [ -f previous-object-checksums.json ]; then
|
||||
BUILD_STATS_ARGS="$BUILD_STATS_ARGS --input-object-checksums previous-object-checksums.json"
|
||||
fi
|
||||
if ! [ -z "$DD_API_KEY" ]; then
|
||||
BUILD_STATS_ARGS="$BUILD_STATS_ARGS --upload-stats"
|
||||
else
|
||||
echo "Skipping build-stats.mjs upload because DD_API_KEY is not set"
|
||||
fi
|
||||
node electron/script/build-stats.mjs $BUILD_STATS_ARGS || true
|
||||
- name: Build Electron (Windows) ${{ inputs.step-suffix }}
|
||||
if: ${{ inputs.target-platform == 'win' }}
|
||||
shell: powershell
|
||||
@@ -95,16 +110,21 @@ runs:
|
||||
Copy-Item out\Default\.ninja_log out\electron_ninja_log
|
||||
node electron\script\check-symlinks.js
|
||||
|
||||
# Upload build stats to Datadog
|
||||
# Build stats and object checksums
|
||||
$statsArgs = @("out\Default\siso.exe.INFO", "--out-dir", "out\Default", "--output-object-checksums", "object-checksums.${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json")
|
||||
if (Test-Path previous-object-checksums.json) {
|
||||
$statsArgs += @("--input-object-checksums", "previous-object-checksums.json")
|
||||
}
|
||||
if ($env:DD_API_KEY) {
|
||||
try {
|
||||
npx node electron\script\build-stats.mjs out\Default\siso.exe.INFO --upload-stats ; $LASTEXITCODE = 0
|
||||
} catch {
|
||||
Write-Host "Build stats upload failed, continuing..."
|
||||
}
|
||||
$statsArgs += "--upload-stats"
|
||||
} else {
|
||||
Write-Host "Skipping build-stats.mjs upload because DD_API_KEY is not set"
|
||||
}
|
||||
try {
|
||||
& node electron\script\build-stats.mjs @statsArgs ; $LASTEXITCODE = 0
|
||||
} catch {
|
||||
Write-Host "Build stats failed, continuing..."
|
||||
}
|
||||
- name: Verify dist.zip ${{ inputs.step-suffix }}
|
||||
shell: bash
|
||||
run: |
|
||||
@@ -302,3 +322,10 @@ runs:
|
||||
with:
|
||||
name: out_gen_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./src/out/Default/gen
|
||||
- name: Upload Object Checksums ${{ inputs.step-suffix }}
|
||||
if: ${{ always() && !cancelled() && inputs.is-asan != 'true' }}
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
||||
with:
|
||||
name: object_checksums_${{ inputs.artifact-platform }}_${{ inputs.target-arch }}
|
||||
path: ./src/object-checksums.${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json
|
||||
archive: false
|
||||
|
||||
82
.github/actions/build-electron/download-previous-object-checksums.mjs
vendored
Normal file
82
.github/actions/build-electron/download-previous-object-checksums.mjs
vendored
Normal file
@@ -0,0 +1,82 @@
|
||||
import { Octokit } from '@octokit/rest';
|
||||
|
||||
import { writeFileSync } from 'node:fs';
|
||||
|
||||
const token = process.env.GITHUB_TOKEN;
|
||||
const repo = process.env.REPO;
|
||||
const artifactName = process.env.ARTIFACT_NAME;
|
||||
const branch = process.env.SEARCH_BRANCH;
|
||||
const outputPath = process.env.OUTPUT_PATH;
|
||||
|
||||
const required = { GITHUB_TOKEN: token, REPO: repo, ARTIFACT_NAME: artifactName, SEARCH_BRANCH: branch, OUTPUT_PATH: outputPath };
|
||||
const missing = Object.entries(required).filter(([, v]) => !v).map(([k]) => k);
|
||||
if (missing.length > 0) {
|
||||
console.error(`Missing required environment variables: ${missing.join(', ')}`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const [owner, repoName] = repo.split('/');
|
||||
const octokit = new Octokit({ auth: token });
|
||||
|
||||
async function main () {
|
||||
console.log(`Searching for artifact '${artifactName}' on branch '${branch}'...`);
|
||||
|
||||
// Resolve the "Build" workflow name to an ID, mirroring how `gh run list --workflow` works
|
||||
// under the hood (it uses /repos/{owner}/{repo}/actions/workflows/{id}/runs).
|
||||
const { data: workflows } = await octokit.actions.listRepoWorkflows({ owner, repo: repoName });
|
||||
const buildWorkflow = workflows.workflows.find((w) => w.name === 'Build');
|
||||
if (!buildWorkflow) {
|
||||
console.log('Could not find "Build" workflow, continuing without previous checksums');
|
||||
return;
|
||||
}
|
||||
|
||||
const { data: runs } = await octokit.actions.listWorkflowRuns({
|
||||
owner,
|
||||
repo: repoName,
|
||||
workflow_id: buildWorkflow.id,
|
||||
branch,
|
||||
status: 'completed',
|
||||
event: 'push',
|
||||
per_page: 20,
|
||||
exclude_pull_requests: true
|
||||
});
|
||||
|
||||
for (const run of runs.workflow_runs) {
|
||||
const { data: artifacts } = await octokit.actions.listWorkflowRunArtifacts({
|
||||
owner,
|
||||
repo: repoName,
|
||||
run_id: run.id,
|
||||
name: artifactName
|
||||
});
|
||||
|
||||
if (artifacts.artifacts.length > 0) {
|
||||
const artifact = artifacts.artifacts[0];
|
||||
console.log(`Found artifact in run ${run.id} (artifact ID: ${artifact.id}), downloading...`);
|
||||
|
||||
// Non-archived artifacts are still downloaded from the /zip endpoint
|
||||
const response = await octokit.actions.downloadArtifact({
|
||||
owner,
|
||||
repo: repoName,
|
||||
artifact_id: artifact.id,
|
||||
archive_format: 'zip'
|
||||
});
|
||||
|
||||
if (response.headers['content-type'] !== 'application/json') {
|
||||
console.error(`Unexpected content type for artifact download: ${response.headers['content-type']}`);
|
||||
console.error('Expected application/json, continuing without previous checksums');
|
||||
return;
|
||||
}
|
||||
|
||||
writeFileSync(outputPath, JSON.stringify(response.data));
|
||||
console.log('Downloaded previous object checksums successfully');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
console.log(`No previous object checksums found in last ${runs.workflow_runs.length} runs, continuing without them`);
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error('Failed to download previous object checksums, continuing without them:', err.message);
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -15,7 +15,7 @@ runs:
|
||||
git config --global core.preloadindex true
|
||||
git config --global core.longpaths true
|
||||
fi
|
||||
export BUILD_TOOLS_SHA=a0cc95a1884a631559bcca0c948465b725d9295a
|
||||
export BUILD_TOOLS_SHA=1b7bd25dae4a780bb3170fff56c9327b53aaf7eb
|
||||
npm i -g @electron/build-tools
|
||||
# Update depot_tools to ensure python
|
||||
e d update_depot_tools
|
||||
@@ -29,4 +29,4 @@ runs:
|
||||
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
|
||||
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
2
.github/workflows/apply-patches.yml
vendored
2
.github/workflows/apply-patches.yml
vendored
@@ -26,7 +26,7 @@ jobs:
|
||||
# Use dorny/paths-filter instead of the path filter under the on: pull_request: block
|
||||
# so that the output can be used to conditionally run the apply-patches job, which lets
|
||||
# the job be marked as a required status check (conditional skip counts as a success).
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
|
||||
3
.github/workflows/audit-branch-ci.yml
vendored
3
.github/workflows/audit-branch-ci.yml
vendored
@@ -86,6 +86,7 @@ jobs:
|
||||
!message.startsWith("Response status code does not indicate success") &&
|
||||
!message.startsWith("The hosted runner lost communication with the server") &&
|
||||
!message.startsWith("Dependabot encountered an error performing the update") &&
|
||||
!message.startsWith("The action 'Run Electron Tests' has timed out") &&
|
||||
!/Unable to make request/.test(message) &&
|
||||
!/The requested URL returned error/.test(message),
|
||||
)
|
||||
@@ -154,7 +155,7 @@ jobs:
|
||||
await core.summary.write();
|
||||
- name: Send Slack message if errors
|
||||
if: ${{ always() && steps.audit-errors.outputs.errorsFound && github.ref == 'refs/heads/main' }}
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||
with:
|
||||
payload: |
|
||||
link: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||
|
||||
25
.github/workflows/branch-created.yml
vendored
25
.github/workflows/branch-created.yml
vendored
@@ -31,8 +31,8 @@ jobs:
|
||||
else
|
||||
echo "Not a release branch: $BRANCH_NAME"
|
||||
fi
|
||||
- name: Determine Unsupported Major Version
|
||||
id: determine-unsupported-major
|
||||
- name: Determine Next Unsupported Major Version
|
||||
id: determine-next-unsupported-major
|
||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||
env:
|
||||
MAJOR: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||
@@ -50,26 +50,27 @@ jobs:
|
||||
|
||||
# Find the oldest version where eolDate >= stableDate of the new major
|
||||
# This gives us the oldest supported version when the new major goes stable
|
||||
UNSUPPORTED_MAJOR=$(echo "$SCHEDULE" | jq -r --arg stableDate "$STABLE_DATE" '
|
||||
NEXT_UNSUPPORTED_MAJOR=$(echo "$SCHEDULE" | jq -r --arg stableDate "$STABLE_DATE" '
|
||||
[.[] | select(.eolDate != null and .eolDate >= $stableDate)] | sort_by(.version | split(".")[0] | tonumber) | first | .version | split(".")[0]
|
||||
')
|
||||
|
||||
if [[ -z "$UNSUPPORTED_MAJOR" || "$UNSUPPORTED_MAJOR" == "null" ]]; then
|
||||
if [[ -z "$NEXT_UNSUPPORTED_MAJOR" || "$NEXT_UNSUPPORTED_MAJOR" == "null" ]]; then
|
||||
echo "Could not determine oldest supported version"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "SCHEDULE=$SCHEDULE" >> "$GITHUB_OUTPUT"
|
||||
echo "UNSUPPORTED_MAJOR=$UNSUPPORTED_MAJOR" >> "$GITHUB_OUTPUT"
|
||||
echo "NEXT_UNSUPPORTED_MAJOR=$NEXT_UNSUPPORTED_MAJOR" >> "$GITHUB_OUTPUT"
|
||||
- name: New Release Branch Tasks
|
||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
GH_REPO: electron/electron
|
||||
MAJOR: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||
UNSUPPORTED_MAJOR: ${{ steps.determine-unsupported-major.outputs.UNSUPPORTED_MAJOR }}
|
||||
NEXT_UNSUPPORTED_MAJOR: ${{ steps.determine-next-unsupported-major.outputs.NEXT_UNSUPPORTED_MAJOR }}
|
||||
run: |
|
||||
PREVIOUS_MAJOR=$((MAJOR - 1))
|
||||
UNSUPPORTED_MAJOR=$((NEXT_UNSUPPORTED_MAJOR - 1))
|
||||
|
||||
# Create new labels
|
||||
gh label create $MAJOR-x-y --color 8d9ee8 || true
|
||||
@@ -108,8 +109,8 @@ jobs:
|
||||
id: generate-project-metadata
|
||||
env:
|
||||
MAJOR: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||
UNSUPPORTED_MAJOR: ${{ steps.determine-unsupported-major.outputs.UNSUPPORTED_MAJOR }}
|
||||
SCHEDULE: ${{ steps.determine-unsupported-major.outputs.SCHEDULE }}
|
||||
NEXT_UNSUPPORTED_MAJOR: ${{ steps.determine-next-unsupported-major.outputs.NEXT_UNSUPPORTED_MAJOR }}
|
||||
SCHEDULE: ${{ steps.determine-next-unsupported-major.outputs.SCHEDULE }}
|
||||
with:
|
||||
script: |
|
||||
const schedule = JSON.parse(process.env.SCHEDULE)
|
||||
@@ -144,7 +145,7 @@ jobs:
|
||||
major,
|
||||
"next-major": nextMajor,
|
||||
"prev-major": prevMajor,
|
||||
"ending-support-major": parseInt(process.env.UNSUPPORTED_MAJOR),
|
||||
"ending-support-major": parseInt(process.env.NEXT_UNSUPPORTED_MAJOR),
|
||||
"beta-date": betaDate,
|
||||
"beta-prep-week": betaPrepWeek.toISOString().split('T')[0],
|
||||
"beta-prep-week-end": betaPrepWeekEnd.toISOString().split('T')[0],
|
||||
@@ -156,7 +157,7 @@ jobs:
|
||||
}))
|
||||
- name: Create Release Project Board
|
||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||
uses: dsanders11/project-actions/copy-project@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/copy-project@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
id: create-release-board
|
||||
with:
|
||||
drafts: true
|
||||
@@ -176,7 +177,7 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
- name: Find Previous Release Project Board
|
||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||
uses: dsanders11/project-actions/find-project@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/find-project@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
id: find-prev-release-board
|
||||
with:
|
||||
fail-if-project-not-found: false
|
||||
@@ -184,7 +185,7 @@ jobs:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
- name: Close Previous Release Project Board
|
||||
if: ${{ steps.find-prev-release-board.outputs.number }}
|
||||
uses: dsanders11/project-actions/close-project@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/close-project@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
project-number: ${{ steps.find-prev-release-board.outputs.number }}
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
31
.github/workflows/build.yml
vendored
31
.github/workflows/build.yml
vendored
@@ -61,7 +61,7 @@ jobs:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
|
||||
- uses: dorny/paths-filter@fbd0ab8f3e69293af611ebaee6363fc25e6d187d # v4.0.1
|
||||
id: filter
|
||||
with:
|
||||
filters: |
|
||||
@@ -442,34 +442,7 @@ jobs:
|
||||
contents: read
|
||||
needs: [docs-only, macos-x64, macos-arm64, linux-x64, linux-x64-asan, linux-arm, linux-arm64, windows-x64, windows-x86, windows-arm64]
|
||||
if: always() && github.repository == 'electron/electron' && !contains(needs.*.result, 'failure')
|
||||
steps:
|
||||
steps:
|
||||
- name: GitHub Actions Jobs Done
|
||||
run: |
|
||||
echo "All GitHub Actions Jobs are done"
|
||||
|
||||
check-signed-commits:
|
||||
name: Check signed commits in green PR
|
||||
needs: gha-done
|
||||
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Check signed commits in PR
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
127
.github/workflows/clean-src-cache.yml
vendored
127
.github/workflows/clean-src-cache.yml
vendored
@@ -7,6 +7,7 @@ name: Clean Source Cache
|
||||
on:
|
||||
schedule:
|
||||
- cron: "0 0 * * *"
|
||||
workflow_dispatch:
|
||||
|
||||
permissions: {}
|
||||
|
||||
@@ -16,6 +17,8 @@ jobs:
|
||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||
permissions:
|
||||
contents: read
|
||||
env:
|
||||
DD_API_KEY: ${{ secrets.DD_API_KEY }}
|
||||
container:
|
||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
||||
options: --user root
|
||||
@@ -23,12 +26,130 @@ jobs:
|
||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||
- /mnt/win-cache:/mnt/win-cache
|
||||
steps:
|
||||
- name: Get Disk Space Before Cleanup
|
||||
id: disk-before
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Disk space before cleanup:"
|
||||
df -h /mnt/cross-instance-cache
|
||||
df -h /mnt/win-cache
|
||||
CROSS_FREE_BEFORE=$(df -k /mnt/cross-instance-cache | tail -1 | awk '{print $4}')
|
||||
CROSS_TOTAL=$(df -k /mnt/cross-instance-cache | tail -1 | awk '{print $2}')
|
||||
WIN_FREE_BEFORE=$(df -k /mnt/win-cache | tail -1 | awk '{print $4}')
|
||||
WIN_TOTAL=$(df -k /mnt/win-cache | tail -1 | awk '{print $2}')
|
||||
echo "cross_free_kb=$CROSS_FREE_BEFORE" >> $GITHUB_OUTPUT
|
||||
echo "cross_total_kb=$CROSS_TOTAL" >> $GITHUB_OUTPUT
|
||||
echo "win_free_kb=$WIN_FREE_BEFORE" >> $GITHUB_OUTPUT
|
||||
echo "win_total_kb=$WIN_TOTAL" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Cleanup Source Cache
|
||||
shell: bash
|
||||
run: |
|
||||
df -h /mnt/cross-instance-cache
|
||||
find /mnt/cross-instance-cache -type f -mtime +15 -delete
|
||||
find /mnt/win-cache -type f -mtime +15 -delete
|
||||
|
||||
- name: Get Disk Space After Cleanup
|
||||
id: disk-after
|
||||
shell: bash
|
||||
run: |
|
||||
echo "Disk space after cleanup:"
|
||||
df -h /mnt/cross-instance-cache
|
||||
df -h /mnt/win-cache
|
||||
find /mnt/win-cache -type f -mtime +15 -delete
|
||||
df -h /mnt/win-cache
|
||||
CROSS_FREE_AFTER=$(df -k /mnt/cross-instance-cache | tail -1 | awk '{print $4}')
|
||||
WIN_FREE_AFTER=$(df -k /mnt/win-cache | tail -1 | awk '{print $4}')
|
||||
echo "cross_free_kb=$CROSS_FREE_AFTER" >> $GITHUB_OUTPUT
|
||||
echo "win_free_kb=$WIN_FREE_AFTER" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Log Disk Space to Datadog
|
||||
if: ${{ env.DD_API_KEY != '' }}
|
||||
shell: bash
|
||||
env:
|
||||
CROSS_FREE_BEFORE: ${{ steps.disk-before.outputs.cross_free_kb }}
|
||||
CROSS_FREE_AFTER: ${{ steps.disk-after.outputs.cross_free_kb }}
|
||||
CROSS_TOTAL: ${{ steps.disk-before.outputs.cross_total_kb }}
|
||||
WIN_FREE_BEFORE: ${{ steps.disk-before.outputs.win_free_kb }}
|
||||
WIN_FREE_AFTER: ${{ steps.disk-after.outputs.win_free_kb }}
|
||||
WIN_TOTAL: ${{ steps.disk-before.outputs.win_total_kb }}
|
||||
run: |
|
||||
TIMESTAMP=$(date +%s)
|
||||
|
||||
CROSS_FREE_BEFORE_GB=$(awk "BEGIN {printf \"%.2f\", $CROSS_FREE_BEFORE / 1024 / 1024}")
|
||||
CROSS_FREE_AFTER_GB=$(awk "BEGIN {printf \"%.2f\", $CROSS_FREE_AFTER / 1024 / 1024}")
|
||||
CROSS_FREED_GB=$(awk "BEGIN {printf \"%.2f\", ($CROSS_FREE_AFTER - $CROSS_FREE_BEFORE) / 1024 / 1024}")
|
||||
CROSS_TOTAL_GB=$(awk "BEGIN {printf \"%.2f\", $CROSS_TOTAL / 1024 / 1024}")
|
||||
|
||||
WIN_FREE_BEFORE_GB=$(awk "BEGIN {printf \"%.2f\", $WIN_FREE_BEFORE / 1024 / 1024}")
|
||||
WIN_FREE_AFTER_GB=$(awk "BEGIN {printf \"%.2f\", $WIN_FREE_AFTER / 1024 / 1024}")
|
||||
WIN_FREED_GB=$(awk "BEGIN {printf \"%.2f\", ($WIN_FREE_AFTER - $WIN_FREE_BEFORE) / 1024 / 1024}")
|
||||
WIN_TOTAL_GB=$(awk "BEGIN {printf \"%.2f\", $WIN_TOTAL / 1024 / 1024}")
|
||||
|
||||
echo "cross-instance-cache: free before=${CROSS_FREE_BEFORE_GB}GB, after=${CROSS_FREE_AFTER_GB}GB, freed=${CROSS_FREED_GB}GB, total=${CROSS_TOTAL_GB}GB"
|
||||
echo "win-cache: free before=${WIN_FREE_BEFORE_GB}GB, after=${WIN_FREE_AFTER_GB}GB, freed=${WIN_FREED_GB}GB, total=${WIN_TOTAL_GB}GB"
|
||||
|
||||
curl -s -X POST "https://api.datadoghq.com/api/v2/series" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "DD-API-KEY: ${DD_API_KEY}" \
|
||||
-d @- << EOF
|
||||
{
|
||||
"series": [
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_before_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_FREE_BEFORE_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_after_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_FREE_AFTER_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.space_freed_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_FREED_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.total_space_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${CROSS_TOTAL_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:cross-instance-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_before_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_FREE_BEFORE_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.free_space_after_cleanup_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_FREE_AFTER_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.space_freed_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_FREED_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
},
|
||||
{
|
||||
"metric": "electron.src_cache.disk.total_space_gb",
|
||||
"points": [{"timestamp": ${TIMESTAMP}, "value": ${WIN_TOTAL_GB}}],
|
||||
"type": 3,
|
||||
"unit": "gigabyte",
|
||||
"tags": ["volume:win-cache", "platform:linux"]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
|
||||
echo "Disk space metrics logged to Datadog"
|
||||
|
||||
6
.github/workflows/issue-labeled.yml
vendored
6
.github/workflows/issue-labeled.yml
vendored
@@ -21,7 +21,7 @@ jobs:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
org: electron
|
||||
- name: Set status
|
||||
uses: dsanders11/project-actions/edit-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/edit-item@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
project-number: 90
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
org: electron
|
||||
- name: Set status
|
||||
uses: dsanders11/project-actions/edit-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/edit-item@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
project-number: 90
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
- name: Create comment
|
||||
if: ${{ steps.check-for-comment.outputs.SHOULD_COMMENT }}
|
||||
uses: actions-cool/issues-helper@71b62d7da76e59ff7b193904feb6e77d4dbb2777 # v3.7.6
|
||||
uses: actions-cool/issues-helper@200c78641dbf33838311e5a1e0c31bbdb92d7cf0 # v3.8.0
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
4
.github/workflows/issue-opened.yml
vendored
4
.github/workflows/issue-opened.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
org: electron
|
||||
- name: Add to Issue Triage
|
||||
uses: dsanders11/project-actions/add-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/add-item@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
field: Reporter
|
||||
field-value: ${{ github.event.issue.user.login }}
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
}
|
||||
- name: Create unsupported major comment
|
||||
if: ${{ steps.add-labels.outputs.unsupportedMajor }}
|
||||
uses: actions-cool/issues-helper@71b62d7da76e59ff7b193904feb6e77d4dbb2777 # v3.7.6
|
||||
uses: actions-cool/issues-helper@200c78641dbf33838311e5a1e0c31bbdb92d7cf0 # v3.8.0
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
2
.github/workflows/issue-transferred.yml
vendored
2
.github/workflows/issue-transferred.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
org: electron
|
||||
- name: Remove from issue triage
|
||||
uses: dsanders11/project-actions/delete-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/delete-item@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
project-number: 90
|
||||
|
||||
2
.github/workflows/issue-unlabeled.yml
vendored
2
.github/workflows/issue-unlabeled.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
org: electron
|
||||
- name: Set status
|
||||
if: ${{ steps.check-for-blocked-labels.outputs.NOT_BLOCKED }}
|
||||
uses: dsanders11/project-actions/edit-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/edit-item@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
project-number: 90
|
||||
|
||||
@@ -51,4 +51,21 @@ jobs:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||
run: |
|
||||
printf "<!-- disallowed-non-maintainer-change -->\n\nHello @${PR_AUTHOR}! It looks like this pull request touches one of our dependency or CI files, and per [our contribution policy](https://github.com/electron/electron/blob/main/CONTRIBUTING.md#dependencies-upgrades-policy) we do not accept these types of changes in PRs." | gh pr review $PR_URL -r --body-file=-
|
||||
cat <<'REVIEW_EOF' | sed "s/%AUTHOR%/$PR_AUTHOR/g" | gh pr review $PR_URL -r --body-file=-
|
||||
<!-- disallowed-non-maintainer-change -->
|
||||
|
||||
Hello @%AUTHOR%! It looks like this pull request touches one of our dependency or CI files, and per [our contribution policy](https://github.com/electron/electron/blob/main/CONTRIBUTING.md#dependencies-upgrades-policy) we do not accept these types of changes in PRs.
|
||||
|
||||
To move this PR forward, please:
|
||||
|
||||
1. Revert the dependency/CI file changes from your branch. (e.g. `yarn.lock`, `.yarn/`, `.yarnrc.yml`, `.github/workflows/`, `.github/actions/`)
|
||||
2. Ensure your branch [allows maintainer commits](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/allowing-changes-to-a-pull-request-branch-created-from-a-fork) so a maintainer can push the necessary dependency changes on your behalf.
|
||||
3. Leave a comment letting reviewers know the dependency change is still needed.
|
||||
|
||||
<details>
|
||||
<summary>For maintainers</summary>
|
||||
|
||||
To land this PR, push a verified commit to the contributor's branch with the required dependency/CI changes, then dismiss this review.
|
||||
|
||||
</details>
|
||||
REVIEW_EOF
|
||||
|
||||
1
.github/workflows/pipeline-electron-lint.yml
vendored
1
.github/workflows/pipeline-electron-lint.yml
vendored
@@ -76,7 +76,6 @@ jobs:
|
||||
- name: Add problem matchers
|
||||
shell: bash
|
||||
run: |
|
||||
echo "::add-matcher::src/electron/.github/problem-matchers/eslint-stylish.json"
|
||||
echo "::add-matcher::src/electron/.github/problem-matchers/markdownlint.json"
|
||||
- name: Run Lint
|
||||
shell: bash
|
||||
|
||||
@@ -126,7 +126,7 @@ jobs:
|
||||
cd src/electron
|
||||
git pack-refs
|
||||
- name: Download Out Gen Artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: out_gen_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./src/out/${{ env.ELECTRON_OUT_DIR }}/gen
|
||||
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
fetch-depth: 0
|
||||
ref: ${{ github.event.pull_request.head.sha }}
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: generated_artifacts_linux_arm64
|
||||
path: ./generated_artifacts_linux_arm64
|
||||
|
||||
@@ -175,12 +175,12 @@ jobs:
|
||||
echo "DISABLE_CRASH_REPORTER_TESTS=true" >> $GITHUB_ENV
|
||||
echo "IS_ASAN=true" >> $GITHUB_ENV
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: generated_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./generated_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: src_artifacts_${{ env.ARTIFACT_KEY }}
|
||||
path: ./src_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||
|
||||
@@ -67,12 +67,12 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
@@ -123,12 +123,12 @@ jobs:
|
||||
- name: Install Dependencies
|
||||
uses: ./src/electron/.github/actions/install-dependencies
|
||||
- name: Download Generated Artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||
- name: Download Src Artifacts
|
||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||
with:
|
||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||
|
||||
58
.github/workflows/pr-template-check.yml
vendored
Normal file
58
.github/workflows/pr-template-check.yml
vendored
Normal file
@@ -0,0 +1,58 @@
|
||||
name: PR Template Check
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [opened, ready_for_review]
|
||||
|
||||
# SECURITY: This workflow uses pull_request_target and has access to secrets.
|
||||
# Do NOT checkout or run code from the PR head. All code execution must use
|
||||
# the base branch only. Adding a ref to PR head would expose secrets to
|
||||
# untrusted code.
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
check-pr-template:
|
||||
if: ${{ github.event.pull_request.head.repo.fork && !github.event.pull_request.draft && !startsWith(github.head_ref, 'roller/') }}
|
||||
name: Check PR Template
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
pull-requests: write
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||
with:
|
||||
sparse-checkout: .github/PULL_REQUEST_TEMPLATE.md
|
||||
sparse-checkout-cone-mode: false
|
||||
- name: Check for required sections
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
||||
with:
|
||||
script: |
|
||||
const fs = require('fs');
|
||||
const template = fs.readFileSync('.github/PULL_REQUEST_TEMPLATE.md', 'utf8');
|
||||
const requiredSections = [...template.matchAll(/^(#{1,4} .+)$/gm)].map(
|
||||
(m) => m[1],
|
||||
);
|
||||
if (requiredSections.length === 0) {
|
||||
console.log('No heading sections found in PR template');
|
||||
return;
|
||||
}
|
||||
const body = context.payload.pull_request.body || '';
|
||||
const missingSections = requiredSections.filter(
|
||||
(section) => !body.includes(section),
|
||||
);
|
||||
if (missingSections.length > 0) {
|
||||
const list = missingSections.map((s) => `- \`${s}\``).join('\n');
|
||||
await github.rest.issues.createComment({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
issue_number: context.payload.pull_request.number,
|
||||
body: `This PR was automatically closed because the PR template was not properly filled out. The following required sections are missing:\n\n${list}\n\nPlease update your PR description to include all required sections and reopen the PR.`,
|
||||
});
|
||||
await github.rest.pulls.update({
|
||||
owner: context.repo.owner,
|
||||
repo: context.repo.repo,
|
||||
pull_number: context.payload.pull_request.number,
|
||||
state: 'closed',
|
||||
});
|
||||
}
|
||||
46
.github/workflows/pr-triage-automation.yml
vendored
Normal file
46
.github/workflows/pr-triage-automation.yml
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
name: PR Triage Automation
|
||||
|
||||
on:
|
||||
pull_request_target:
|
||||
types: [synchronize, review_requested]
|
||||
issue_comment:
|
||||
types: [created]
|
||||
|
||||
# SECURITY: This workflow uses pull_request_target and has access to secrets.
|
||||
# Do NOT checkout or run code from the PR head. All code execution must use
|
||||
# the base branch only. Adding a ref to PR head would expose secrets to
|
||||
# untrusted code.
|
||||
permissions: {}
|
||||
|
||||
jobs:
|
||||
set-needs-review:
|
||||
name: Set status to Needs Review
|
||||
if: >-
|
||||
(github.event_name == 'pull_request_target'
|
||||
&& github.event.pull_request.state == 'open'
|
||||
&& github.event.pull_request.draft != true
|
||||
&& !contains(github.event.pull_request.labels.*.name, 'wip ⚒')
|
||||
&& (github.event.action == 'synchronize' || github.event.action == 'review_requested'))
|
||||
|| (github.event_name == 'issue_comment'
|
||||
&& github.event.issue.pull_request
|
||||
&& github.event.issue.state == 'open'
|
||||
&& !contains(github.event.issue.labels.*.name, 'wip ⚒')
|
||||
&& github.event.comment.user.login == github.event.issue.user.login)
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
steps:
|
||||
- name: Generate GitHub App token
|
||||
uses: electron/github-app-auth-action@e14e47722ed120360649d0789e25b9baece12725 # v2.0.0
|
||||
id: generate-token
|
||||
with:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
org: electron
|
||||
- name: Set status to Needs Review
|
||||
uses: dsanders11/project-actions/edit-item@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
project-number: 118
|
||||
field: Status
|
||||
field-value: 🌀 Needs Review
|
||||
fail-if-item-not-found: false
|
||||
10
.github/workflows/pull-request-labeled.yml
vendored
10
.github/workflows/pull-request-labeled.yml
vendored
@@ -18,7 +18,7 @@ jobs:
|
||||
permissions: {}
|
||||
steps:
|
||||
- name: Trigger Slack workflow
|
||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
||||
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||
with:
|
||||
webhook: ${{ secrets.BACKPORT_REQUESTED_SLACK_WEBHOOK_URL }}
|
||||
webhook-type: webhook-trigger
|
||||
@@ -42,7 +42,7 @@ jobs:
|
||||
creds: ${{ secrets.RELEASE_BOARD_GH_APP_CREDS }}
|
||||
org: electron
|
||||
- name: Set status
|
||||
uses: dsanders11/project-actions/edit-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/edit-item@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
project-number: 94
|
||||
@@ -50,7 +50,7 @@ jobs:
|
||||
field-value: ✅ Reviewed
|
||||
pull-request-labeled-ai-pr:
|
||||
name: ai-pr label added
|
||||
if: github.event.label.name == 'ai-pr'
|
||||
if: github.event.label.name == 'ai-pr' && github.event.pull_request.state != 'closed'
|
||||
runs-on: ubuntu-latest
|
||||
permissions: {}
|
||||
steps:
|
||||
@@ -60,7 +60,7 @@ jobs:
|
||||
with:
|
||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||
- name: Create comment
|
||||
uses: actions-cool/issues-helper@71b62d7da76e59ff7b193904feb6e77d4dbb2777 # v3.7.6
|
||||
uses: actions-cool/issues-helper@200c78641dbf33838311e5a1e0c31bbdb92d7cf0 # v3.8.0
|
||||
with:
|
||||
actions: 'create-comment'
|
||||
token: ${{ steps.generate-token.outputs.token }}
|
||||
@@ -72,7 +72,7 @@ jobs:
|
||||
|
||||
Hello @${{ github.event.pull_request.user.login }}. Due to the high amount of AI spam PRs we receive, if a PR is detected to be majority AI-generated without disclosure and untested, we will automatically close the PR.
|
||||
|
||||
We welcome the use of AI tools, as long as the PR meets our quality standards and has clearly been built and tested. If you believe your PR was closed in error, we welcome you to resubmit. However, please read our [CONTRIBUTING.md](http://contributing.md/) carefully before reopening. Thanks for your contribution.
|
||||
We welcome the use of AI tools, as long as the PR meets our quality standards and has clearly been built and tested. If you believe your PR was closed in error, we welcome you to resubmit. However, please read our [CONTRIBUTING.md](https://github.com/electron/electron/blob/main/CONTRIBUTING.md) and [AI Tool Policy](https://github.com/electron/governance/blob/main/policy/ai.md) carefully before reopening. Thanks for your contribution.
|
||||
- name: Close the pull request
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||
|
||||
@@ -13,7 +13,6 @@ permissions: {}
|
||||
jobs:
|
||||
check-signed-commits:
|
||||
name: Check signed commits in PR
|
||||
if: ${{ !contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
|
||||
runs-on: ubuntu-slim
|
||||
permissions:
|
||||
contents: read
|
||||
@@ -23,9 +22,9 @@ jobs:
|
||||
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
|
||||
with:
|
||||
comment: |
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
|
||||
for all incoming PRs. To get your PR merged, please sign those commits
|
||||
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
|
||||
(`git push --force-with-lease`)
|
||||
|
||||
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
|
||||
@@ -37,3 +36,11 @@ jobs:
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --add-label needs-signed-commits
|
||||
|
||||
- name: Remove needs-signed-commits label
|
||||
if: ${{ success() && contains(github.event.pull_request.labels.*.name, 'needs-signed-commits') }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||
run: |
|
||||
gh pr edit $PR_URL --remove-label needs-signed-commits
|
||||
|
||||
2
.github/workflows/scorecards.yml
vendored
2
.github/workflows/scorecards.yml
vendored
@@ -51,6 +51,6 @@ jobs:
|
||||
|
||||
# Upload the results to GitHub's code scanning dashboard.
|
||||
- name: "Upload to code-scanning"
|
||||
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5
|
||||
uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v3.29.5
|
||||
with:
|
||||
sarif_file: results.sarif
|
||||
|
||||
2
.github/workflows/stable-prep-items.yml
vendored
2
.github/workflows/stable-prep-items.yml
vendored
@@ -29,7 +29,7 @@ jobs:
|
||||
PROJECT_NUMBER=$(gh project list --owner electron --format json | jq -r '.projects | map(select(.title | test("^[0-9]+-x-y$"))) | max_by(.number) | .number')
|
||||
echo "PROJECT_NUMBER=$PROJECT_NUMBER" >> "$GITHUB_OUTPUT"
|
||||
- name: Update Completed Stable Prep Items
|
||||
uses: dsanders11/project-actions/completed-by@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
||||
uses: dsanders11/project-actions/completed-by@5767984408ccc6742f83acc8b8d8ea5e09f329af # v2.0.0
|
||||
with:
|
||||
field: Prep Status
|
||||
field-value: ✅ Complete
|
||||
|
||||
329
.oxlintrc.json
Normal file
329
.oxlintrc.json
Normal file
@@ -0,0 +1,329 @@
|
||||
{
|
||||
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||
"plugins": [
|
||||
"typescript",
|
||||
"import",
|
||||
"node",
|
||||
"promise",
|
||||
"unicorn"
|
||||
],
|
||||
"jsPlugins": [
|
||||
{
|
||||
"name": "no-only-tests",
|
||||
"specifier": "./script/lint-plugins/no-only-tests.mjs"
|
||||
}
|
||||
],
|
||||
"categories": {
|
||||
"correctness": "off"
|
||||
},
|
||||
"options": {
|
||||
"typeAware": false
|
||||
},
|
||||
"env": {
|
||||
"builtin": true,
|
||||
"browser": true
|
||||
},
|
||||
"ignorePatterns": [
|
||||
".github/workflows/node_modules",
|
||||
"spec/node_modules",
|
||||
"spec/fixtures/native-addon",
|
||||
"shell/browser/resources/win/resource.h",
|
||||
"shell/common/node_includes.h",
|
||||
"spec/fixtures/pages/jquery-3.6.0.min.js"
|
||||
],
|
||||
"rules": {
|
||||
"no-var": "error",
|
||||
"accessor-pairs": [
|
||||
"error",
|
||||
{
|
||||
"setWithoutGet": true,
|
||||
"enforceForClassMembers": true
|
||||
}
|
||||
],
|
||||
"array-callback-return": [
|
||||
"error",
|
||||
{
|
||||
"allowImplicit": false,
|
||||
"checkForEach": false
|
||||
}
|
||||
],
|
||||
"constructor-super": "error",
|
||||
"curly": [
|
||||
"error",
|
||||
"multi-line"
|
||||
],
|
||||
"default-case-last": "error",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always",
|
||||
{
|
||||
"null": "ignore"
|
||||
}
|
||||
],
|
||||
"new-cap": [
|
||||
"error",
|
||||
{
|
||||
"newIsCap": true,
|
||||
"capIsNew": false,
|
||||
"properties": true
|
||||
}
|
||||
],
|
||||
"no-array-constructor": "error",
|
||||
"no-async-promise-executor": "error",
|
||||
"no-caller": "error",
|
||||
"no-case-declarations": "error",
|
||||
"no-class-assign": "error",
|
||||
"no-compare-neg-zero": "error",
|
||||
"no-cond-assign": "error",
|
||||
"no-const-assign": "error",
|
||||
"no-constant-condition": [
|
||||
"error",
|
||||
{
|
||||
"checkLoops": false
|
||||
}
|
||||
],
|
||||
"no-control-regex": "error",
|
||||
"no-debugger": "error",
|
||||
"no-delete-var": "error",
|
||||
"no-dupe-class-members": "error",
|
||||
"no-dupe-keys": "error",
|
||||
"no-duplicate-case": "error",
|
||||
"no-useless-backreference": "error",
|
||||
"no-empty": [
|
||||
"error",
|
||||
{
|
||||
"allowEmptyCatch": true
|
||||
}
|
||||
],
|
||||
"no-empty-character-class": "error",
|
||||
"no-empty-pattern": "error",
|
||||
"no-eval": "error",
|
||||
"no-ex-assign": "error",
|
||||
"no-extend-native": "error",
|
||||
"no-extra-bind": "error",
|
||||
"no-extra-boolean-cast": "error",
|
||||
"no-fallthrough": "error",
|
||||
"no-func-assign": "error",
|
||||
"no-global-assign": "error",
|
||||
"no-import-assign": "error",
|
||||
"no-invalid-regexp": "error",
|
||||
"no-irregular-whitespace": "error",
|
||||
"no-iterator": "error",
|
||||
"no-labels": [
|
||||
"error",
|
||||
{
|
||||
"allowLoop": false,
|
||||
"allowSwitch": false
|
||||
}
|
||||
],
|
||||
"no-lone-blocks": "error",
|
||||
"no-loss-of-precision": "error",
|
||||
"no-misleading-character-class": "error",
|
||||
"no-prototype-builtins": "error",
|
||||
"no-useless-catch": "error",
|
||||
"no-useless-constructor": "error",
|
||||
"no-use-before-define": [
|
||||
"error",
|
||||
{
|
||||
"functions": false,
|
||||
"classes": false,
|
||||
"variables": false
|
||||
}
|
||||
],
|
||||
"no-multi-str": "error",
|
||||
"no-new": "error",
|
||||
"no-new-func": "error",
|
||||
"no-new-wrappers": "error",
|
||||
"no-obj-calls": "error",
|
||||
"no-proto": "error",
|
||||
"no-redeclare": [
|
||||
"error"
|
||||
],
|
||||
"no-regex-spaces": "error",
|
||||
"no-return-assign": [
|
||||
"error",
|
||||
"except-parens"
|
||||
],
|
||||
"no-self-assign": [
|
||||
"error",
|
||||
{
|
||||
"props": true
|
||||
}
|
||||
],
|
||||
"no-self-compare": "error",
|
||||
"no-sequences": "error",
|
||||
"no-shadow-restricted-names": "error",
|
||||
"no-sparse-arrays": "error",
|
||||
"no-template-curly-in-string": "error",
|
||||
"no-this-before-super": "error",
|
||||
"no-throw-literal": "error",
|
||||
"no-unexpected-multiline": "error",
|
||||
"no-unmodified-loop-condition": "error",
|
||||
"no-unneeded-ternary": [
|
||||
"error",
|
||||
{
|
||||
"defaultAssignment": false
|
||||
}
|
||||
],
|
||||
"no-unreachable": "error",
|
||||
"no-unsafe-finally": "error",
|
||||
"no-unsafe-negation": "error",
|
||||
"no-unused-vars": [
|
||||
"error",
|
||||
{
|
||||
"vars": "all",
|
||||
"args": "after-used",
|
||||
"argsIgnorePattern": "^_",
|
||||
"ignoreRestSiblings": true
|
||||
}
|
||||
],
|
||||
"no-useless-call": "error",
|
||||
"no-useless-computed-key": "error",
|
||||
"no-useless-escape": "error",
|
||||
"no-useless-rename": "error",
|
||||
"no-useless-return": "error",
|
||||
"no-void": "error",
|
||||
"no-with": "error",
|
||||
"prefer-const": [
|
||||
"error",
|
||||
{
|
||||
"destructuring": "all"
|
||||
}
|
||||
],
|
||||
"prefer-promise-reject-errors": "error",
|
||||
"symbol-description": "error",
|
||||
"unicode-bom": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"use-isnan": [
|
||||
"error",
|
||||
{
|
||||
"enforceForSwitchCase": true,
|
||||
"enforceForIndexOf": true
|
||||
}
|
||||
],
|
||||
"valid-typeof": [
|
||||
"error",
|
||||
{
|
||||
"requireStringLiterals": true
|
||||
}
|
||||
],
|
||||
"yoda": [
|
||||
"error",
|
||||
"never"
|
||||
],
|
||||
"import/export": "error",
|
||||
"import/first": "error",
|
||||
"import/no-absolute-path": [
|
||||
"error",
|
||||
{
|
||||
"esmodule": true,
|
||||
"commonjs": true,
|
||||
"amd": false
|
||||
}
|
||||
],
|
||||
"import/no-duplicates": "error",
|
||||
"import/no-named-default": "error",
|
||||
"import/no-webpack-loader-syntax": "error",
|
||||
"promise/param-names": "error",
|
||||
"guard-for-in": "error",
|
||||
"node/handle-callback-err": [
|
||||
"error",
|
||||
"^(err|error)$"
|
||||
],
|
||||
"node/no-exports-assign": "error",
|
||||
"node/no-new-require": "error",
|
||||
"node/no-path-concat": "error"
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["**/*.ts", "**/*.tsx", "**/*.mts", "**/*.cts"],
|
||||
"rules": {
|
||||
"no-use-before-define": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["lib/browser/**", "lib/utility/**"],
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": ["electron", "electron/renderer"],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/isolated_renderer/*",
|
||||
"@electron/internal/renderer/*",
|
||||
"@electron/internal/sandboxed_worker/*",
|
||||
"@electron/internal/worker/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"lib/renderer/**",
|
||||
"lib/worker/**",
|
||||
"lib/preload_realm/**",
|
||||
"lib/sandboxed_renderer/**",
|
||||
"lib/isolated_renderer/**"
|
||||
],
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": ["electron", "electron/main"],
|
||||
"patterns": ["./*", "../*", "@electron/internal/browser/*"]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["lib/common/**"],
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": ["electron", "electron/main", "electron/renderer"],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/browser/*",
|
||||
"@electron/internal/isolated_renderer/*",
|
||||
"@electron/internal/renderer/*",
|
||||
"@electron/internal/sandboxed_worker/*",
|
||||
"@electron/internal/worker/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": [
|
||||
"build/**",
|
||||
"script/**",
|
||||
"docs/**",
|
||||
"default_app/**",
|
||||
"spec/**"
|
||||
],
|
||||
"rules": {
|
||||
"unicorn/prefer-node-protocol": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["spec/**/*.ts", "spec/**/*.js", "spec/**/*.mjs"],
|
||||
"rules": {
|
||||
"no-only-tests/no-only-tests": "error"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["**/*.d.ts"],
|
||||
"rules": {
|
||||
"no-useless-constructor": "off",
|
||||
"no-unused-vars": "off"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
12
CLAUDE.md
12
CLAUDE.md
@@ -1,5 +1,14 @@
|
||||
# Electron Development Guide
|
||||
|
||||
## Running node_modules binaries
|
||||
|
||||
**Never use `npx`.** It is considered dangerous because it can silently fetch and execute arbitrary packages from the registry. Always run binaries through one of these safer mechanisms instead:
|
||||
|
||||
1. **Preferred** — spawn the executable directly from `node_modules/.bin/<tool>` (or the platform equivalent on Windows). This is what `script/lint.js` does for `oxlint`.
|
||||
2. **Acceptable** — invoke via `yarn <tool>` or `yarn run <tool>`, which resolves to the locally installed version without the registry fallback that `npx` performs.
|
||||
|
||||
This rule applies to shell commands you run yourself and to any scripts you author or modify in this repo.
|
||||
|
||||
## Project Overview
|
||||
|
||||
Electron is a framework for building cross-platform desktop applications using web technologies. It embeds Chromium for rendering and Node.js for backend functionality.
|
||||
@@ -201,12 +210,13 @@ gh label list --repo electron/electron --search target/ --json name,color --jq '
|
||||
## Code Style
|
||||
|
||||
**C++:** Follows Chromium style, enforced by clang-format
|
||||
**TypeScript/JavaScript:** ESLint configuration in `.eslintrc.json`
|
||||
**TypeScript/JavaScript:** [oxlint](https://oxc.rs/docs/guide/usage/linter) configuration in `.oxlintrc.json`
|
||||
|
||||
**Linting:**
|
||||
|
||||
```bash
|
||||
npm run lint # Run all linters
|
||||
npm run lint:js # Run oxlint over all JS/TS/MJS sources
|
||||
npm run lint:clang-format # C++ formatting
|
||||
npm run lint:api-history # Validate API history YAML blocks in docs
|
||||
```
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"import"
|
||||
],
|
||||
"rules": {
|
||||
"import/enforce-node-protocol-usage": ["error", "always"]
|
||||
}
|
||||
}
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"import"
|
||||
],
|
||||
"rules": {
|
||||
"import/enforce-node-protocol-usage": ["error", "always"]
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { shell } from 'electron/common';
|
||||
import { app, dialog, BrowserWindow, ipcMain, Menu } from 'electron/main';
|
||||
import { app, dialog, BrowserWindow, ipcMain } from 'electron/main';
|
||||
|
||||
import * as path from 'node:path';
|
||||
import * as url from 'node:url';
|
||||
@@ -11,60 +11,6 @@ app.on('window-all-closed', () => {
|
||||
app.quit();
|
||||
});
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
app.whenReady().then(() => {
|
||||
const helpMenu: Electron.MenuItemConstructorOptions = {
|
||||
role: 'help',
|
||||
submenu: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://electronjs.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: async () => {
|
||||
const version = process.versions.electron;
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://discord.gg/electronjs');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://github.com/electron/electron/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [macAppMenu] : []),
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' },
|
||||
helpMenu
|
||||
];
|
||||
|
||||
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
|
||||
});
|
||||
|
||||
function decorateURL (url: string) {
|
||||
// safely add `?utm_source=default_app
|
||||
const parsedUrl = new URL(url);
|
||||
parsedUrl.searchParams.append('utm_source', 'default_app');
|
||||
return parsedUrl.toString();
|
||||
}
|
||||
|
||||
// Find the shortest path to the electron binary
|
||||
const absoluteElectronPath = process.execPath;
|
||||
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
|
||||
@@ -116,7 +62,7 @@ async function createWindow (backgroundColor?: string) {
|
||||
mainWindow.on('ready-to-show', () => mainWindow!.show());
|
||||
|
||||
mainWindow.webContents.setWindowOpenHandler(details => {
|
||||
shell.openExternal(decorateURL(details.url));
|
||||
shell.openExternal(details.url);
|
||||
return { action: 'deny' };
|
||||
});
|
||||
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
{
|
||||
"extends": "standard",
|
||||
"plugins": [
|
||||
"import",
|
||||
"markdown"
|
||||
],
|
||||
"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",
|
||||
"import/enforce-node-protocol-usage": ["error", "always"]
|
||||
}
|
||||
}
|
||||
@@ -28,7 +28,10 @@ added:
|
||||
* `window` [BaseWindow](base-window.md) (optional)
|
||||
* `options` Object
|
||||
* `title` string (optional)
|
||||
* `defaultPath` string (optional)
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
@@ -109,7 +112,10 @@ changes:
|
||||
* `window` [BaseWindow](base-window.md) (optional)
|
||||
* `options` Object
|
||||
* `title` string (optional)
|
||||
* `defaultPath` string (optional)
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
@@ -198,7 +204,9 @@ added:
|
||||
* `options` Object
|
||||
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default.
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
@@ -238,7 +246,9 @@ changes:
|
||||
* `options` Object
|
||||
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
||||
path, or file name to use by default.
|
||||
path, or file name to use by default. If not provided, the dialog will
|
||||
default to the user's Downloads folder, or their home directory if Downloads
|
||||
doesn't exist.
|
||||
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||
left empty the default label will be used.
|
||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||
|
||||
@@ -56,6 +56,9 @@ Returns `string` - The badge string of the dock.
|
||||
|
||||
Hides the dock icon.
|
||||
|
||||
> [!IMPORTANT]
|
||||
> **Known issue:** Calling `dock.hide()` within one second of a previous call will have no effect. As a workaround, ensure at least one second has elapsed between calls — for example, by deferring with a `setTimeout` of 1100ms or more after a previous call.
|
||||
|
||||
#### `dock.show()` _macOS_
|
||||
|
||||
Returns `Promise<void>` - Resolves when the dock icon is shown.
|
||||
|
||||
@@ -46,7 +46,7 @@ this has the additional effect of removing the menu bar from the window.
|
||||
|
||||
> [!NOTE]
|
||||
> The default menu will be created automatically if the app does not set one.
|
||||
> It contains standard items such as `File`, `Edit`, `View`, and `Window`.
|
||||
> It contains standard items such as `File`, `Edit`, `View`, `Window` and `Help`.
|
||||
|
||||
#### `Menu.getApplicationMenu()`
|
||||
|
||||
|
||||
@@ -59,7 +59,12 @@ On Windows, returns true once the app has emitted the `ready` event.
|
||||
|
||||
### `safeStorage.isAsyncEncryptionAvailable()`
|
||||
|
||||
Returns `Promise<Boolean>` - Whether encryption is available for asynchronous safeStorage operations.
|
||||
Returns `Promise<boolean>` - Resolves with whether encryption is available for
|
||||
asynchronous safeStorage operations.
|
||||
|
||||
The asynchronous encryptor is initialized lazily the first time this method,
|
||||
`encryptStringAsync`, or `decryptStringAsync` is called after the app is ready.
|
||||
The returned promise resolves once initialization completes.
|
||||
|
||||
### `safeStorage.encryptString(plainText)`
|
||||
|
||||
|
||||
@@ -94,6 +94,7 @@
|
||||
The actual output pixel format and color space of the texture should refer to [`OffscreenSharedTexture`](../structures/offscreen-shared-texture.md) object in the `paint` event.
|
||||
* `argb` - The requested output texture format is 8-bit unorm RGBA, with SRGB SDR color space.
|
||||
* `rgbaf16` - The requested output texture format is 16-bit float RGBA, with scRGB HDR color space.
|
||||
* `nv12` - The requested output texture format is 12bpp with Y plane followed by a 2x2 interleaved UV plane, with REC709 color space.
|
||||
* `deviceScaleFactor` number (optional) _Experimental_ - The device scale factor of the offscreen rendering output. If not set, will use `1` as default.
|
||||
* `contextIsolation` boolean (optional) - Whether to run Electron APIs and
|
||||
the specified `preload` script in a separate JavaScript context. Defaults
|
||||
|
||||
@@ -1585,6 +1585,20 @@ Centers the current text selection in web page.
|
||||
|
||||
Copy the image at the given position to the clipboard.
|
||||
|
||||
#### `contents.copyVideoFrameAt(x, y)`
|
||||
|
||||
* `x` Integer
|
||||
* `y` Integer
|
||||
|
||||
When executed on a video media element, copies the frame at (x, y) to the clipboard.
|
||||
|
||||
#### `contents.saveVideoFrameAs(x, y)`
|
||||
|
||||
* `x` Integer
|
||||
* `y` Integer
|
||||
|
||||
When executed on a video media element, shows a save dialog and saves the frame at (x, y) to disk.
|
||||
|
||||
#### `contents.paste()`
|
||||
|
||||
Executes the editing command `paste` in web page.
|
||||
|
||||
@@ -175,6 +175,20 @@ app.on('web-contents-created', (_, webContents) => {
|
||||
})
|
||||
```
|
||||
|
||||
#### `frame.copyVideoFrameAt(x, y)`
|
||||
|
||||
* `x` Integer
|
||||
* `y` Integer
|
||||
|
||||
When executed on a video media element, copies the frame at (x, y) to the clipboard.
|
||||
|
||||
#### `frame.saveVideoFrameAs(x, y)`
|
||||
|
||||
* `x` Integer
|
||||
* `y` Integer
|
||||
|
||||
When executed on a video media element, shows a save dialog and saves the frame at (x, y) to disk.
|
||||
|
||||
### Instance Properties
|
||||
|
||||
#### `frame.ipc` _Readonly_
|
||||
|
||||
@@ -12,6 +12,34 @@ This document uses the following convention to categorize breaking changes:
|
||||
* **Deprecated:** An API was marked as deprecated. The API will continue to function, but will emit a deprecation warning, and will be removed in a future release.
|
||||
* **Removed:** An API or feature was removed, and is no longer supported by Electron.
|
||||
|
||||
## Planned Breaking API Changes (43.0)
|
||||
|
||||
### Behavior Changed: Dialog methods default to Downloads directory
|
||||
|
||||
The `defaultPath` option for the following methods now defaults to the user's Downloads folder (or their home directory if Downloads doesn't exist) when not explicitly provided:
|
||||
|
||||
* `dialog.showOpenDialog`
|
||||
* `dialog.showOpenDialogSync`
|
||||
* `dialog.showSaveDialog`
|
||||
* `dialog.showSaveDialogSync`
|
||||
|
||||
Previously, when no `defaultPath` was provided, the underlying OS file dialog would determine the initial directory — typically remembering the last directory the user navigated to, or falling back to an OS-specific default. Now, Electron explicitly sets the initial directory to Downloads, which also means the OS will no longer track and restore the last-used directory between dialog invocations.
|
||||
|
||||
To preserve the old behavior, you can track the last-used directory yourself and pass it as `defaultPath`:
|
||||
|
||||
```js
|
||||
const path = require('node:path')
|
||||
|
||||
let lastUsedPath
|
||||
const result = await dialog.showOpenDialog({
|
||||
defaultPath: lastUsedPath
|
||||
})
|
||||
|
||||
if (!result.canceled && result.filePaths.length > 0) {
|
||||
lastUsedPath = path.dirname(result.filePaths[0])
|
||||
}
|
||||
```
|
||||
|
||||
## Planned Breaking API Changes (42.0)
|
||||
|
||||
### Behavior Changed: macOS notifications now use `UNNotification` API
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
These are the style guidelines for coding in Electron.
|
||||
|
||||
You can run `npm run lint` to show any style issues detected by `cpplint` and
|
||||
`eslint`.
|
||||
`oxlint`.
|
||||
|
||||
## General Code
|
||||
|
||||
|
||||
@@ -87,6 +87,13 @@ if (!gotTheLock) {
|
||||
// Create mainWindow, load the rest of the app, etc...
|
||||
app.whenReady().then(() => {
|
||||
createWindow()
|
||||
// Check for deep link on cold start
|
||||
if (process.argv.length >= 2) {
|
||||
const lastArg = process.argv[process.argv.length - 1]
|
||||
if (lastArg.startsWith('electron-fiddle://')) {
|
||||
dialog.showErrorBox('Welcome Back', `You arrived from: ${lastArg}`)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
@@ -179,6 +179,7 @@ auto_filenames = {
|
||||
"lib/common/define-properties.ts",
|
||||
"lib/common/deprecate.ts",
|
||||
"lib/common/ipc-messages.ts",
|
||||
"lib/common/timers-shim.ts",
|
||||
"lib/common/web-view-methods.ts",
|
||||
"lib/common/webpack-globals-provider.ts",
|
||||
"lib/renderer/api/context-bridge.ts",
|
||||
|
||||
@@ -793,8 +793,6 @@ filenames = {
|
||||
"shell/browser/extensions/electron_kiosk_delegate.h",
|
||||
"shell/browser/extensions/electron_messaging_delegate.cc",
|
||||
"shell/browser/extensions/electron_messaging_delegate.h",
|
||||
"shell/browser/extensions/electron_navigation_ui_data.cc",
|
||||
"shell/browser/extensions/electron_navigation_ui_data.h",
|
||||
"shell/browser/extensions/electron_process_manager_delegate.cc",
|
||||
"shell/browser/extensions/electron_process_manager_delegate.h",
|
||||
"shell/common/extensions/electron_extensions_api_provider.cc",
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/renderer"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/isolated_renderer/*",
|
||||
"@electron/internal/renderer/*",
|
||||
"@electron/internal/sandboxed_worker/*",
|
||||
"@electron/internal/worker/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -73,6 +73,7 @@ export async function getSources (args: Electron.SourcesOptions) {
|
||||
|
||||
capturer._onerror = (error: string) => {
|
||||
stopRunning();
|
||||
// eslint-disable-next-line prefer-promise-reject-errors
|
||||
reject(error);
|
||||
};
|
||||
|
||||
|
||||
@@ -437,6 +437,14 @@ WebContents.prototype.loadURL = function (url, options) {
|
||||
return p;
|
||||
};
|
||||
|
||||
WebContents.prototype.copyVideoFrameAt = function (x: number, y: number) {
|
||||
this.mainFrame.copyVideoFrameAt(x, y);
|
||||
};
|
||||
|
||||
WebContents.prototype.saveVideoFrameAs = function (x: number, y: number) {
|
||||
this.mainFrame.saveVideoFrameAs(x, y);
|
||||
};
|
||||
|
||||
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => Electron.WindowOpenHandlerResponse) {
|
||||
this._windowOpenHandler = handler;
|
||||
};
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { Menu } from 'electron/main';
|
||||
import { shell } from 'electron/common';
|
||||
import { app, Menu } from 'electron/main';
|
||||
|
||||
const isMac = process.platform === 'darwin';
|
||||
|
||||
@@ -11,13 +12,47 @@ export const setApplicationMenuWasSet = () => {
|
||||
export const setDefaultApplicationMenu = () => {
|
||||
if (applicationMenuWasSet) return;
|
||||
|
||||
const helpMenu: Electron.MenuItemConstructorOptions = {
|
||||
role: 'help',
|
||||
submenu: app.isPackaged
|
||||
? []
|
||||
: [
|
||||
{
|
||||
label: 'Learn More',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://electronjs.org');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Documentation',
|
||||
click: async () => {
|
||||
const version = process.versions.electron;
|
||||
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Community Discussions',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://discord.gg/electronjs');
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Search Issues',
|
||||
click: async () => {
|
||||
await shell.openExternal('https://github.com/electron/electron/issues');
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
|
||||
const template: Electron.MenuItemConstructorOptions[] = [
|
||||
...(isMac ? [macAppMenu] : []),
|
||||
{ role: 'fileMenu' },
|
||||
{ role: 'editMenu' },
|
||||
{ role: 'viewMenu' },
|
||||
{ role: 'windowMenu' }
|
||||
{ role: 'windowMenu' },
|
||||
helpMenu
|
||||
];
|
||||
|
||||
const menu = Menu.buildFromTemplate(template);
|
||||
|
||||
@@ -1,23 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/main",
|
||||
"electron/renderer"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/browser/*",
|
||||
"@electron/internal/isolated_renderer/*",
|
||||
"@electron/internal/renderer/*",
|
||||
"@electron/internal/sandboxed_worker/*",
|
||||
"@electron/internal/worker/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/main"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/browser/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/main"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/browser/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/main"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/browser/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,9 @@ if (nodeIntegration) {
|
||||
delete (global as any).setImmediate;
|
||||
delete (global as any).clearImmediate;
|
||||
delete (global as any).global;
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
delete (global as any).root;
|
||||
// eslint-disable-next-line n/no-deprecated-api
|
||||
delete (global as any).GLOBAL;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/main"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/browser/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -90,6 +90,7 @@ export function executeSandboxedPreloadScripts (context: PreloadContext, preload
|
||||
if (contents) {
|
||||
runPreloadScript(context, contents);
|
||||
} else if (error) {
|
||||
// eslint-disable-next-line no-throw-literal
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
|
||||
@@ -1,21 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/renderer"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/isolated_renderer/*",
|
||||
"@electron/internal/renderer/*",
|
||||
"@electron/internal/sandboxed_worker/*",
|
||||
"@electron/internal/worker/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
{
|
||||
"rules": {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
"paths": [
|
||||
"electron",
|
||||
"electron/main"
|
||||
],
|
||||
"patterns": [
|
||||
"./*",
|
||||
"../*",
|
||||
"@electron/internal/browser/*"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@
|
||||
"install-electron": "install.js"
|
||||
},
|
||||
"dependencies": {
|
||||
"@electron/get": "^2.0.0",
|
||||
"@electron/get": "^4.0.3",
|
||||
"@types/node": "^24.9.0",
|
||||
"extract-zip": "^2.0.1"
|
||||
},
|
||||
|
||||
19
package.json
19
package.json
@@ -5,7 +5,7 @@
|
||||
"description": "Build cross platform desktop apps with JavaScript, HTML, and CSS",
|
||||
"devDependencies": {
|
||||
"@azure/storage-blob": "^12.28.0",
|
||||
"@datadog/datadog-ci": "^4.1.2",
|
||||
"@datadog/datadog-ci": "^5.9.1",
|
||||
"@electron/asar": "^4.0.1",
|
||||
"@electron/docs-parser": "^2.0.0",
|
||||
"@electron/fiddle-core": "^1.3.4",
|
||||
@@ -13,7 +13,7 @@
|
||||
"@electron/lint-roller": "^3.2.0",
|
||||
"@electron/typescript-definitions": "^9.1.5",
|
||||
"@hurdlegroup/robotjs": "^0.12.3",
|
||||
"@octokit/rest": "^20.1.2",
|
||||
"@octokit/rest": "^22.0.1",
|
||||
"@primer/octicons": "^10.0.0",
|
||||
"@sentry/cli": "1.72.0",
|
||||
"@types/minimist": "^1.2.5",
|
||||
@@ -21,22 +21,12 @@
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/stream-json": "^1.7.8",
|
||||
"@types/temp": "^0.9.4",
|
||||
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
||||
"@typescript-eslint/parser": "^8.7.0",
|
||||
"@xmldom/xmldom": "^0.8.11",
|
||||
"@xmldom/xmldom": "^0.8.12",
|
||||
"buffer": "^6.0.3",
|
||||
"chalk": "^4.1.0",
|
||||
"check-for-leaks": "^1.2.1",
|
||||
"eslint": "^8.57.1",
|
||||
"eslint-config-standard": "^17.1.0",
|
||||
"eslint-plugin-import": "^2.32.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",
|
||||
"events": "^3.2.0",
|
||||
"folder-hash": "^4.1.1",
|
||||
"folder-hash": "^4.1.2",
|
||||
"got": "^11.8.5",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.1.0",
|
||||
@@ -44,6 +34,7 @@
|
||||
"minimist": "^1.2.8",
|
||||
"node-gyp": "^11.4.2",
|
||||
"null-loader": "^4.0.1",
|
||||
"oxlint": "^1.57.0",
|
||||
"pre-flight": "^2.0.0",
|
||||
"process": "^0.11.10",
|
||||
"semver": "^7.6.3",
|
||||
|
||||
@@ -125,7 +125,6 @@ feat_separate_content_settings_callback_for_sync_and_async_clipboard.patch
|
||||
fix_win32_synchronous_spellcheck.patch
|
||||
chore_grandfather_in_electron_views_and_delegates.patch
|
||||
refactor_patch_electron_permissiontypes_into_blink.patch
|
||||
revert_views_remove_desktopwindowtreehostwin_window_enlargement.patch
|
||||
fix_add_macos_memory_query_fallback_to_avoid_crash.patch
|
||||
fix_resolve_dynamic_background_material_update_issue_on_windows_11.patch
|
||||
feat_add_support_for_embedder_snapshot_validation.patch
|
||||
@@ -146,5 +145,8 @@ fix_set_correct_app_id_on_linux.patch
|
||||
fix_pass_trigger_for_global_shortcuts_on_wayland.patch
|
||||
feat_plumb_node_integration_in_worker_through_workersettings.patch
|
||||
fix_restore_sdk_inputs_cross-toolchain_deps_for_macos.patch
|
||||
fix_use_fresh_lazynow_for_onendworkitemimpl_after_didruntask.patch
|
||||
fix_pulseaudio_stream_and_icon_names.patch
|
||||
fix_fire_menu_popup_start_for_dynamically_created_aria_menus.patch
|
||||
feat_allow_enabling_extensions_on_custom_protocols.patch
|
||||
fix_initialize_com_on_desktopmedialistcapturethread_on_windows.patch
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Charles Kerr <charles@charleskerr.com>
|
||||
Date: Tue, 24 Mar 2026 16:33:20 -0500
|
||||
Subject: fix: initialize COM on DesktopMediaListCaptureThread on Windows
|
||||
|
||||
On Windows, the DesktopMediaListCaptureThread runs with a UI message
|
||||
pump that doesn't initialize COM. When WebRTC's window capturer
|
||||
enumerates windows, interacting with UWP/Metro windows causes twinapi.dll
|
||||
to internally call ::CoCreateInstance(), which hits Chromium's
|
||||
DCheckedCoCreateInstance hook and triggers a crash.
|
||||
|
||||
This patch fixes the crash by ensuring COM is initialized on the
|
||||
capture thread by calling `init_com_with_mta(false)`.
|
||||
|
||||
diff --git a/chrome/browser/media/webrtc/native_desktop_media_list.cc b/chrome/browser/media/webrtc/native_desktop_media_list.cc
|
||||
index 9a8ebb4edfb92d9fe28ae4b87463a68547ea1ab3..13446d9849c54f1bfe515c3db4d69dd181ec6d39 100644
|
||||
--- a/chrome/browser/media/webrtc/native_desktop_media_list.cc
|
||||
+++ b/chrome/browser/media/webrtc/native_desktop_media_list.cc
|
||||
@@ -786,6 +786,13 @@ NativeDesktopMediaList::NativeDesktopMediaList(
|
||||
base::MessagePumpType thread_type = base::MessagePumpType::UI;
|
||||
#else
|
||||
base::MessagePumpType thread_type = base::MessagePumpType::DEFAULT;
|
||||
+#endif
|
||||
+#if BUILDFLAG(IS_WIN)
|
||||
+ // On Windows, window enumeration via webrtc::DesktopCapturer may interact
|
||||
+ // with UWP/Metro windows through twinapi.dll, which internally calls
|
||||
+ // CoCreateInstance. Initialize COM on this thread to prevent crashes in
|
||||
+ // Chromium's DCheckedCoCreateInstance hook.
|
||||
+ thread_.init_com_with_mta(false);
|
||||
#endif
|
||||
thread_.StartWithOptions(base::Thread::Options(thread_type, 0));
|
||||
|
||||
120
patches/chromium/fix_pulseaudio_stream_and_icon_names.patch
Normal file
120
patches/chromium/fix_pulseaudio_stream_and_icon_names.patch
Normal file
@@ -0,0 +1,120 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Damglador <vse.stopchanskyi@gmail.com>
|
||||
Date: Fri, 26 Dec 2025 21:26:43 +0100
|
||||
Subject: fix: pulseaudio stream and icon names
|
||||
|
||||
Use platform_util::GetXdgAppId() with fallback to argv0 as PA_PROP_APPLICATION_ICON_NAME.
|
||||
Use electron::GetPossiblyOverriddenApplicationName()
|
||||
to set environment variable "ELECTRON_PA_APP_NAME" in audio_service.cc,
|
||||
to use it in pulse_util.cc for setting input/output pa_context name.
|
||||
|
||||
This replaces hard-codded kBrowserDisplayName that was used for PA_PROP_APPLICATION_ICON_NAME,
|
||||
and PRODUCT_STRING that was used for pa_context names.
|
||||
|
||||
This is done to make audio streams recognizable in tools like qpwgrapth and general audio managers,
|
||||
instead of having 20 "Chromium" outputs and "Chromium input" inputs, that are actually coming from
|
||||
completely different applications.
|
||||
|
||||
This patch can be removed when upstream starts using AudioManager::SetGlobalAppName()
|
||||
for all pa_context names (and when actually works with AudioServiceOutOfProcess).
|
||||
|
||||
diff --git a/content/browser/audio/audio_service.cc b/content/browser/audio/audio_service.cc
|
||||
index 70615782c50d18606c3baa42a223e54f8619bc07..fb67e69f9ff46b432236b46913a1b10dd8302887 100644
|
||||
--- a/content/browser/audio/audio_service.cc
|
||||
+++ b/content/browser/audio/audio_service.cc
|
||||
@@ -29,6 +29,9 @@
|
||||
#include "services/audio/public/mojom/audio_service.mojom.h"
|
||||
#include "services/audio/service.h"
|
||||
#include "services/audio/service_factory.h"
|
||||
+#if BUILDFLAG(IS_LINUX)
|
||||
+#include "electron/shell/common/application_info.h"
|
||||
+#endif
|
||||
|
||||
#if BUILDFLAG(ENABLE_PASSTHROUGH_AUDIO_CODECS) && BUILDFLAG(IS_WIN)
|
||||
#define PASS_EDID_ON_COMMAND_LINE 1
|
||||
@@ -109,6 +112,10 @@ void LaunchAudioServiceOutOfProcess(
|
||||
mojo::PendingReceiver<audio::mojom::AudioService> receiver,
|
||||
uint32_t codec_bitmask) {
|
||||
std::vector<std::string> switches;
|
||||
+#if BUILDFLAG(IS_LINUX)
|
||||
+ // Set ELECTRON_PA_APP_NAME variable for pulse_util to grab and set pa_context name
|
||||
+ setenv("ELECTRON_PA_APP_NAME", electron::GetPossiblyOverriddenApplicationName().c_str(), 1);
|
||||
+#endif
|
||||
#if BUILDFLAG(IS_MAC)
|
||||
// On Mac, the audio service requires a CFRunLoop provided by a
|
||||
// UI MessageLoop type, to run AVFoundation and CoreAudio code.
|
||||
diff --git a/media/audio/pulse/pulse_util.cc b/media/audio/pulse/pulse_util.cc
|
||||
index a08e42a464a3894cbf2b8e3cf8a320a33423b719..e5d69506e1585710a2540c91ca51cba7a4692575 100644
|
||||
--- a/media/audio/pulse/pulse_util.cc
|
||||
+++ b/media/audio/pulse/pulse_util.cc
|
||||
@@ -10,6 +10,7 @@
|
||||
#include <memory>
|
||||
#include <type_traits>
|
||||
|
||||
+#include "base/command_line.h"
|
||||
#include "base/files/file_path.h"
|
||||
#include "base/logging.h"
|
||||
#include "base/memory/ptr_util.h"
|
||||
@@ -20,6 +21,7 @@
|
||||
#include "build/branding_buildflags.h"
|
||||
#include "media/audio/audio_device_description.h"
|
||||
#include "media/base/audio_timestamp_helper.h"
|
||||
+#include "electron/shell/common/platform_util.h"
|
||||
|
||||
#if defined(DLOPEN_PULSEAUDIO)
|
||||
#include "media/audio/pulse/pulse_stubs.h"
|
||||
@@ -36,10 +38,8 @@ namespace pulse {
|
||||
namespace {
|
||||
|
||||
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
|
||||
-constexpr char kBrowserDisplayName[] = "google-chrome";
|
||||
#define PRODUCT_STRING "Google Chrome"
|
||||
#else
|
||||
-constexpr char kBrowserDisplayName[] = "chromium-browser";
|
||||
#define PRODUCT_STRING "Chromium"
|
||||
#endif
|
||||
|
||||
@@ -236,7 +236,7 @@ bool InitPulse(pa_threaded_mainloop** mainloop, pa_context** context) {
|
||||
|
||||
pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(pa_mainloop);
|
||||
pa_context* pa_context =
|
||||
- pa_context_new(pa_mainloop_api, PRODUCT_STRING " input");
|
||||
+ pa_context_new(pa_mainloop_api, getenv("ELECTRON_PA_APP_NAME"));
|
||||
if (!pa_context) {
|
||||
pa_threaded_mainloop_free(pa_mainloop);
|
||||
return false;
|
||||
@@ -464,8 +464,11 @@ bool CreateInputStream(pa_threaded_mainloop* mainloop,
|
||||
// Create a new recording stream and
|
||||
// tells PulseAudio what the stream icon should be.
|
||||
ScopedPropertyList property_list;
|
||||
+ const std::string cmd_name =
|
||||
+ base::CommandLine::ForCurrentProcess()->GetProgram().BaseName().value();
|
||||
+ const std::string app_id = platform_util::GetXdgAppId().value_or(cmd_name);
|
||||
pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_ICON_NAME,
|
||||
- kBrowserDisplayName);
|
||||
+ app_id.c_str());
|
||||
*stream = pa_stream_new_with_proplist(context, "RecordStream",
|
||||
&sample_specifications, map,
|
||||
property_list.get());
|
||||
@@ -526,7 +529,7 @@ bool CreateOutputStream(raw_ptr<pa_threaded_mainloop>* mainloop,
|
||||
|
||||
pa_mainloop_api* pa_mainloop_api = pa_threaded_mainloop_get_api(*mainloop);
|
||||
*context = pa_context_new(
|
||||
- pa_mainloop_api, app_name.empty() ? PRODUCT_STRING : app_name.c_str());
|
||||
+ pa_mainloop_api, getenv("ELECTRON_PA_APP_NAME"));
|
||||
RETURN_ON_FAILURE(*context, "Failed to create PulseAudio context.");
|
||||
|
||||
// A state callback must be set before calling pa_threaded_mainloop_lock() or
|
||||
@@ -574,8 +577,11 @@ bool CreateOutputStream(raw_ptr<pa_threaded_mainloop>* mainloop,
|
||||
// Open playback stream and
|
||||
// tell PulseAudio what the stream icon should be.
|
||||
ScopedPropertyList property_list;
|
||||
+ const std::string cmd_name =
|
||||
+ base::CommandLine::ForCurrentProcess()->GetProgram().BaseName().value();
|
||||
+ const std::string app_id = platform_util::GetXdgAppId().value_or(cmd_name);
|
||||
pa_proplist_sets(property_list.get(), PA_PROP_APPLICATION_ICON_NAME,
|
||||
- kBrowserDisplayName);
|
||||
+ app_id.c_str());
|
||||
*stream = pa_stream_new_with_proplist(
|
||||
*context, "Playback", &sample_specifications, map, property_list.get());
|
||||
RETURN_ON_FAILURE(*stream, "failed to create PA playback stream");
|
||||
@@ -8,10 +8,10 @@ such as the background turning black when maximizing the window and
|
||||
dynamic background material settings not taking effect.
|
||||
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
index d1e06b675b19226cf3b78e1aada8d8f2d684fada..ce810555b8501797643987916a728cad8f5adaa5 100644
|
||||
index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..3a5833fcc018f32e86c0a95a42937fb9ac6c5a40 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
@@ -184,6 +184,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
|
||||
@@ -167,6 +167,10 @@ void DesktopWindowTreeHostWin::FinishTouchDrag(gfx::Point screen_point) {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ index d1e06b675b19226cf3b78e1aada8d8f2d684fada..ce810555b8501797643987916a728cad
|
||||
|
||||
void DesktopWindowTreeHostWin::Init(const Widget::InitParams& params) {
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
index a40bd9f25fa07a553c011cf19f155f8158f4ae5f..ae2baec731b5fcd8be97f2177d23b860d67ab8bc 100644
|
||||
index 27322ef34edf3fa8bfbd20b1baddcaf3b7555618..b8d1fa863fd05ebc3ab8ac5ef8c4d81361ce45fe 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
@@ -93,6 +93,8 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Sam Attard <sattard@anthropic.com>
|
||||
Date: Sun, 22 Mar 2026 19:21:45 +0000
|
||||
Subject: fix: use fresh LazyNow for OnEndWorkItemImpl after DidRunTask
|
||||
|
||||
DidRunTask can cache the LazyNow early (via RecordTaskEnd in
|
||||
BrowserUIThreadScheduler::OnTaskCompleted) before running task observers.
|
||||
If a task observer's DidProcessTask triggers nested pump activity (nested
|
||||
RunLoop, sync IPC, etc.), TimeKeeper's last_phase_end_ advances past the
|
||||
cached value. The subsequent OnEndWorkItemImpl then computes a negative
|
||||
delta and hits DCHECK(!delta.is_negative()) in RecordTimeInPhase.
|
||||
|
||||
Using a fresh LazyNow for OnEndWorkItemImpl samples the time after all
|
||||
observers have run, which matches the existing comment's intent that
|
||||
microtasks are extensions of the RunTask and the work item ends after them.
|
||||
|
||||
This is upstreamable: the bug exists whenever any TaskObserver::DidProcessTask
|
||||
triggers nested pump activity, which is not forbidden by the contract.
|
||||
|
||||
diff --git a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
|
||||
index bb09c99ea0b37a139440d0fe98c7f2f5e9c147e0..d27c34f8090ff54d20d8339c0ad56d37d6d61ab2 100644
|
||||
--- a/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
|
||||
+++ b/base/task/sequence_manager/thread_controller_with_message_pump_impl.cc
|
||||
@@ -481,15 +481,22 @@ std::optional<WakeUp> ThreadControllerWithMessagePumpImpl::DoWorkImpl(
|
||||
// `PendingTask` reference dangling.
|
||||
selected_task.reset();
|
||||
|
||||
- LazyNow lazy_now_after_run_task(time_source_);
|
||||
- main_thread_only().task_source->DidRunTask(lazy_now_after_run_task);
|
||||
+ {
|
||||
+ LazyNow lazy_now_did_run_task(time_source_);
|
||||
+ main_thread_only().task_source->DidRunTask(lazy_now_did_run_task);
|
||||
+ }
|
||||
// End the work item scope after DidRunTask() as it can process microtasks
|
||||
- // (which are extensions of the RunTask).
|
||||
+ // (which are extensions of the RunTask). Use a fresh LazyNow here because
|
||||
+ // DidRunTask may cache the LazyNow (via RecordTaskEnd) before running task
|
||||
+ // observers, and those observers may trigger nested pump activity that
|
||||
+ // advances TimeKeeper's last_phase_end_ past the cached value, resulting
|
||||
+ // in a negative delta in RecordTimeInPhase.
|
||||
+ LazyNow lazy_now_after_run_task(time_source_);
|
||||
OnEndWorkItemImpl(lazy_now_after_run_task, run_depth);
|
||||
|
||||
- // If DidRunTask() read the clock (lazy_now_after_run_task.has_value()) or
|
||||
- // if |batch_duration| > 0, store the clock value in `recent_time` so it can
|
||||
- // be reused by SelectNextTask() at the next loop iteration.
|
||||
+ // If OnEndWorkItemImpl() read the clock (lazy_now_after_run_task.has_value())
|
||||
+ // or if |batch_duration| > 0, store the clock value in `recent_time` so it
|
||||
+ // can be reused by SelectNextTask() at the next loop iteration.
|
||||
if (lazy_now_after_run_task.has_value() || !batch_duration.is_zero()) {
|
||||
recent_time = lazy_now_after_run_task.Now();
|
||||
} else {
|
||||
@@ -1,285 +0,0 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: John Kleinschmidt <jkleinsc@electronjs.org>
|
||||
Date: Sat, 14 Jun 2025 16:21:07 -0400
|
||||
Subject: Revert "[views] Remove DesktopWindowTreeHostWin::window_enlargement_"
|
||||
|
||||
This reverts commit 1771dbae6961e7bb7c22bbc6c77f84d90ef2be46.
|
||||
|
||||
Electron needs this patch to allow windows smaller than 64x64
|
||||
on Windows. We should refactor our code so that this patch isn't
|
||||
necessary.
|
||||
|
||||
diff --git a/testing/variations/fieldtrial_testing_config.json b/testing/variations/fieldtrial_testing_config.json
|
||||
index 988866d79a5d1dbd366ebdbff0e8eb2c0c498168..5761ac48be0a64618be0a94606149dd944e46e27 100644
|
||||
--- a/testing/variations/fieldtrial_testing_config.json
|
||||
+++ b/testing/variations/fieldtrial_testing_config.json
|
||||
@@ -22626,6 +22626,21 @@
|
||||
]
|
||||
}
|
||||
],
|
||||
+ "TransparentHwndEnlargement": [
|
||||
+ {
|
||||
+ "platforms": [
|
||||
+ "windows"
|
||||
+ ],
|
||||
+ "experiments": [
|
||||
+ {
|
||||
+ "name": "DisableTransparentHwndEnlargement",
|
||||
+ "disable_features": [
|
||||
+ "EnableTransparentHwndEnlargement"
|
||||
+ ]
|
||||
+ }
|
||||
+ ]
|
||||
+ }
|
||||
+ ],
|
||||
"TransportSecurityFileWriterScheduleAndroid": [
|
||||
{
|
||||
"platforms": [
|
||||
diff --git a/ui/views/views_features.cc b/ui/views/views_features.cc
|
||||
index 47077e16870889ef8f8c8b2adf58015bd5aff7fa..ba59e6e1609e61579bf49aca095490b083d72051 100644
|
||||
--- a/ui/views/views_features.cc
|
||||
+++ b/ui/views/views_features.cc
|
||||
@@ -30,6 +30,14 @@ BASE_FEATURE(kEnableInputProtection, base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
// crbug.com/370856871.
|
||||
BASE_FEATURE(kEnableTouchDragCursorSync, base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
|
||||
+// Enables enlargement of HWNDs to a minimum size of 64x64 to handle reported
|
||||
+// graphical glitches on certain hardware.
|
||||
+// TODO(crbug.com/401996981): Remove this once enlargement is confirmed to no
|
||||
+// longer be needed.
|
||||
+BASE_FEATURE(kEnableTransparentHwndEnlargement,
|
||||
+ "EnableTransparentHwndEnlargement",
|
||||
+ base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
+
|
||||
// Used to enable keyboard-accessible tooltips in Views UI, as opposed
|
||||
// to kKeyboardAccessibleTooltip in //ui/base/ui_base_features.cc.
|
||||
BASE_FEATURE(kKeyboardAccessibleTooltipInViews,
|
||||
diff --git a/ui/views/views_features.h b/ui/views/views_features.h
|
||||
index 888a16fb6213eceb131ae636dc643d7f2d5bcad9..6077412165081cd4abeaf0b061feb2f795ee8131 100644
|
||||
--- a/ui/views/views_features.h
|
||||
+++ b/ui/views/views_features.h
|
||||
@@ -15,6 +15,7 @@ namespace views::features {
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kApplyInitialUrlToWebContents);
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableInputProtection);
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableTouchDragCursorSync);
|
||||
+VIEWS_EXPORT BASE_DECLARE_FEATURE(kEnableTransparentHwndEnlargement);
|
||||
VIEWS_EXPORT BASE_DECLARE_FEATURE(kKeyboardAccessibleTooltipInViews);
|
||||
|
||||
} // namespace views::features
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
index e4da40256ce94d6a0896792a8ef2faa18e1fa5d2..d1e06b675b19226cf3b78e1aada8d8f2d684fada 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.cc
|
||||
@@ -85,6 +85,23 @@ namespace {
|
||||
// This constant controls how many pixels wide that border is.
|
||||
const int kMouseCaptureRegionBorder = 5;
|
||||
|
||||
+gfx::Size GetExpandedWindowSize(bool is_translucent, gfx::Size size) {
|
||||
+ if (!base::FeatureList::IsEnabled(
|
||||
+ features::kEnableTransparentHwndEnlargement) ||
|
||||
+ !is_translucent) {
|
||||
+ return size;
|
||||
+ }
|
||||
+
|
||||
+ // Some AMD drivers can't display windows that are less than 64x64 pixels,
|
||||
+ // so expand them to be at least that size. http://crbug.com/286609
|
||||
+ gfx::Size expanded(std::max(size.width(), 64), std::max(size.height(), 64));
|
||||
+ return expanded;
|
||||
+}
|
||||
+
|
||||
+void InsetBottomRight(gfx::Rect* rect, const gfx::Vector2d& vector) {
|
||||
+ rect->Inset(gfx::Insets::TLBR(0, 0, vector.y(), vector.x()));
|
||||
+}
|
||||
+
|
||||
// Updates the cursor clip region. Used for mouse locking.
|
||||
void UpdateMouseLockRegion(aura::Window* window, bool locked) {
|
||||
if (!locked) {
|
||||
@@ -342,9 +359,14 @@ bool DesktopWindowTreeHostWin::IsVisible() const {
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::SetSize(const gfx::Size& size) {
|
||||
- const gfx::Size size_in_pixels =
|
||||
+ gfx::Size size_in_pixels =
|
||||
display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
|
||||
- message_handler_->SetSize(size_in_pixels);
|
||||
+ gfx::Size expanded =
|
||||
+ GetExpandedWindowSize(message_handler_->is_translucent(), size_in_pixels);
|
||||
+ window_enlargement_ =
|
||||
+ gfx::Vector2d(expanded.width() - size_in_pixels.width(),
|
||||
+ expanded.height() - size_in_pixels.height());
|
||||
+ message_handler_->SetSize(expanded);
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::StackAbove(aura::Window* window) {
|
||||
@@ -359,30 +381,40 @@ void DesktopWindowTreeHostWin::StackAtTop() {
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::CenterWindow(const gfx::Size& size) {
|
||||
- const gfx::Size size_in_pixels =
|
||||
+ gfx::Size size_in_pixels =
|
||||
display::win::GetScreenWin()->DIPToScreenSize(GetHWND(), size);
|
||||
- message_handler_->CenterWindow(size_in_pixels);
|
||||
+ gfx::Size expanded_size;
|
||||
+ expanded_size =
|
||||
+ GetExpandedWindowSize(message_handler_->is_translucent(), size_in_pixels);
|
||||
+ window_enlargement_ =
|
||||
+ gfx::Vector2d(expanded_size.width() - size_in_pixels.width(),
|
||||
+ expanded_size.height() - size_in_pixels.height());
|
||||
+ message_handler_->CenterWindow(expanded_size);
|
||||
}
|
||||
|
||||
void DesktopWindowTreeHostWin::GetWindowPlacement(
|
||||
gfx::Rect* bounds,
|
||||
ui::mojom::WindowShowState* show_state) const {
|
||||
message_handler_->GetWindowPlacement(bounds, show_state);
|
||||
+ InsetBottomRight(bounds, window_enlargement_);
|
||||
*bounds = display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), *bounds);
|
||||
}
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetWindowBoundsInScreen() const {
|
||||
gfx::Rect pixel_bounds = message_handler_->GetWindowBoundsInScreen();
|
||||
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
|
||||
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
|
||||
}
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetClientAreaBoundsInScreen() const {
|
||||
gfx::Rect pixel_bounds = message_handler_->GetClientAreaBoundsInScreen();
|
||||
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
|
||||
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
|
||||
}
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetRestoredBounds() const {
|
||||
gfx::Rect pixel_bounds = message_handler_->GetRestoredBounds();
|
||||
+ InsetBottomRight(&pixel_bounds, window_enlargement_);
|
||||
return display::win::GetScreenWin()->ScreenToDIPRect(GetHWND(), pixel_bounds);
|
||||
}
|
||||
|
||||
@@ -701,37 +733,44 @@ void DesktopWindowTreeHostWin::HideImpl() {
|
||||
// other get/set methods work in DIP.
|
||||
|
||||
gfx::Rect DesktopWindowTreeHostWin::GetBoundsInPixels() const {
|
||||
- const gfx::Rect bounds_px(message_handler_->GetClientAreaBounds());
|
||||
+ gfx::Rect bounds(message_handler_->GetClientAreaBounds());
|
||||
// If the window bounds were expanded we need to return the original bounds
|
||||
// To achieve this we do the reverse of the expansion, i.e. add the
|
||||
// window_expansion_top_left_delta_ to the origin and subtract the
|
||||
// window_expansion_bottom_right_delta_ from the width and height.
|
||||
- const gfx::Rect without_expansion_bounds_px(
|
||||
- bounds_px.x() + window_expansion_top_left_delta_.x(),
|
||||
- bounds_px.y() + window_expansion_top_left_delta_.y(),
|
||||
- bounds_px.width() - window_expansion_bottom_right_delta_.x(),
|
||||
- bounds_px.height() - window_expansion_bottom_right_delta_.y());
|
||||
- return without_expansion_bounds_px;
|
||||
+ gfx::Rect without_expansion(
|
||||
+ bounds.x() + window_expansion_top_left_delta_.x(),
|
||||
+ bounds.y() + window_expansion_top_left_delta_.y(),
|
||||
+ bounds.width() - window_expansion_bottom_right_delta_.x() -
|
||||
+ window_enlargement_.x(),
|
||||
+ bounds.height() - window_expansion_bottom_right_delta_.y() -
|
||||
+ window_enlargement_.y());
|
||||
+ return without_expansion;
|
||||
}
|
||||
|
||||
-void DesktopWindowTreeHostWin::SetBoundsInPixels(
|
||||
- const gfx::Rect& bounds_in_pixels) {
|
||||
+void DesktopWindowTreeHostWin::SetBoundsInPixels(const gfx::Rect& bounds) {
|
||||
// If the window bounds have to be expanded we need to subtract the
|
||||
// window_expansion_top_left_delta_ from the origin and add the
|
||||
// window_expansion_bottom_right_delta_ to the width and height
|
||||
- const gfx::Size old_content_size_px = GetBoundsInPixels().size();
|
||||
-
|
||||
- const gfx::Rect expanded_bounds_px(
|
||||
- bounds_in_pixels.x() - window_expansion_top_left_delta_.x(),
|
||||
- bounds_in_pixels.y() - window_expansion_top_left_delta_.y(),
|
||||
- bounds_in_pixels.width() + window_expansion_bottom_right_delta_.x(),
|
||||
- bounds_in_pixels.height() + window_expansion_bottom_right_delta_.y());
|
||||
-
|
||||
- // When `expanded_bounds_px` causes the window to be moved to a display with a
|
||||
+ gfx::Size old_content_size = GetBoundsInPixels().size();
|
||||
+
|
||||
+ gfx::Rect expanded(
|
||||
+ bounds.x() - window_expansion_top_left_delta_.x(),
|
||||
+ bounds.y() - window_expansion_top_left_delta_.y(),
|
||||
+ bounds.width() + window_expansion_bottom_right_delta_.x(),
|
||||
+ bounds.height() + window_expansion_bottom_right_delta_.y());
|
||||
+
|
||||
+ gfx::Rect new_expanded(
|
||||
+ expanded.origin(),
|
||||
+ GetExpandedWindowSize(message_handler_->is_translucent(),
|
||||
+ expanded.size()));
|
||||
+ window_enlargement_ =
|
||||
+ gfx::Vector2d(new_expanded.width() - expanded.width(),
|
||||
+ new_expanded.height() - expanded.height());
|
||||
+ // When |new_expanded| causes the window to be moved to a display with a
|
||||
// different DSF, HWNDMessageHandler::OnDpiChanged() will be called and the
|
||||
// window size will be scaled automatically.
|
||||
- message_handler_->SetBounds(expanded_bounds_px,
|
||||
- old_content_size_px != bounds_in_pixels.size());
|
||||
+ message_handler_->SetBounds(new_expanded, old_content_size != bounds.size());
|
||||
}
|
||||
|
||||
gfx::Rect
|
||||
@@ -943,18 +982,26 @@ int DesktopWindowTreeHostWin::GetNonClientComponent(
|
||||
|
||||
void DesktopWindowTreeHostWin::GetWindowMask(const gfx::Size& size_px,
|
||||
SkPath* path) {
|
||||
- Widget* widget = GetWidget();
|
||||
- if (!widget || !widget->non_client_view()) {
|
||||
- return;
|
||||
- }
|
||||
+ // Request the window mask for hwnd of `size_px`. The hwnd size must be
|
||||
+ // adjusted by `window_enlargement` to return to the client-expected window
|
||||
+ // size (see crbug.com/41047830).
|
||||
+ const gfx::Size adjusted_size_in_px =
|
||||
+ size_px - gfx::Size(window_enlargement_.x(), window_enlargement_.y());
|
||||
|
||||
- widget->non_client_view()->GetWindowMask(
|
||||
- display::win::GetScreenWin()->ScreenToDIPSize(GetHWND(), size_px), path);
|
||||
- // Convert path in DIPs to pixels.
|
||||
- if (!path->isEmpty()) {
|
||||
- const float scale =
|
||||
- display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
|
||||
- *path = path->makeTransform(SkMatrix::Scale(scale, scale));
|
||||
+ if (Widget* widget = GetWidget(); widget && widget->non_client_view()) {
|
||||
+ widget->non_client_view()->GetWindowMask(
|
||||
+ display::win::GetScreenWin()->ScreenToDIPSize(GetHWND(),
|
||||
+ adjusted_size_in_px),
|
||||
+ path);
|
||||
+ // Convert path in DIPs to pixels.
|
||||
+ if (!path->isEmpty()) {
|
||||
+ const float scale =
|
||||
+ display::win::GetScreenWin()->GetScaleFactorForHWND(GetHWND());
|
||||
+ *path = path->makeTransform(SkMatrix::Scale(scale, scale));
|
||||
+ }
|
||||
+ } else if (!window_enlargement_.IsZero()) {
|
||||
+ *path = SkPath::Rect(SkRect::MakeXYWH(0, 0, adjusted_size_in_px.width(),
|
||||
+ adjusted_size_in_px.height()));
|
||||
}
|
||||
}
|
||||
|
||||
diff --git a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
index 27322ef34edf3fa8bfbd20b1baddcaf3b7555618..a40bd9f25fa07a553c011cf19f155f8158f4ae5f 100644
|
||||
--- a/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
+++ b/ui/views/widget/desktop_aura/desktop_window_tree_host_win.h
|
||||
@@ -175,7 +175,7 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
|
||||
void ShowImpl() override;
|
||||
void HideImpl() override;
|
||||
gfx::Rect GetBoundsInPixels() const override;
|
||||
- void SetBoundsInPixels(const gfx::Rect& bounds_in_pixels) override;
|
||||
+ void SetBoundsInPixels(const gfx::Rect& bounds) override;
|
||||
gfx::Rect GetBoundsInAcceleratedWidgetPixelCoordinates() override;
|
||||
gfx::Point GetLocationOnScreenInPixels() const override;
|
||||
void SetCapture() override;
|
||||
@@ -330,6 +330,12 @@ class VIEWS_EXPORT DesktopWindowTreeHostWin
|
||||
gfx::Vector2d window_expansion_top_left_delta_;
|
||||
gfx::Vector2d window_expansion_bottom_right_delta_;
|
||||
|
||||
+ // Windows are enlarged to be at least 64x64 pixels, so keep track of the
|
||||
+ // extra added here.
|
||||
+ // TODO(crbug.com/401996981): This is likely no longer necessary and should be
|
||||
+ // removed.
|
||||
+ gfx::Vector2d window_enlargement_;
|
||||
+
|
||||
// Whether the window close should be converted to a hide, and then actually
|
||||
// closed on the completion of the hide animation. This is cached because
|
||||
// the property is set on the contained window which has a shorter lifetime.
|
||||
@@ -1,8 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
"import"
|
||||
],
|
||||
"rules": {
|
||||
"import/enforce-node-protocol-usage": ["error", "always"]
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,16 @@
|
||||
#!/usr/bin/env python3
|
||||
|
||||
import argparse
|
||||
import concurrent.futures
|
||||
import json
|
||||
import os
|
||||
import subprocess
|
||||
import warnings
|
||||
|
||||
from lib import git
|
||||
from lib.patches import patch_from_dir
|
||||
|
||||
ELECTRON_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
THREEWAY = "ELECTRON_USE_THREE_WAY_MERGE_FOR_PATCHES" in os.environ
|
||||
|
||||
def apply_patches(target):
|
||||
@@ -19,14 +22,43 @@ def apply_patches(target):
|
||||
git.import_patches(
|
||||
committer_email="scripts@electron",
|
||||
committer_name="Electron Scripts",
|
||||
output_prefix=f'[{os.path.basename(patch_dir)}] ',
|
||||
patch_data=patch_from_dir(patch_dir),
|
||||
repo=repo,
|
||||
threeway=THREEWAY,
|
||||
)
|
||||
|
||||
def is_roller_branch():
|
||||
try:
|
||||
branch = subprocess.check_output(
|
||||
['git', '-C', ELECTRON_DIR, 'rev-parse', '--abbrev-ref', 'HEAD'],
|
||||
stderr=subprocess.DEVNULL,
|
||||
).decode('utf-8').strip()
|
||||
return branch.startswith('roller/')
|
||||
except subprocess.CalledProcessError:
|
||||
return False
|
||||
|
||||
def apply_config(config):
|
||||
for target in config:
|
||||
apply_patches(target)
|
||||
# Targets are independent git repos, so apply in parallel. The work is
|
||||
# subprocess-bound (git am), so threads are sufficient. On roller/
|
||||
# branches, patch conflicts are expected and interleaved failure output
|
||||
# from multiple repos is hard to read, so force sequential there.
|
||||
if is_roller_branch():
|
||||
max_workers = 1
|
||||
else:
|
||||
max_workers = max(1, (os.cpu_count() or 4) - 2)
|
||||
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as pool:
|
||||
futures = {pool.submit(apply_patches, t): t for t in config}
|
||||
failed = []
|
||||
for f in concurrent.futures.as_completed(futures):
|
||||
try:
|
||||
f.result()
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
failed.append((futures[f].get('repo'), e))
|
||||
if failed:
|
||||
for repo, e in failed:
|
||||
print(f'ERROR applying patches to {repo}: {e}')
|
||||
raise failed[0][1]
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(description='Apply Electron patches')
|
||||
|
||||
@@ -1,22 +1,143 @@
|
||||
import { createHash } from 'node:crypto';
|
||||
import * as fs from 'node:fs/promises';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { parseArgs } from 'node:util';
|
||||
|
||||
import { getChromiumVersionFromDEPS } from './lib/utils.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ELECTRON_DIR = resolve(__dirname, '..');
|
||||
|
||||
function getCommonTags () {
|
||||
const tags = [];
|
||||
|
||||
if (process.env.TARGET_ARCH) tags.push(`target-arch:${process.env.TARGET_ARCH}`);
|
||||
if (process.env.TARGET_PLATFORM) tags.push(`target-platform:${process.env.TARGET_PLATFORM}`);
|
||||
if (process.env.GITHUB_HEAD_REF) {
|
||||
// Will be set in pull requests
|
||||
tags.push(`branch:${process.env.GITHUB_HEAD_REF}`);
|
||||
} else if (process.env.GITHUB_REF_NAME) {
|
||||
// Will be set for release branches
|
||||
tags.push(`branch:${process.env.GITHUB_REF_NAME}`);
|
||||
}
|
||||
|
||||
return tags;
|
||||
}
|
||||
|
||||
async function uploadSeriesToDatadog (series) {
|
||||
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 })
|
||||
});
|
||||
}
|
||||
|
||||
async function uploadCacheHitRateStats (hitRate, stats) {
|
||||
const timestamp = Math.round(new Date().getTime() / 1000);
|
||||
const tags = getCommonTags();
|
||||
|
||||
const series = [
|
||||
{
|
||||
metric: 'electron.build.effective-cache-hit-rate',
|
||||
points: [{ timestamp, value: (hitRate * 100).toFixed(2) }],
|
||||
type: 3, // GAUGE
|
||||
unit: 'percent',
|
||||
tags
|
||||
}
|
||||
];
|
||||
|
||||
// Add all raw stats as individual metrics
|
||||
for (const [key, value] of Object.entries(stats)) {
|
||||
series.push({
|
||||
metric: `electron.build.stats.${key.toLowerCase()}`,
|
||||
points: [{ timestamp, value }],
|
||||
type: 1, // COUNT
|
||||
tags
|
||||
});
|
||||
}
|
||||
|
||||
await uploadSeriesToDatadog(series);
|
||||
}
|
||||
|
||||
async function uploadObjectChangeStats (stats) {
|
||||
const timestamp = Math.round(new Date().getTime() / 1000);
|
||||
const tags = getCommonTags();
|
||||
|
||||
if (stats['previous-chromium-version']) tags.push(`previous-chromium-version:${stats['previous-chromium-version']}`);
|
||||
if (stats['chromium-version']) tags.push(`chromium-version:${stats['chromium-version']}`);
|
||||
|
||||
if (stats['previous-chromium-version'] && stats['chromium-version']) {
|
||||
tags.push(`chromium-version-changed:${stats['previous-chromium-version'] !== stats['chromium-version']}`);
|
||||
}
|
||||
|
||||
const series = [
|
||||
{
|
||||
metric: 'electron.build.object-change-rate',
|
||||
points: [{ timestamp, value: (stats['change-rate'] * 100).toFixed(2) }],
|
||||
type: 3, // GAUGE
|
||||
unit: 'percent',
|
||||
tags
|
||||
},
|
||||
{
|
||||
metric: 'electron.build.object-change-size',
|
||||
points: [{ timestamp, value: stats['change-size'] }],
|
||||
type: 1, // COUNT
|
||||
unit: 'byte',
|
||||
tags
|
||||
},
|
||||
{
|
||||
metric: 'electron.build.new-object-count',
|
||||
points: [{ timestamp, value: stats['new-object-count'] }],
|
||||
type: 1, // COUNT
|
||||
unit: 'count',
|
||||
tags
|
||||
}
|
||||
];
|
||||
|
||||
await uploadSeriesToDatadog(series);
|
||||
}
|
||||
|
||||
async function main () {
|
||||
const { positionals: [filename], values: { 'upload-stats': uploadStats } } = parseArgs({
|
||||
const { positionals: [filename], values } = parseArgs({
|
||||
allowPositionals: true,
|
||||
options: {
|
||||
'upload-stats': {
|
||||
type: 'boolean',
|
||||
default: false
|
||||
},
|
||||
'out-dir': {
|
||||
type: 'string'
|
||||
},
|
||||
'input-object-checksums': {
|
||||
type: 'string'
|
||||
},
|
||||
'output-object-checksums': {
|
||||
type: 'string'
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const {
|
||||
'upload-stats': uploadStats,
|
||||
'out-dir': outDir,
|
||||
'input-object-checksums': inputObjectChecksums,
|
||||
'output-object-checksums': outputObjectChecksums
|
||||
} = values;
|
||||
|
||||
if (!filename) {
|
||||
throw new Error('filename is required (should be a siso.INFO file)');
|
||||
}
|
||||
|
||||
if ((inputObjectChecksums || outputObjectChecksums) && !outDir) {
|
||||
throw new Error('--out-dir is required when using --input-object-checksums or --output-object-checksums');
|
||||
} else if (outDir && (!inputObjectChecksums && !outputObjectChecksums)) {
|
||||
throw new Error('--out-dir only makes sense with --input-object-checksums or --output-object-checksums');
|
||||
}
|
||||
|
||||
const log = await fs.readFile(filename, 'utf-8');
|
||||
|
||||
// We expect to find a line which looks like stats=build.Stats{..., CacheHit:39008, Local:4778, Remote:0, LocalFallback:0, ...}
|
||||
@@ -33,55 +154,84 @@ async function main () {
|
||||
const hitRate = stats.CacheHit / (stats.Remote + stats.CacheHit + stats.LocalFallback);
|
||||
|
||||
const messagePrefix = process.env.GITHUB_ACTIONS ? '::notice title=Build Stats::' : '';
|
||||
|
||||
console.log(`${messagePrefix}Effective cache hit rate: ${(hitRate * 100).toFixed(2)}%`);
|
||||
|
||||
const objectChangeStats = {};
|
||||
|
||||
if (inputObjectChecksums || outputObjectChecksums) {
|
||||
const depsContent = await fs.readFile(resolve(ELECTRON_DIR, 'DEPS'), 'utf8');
|
||||
const currentVersion = getChromiumVersionFromDEPS(depsContent);
|
||||
|
||||
// Calculate the SHA256 for each object file under `outDir`
|
||||
const files = await fs.readdir(outDir, { encoding: 'utf8', recursive: true });
|
||||
const objectFiles = files.filter(file => file.endsWith('.o') || file.endsWith('.obj'));
|
||||
const checksums = {};
|
||||
for (const file of objectFiles) {
|
||||
const content = await fs.readFile(resolve(outDir, file));
|
||||
checksums[file] = createHash('sha256').update(content).digest('hex');
|
||||
}
|
||||
|
||||
if (outputObjectChecksums) {
|
||||
const outputData = {
|
||||
chromiumVersion: currentVersion,
|
||||
checksums
|
||||
};
|
||||
|
||||
await fs.writeFile(outputObjectChecksums, JSON.stringify(outputData, null, 2));
|
||||
}
|
||||
|
||||
if (inputObjectChecksums) {
|
||||
const inputData = JSON.parse(await fs.readFile(inputObjectChecksums, 'utf8'));
|
||||
const inputFiles = Object.keys(inputData.checksums);
|
||||
let changedCount = 0;
|
||||
let newObjectCount = 0;
|
||||
let changedSize = 0;
|
||||
|
||||
// Count changed files (only those present in both input and current)
|
||||
for (const file of inputFiles) {
|
||||
if (!(file in checksums)) continue; // Skip deleted files
|
||||
if (inputData.checksums[file] !== checksums[file]) {
|
||||
changedCount++;
|
||||
const stat = await fs.stat(resolve(outDir, file));
|
||||
changedSize += stat.size;
|
||||
}
|
||||
}
|
||||
|
||||
// Count new files (in current but not in input)
|
||||
for (const file of Object.keys(checksums)) {
|
||||
if (!(file in inputData.checksums)) {
|
||||
newObjectCount++;
|
||||
const stat = await fs.stat(resolve(outDir, file));
|
||||
changedSize += stat.size;
|
||||
}
|
||||
}
|
||||
|
||||
const changeRate = inputFiles.length > 0 ? changedCount / inputFiles.length : 0;
|
||||
console.log(`${messagePrefix}Object change rate: ${(changeRate * 100).toFixed(2)}%`);
|
||||
if (newObjectCount > 0) {
|
||||
console.log(`${messagePrefix}New object count: ${newObjectCount}`);
|
||||
}
|
||||
console.log(`${messagePrefix}Cumulative changed object sizes: ${changedSize.toLocaleString()} bytes`);
|
||||
|
||||
objectChangeStats['change-rate'] = changeRate;
|
||||
objectChangeStats['change-size'] = changedSize;
|
||||
objectChangeStats['new-object-count'] = newObjectCount;
|
||||
objectChangeStats['previous-chromium-version'] = inputData.chromiumVersion;
|
||||
objectChangeStats['chromium-version'] = currentVersion;
|
||||
}
|
||||
}
|
||||
|
||||
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 uploadCacheHitRateStats(hitRate, stats);
|
||||
|
||||
const tags = [];
|
||||
|
||||
if (process.env.TARGET_ARCH) tags.push(`target-arch:${process.env.TARGET_ARCH}`);
|
||||
if (process.env.TARGET_PLATFORM) tags.push(`target-platform:${process.env.TARGET_PLATFORM}`);
|
||||
if (process.env.GITHUB_HEAD_REF) {
|
||||
// Will be set in pull requests
|
||||
tags.push(`branch:${process.env.GITHUB_HEAD_REF}`);
|
||||
} else if (process.env.GITHUB_REF_NAME) {
|
||||
// Will be set for release branches
|
||||
tags.push(`branch:${process.env.GITHUB_REF_NAME}`);
|
||||
if (Object.keys(objectChangeStats).length > 0) {
|
||||
await uploadObjectChangeStats(objectChangeStats);
|
||||
}
|
||||
|
||||
const series = [
|
||||
{
|
||||
metric: 'electron.build.effective-cache-hit-rate',
|
||||
points: [{ timestamp, value: (hitRate * 100).toFixed(2) }],
|
||||
type: 3, // GAUGE
|
||||
unit: 'percent',
|
||||
tags
|
||||
}
|
||||
];
|
||||
|
||||
// Add all raw stats as individual metrics
|
||||
for (const [key, value] of Object.entries(stats)) {
|
||||
series.push({
|
||||
metric: `electron.build.stats.${key.toLowerCase()}`,
|
||||
points: [{ timestamp, value }],
|
||||
type: 1, // COUNT
|
||||
tags
|
||||
});
|
||||
}
|
||||
|
||||
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 })
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import posixpath
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import threading
|
||||
|
||||
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
||||
sys.path.append(SCRIPT_DIR)
|
||||
@@ -59,7 +60,8 @@ def get_repo_root(path):
|
||||
|
||||
|
||||
def am(repo, patch_data, threeway=False, directory=None, exclude=None,
|
||||
committer_name=None, committer_email=None, keep_cr=True):
|
||||
committer_name=None, committer_email=None, keep_cr=True,
|
||||
output_prefix=None):
|
||||
# --keep-non-patch prevents stripping leading bracketed strings on the subject line
|
||||
args = ['--keep-non-patch']
|
||||
if threeway:
|
||||
@@ -80,9 +82,42 @@ def am(repo, patch_data, threeway=False, directory=None, exclude=None,
|
||||
if committer_email is not None:
|
||||
root_args += ['-c', 'user.email=' + committer_email]
|
||||
root_args += ['-c', 'commit.gpgsign=false']
|
||||
# git am rewrites the index 2-3x per patch. In large repos (Chromium's
|
||||
# index is ~70MB / ~500K files) this dominates wall time. skipHash
|
||||
# avoids recomputing the trailing SHA over the full index on every
|
||||
# write, and index v4 roughly halves the on-disk size via path prefix
|
||||
# compression. Also skip per-object fsync and auto-gc since a crashed
|
||||
# apply is simply re-run from a clean reset.
|
||||
root_args += [
|
||||
'-c', 'index.skipHash=true',
|
||||
'-c', 'index.version=4',
|
||||
'-c', 'core.fsync=none',
|
||||
'-c', 'gc.auto=0',
|
||||
]
|
||||
command = ['git'] + root_args + ['am'] + args
|
||||
with subprocess.Popen(command, stdin=subprocess.PIPE) as proc:
|
||||
proc.communicate(patch_data.encode('utf-8'))
|
||||
popen_kwargs = {'stdin': subprocess.PIPE}
|
||||
if output_prefix is not None:
|
||||
popen_kwargs['stdout'] = subprocess.PIPE
|
||||
popen_kwargs['stderr'] = subprocess.STDOUT
|
||||
with subprocess.Popen(command, **popen_kwargs) as proc:
|
||||
def feed_stdin():
|
||||
proc.stdin.write(patch_data.encode('utf-8'))
|
||||
proc.stdin.close()
|
||||
if output_prefix is not None:
|
||||
writer = threading.Thread(target=feed_stdin)
|
||||
writer.start()
|
||||
for line in proc.stdout:
|
||||
try:
|
||||
sys.stdout.write(
|
||||
f'{output_prefix}{line.decode("utf-8", "replace")}')
|
||||
sys.stdout.flush()
|
||||
except BrokenPipeError:
|
||||
pass
|
||||
writer.join()
|
||||
proc.wait()
|
||||
else:
|
||||
feed_stdin()
|
||||
proc.wait()
|
||||
if proc.returncode != 0:
|
||||
raise RuntimeError(f"Command {command} returned {proc.returncode}")
|
||||
|
||||
@@ -93,6 +128,12 @@ def import_patches(repo, ref=UPSTREAM_HEAD, **kwargs):
|
||||
update_ref(repo=repo, ref=ref, newvalue='HEAD')
|
||||
if ref != _LEGACY_UPSTREAM_HEAD:
|
||||
update_ref(repo=repo, ref=_LEGACY_UPSTREAM_HEAD, newvalue='HEAD')
|
||||
# Upgrade to index v4 before applying so every intermediate index write
|
||||
# during am benefits from path-prefix compression (roughly halves index
|
||||
# size in large repos).
|
||||
subprocess.call(
|
||||
['git', '-C', repo, 'update-index', '--index-version', '4'],
|
||||
stderr=subprocess.DEVNULL)
|
||||
am(repo=repo, **kwargs)
|
||||
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ const path = require('node:path');
|
||||
const ELECTRON_DIR = path.resolve(__dirname, '..', '..');
|
||||
const SRC_DIR = path.resolve(ELECTRON_DIR, '..');
|
||||
|
||||
const CHROMIUM_VERSION_DEPS_REGEX = /chromium_version':\n +'(.+?)',/m;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const pass = chalk.green('✓');
|
||||
const fail = chalk.red('✗');
|
||||
@@ -162,10 +164,15 @@ function compareVersions (v1, v2) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
function getChromiumVersionFromDEPS (depsContent) {
|
||||
return CHROMIUM_VERSION_DEPS_REGEX.exec(depsContent)?.[1] ?? null;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
chunkFilenames,
|
||||
compareVersions,
|
||||
findMatchingFiles,
|
||||
getChromiumVersionFromDEPS,
|
||||
getCurrentBranch,
|
||||
getElectronExec,
|
||||
getOutDir,
|
||||
|
||||
27
script/lint-plugins/no-only-tests.mjs
Normal file
27
script/lint-plugins/no-only-tests.mjs
Normal file
@@ -0,0 +1,27 @@
|
||||
const BLOCK_NAMES = new Set(['describe', 'it', 'context', 'test', 'specify', 'suite']);
|
||||
|
||||
export default {
|
||||
meta: { name: 'no-only-tests' },
|
||||
rules: {
|
||||
'no-only-tests': {
|
||||
meta: { type: 'problem' },
|
||||
create (context) {
|
||||
return {
|
||||
MemberExpression (node) {
|
||||
if (
|
||||
node.property.type === 'Identifier' &&
|
||||
node.property.name === 'only' &&
|
||||
node.object.type === 'Identifier' &&
|
||||
BLOCK_NAMES.has(node.object.name)
|
||||
) {
|
||||
context.report({
|
||||
node: node.property,
|
||||
message: `${node.object.name}.only not permitted`
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -5,12 +5,11 @@ import * as fs from 'node:fs/promises';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { compareVersions } from './lib/utils.js';
|
||||
import { compareVersions, getChromiumVersionFromDEPS } from './lib/utils.js';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const ELECTRON_DIR = resolve(__dirname, '..');
|
||||
|
||||
const DEPS_REGEX = /chromium_version':\n +'(.+?)',/m;
|
||||
const CL_REGEX = /https:\/\/chromium-review\.googlesource\.com\/c\/(chromium\/src|devtools\/devtools-frontend|v8\/v8)\/\+\/(\d+)(#\S+)?/g;
|
||||
const ROLLER_BRANCH_PATTERN = /^roller\/chromium\/(.+)$/;
|
||||
|
||||
@@ -140,12 +139,12 @@ async function main () {
|
||||
cwd: ELECTRON_DIR,
|
||||
encoding: 'utf8'
|
||||
});
|
||||
baseVersion = DEPS_REGEX.exec(baseDepsContent)?.[1] ?? null;
|
||||
baseVersion = getChromiumVersionFromDEPS(baseDepsContent);
|
||||
} catch {
|
||||
// baseVersion remains null
|
||||
}
|
||||
const depsContent = await fs.readFile(resolve(ELECTRON_DIR, 'DEPS'), 'utf8');
|
||||
const newVersion = DEPS_REGEX.exec(depsContent)?.[1] ?? null;
|
||||
const newVersion = getChromiumVersionFromDEPS(depsContent);
|
||||
|
||||
if (!baseVersion || !newVersion) {
|
||||
console.error('Could not determine Chromium version range');
|
||||
|
||||
112
script/lint.js
112
script/lint.js
@@ -1,10 +1,8 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { ESLint } = require('eslint');
|
||||
const minimist = require('minimist');
|
||||
|
||||
const childProcess = require('node:child_process');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
|
||||
@@ -72,22 +70,31 @@ 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`);
|
||||
function runOxlint (filenames, { fix } = {}) {
|
||||
const oxlintBin = path.join(
|
||||
ELECTRON_ROOT,
|
||||
'node_modules',
|
||||
'.bin',
|
||||
IS_WINDOWS ? 'oxlint.cmd' : 'oxlint'
|
||||
);
|
||||
const args = [];
|
||||
if (fix) args.push('--fix');
|
||||
// Emit GitHub Actions annotations directly when running in CI so errors
|
||||
// surface inline on the PR without a separate problem matcher.
|
||||
if (process.env.GITHUB_ACTIONS === 'true') {
|
||||
args.push('--format=github');
|
||||
}
|
||||
for (const chunk of chunkFilenames(filenames)) {
|
||||
const result = childProcess.spawnSync(oxlintBin, [...args, '--', ...chunk], {
|
||||
stdio: 'inherit',
|
||||
shell: IS_WINDOWS,
|
||||
cwd: ELECTRON_ROOT
|
||||
});
|
||||
if (result.status !== 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
console.log(formatter.format(results));
|
||||
if (fix) {
|
||||
await ESLint.outputFixes(results);
|
||||
}
|
||||
|
||||
return successCount === filenames.length;
|
||||
return true;
|
||||
}
|
||||
|
||||
function cpplint (args) {
|
||||
@@ -155,15 +162,7 @@ const LINTERS = [{
|
||||
ignoreRoots: ['.github/workflows/node_modules', 'spec/node_modules', 'spec/fixtures/native-addon'],
|
||||
test: filename => filename.endsWith('.js') || filename.endsWith('.ts') || filename.endsWith('.mjs'),
|
||||
run: async (opts, filenames) => {
|
||||
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')}`,
|
||||
extensions: ['.js', '.ts'],
|
||||
fix: opts.fix,
|
||||
resolvePluginsRelativeTo: ELECTRON_ROOT
|
||||
});
|
||||
const clean = await runEslint(eslint, filenames, { fix: opts.fix, verbose: opts.verbose });
|
||||
const clean = runOxlint(filenames, { fix: opts.fix });
|
||||
if (!clean) {
|
||||
console.error('Linting had errors');
|
||||
process.exit(1);
|
||||
@@ -295,13 +294,56 @@ const LINTERS = [{
|
||||
// Run the remaining checks only in docs
|
||||
const docs = filenames.filter(filename => path.dirname(filename).split(path.sep)[0] === 'docs');
|
||||
|
||||
// Node.js builtin modules that should be imported with the `node:` protocol
|
||||
// in docs code blocks. This mirrors what the old docs/.eslintrc.json
|
||||
// enforced via `import/enforce-node-protocol-usage` (added in #42113,
|
||||
// originally as `unicorn/prefer-node-protocol`).
|
||||
const NODE_BUILTINS = new Set([
|
||||
'assert', 'async_hooks', 'buffer', 'child_process', 'cluster', 'console',
|
||||
'constants', 'crypto', 'dgram', 'diagnostics_channel', 'dns', 'domain',
|
||||
'events', 'fs', 'http', 'http2', 'https', 'inspector', 'module', 'net',
|
||||
'os', 'path', 'perf_hooks', 'process', 'punycode', 'querystring',
|
||||
'readline', 'repl', 'stream', 'string_decoder', 'sys', 'timers', 'tls',
|
||||
'trace_events', 'tty', 'url', 'util', 'v8', 'vm', 'wasi',
|
||||
'worker_threads', 'zlib'
|
||||
]);
|
||||
const NODE_IMPORT_RE = /(?:require\s*\(\s*|from\s+|import\s*\(\s*)['"]([^'"/]+)(?:\/[^'"]*)?['"]/g;
|
||||
const BREAKING_CHANGES_MD = path.join('docs', 'breaking-changes.md');
|
||||
|
||||
// Strip line and block comments from a code snippet so the import check
|
||||
// does not flag bare-specifier examples that appear inside `// ...` or
|
||||
// `/* ... */` explanations. This is a conservative textual strip — it is
|
||||
// not a full JS parser, but it is good enough for docs code blocks and
|
||||
// matches the behavior of the AST-based rule it replaced.
|
||||
const stripComments = (source) => source
|
||||
.replace(/\/\*[\s\S]*?\*\//g, (m) => m.replace(/[^\n]/g, ' '))
|
||||
.replace(/(^|[^:])\/\/[^\n]*/g, (_m, prefix) => prefix);
|
||||
|
||||
for (const filename of docs) {
|
||||
const contents = fs.readFileSync(filename, 'utf8');
|
||||
const codeBlocks = await getCodeBlocks(contents);
|
||||
|
||||
const skipNodeProtocolCheck = path.normalize(filename).endsWith(BREAKING_CHANGES_MD);
|
||||
|
||||
for (const codeBlock of codeBlocks) {
|
||||
const line = codeBlock.position.start.line;
|
||||
|
||||
// Check for bare Node.js builtin imports in JS/TS/fiddle code blocks.
|
||||
if (!skipNodeProtocolCheck && codeBlock.lang && ['js', 'ts', 'javascript', 'typescript', 'fiddle'].includes(codeBlock.lang.toLowerCase())) {
|
||||
const blockLines = stripComments(codeBlock.value).split('\n');
|
||||
for (let i = 0; i < blockLines.length; i++) {
|
||||
NODE_IMPORT_RE.lastIndex = 0;
|
||||
let match;
|
||||
while ((match = NODE_IMPORT_RE.exec(blockLines[i])) !== null) {
|
||||
const mod = match[1];
|
||||
if (NODE_BUILTINS.has(mod)) {
|
||||
console.log(`${filename}:${line + 1 + i} Use 'node:${mod}' instead of bare '${mod}' in code blocks`);
|
||||
errors = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (codeBlock.lang) {
|
||||
// Enforce all lowercase language identifiers
|
||||
if (codeBlock.lang.toLowerCase() !== codeBlock.lang) {
|
||||
@@ -368,26 +410,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);
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ from pathlib import Path
|
||||
|
||||
SRC_DIR = Path(__file__).resolve().parents[3]
|
||||
sys.path.append(os.path.join(SRC_DIR, 'third_party/electron_node/tools'))
|
||||
sys.path.append(str(Path(__file__).resolve().parents[1])) # electron/script/
|
||||
|
||||
from lib.util import get_out_dir
|
||||
import install
|
||||
|
||||
class LoadPythonDictionaryError(Exception):
|
||||
@@ -31,13 +33,6 @@ def LoadPythonDictionary(path):
|
||||
)
|
||||
return file_data
|
||||
|
||||
def get_out_dir():
|
||||
out_dir = 'Testing'
|
||||
override = os.environ.get('ELECTRON_OUT_DIR')
|
||||
if override is not None:
|
||||
out_dir = override
|
||||
return os.path.join(SRC_DIR, 'out', out_dir)
|
||||
|
||||
if __name__ == '__main__':
|
||||
node_root_dir = os.path.join(SRC_DIR, 'third_party/electron_node')
|
||||
out = {}
|
||||
|
||||
@@ -77,6 +77,7 @@
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/error_thrower.h"
|
||||
#include "shell/common/gin_helper/object_template_builder.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/language_util.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/options_switches.h"
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
#include "base/path_service.h"
|
||||
#include "shell/common/electron_paths.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/process_util.h"
|
||||
#include "shell/common/thread_restrictions.h"
|
||||
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "shell/browser/api/electron_api_web_frame_main.h"
|
||||
#include "shell/browser/native_window.h"
|
||||
#include "shell/common/keyboard_util.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "ui/base/cocoa/menu_utils.h"
|
||||
#include "v8/include/cppgc/allocation.h"
|
||||
#include "v8/include/v8-cppgc.h"
|
||||
|
||||
@@ -54,17 +54,9 @@ gin_helper::Handle<SafeStorage> SafeStorage::Create(v8::Isolate* isolate) {
|
||||
return gin_helper::CreateHandle(isolate, new SafeStorage(isolate));
|
||||
}
|
||||
|
||||
SafeStorage::SafeStorage(v8::Isolate* isolate) {
|
||||
if (electron::Browser::Get()->is_ready()) {
|
||||
OnFinishLaunching({});
|
||||
} else {
|
||||
Browser::Get()->AddObserver(this);
|
||||
}
|
||||
}
|
||||
SafeStorage::SafeStorage(v8::Isolate* isolate) {}
|
||||
|
||||
SafeStorage::~SafeStorage() {
|
||||
Browser::Get()->RemoveObserver(this);
|
||||
}
|
||||
SafeStorage::~SafeStorage() = default;
|
||||
|
||||
gin::ObjectTemplateBuilder SafeStorage::GetObjectTemplateBuilder(
|
||||
v8::Isolate* isolate) {
|
||||
@@ -85,7 +77,11 @@ gin::ObjectTemplateBuilder SafeStorage::GetObjectTemplateBuilder(
|
||||
;
|
||||
}
|
||||
|
||||
void SafeStorage::OnFinishLaunching(base::DictValue launch_info) {
|
||||
void SafeStorage::EnsureAsyncEncryptorRequested() {
|
||||
DCHECK(electron::Browser::Get()->is_ready());
|
||||
if (encryptor_requested_)
|
||||
return;
|
||||
encryptor_requested_ = true;
|
||||
g_browser_process->os_crypt_async()->GetInstance(
|
||||
base::BindOnce(&SafeStorage::OnOsCryptReady, base::Unretained(this)),
|
||||
os_crypt_async::Encryptor::Option::kEncryptSyncCompat);
|
||||
@@ -95,13 +91,21 @@ void SafeStorage::OnOsCryptReady(os_crypt_async::Encryptor encryptor) {
|
||||
encryptor_ = std::move(encryptor);
|
||||
is_available_ = true;
|
||||
|
||||
// This callback may fire from a posted task without an active V8 scope.
|
||||
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
|
||||
for (auto& pending : pending_availability_checks_) {
|
||||
pending.Resolve(true);
|
||||
}
|
||||
pending_availability_checks_.clear();
|
||||
|
||||
for (auto& pending : pending_encrypts_) {
|
||||
std::string ciphertext;
|
||||
bool encrypted = encryptor_->EncryptString(pending.plaintext, &ciphertext);
|
||||
if (encrypted) {
|
||||
pending.promise.Resolve(
|
||||
electron::Buffer::Copy(pending.promise.isolate(), ciphertext)
|
||||
.ToLocalChecked());
|
||||
electron::Buffer::Copy(isolate, ciphertext).ToLocalChecked());
|
||||
} else {
|
||||
pending.promise.RejectWithErrorMessage(
|
||||
"Error while encrypting the text provided to "
|
||||
@@ -117,8 +121,6 @@ void SafeStorage::OnOsCryptReady(os_crypt_async::Encryptor encryptor) {
|
||||
encryptor_->DecryptString(pending.ciphertext, &plaintext, &flags);
|
||||
|
||||
if (decrypted) {
|
||||
v8::Isolate* isolate = pending.promise.isolate();
|
||||
v8::HandleScope handle_scope(isolate);
|
||||
auto dict = gin_helper::Dictionary::CreateEmpty(isolate);
|
||||
|
||||
dict.Set("shouldReEncrypt", flags.should_reencrypt);
|
||||
@@ -155,16 +157,33 @@ bool SafeStorage::IsEncryptionAvailable() {
|
||||
#endif
|
||||
}
|
||||
|
||||
bool SafeStorage::IsAsyncEncryptionAvailable() {
|
||||
if (!electron::Browser::Get()->is_ready())
|
||||
return false;
|
||||
v8::Local<v8::Promise> SafeStorage::IsAsyncEncryptionAvailable(
|
||||
v8::Isolate* isolate) {
|
||||
gin_helper::Promise<bool> promise(isolate);
|
||||
v8::Local<v8::Promise> handle = promise.GetHandle();
|
||||
|
||||
if (!electron::Browser::Get()->is_ready()) {
|
||||
promise.Resolve(false);
|
||||
return handle;
|
||||
}
|
||||
|
||||
#if BUILDFLAG(IS_LINUX)
|
||||
return is_available_ || (use_password_v10_ &&
|
||||
static_cast<BrowserProcessImpl*>(g_browser_process)
|
||||
->linux_storage_backend() == "basic_text");
|
||||
#else
|
||||
return is_available_;
|
||||
if (use_password_v10_ && static_cast<BrowserProcessImpl*>(g_browser_process)
|
||||
->linux_storage_backend() == "basic_text") {
|
||||
promise.Resolve(true);
|
||||
return handle;
|
||||
}
|
||||
#endif
|
||||
|
||||
EnsureAsyncEncryptorRequested();
|
||||
|
||||
if (is_available_) {
|
||||
promise.Resolve(true);
|
||||
return handle;
|
||||
}
|
||||
|
||||
pending_availability_checks_.push_back(std::move(promise));
|
||||
return handle;
|
||||
}
|
||||
|
||||
void SafeStorage::SetUsePasswordV10(bool use) {
|
||||
@@ -270,6 +289,8 @@ v8::Local<v8::Promise> SafeStorage::encryptStringAsync(
|
||||
return handle;
|
||||
}
|
||||
|
||||
EnsureAsyncEncryptorRequested();
|
||||
|
||||
if (is_available_) {
|
||||
std::string ciphertext;
|
||||
bool encrypted = encryptor_->EncryptString(plaintext, &ciphertext);
|
||||
@@ -318,6 +339,8 @@ v8::Local<v8::Promise> SafeStorage::decryptStringAsync(
|
||||
return handle;
|
||||
}
|
||||
|
||||
EnsureAsyncEncryptorRequested();
|
||||
|
||||
if (is_available_) {
|
||||
std::string plaintext;
|
||||
os_crypt_async::Encryptor::DecryptFlags flags;
|
||||
|
||||
@@ -10,7 +10,6 @@
|
||||
|
||||
#include "build/build_config.h"
|
||||
#include "components/os_crypt/async/common/encryptor.h"
|
||||
#include "shell/browser/browser_observer.h"
|
||||
#include "shell/browser/event_emitter_mixin.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
@@ -37,8 +36,7 @@ class Handle;
|
||||
namespace electron::api {
|
||||
|
||||
class SafeStorage final : public gin_helper::DeprecatedWrappable<SafeStorage>,
|
||||
public gin_helper::EventEmitterMixin<SafeStorage>,
|
||||
private BrowserObserver {
|
||||
public gin_helper::EventEmitterMixin<SafeStorage> {
|
||||
public:
|
||||
static gin_helper::Handle<SafeStorage> Create(v8::Isolate* isolate);
|
||||
|
||||
@@ -57,14 +55,16 @@ class SafeStorage final : public gin_helper::DeprecatedWrappable<SafeStorage>,
|
||||
~SafeStorage() override;
|
||||
|
||||
private:
|
||||
// BrowserObserver:
|
||||
void OnFinishLaunching(base::DictValue launch_info) override;
|
||||
// Lazily request the async encryptor on first use. ESM named imports
|
||||
// eagerly evaluate all electron module getters, so requesting in the
|
||||
// constructor would touch the OS keychain even when safeStorage is unused.
|
||||
void EnsureAsyncEncryptorRequested();
|
||||
|
||||
void OnOsCryptReady(os_crypt_async::Encryptor encryptor);
|
||||
|
||||
bool IsEncryptionAvailable();
|
||||
|
||||
bool IsAsyncEncryptionAvailable();
|
||||
v8::Local<v8::Promise> IsAsyncEncryptionAvailable(v8::Isolate* isolate);
|
||||
|
||||
void SetUsePasswordV10(bool use);
|
||||
|
||||
@@ -85,6 +85,7 @@ class SafeStorage final : public gin_helper::DeprecatedWrappable<SafeStorage>,
|
||||
|
||||
bool use_password_v10_ = false;
|
||||
|
||||
bool encryptor_requested_ = false;
|
||||
bool is_available_ = false;
|
||||
|
||||
std::optional<os_crypt_async::Encryptor> encryptor_;
|
||||
@@ -114,6 +115,8 @@ class SafeStorage final : public gin_helper::DeprecatedWrappable<SafeStorage>,
|
||||
std::string ciphertext;
|
||||
};
|
||||
std::vector<PendingDecrypt> pending_decrypts_;
|
||||
|
||||
std::vector<gin_helper::Promise<bool>> pending_availability_checks_;
|
||||
};
|
||||
|
||||
} // namespace electron::api
|
||||
|
||||
@@ -26,7 +26,6 @@
|
||||
#include "shell/common/gin_converters/gurl_converter.h"
|
||||
#include "shell/common/gin_converters/value_converter.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/process_util.h"
|
||||
#include "skia/ext/skia_utils_mac.h"
|
||||
|
||||
|
||||
@@ -47,11 +47,13 @@
|
||||
#include "content/browser/renderer_host/render_widget_host_impl.h" // nogncheck
|
||||
#include "content/browser/renderer_host/render_widget_host_view_base.h" // nogncheck
|
||||
#include "content/browser/web_contents/web_contents_impl.h" // nogncheck
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/child_process_security_policy.h"
|
||||
#include "content/public/browser/context_menu_params.h"
|
||||
#include "content/public/browser/desktop_media_id.h"
|
||||
#include "content/public/browser/desktop_streams_registry.h"
|
||||
#include "content/public/browser/devtools_agent_host.h"
|
||||
#include "content/public/browser/download_manager.h"
|
||||
#include "content/public/browser/download_request_utils.h"
|
||||
#include "content/public/browser/favicon_status.h"
|
||||
#include "content/public/browser/file_select_listener.h"
|
||||
@@ -90,9 +92,11 @@
|
||||
#include "services/service_manager/public/cpp/interface_provider.h"
|
||||
#include "shell/browser/api/electron_api_browser_window.h"
|
||||
#include "shell/browser/api/electron_api_debugger.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/api/electron_api_web_frame_main.h"
|
||||
#include "shell/browser/api/frame_subscriber.h"
|
||||
#include "shell/browser/api/message_port.h"
|
||||
#include "shell/browser/api/save_page_handler.h"
|
||||
#include "shell/browser/browser.h"
|
||||
#include "shell/browser/child_web_contents_tracker.h"
|
||||
#include "shell/browser/electron_autofill_driver_factory.h"
|
||||
@@ -2885,8 +2889,8 @@ v8::Local<v8::Promise> WebContents::SavePage(
|
||||
return handle;
|
||||
}
|
||||
|
||||
auto* handler = new SavePageHandler(web_contents(), std::move(promise));
|
||||
handler->Handle(full_file_path, save_type);
|
||||
auto* handler = new SavePageHandler{std::move(promise)};
|
||||
handler->Handle(full_file_path, save_type, web_contents());
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
@@ -22,8 +22,6 @@
|
||||
#include "chrome/browser/ui/exclusive_access/exclusive_access_context.h" // nogncheck
|
||||
#include "chrome/browser/ui/exclusive_access/exclusive_access_manager.h"
|
||||
#include "content/common/frame.mojom-forward.h"
|
||||
#include "content/public/browser/browser_thread.h"
|
||||
#include "content/public/browser/devtools_agent_host.h"
|
||||
#include "content/public/browser/frame_tree_node_id.h"
|
||||
#include "content/public/browser/global_routing_id.h"
|
||||
#include "content/public/browser/javascript_dialog_manager.h"
|
||||
@@ -33,9 +31,6 @@
|
||||
#include "content/public/common/stop_find_action.h"
|
||||
#include "electron/buildflags/buildflags.h"
|
||||
#include "printing/buildflags/buildflags.h"
|
||||
#include "shell/browser/api/electron_api_debugger.h"
|
||||
#include "shell/browser/api/electron_api_session.h"
|
||||
#include "shell/browser/api/save_page_handler.h"
|
||||
#include "shell/browser/background_throttling_source.h"
|
||||
#include "shell/browser/event_emitter_mixin.h"
|
||||
#include "shell/browser/extended_web_contents_observer.h"
|
||||
@@ -43,13 +38,10 @@
|
||||
#include "shell/browser/preload_script.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_delegate.h"
|
||||
#include "shell/browser/ui/inspectable_web_contents_view_delegate.h"
|
||||
#include "shell/common/api/api.mojom.h"
|
||||
#include "shell/common/gin_helper/cleaned_up_at_exit.h"
|
||||
#include "shell/common/gin_helper/constructible.h"
|
||||
#include "shell/common/gin_helper/handle.h"
|
||||
#include "shell/common/gin_helper/pinnable.h"
|
||||
#include "shell/common/gin_helper/wrappable.h"
|
||||
#include "shell/common/web_contents_utility.mojom.h"
|
||||
#include "ui/base/models/image_model.h"
|
||||
#include "v8/include/cppgc/persistent.h"
|
||||
|
||||
@@ -66,8 +58,14 @@ struct DeviceEmulationParams;
|
||||
// enum class PermissionType;
|
||||
} // namespace blink
|
||||
|
||||
namespace base {
|
||||
class FilePath;
|
||||
class Value;
|
||||
} // namespace base
|
||||
|
||||
namespace content {
|
||||
enum class KeyboardEventProcessingResult;
|
||||
class DevToolsAgentHost;
|
||||
class WebContents;
|
||||
} // namespace content
|
||||
|
||||
@@ -79,6 +77,8 @@ namespace gin_helper {
|
||||
class Dictionary;
|
||||
class ErrorThrower;
|
||||
template <typename T>
|
||||
class Handle;
|
||||
template <typename T>
|
||||
class Promise;
|
||||
} // namespace gin_helper
|
||||
|
||||
@@ -103,7 +103,6 @@ class ElectronBrowserContext;
|
||||
class InspectableWebContents;
|
||||
class WebContentsZoomController;
|
||||
class WebViewGuestDelegate;
|
||||
class WebDialogHelper;
|
||||
class NativeWindow;
|
||||
class OffScreenRenderWidgetHostView;
|
||||
class OffScreenWebContentsView;
|
||||
@@ -111,7 +110,9 @@ class OffScreenWebContentsView;
|
||||
namespace api {
|
||||
|
||||
class BaseWindow;
|
||||
class Debugger;
|
||||
class FrameSubscriber;
|
||||
class Session;
|
||||
|
||||
// Wrapper around the content::WebContents.
|
||||
class WebContents final : public ExclusiveAccessContext,
|
||||
|
||||
@@ -36,6 +36,8 @@
|
||||
#include "shell/common/node_includes.h"
|
||||
#include "shell/common/v8_util.h"
|
||||
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
|
||||
#include "third_party/blink/public/mojom/frame/media_player_action.mojom.h"
|
||||
#include "ui/gfx/geometry/point.h"
|
||||
|
||||
namespace {
|
||||
|
||||
@@ -260,6 +262,28 @@ v8::Local<v8::Promise> WebFrameMain::ExecuteJavaScript(
|
||||
return handle;
|
||||
}
|
||||
|
||||
void WebFrameMain::CopyVideoFrameAt(int x, int y) {
|
||||
if (!CheckRenderFrame())
|
||||
return;
|
||||
auto location = gfx::Point(x, y);
|
||||
auto action = blink::mojom::MediaPlayerAction(
|
||||
blink::mojom::MediaPlayerActionType::kCopyVideoFrame,
|
||||
/*enable=*/true);
|
||||
return render_frame_host()->ExecuteMediaPlayerActionAtLocation(location,
|
||||
action);
|
||||
}
|
||||
|
||||
void WebFrameMain::SaveVideoFrameAs(int x, int y) {
|
||||
if (!CheckRenderFrame())
|
||||
return;
|
||||
auto location = gfx::Point(x, y);
|
||||
auto action = blink::mojom::MediaPlayerAction(
|
||||
blink::mojom::MediaPlayerActionType::kSaveVideoFrameAs,
|
||||
/*enable=*/true);
|
||||
return render_frame_host()->ExecuteMediaPlayerActionAtLocation(location,
|
||||
action);
|
||||
}
|
||||
|
||||
bool WebFrameMain::Reload() {
|
||||
if (!CheckRenderFrame())
|
||||
return false;
|
||||
@@ -596,6 +620,8 @@ void WebFrameMain::FillObjectTemplate(v8::Isolate* isolate,
|
||||
.SetMethod("executeJavaScript", &WebFrameMain::ExecuteJavaScript)
|
||||
.SetMethod("collectJavaScriptCallStack",
|
||||
&WebFrameMain::CollectDocumentJSCallStack)
|
||||
.SetMethod("copyVideoFrameAt", &WebFrameMain::CopyVideoFrameAt)
|
||||
.SetMethod("saveVideoFrameAs", &WebFrameMain::SaveVideoFrameAs)
|
||||
.SetMethod("reload", &WebFrameMain::Reload)
|
||||
.SetMethod("isDestroyed", &WebFrameMain::IsDestroyed)
|
||||
.SetMethod("_send", &WebFrameMain::Send)
|
||||
|
||||
@@ -118,6 +118,8 @@ class WebFrameMain final : public gin_helper::DeprecatedWrappable<WebFrameMain>,
|
||||
|
||||
v8::Local<v8::Promise> ExecuteJavaScript(gin::Arguments* args,
|
||||
const std::u16string& code);
|
||||
void CopyVideoFrameAt(int x, int y);
|
||||
void SaveVideoFrameAs(int x, int y);
|
||||
bool Reload();
|
||||
bool IsDestroyed() const;
|
||||
void Send(v8::Isolate* isolate,
|
||||
|
||||
@@ -12,9 +12,8 @@
|
||||
|
||||
namespace electron::api {
|
||||
|
||||
SavePageHandler::SavePageHandler(content::WebContents* web_contents,
|
||||
gin_helper::Promise<void> promise)
|
||||
: web_contents_(web_contents), promise_(std::move(promise)) {}
|
||||
SavePageHandler::SavePageHandler(gin_helper::Promise<void> promise)
|
||||
: promise_{std::move(promise)} {}
|
||||
|
||||
SavePageHandler::~SavePageHandler() = default;
|
||||
|
||||
@@ -26,9 +25,10 @@ void SavePageHandler::OnDownloadCreated(content::DownloadManager* manager,
|
||||
}
|
||||
|
||||
bool SavePageHandler::Handle(const base::FilePath& full_path,
|
||||
const content::SavePageType& save_type) {
|
||||
const content::SavePageType& save_type,
|
||||
content::WebContents* web_contents) {
|
||||
auto* download_manager =
|
||||
web_contents_->GetBrowserContext()->GetDownloadManager();
|
||||
web_contents->GetBrowserContext()->GetDownloadManager();
|
||||
download_manager->AddObserver(this);
|
||||
// Chromium will create a 'foo_files' directory under the directory of saving
|
||||
// page 'foo.html' for holding other resource files of 'foo.html'.
|
||||
@@ -36,7 +36,7 @@ bool SavePageHandler::Handle(const base::FilePath& full_path,
|
||||
full_path.RemoveExtension().BaseName().value() +
|
||||
FILE_PATH_LITERAL("_files"));
|
||||
bool result =
|
||||
web_contents_->SavePage(full_path, saved_main_directory_path, save_type);
|
||||
web_contents->SavePage(full_path, saved_main_directory_path, save_type);
|
||||
download_manager->RemoveObserver(this);
|
||||
// If initialization fails which means fail to create |DownloadItem|, we need
|
||||
// to delete the |SavePageHandler| instance to avoid memory-leak.
|
||||
|
||||
@@ -5,7 +5,6 @@
|
||||
#ifndef ELECTRON_SHELL_BROWSER_API_SAVE_PAGE_HANDLER_H_
|
||||
#define ELECTRON_SHELL_BROWSER_API_SAVE_PAGE_HANDLER_H_
|
||||
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "components/download/public/common/download_item.h"
|
||||
#include "content/public/browser/download_manager.h"
|
||||
#include "content/public/browser/save_page_type.h"
|
||||
@@ -26,12 +25,12 @@ namespace electron::api {
|
||||
class SavePageHandler : private content::DownloadManager::Observer,
|
||||
private download::DownloadItem::Observer {
|
||||
public:
|
||||
SavePageHandler(content::WebContents* web_contents,
|
||||
gin_helper::Promise<void> promise);
|
||||
explicit SavePageHandler(gin_helper::Promise<void> promise);
|
||||
~SavePageHandler() override;
|
||||
|
||||
bool Handle(const base::FilePath& full_path,
|
||||
const content::SavePageType& save_type);
|
||||
const content::SavePageType& save_type,
|
||||
content::WebContents* web_contents);
|
||||
|
||||
private:
|
||||
void Destroy(download::DownloadItem* item);
|
||||
@@ -43,7 +42,6 @@ class SavePageHandler : private content::DownloadManager::Observer,
|
||||
// download::DownloadItem::Observer:
|
||||
void OnDownloadUpdated(download::DownloadItem* item) override;
|
||||
|
||||
raw_ptr<content::WebContents> web_contents_; // weak
|
||||
gin_helper::Promise<void> promise_;
|
||||
};
|
||||
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include "shell/browser/window_list.h"
|
||||
#include "shell/common/application_info.h"
|
||||
#include "shell/common/gin_converters/login_item_settings_converter.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/thread_restrictions.h"
|
||||
|
||||
namespace electron {
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
#include "base/task/cancelable_task_tracker.h"
|
||||
#include "base/values.h"
|
||||
#include "shell/browser/window_list_observer.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
|
||||
#if BUILDFLAG(IS_WIN)
|
||||
#include <windows.h>
|
||||
@@ -35,7 +34,17 @@ class Arguments;
|
||||
|
||||
namespace gin_helper {
|
||||
class Arguments;
|
||||
}
|
||||
template <typename T>
|
||||
class Promise;
|
||||
} // namespace gin_helper
|
||||
|
||||
namespace v8 {
|
||||
template <typename T>
|
||||
class Local;
|
||||
class Isolate;
|
||||
class Promise;
|
||||
class Value;
|
||||
} // namespace v8
|
||||
|
||||
namespace electron {
|
||||
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
#include "shell/common/gin_converters/image_converter.h"
|
||||
#include "shell/common/gin_converters/login_item_settings_converter.h"
|
||||
#include "shell/common/gin_helper/dictionary.h"
|
||||
#include "shell/common/gin_helper/promise.h"
|
||||
#include "shell/common/skia_util.h"
|
||||
#include "shell/common/thread_restrictions.h"
|
||||
#include "skia/ext/font_utils.h"
|
||||
|
||||
@@ -73,9 +73,8 @@ void CertificateManagerModel::Create(CreationCallback callback) {
|
||||
}
|
||||
|
||||
CertificateManagerModel::CertificateManagerModel(
|
||||
net::NSSCertDatabase* nss_cert_database,
|
||||
bool is_user_db_available)
|
||||
: cert_db_(nss_cert_database), is_user_db_available_(is_user_db_available) {
|
||||
net::NSSCertDatabase* nss_cert_database)
|
||||
: cert_db_(nss_cert_database) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
}
|
||||
|
||||
@@ -91,24 +90,6 @@ int CertificateManagerModel::ImportFromPKCS12(
|
||||
imported_certs);
|
||||
}
|
||||
|
||||
int CertificateManagerModel::ImportUserCert(const std::string& data) {
|
||||
return cert_db_->ImportUserCert(data);
|
||||
}
|
||||
|
||||
bool CertificateManagerModel::ImportCACerts(
|
||||
const net::ScopedCERTCertificateList& certificates,
|
||||
net::NSSCertDatabase::TrustBits trust_bits,
|
||||
net::NSSCertDatabase::ImportCertFailureList* not_imported) {
|
||||
return cert_db_->ImportCACerts(certificates, trust_bits, not_imported);
|
||||
}
|
||||
|
||||
bool CertificateManagerModel::ImportServerCert(
|
||||
const net::ScopedCERTCertificateList& certificates,
|
||||
net::NSSCertDatabase::TrustBits trust_bits,
|
||||
net::NSSCertDatabase::ImportCertFailureList* not_imported) {
|
||||
return cert_db_->ImportServerCert(certificates, trust_bits, not_imported);
|
||||
}
|
||||
|
||||
bool CertificateManagerModel::SetCertTrust(
|
||||
CERTCertificate* cert,
|
||||
net::CertType type,
|
||||
@@ -116,19 +97,13 @@ bool CertificateManagerModel::SetCertTrust(
|
||||
return cert_db_->SetCertTrust(cert, type, trust_bits);
|
||||
}
|
||||
|
||||
bool CertificateManagerModel::Delete(CERTCertificate* cert) {
|
||||
return cert_db_->DeleteCertAndKey(cert);
|
||||
}
|
||||
|
||||
// static
|
||||
void CertificateManagerModel::DidGetCertDBOnUIThread(
|
||||
net::NSSCertDatabase* cert_db,
|
||||
bool is_user_db_available,
|
||||
CreationCallback callback) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::UI);
|
||||
|
||||
auto model = base::WrapUnique(
|
||||
new CertificateManagerModel(cert_db, is_user_db_available));
|
||||
auto model = base::WrapUnique(new CertificateManagerModel(cert_db));
|
||||
std::move(callback).Run(std::move(model));
|
||||
}
|
||||
|
||||
@@ -138,11 +113,10 @@ void CertificateManagerModel::DidGetCertDBOnIOThread(
|
||||
net::NSSCertDatabase* cert_db) {
|
||||
DCHECK_CURRENTLY_ON(BrowserThread::IO);
|
||||
|
||||
bool is_user_db_available = !!cert_db->GetPublicSlot();
|
||||
content::GetUIThreadTaskRunner({})->PostTask(
|
||||
FROM_HERE,
|
||||
base::BindOnce(&CertificateManagerModel::DidGetCertDBOnUIThread, cert_db,
|
||||
is_user_db_available, std::move(callback)));
|
||||
std::move(callback)));
|
||||
}
|
||||
|
||||
// static
|
||||
|
||||
@@ -29,8 +29,6 @@ class CertificateManagerModel {
|
||||
|
||||
~CertificateManagerModel();
|
||||
|
||||
bool is_user_db_available() const { return is_user_db_available_; }
|
||||
|
||||
// Accessor for read-only access to the underlying NSSCertDatabase.
|
||||
const net::NSSCertDatabase* cert_db() const { return cert_db_; }
|
||||
|
||||
@@ -44,37 +42,6 @@ class CertificateManagerModel {
|
||||
bool is_extractable,
|
||||
net::ScopedCERTCertificateList* imported_certs);
|
||||
|
||||
// Import user certificate from DER encoded |data|.
|
||||
// Returns a net error code on failure.
|
||||
int ImportUserCert(const std::string& data);
|
||||
|
||||
// Import CA certificates.
|
||||
// Tries to import all the certificates given. The root will be trusted
|
||||
// according to |trust_bits|. Any certificates that could not be imported
|
||||
// will be listed in |not_imported|.
|
||||
// |trust_bits| should be a bit field of TRUST* values from NSSCertDatabase.
|
||||
// Returns false if there is an internal error, otherwise true is returned and
|
||||
// |not_imported| should be checked for any certificates that were not
|
||||
// imported.
|
||||
bool ImportCACerts(const net::ScopedCERTCertificateList& certificates,
|
||||
net::NSSCertDatabase::TrustBits trust_bits,
|
||||
net::NSSCertDatabase::ImportCertFailureList* not_imported);
|
||||
|
||||
// Import server certificate. The first cert should be the server cert. Any
|
||||
// additional certs should be intermediate/CA certs and will be imported but
|
||||
// not given any trust.
|
||||
// Any certificates that could not be imported will be listed in
|
||||
// |not_imported|.
|
||||
// |trust_bits| can be set to explicitly trust or distrust the certificate, or
|
||||
// use TRUST_DEFAULT to inherit trust as normal.
|
||||
// Returns false if there is an internal error, otherwise true is returned and
|
||||
// |not_imported| should be checked for any certificates that were not
|
||||
// imported.
|
||||
bool ImportServerCert(
|
||||
const net::ScopedCERTCertificateList& certificates,
|
||||
net::NSSCertDatabase::TrustBits trust_bits,
|
||||
net::NSSCertDatabase::ImportCertFailureList* not_imported);
|
||||
|
||||
// Set trust values for certificate.
|
||||
// |trust_bits| should be a bit field of TRUST* values from NSSCertDatabase.
|
||||
// Returns true on success or false on failure.
|
||||
@@ -82,27 +49,18 @@ class CertificateManagerModel {
|
||||
net::CertType type,
|
||||
net::NSSCertDatabase::TrustBits trust_bits);
|
||||
|
||||
// Delete the cert. Returns true on success. |cert| is still valid when this
|
||||
// function returns.
|
||||
bool Delete(CERTCertificate* cert);
|
||||
|
||||
private:
|
||||
CertificateManagerModel(net::NSSCertDatabase* nss_cert_database,
|
||||
bool is_user_db_available);
|
||||
explicit CertificateManagerModel(net::NSSCertDatabase* nss_cert_database);
|
||||
|
||||
// Methods used during initialization, see the comment at the top of the .cc
|
||||
// file for details.
|
||||
static void DidGetCertDBOnUIThread(net::NSSCertDatabase* cert_db,
|
||||
bool is_user_db_available,
|
||||
CreationCallback callback);
|
||||
static void DidGetCertDBOnIOThread(CreationCallback callback,
|
||||
net::NSSCertDatabase* cert_db);
|
||||
static void GetCertDBOnIOThread(CreationCallback callback);
|
||||
|
||||
raw_ptr<net::NSSCertDatabase> cert_db_;
|
||||
// Whether the certificate database has a public slot associated with the
|
||||
// profile. If not set, importing certificates is not allowed with this model.
|
||||
bool is_user_db_available_;
|
||||
};
|
||||
|
||||
#endif // ELECTRON_SHELL_BROWSER_CERTIFICATE_MANAGER_MODEL_H_
|
||||
|
||||
@@ -91,16 +91,6 @@ AutofillDriver* AutofillDriverFactory::DriverForFrame(
|
||||
return driver.get();
|
||||
}
|
||||
|
||||
void AutofillDriverFactory::AddDriverForFrame(
|
||||
content::RenderFrameHost* render_frame_host,
|
||||
CreationCallback factory_method) {
|
||||
auto insertion_result = driver_map_.try_emplace(render_frame_host, nullptr);
|
||||
// This can be called twice for the key representing the main frame.
|
||||
if (insertion_result.second) {
|
||||
insertion_result.first->second = std::move(factory_method).Run();
|
||||
}
|
||||
}
|
||||
|
||||
void AutofillDriverFactory::DeleteDriverForFrame(
|
||||
content::RenderFrameHost* render_frame_host) {
|
||||
driver_map_.erase(render_frame_host);
|
||||
|
||||
@@ -7,7 +7,6 @@
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "base/functional/callback_forward.h"
|
||||
#include "content/public/browser/web_contents_observer.h"
|
||||
#include "content/public/browser/web_contents_user_data.h"
|
||||
#include "shell/common/api/api.mojom.h"
|
||||
@@ -21,9 +20,6 @@ class AutofillDriverFactory
|
||||
: private content::WebContentsObserver,
|
||||
public content::WebContentsUserData<AutofillDriverFactory> {
|
||||
public:
|
||||
typedef base::OnceCallback<std::unique_ptr<AutofillDriver>()>
|
||||
CreationCallback;
|
||||
|
||||
~AutofillDriverFactory() override;
|
||||
|
||||
static void BindAutofillDriver(
|
||||
@@ -32,8 +28,6 @@ class AutofillDriverFactory
|
||||
content::RenderFrameHost* render_frame_host);
|
||||
|
||||
AutofillDriver* DriverForFrame(content::RenderFrameHost* render_frame_host);
|
||||
void AddDriverForFrame(content::RenderFrameHost* render_frame_host,
|
||||
CreationCallback factory_method);
|
||||
void DeleteDriverForFrame(content::RenderFrameHost* render_frame_host);
|
||||
|
||||
void CloseAllPopups();
|
||||
|
||||
@@ -63,6 +63,7 @@
|
||||
#include "mojo/public/cpp/bindings/self_owned_associated_receiver.h"
|
||||
#include "net/ssl/ssl_cert_request_info.h"
|
||||
#include "net/ssl/ssl_private_key.h"
|
||||
#include "pdf/pdf_features.h"
|
||||
#include "printing/buildflags/buildflags.h"
|
||||
#include "services/device/public/cpp/geolocation/geolocation_system_permission_manager.h"
|
||||
#include "services/device/public/cpp/geolocation/location_provider.h"
|
||||
@@ -1591,6 +1592,46 @@ bool ElectronBrowserClient::ShouldEnableStrictSiteIsolation() {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ElectronBrowserClient::ShouldEnableSubframeZoom() {
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
return chrome_pdf::features::IsOopifPdfEnabled();
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
std::optional<network::CrossOriginEmbedderPolicy>
|
||||
ElectronBrowserClient::MaybeOverrideLocalURLCrossOriginEmbedderPolicy(
|
||||
content::NavigationHandle* navigation_handle) {
|
||||
if (!chrome_pdf::features::IsOopifPdfEnabled() ||
|
||||
!navigation_handle->IsPdf()) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
content::RenderFrameHost* pdf_extension = navigation_handle->GetParentFrame();
|
||||
if (!pdf_extension) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
content::RenderFrameHost* pdf_embedder = pdf_extension->GetParent();
|
||||
CHECK(pdf_embedder);
|
||||
return pdf_embedder->GetCrossOriginEmbedderPolicy();
|
||||
}
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
|
||||
bool ElectronBrowserClient::DoesSiteRequireDedicatedProcess(
|
||||
content::BrowserContext* browser_context,
|
||||
const GURL& effective_site_url) {
|
||||
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
|
||||
return GetEnabledExtensionFromEffectiveURL(browser_context,
|
||||
effective_site_url) != nullptr;
|
||||
#else
|
||||
return content::ContentBrowserClient::DoesSiteRequireDedicatedProcess(
|
||||
browser_context, effective_site_url);
|
||||
#endif
|
||||
}
|
||||
|
||||
void ElectronBrowserClient::BindHostReceiverForRenderer(
|
||||
content::RenderProcessHost* render_process_host,
|
||||
mojo::GenericPendingReceiver receiver) {
|
||||
|
||||
@@ -30,6 +30,7 @@ class FilePath;
|
||||
|
||||
namespace content {
|
||||
class ClientCertificateDelegate;
|
||||
class NavigationHandle;
|
||||
class PlatformNotificationService;
|
||||
class NavigationThrottleRegistry;
|
||||
class QuotaPermissionContext;
|
||||
@@ -82,6 +83,14 @@ class ElectronBrowserClient : public content::ContentBrowserClient,
|
||||
// content::ContentBrowserClient:
|
||||
std::string GetApplicationLocale() override;
|
||||
bool ShouldEnableStrictSiteIsolation() override;
|
||||
bool ShouldEnableSubframeZoom() override;
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
std::optional<network::CrossOriginEmbedderPolicy>
|
||||
MaybeOverrideLocalURLCrossOriginEmbedderPolicy(
|
||||
content::NavigationHandle* navigation_handle) override;
|
||||
#endif // BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
bool DoesSiteRequireDedicatedProcess(content::BrowserContext* browser_context,
|
||||
const GURL& effective_site_url) override;
|
||||
void BindHostReceiverForRenderer(
|
||||
content::RenderProcessHost* render_process_host,
|
||||
mojo::GenericPendingReceiver receiver) override;
|
||||
|
||||
@@ -71,7 +71,7 @@ void StreamsPrivateAPI::SendExecuteMimeTypeHandlerEvent(
|
||||
std::move(transferrable_loader), original_url);
|
||||
|
||||
#if BUILDFLAG(ENABLE_PDF_VIEWER)
|
||||
if (base::FeatureList::IsEnabled(chrome_pdf::features::kPdfOopif) &&
|
||||
if (chrome_pdf::features::IsOopifPdfEnabled() &&
|
||||
extension_id == extension_misc::kPdfExtensionId) {
|
||||
pdf::PdfViewerStreamManager::Create(web_contents);
|
||||
pdf::PdfViewerStreamManager::FromWebContents(web_contents)
|
||||
|
||||
@@ -26,7 +26,6 @@ class BrowserContext;
|
||||
namespace extensions {
|
||||
|
||||
class ElectronExtensionLoader;
|
||||
class ValueStoreFactory;
|
||||
|
||||
// A simplified version of ExtensionSystem for app_shell. Allows
|
||||
// app_shell to skip initialization of services it doesn't need.
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user