mirror of
https://github.com/electron/electron.git
synced 2026-05-02 03:00:22 -04:00
Compare commits
286 Commits
v42.0.0-be
...
fix/win-th
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26437c6202 | ||
|
|
f038413cf9 | ||
|
|
099c5c0038 | ||
|
|
2c46abe361 | ||
|
|
05e0cd085c | ||
|
|
7c56577639 | ||
|
|
350de668e2 | ||
|
|
111b6275ee | ||
|
|
54eb30a642 | ||
|
|
71e8a5ca80 | ||
|
|
c74de25b01 | ||
|
|
44e4839580 | ||
|
|
fac0a1624b | ||
|
|
23d95ea9f8 | ||
|
|
89050762c6 | ||
|
|
b8be33814e | ||
|
|
76a03e1010 | ||
|
|
2ba6d28c09 | ||
|
|
5ed6e4bf62 | ||
|
|
4d780c67f9 | ||
|
|
313f8955d1 | ||
|
|
1ad6173286 | ||
|
|
9861250310 | ||
|
|
0396220dce | ||
|
|
a0c3be5656 | ||
|
|
b5c68d13d5 | ||
|
|
f7ba34064e | ||
|
|
61e815c28a | ||
|
|
51c0b025d8 | ||
|
|
bc8ed1808c | ||
|
|
db9bcbd3e4 | ||
|
|
92f0993d94 | ||
|
|
bef68b6bb7 | ||
|
|
0866d39006 | ||
|
|
2e17a57d49 | ||
|
|
0ab23201e7 | ||
|
|
1bea748a45 | ||
|
|
09361b6d7b | ||
|
|
a98d19a3ba | ||
|
|
e2143f5e8e | ||
|
|
a1d28e6764 | ||
|
|
72a168f653 | ||
|
|
f6f71fa787 | ||
|
|
9d77099a8c | ||
|
|
85be1a05e1 | ||
|
|
2f749e24ed | ||
|
|
15ed78d807 | ||
|
|
2fbd11d978 | ||
|
|
d813618538 | ||
|
|
99e8170f3f | ||
|
|
02d90a5ba3 | ||
|
|
3c826c7503 | ||
|
|
f35122b21e | ||
|
|
5523130c92 | ||
|
|
abffba4548 | ||
|
|
d164b7af01 | ||
|
|
2434c5a73c | ||
|
|
cc738f2a6c | ||
|
|
9569c48bfe | ||
|
|
0a80d4d879 | ||
|
|
0227bcfb9f | ||
|
|
0ad0d44b3d | ||
|
|
04b9b7bc22 | ||
|
|
22f15ec476 | ||
|
|
53bf94fdf4 | ||
|
|
21c5e25f04 | ||
|
|
cf2cc4d80f | ||
|
|
8ea3d16ce9 | ||
|
|
c30655785b | ||
|
|
d223ad67ea | ||
|
|
dad4ab658a | ||
|
|
9b85b9c0bc | ||
|
|
abd29a397e | ||
|
|
604e7e82f2 | ||
|
|
2e3da1d079 | ||
|
|
edbff16029 | ||
|
|
b5fbbed4db | ||
|
|
663773a677 | ||
|
|
351f35da8c | ||
|
|
b9e462f397 | ||
|
|
a39108c5a4 | ||
|
|
04d9de6f73 | ||
|
|
b8dbe21b38 | ||
|
|
a57dbb55cc | ||
|
|
860a544534 | ||
|
|
e31cd64fe5 | ||
|
|
10eb512d1d | ||
|
|
052efc9727 | ||
|
|
a007bafaf1 | ||
|
|
ea757689b3 | ||
|
|
2c94aac330 | ||
|
|
dcb844c201 | ||
|
|
9bc55a255c | ||
|
|
12b74eac26 | ||
|
|
6e7938af1d | ||
|
|
5fded05add | ||
|
|
1879998865 | ||
|
|
b1b02d9123 | ||
|
|
3f140e1b4b | ||
|
|
3ff923990d | ||
|
|
b4e14a9004 | ||
|
|
61bb03ca75 | ||
|
|
1a2029c3a2 | ||
|
|
bfa5c93332 | ||
|
|
f36def6601 | ||
|
|
97347c4223 | ||
|
|
8d8847d478 | ||
|
|
b9825ba835 | ||
|
|
9be03cbe54 | ||
|
|
861ef95598 | ||
|
|
20ed34a2fd | ||
|
|
82aa603698 | ||
|
|
b4f725a763 | ||
|
|
05f1cb553d | ||
|
|
c0f187f90d | ||
|
|
5f820b7f69 | ||
|
|
8b7e7de208 | ||
|
|
b59f573097 | ||
|
|
b417696d6b | ||
|
|
4203d7688f | ||
|
|
62e637275a | ||
|
|
28c0eb29df | ||
|
|
8a730e2aec | ||
|
|
044be7ce40 | ||
|
|
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 |
106
.claude/skills/chrome-release-cls/SKILL.md
Normal file
106
.claude/skills/chrome-release-cls/SKILL.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
---
|
||||||
|
name: chrome-release-cls
|
||||||
|
description: Given a Chrome Releases blog post URL (chromereleases.googleblog.com), extract every CVE/bug and find the underlying Gerrit CL that fixed it by searching the local Chromium checkout and sub-repos. Use when asked to map Chrome security release notes to fixing CLs, or to find which commits correspond to CVEs in a Chrome stable update.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Chrome Release → Fixing CL Mapper
|
||||||
|
|
||||||
|
Maps every security fix in a Chrome Releases blog post to the Gerrit CL(s) that fixed it.
|
||||||
|
|
||||||
|
## Input
|
||||||
|
|
||||||
|
`$ARGUMENTS` — a `https://chromereleases.googleblog.com/...` URL. If empty, ask the user for one.
|
||||||
|
|
||||||
|
## Procedure
|
||||||
|
|
||||||
|
### 1. Extract CVE → bug ID pairs from the blog post
|
||||||
|
|
||||||
|
The blog HTML buries bug IDs inside `<a>` tags, so strip tags first. Run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -sL "$URL" | python3 -c '
|
||||||
|
import sys, re, html
|
||||||
|
t = re.sub(r"<[^>]+>", " ", sys.stdin.read())
|
||||||
|
t = re.sub(r"\s+", " ", html.unescape(t))
|
||||||
|
seen = set()
|
||||||
|
for m in re.finditer(r"\[\s*(\d{6,})\s*\]\s*(Critical|High|Medium|Low)\s*(CVE-\d{4}-\d+):\s*([^.]+?)\.", t):
|
||||||
|
if m.group(3) in seen: continue
|
||||||
|
seen.add(m.group(3))
|
||||||
|
print(f"{m.group(3)}|{m.group(1)}|{m.group(2)}|{m.group(4).strip()}")
|
||||||
|
' > /tmp/cve_bugs.txt
|
||||||
|
cat /tmp/cve_bugs.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
If this yields nothing, the page may have changed format — fall back to `grep -oE 'CVE-[0-9]{4}-[0-9]+'` and `grep -oE 'crbug\.com/[0-9]+'` and pair them by order.
|
||||||
|
|
||||||
|
### 2. Find the fixing CL for each bug
|
||||||
|
|
||||||
|
Search git history in the Chromium checkout and relevant sub-repos for commits whose `Bug:` or `Fixed:` footer references the bug ID, then extract the `Reviewed-on:` Gerrit URL.
|
||||||
|
|
||||||
|
Repo selection by component keyword:
|
||||||
|
- ANGLE → `third_party/angle`
|
||||||
|
- Skia, Graphite → `third_party/skia`
|
||||||
|
- PDFium → `third_party/pdfium`
|
||||||
|
- Dawn → `third_party/dawn`
|
||||||
|
- V8, Turbofan, Maglev, Turboshaft → `v8`
|
||||||
|
- everything else → `.` (chromium/src)
|
||||||
|
|
||||||
|
Always also fall back to `.` if the hinted repo has no match.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /root/src/electron/src # chromium root (parent of electron/)
|
||||||
|
|
||||||
|
lookup() {
|
||||||
|
local bug="$1" repos="$2"
|
||||||
|
for repo in $repos . v8 third_party/skia third_party/angle third_party/pdfium third_party/dawn; do
|
||||||
|
local hits
|
||||||
|
hits=$(git -C "$repo" log --all --since='6 months ago' -E \
|
||||||
|
--grep="(Bug|Fixed):.*\\b${bug}\\b" --format='%H' 2>/dev/null | sort -u)
|
||||||
|
[[ -z "$hits" ]] && continue
|
||||||
|
while read -r h; do
|
||||||
|
git -C "$repo" log -1 --format='%B' "$h" | grep '^Reviewed-on:' | sed 's/^/ /'
|
||||||
|
echo " ↳ $(git -C "$repo" log -1 --format='%s' "$h")"
|
||||||
|
done <<<"$hits"
|
||||||
|
return 0
|
||||||
|
done
|
||||||
|
echo " (not found locally)"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Drive it from `/tmp/cve_bugs.txt`. Prefer the **non-`[M1xx]`-prefixed** commit subject as the canonical main CL; the `[M1xx]` ones are branch cherry-picks.
|
||||||
|
|
||||||
|
### 3. Handle misses
|
||||||
|
|
||||||
|
For any bug with no local hit:
|
||||||
|
- `git -C <repo> fetch origin` then re-search `--remotes` (fix may be newer than the checkout).
|
||||||
|
- Query Gerrit directly: `curl -s "https://chromium-review.googlesource.com/changes/?q=bug:${BUG}&n=10" | tail -n +2 | python3 -m json.tool` (also try `skia-review`, `pdfium-review`, `dawn-review`, `aomedia-review`).
|
||||||
|
- **`b/` bug format (Skia, Graphite, Dawn):** These repos reference bugs as `b/<id>` in commit messages rather than `Bug: <id>` footers. The Gerrit `bug:` query will return nothing. Use `message:<id>` search instead:
|
||||||
|
```bash
|
||||||
|
curl -s "https://skia-review.googlesource.com/changes/?q=message:${BUG}&n=5" | tail -n +2
|
||||||
|
```
|
||||||
|
Apply the same pattern for `dawn-review.googlesource.com` when the component is Dawn.
|
||||||
|
- **Tracing main CLs from merges:** When only `[M1xx]` merge CLs are found, query the CL detail for `cherry_pick_of_change` to find the original main CL number:
|
||||||
|
```bash
|
||||||
|
curl -s "https://chromium-review.googlesource.com/changes/${CL_NUM}?o=CURRENT_REVISION" | tail -n +2 | python3 -c "
|
||||||
|
import sys, json
|
||||||
|
d = json.load(sys.stdin)
|
||||||
|
print(d.get('cherry_pick_of_change', 'none'))
|
||||||
|
"
|
||||||
|
```
|
||||||
|
- If still nothing and the bug was reported very recently (especially by "Google Threat Intelligence" or marked in-the-wild), the CL is likely still access-restricted — report it as such rather than guessing.
|
||||||
|
|
||||||
|
### 4. Special cases
|
||||||
|
|
||||||
|
- **Roll CLs — skip and find the upstream fix:** For components whose fixes land in upstream repos (PDFium, Dawn, Skia, Graphite, libaom, libvpx, ffmpeg), the chromium-review hit will be a `Roll src/third_party/...` commit. Do not report the roll CL as the fix. Instead, query the component's own Gerrit instance directly for the actual fixing CL:
|
||||||
|
- PDFium → `pdfium-review.googlesource.com` (use `bug:` or `message:` query)
|
||||||
|
- Dawn → `dawn-review.googlesource.com` (use `message:` query — uses `b/` format)
|
||||||
|
- Skia / Graphite → `skia-review.googlesource.com` (use `message:` query — uses `b/` format)
|
||||||
|
- libaom → `aomedia-review.googlesource.com`
|
||||||
|
|
||||||
|
Only if the upstream Gerrit instance returns no results should you fall back to reporting the roll CL — in that case, include the roll CL and note that the actual fix is upstream but the specific CL could not be identified.
|
||||||
|
- Multiple `Reviewed-on:` lines in one commit body: cherry-picks keep the original line plus a new one. The **first** `Reviewed-on:` is the original CL.
|
||||||
|
- A bug may have multiple distinct fix CLs (fix + follow-up hardening) — list all of them.
|
||||||
|
|
||||||
|
### 5. Output
|
||||||
|
|
||||||
|
Produce a markdown table per severity level: `CVE | Bug | Component | Fix CL (main)`. Link bugs as `https://crbug.com/<id>`. Save raw output (including all branch merges) to `/tmp/cve_cls.txt` and mention the path.
|
||||||
123
.claude/skills/chrome-release-verify/SKILL.md
Normal file
123
.claude/skills/chrome-release-verify/SKILL.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
---
|
||||||
|
name: chrome-release-verify
|
||||||
|
description: End-to-end Chrome security backport for an Electron release branch. Given a Chrome Releases blog URL and a branch (e.g. 41-x-y), determines which CVE fixes are missing from the *actual synced source*, writes the cherry-pick patches locally, validates them with `e sync --3` + `lint --patches`, then pushes a single PR. Use when asked to backport a Chrome security release to N-x-y, "is CVE-X already in N-x-y?", or to produce/validate the cherry-pick set for a release branch.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Chrome Release → Validated Backport PR
|
||||||
|
|
||||||
|
Input: `$ARGUMENTS` = `<release-branch> <chrome-releases-blog-url>` (e.g. `41-x-y https://chromereleases.googleblog.com/2026/04/stable-channel-update-for-desktop_15.html`). Ask if either is missing.
|
||||||
|
|
||||||
|
The flow is **local-first**: nothing is pushed until every patch applies via `e sync --3` and passes `lint --patches`.
|
||||||
|
|
||||||
|
## 1. Map CVE → bug → fix CL
|
||||||
|
|
||||||
|
Run `/chrome-release-cls <blog-url>` (or its inline procedure) to produce `/tmp/cve_bugs.txt` (`CVE|bug|severity|desc`) and a per-bug canonical fix CL. For each CL also note `repo` (path under `src/`: `.`, `v8`, `third_party/{skia,angle,pdfium,dawn}`, `third_party/libaom/source/libaom`) and `gerrit-host`.
|
||||||
|
|
||||||
|
**Prefer the target-milestone merge CL** if one exists (e.g. on `41-x-y` ≈ M146, prefer the `[M146]` cherry-pick over the main CL) — it's already rebased and far less likely to conflict. Find it via `git log --all --grep` on the Change-Id, or Gerrit `?q=bug:<n>`. If Chrome did *not* merge a fix to the target milestone, that's a strong signal the vulnerable code doesn't exist there — flag it for skip rather than forcing a port.
|
||||||
|
|
||||||
|
## 2. Prepare a synced worktree
|
||||||
|
|
||||||
|
Reuse `bp-<NN>` from `e show configs` if present, else `e worktree add bp-<NN> ~/src/electron-bp-<NN> --source <current> --no-sync`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd <root>/src/electron
|
||||||
|
git fetch origin <branch>
|
||||||
|
git checkout -B security-backport/<branch>/<short-date> origin/<branch>
|
||||||
|
e use bp-<NN>
|
||||||
|
e sync 2>&1 | tee /tmp/bp_sync.log
|
||||||
|
```
|
||||||
|
|
||||||
|
If sync fails with `NotADirectoryError: '<root>/src/.git/objects/info/alternates'`, remove `GIT_CACHE_PATH` from the bp config's `env` and retry.
|
||||||
|
|
||||||
|
## 3. Verify IN-TREE vs NEEDS-BACKPORT
|
||||||
|
|
||||||
|
For each bug, three checks against the **synced** repo:
|
||||||
|
|
||||||
|
1. `git -C "$repo" log HEAD --since='1 year ago' -E --grep="\b${bug}\b" --format='%h %s'`
|
||||||
|
2. Fetch Change-Id from Gerrit, then `git log HEAD --grep="^Change-Id: ${cid}$"`
|
||||||
|
3. `grep -rlE "(\b${bug}\b|${cid})" <root>/src/electron/patches/`
|
||||||
|
|
||||||
|
Any hit ⇒ IN-TREE. All empty ⇒ NEEDS-BACKPORT.
|
||||||
|
|
||||||
|
For each NEEDS-BACKPORT CL, also fetch its file list (`/changes/<proj>~<cl>/revisions/current/files`) and **skip** if every file is under `chrome/browser/`, `chrome/android/`, `ios/`, or `components/**/android/` — Electron doesn't compile those.
|
||||||
|
|
||||||
|
Report the table now (`CVE | Sev | Bug | Component | Verdict | CL`) and the proposed backport set; get user sign-off before continuing.
|
||||||
|
|
||||||
|
## 4. Write patches locally (no push yet)
|
||||||
|
|
||||||
|
For each backport CL, fetch the raw patch and write it into `patches/<dir>/`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s "https://${host}.googlesource.com/changes/${proj//\//%2F}~${cl}/revisions/current/patch" \
|
||||||
|
| base64 -d > "patches/${dir}/cherry-pick-${short}.patch"
|
||||||
|
echo "cherry-pick-${short}.patch" >> "patches/${dir}/.patches"
|
||||||
|
```
|
||||||
|
|
||||||
|
For repos with no Gerrit host `e cherry-pick` supports (e.g. **libaom** on aomedia), instead `git cherry-pick` the upstream commits onto the synced sub-repo HEAD and `git format-patch` the result.
|
||||||
|
|
||||||
|
For any newly-created `patches/<dir>/`, append to `patches/config.json` **preserving the compact one-line-per-entry style**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "patch_dir": "src/electron/patches/<dir>", "repo": "src/third_party/<dir-or-nested-path>" }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Validate with `e sync --3`
|
||||||
|
|
||||||
|
```bash
|
||||||
|
e sync --3 2>&1 | tee /tmp/bp_sync3.log
|
||||||
|
```
|
||||||
|
|
||||||
|
On `Patch failed at NNNN <subject>`:
|
||||||
|
|
||||||
|
- `cd` into the failing repo, inspect `git diff` for conflict markers.
|
||||||
|
- **Test-only files** (e.g. `web_tests/VirtualTestSuites`, `*_unittest.cc` context drift): take ours (`git checkout --ours -- <file>`) if the security-relevant hunks merged cleanly.
|
||||||
|
- **Substantive code conflicts**: check whether a target-milestone merge CL exists and swap to it. If none exists upstream and the surrounding code is structurally different, **drop the patch** (delete the file, remove from `.patches` and `config.json`) and note it for a separate manual-port PR — do not improvise security-fix semantics.
|
||||||
|
- After resolving: `git add <files> && git -c commit.gpgsign=false am --continue`, then `e patches <repo>` to export the resolved patch, then re-run `e sync --3`. Repeat until clean.
|
||||||
|
|
||||||
|
## 6. Export → lint → re-apply loop
|
||||||
|
|
||||||
|
```bash
|
||||||
|
e patches all
|
||||||
|
node script/lint.js --patches # must exit 0
|
||||||
|
```
|
||||||
|
|
||||||
|
If lint reports findings (typically trailing whitespace on `+` content lines), fixing them **changes the bytes the patch writes**, which invalidates the `index <old>..<new>` blob hashes that `e patches` baked in. Hand-editing a `.patch` and pushing it as-is will pass lint locally but fail CI's Apply Patches re-export check with a one-line `index` hash diff.
|
||||||
|
|
||||||
|
So whenever lint (or you) modifies any `.patch` file after export, round-trip once more:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# fix the lint findings in patches/**/*.patch, then:
|
||||||
|
e sync # re-apply the edited patches (no --3 needed; they applied cleanly last time)
|
||||||
|
e patches all # re-export so index blob hashes match the edited content
|
||||||
|
node script/lint.js --patches # must now exit 0
|
||||||
|
git diff --quiet -- patches/ || { echo "patches changed again — repeat the loop"; }
|
||||||
|
```
|
||||||
|
|
||||||
|
Repeat until `lint --patches` exits 0 **and** `git diff -- patches/` is empty after the final `e patches all`. Only then is the patch set CI-stable.
|
||||||
|
|
||||||
|
## 7. Commit, push, PR
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git add patches/
|
||||||
|
git commit -m "chore: cherry-pick <N> changes from <dirs>"
|
||||||
|
git push origin HEAD
|
||||||
|
gh pr create --repo electron/electron --base <branch> --head <this-branch> \
|
||||||
|
--title "chore: cherry-pick <N> changes from <dirs>" \
|
||||||
|
--label "<branch>" --label backport-check-skip --label semver/patch --label "security 🔒" \
|
||||||
|
--body-file /tmp/pr_body.md
|
||||||
|
```
|
||||||
|
|
||||||
|
PR body format:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
Backports the following changes:
|
||||||
|
|
||||||
|
* [`<shortCommit>`](<gerrit-CL-url>) from <patchDir> — <subject> ([<bug>](https://crbug.com/<bug>), CVE-YYYY-NNNN)
|
||||||
|
* ...
|
||||||
|
|
||||||
|
Notes: Security: backported fixes for CVE-YYYY-NNNN, CVE-YYYY-NNNN, ....
|
||||||
|
```
|
||||||
|
|
||||||
|
Short commit links to the **Gerrit CL**; bug links to `crbug.com`; CVE comes from the blog mapping (the patch's own `Bug:` footer may differ); `Notes:` is the last line. Mention any dropped patches (with reason) above the `Notes:` line.
|
||||||
|
|
||||||
|
Restore `e use <previous>` when done.
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
"ms-vscode.cpptools",
|
"ms-vscode.cpptools",
|
||||||
"mutantdino.resourcemonitor",
|
"mutantdino.resourcemonitor",
|
||||||
"dsanders11.vscode-electron-build-tools",
|
"dsanders11.vscode-electron-build-tools",
|
||||||
"dbaeumer.vscode-eslint",
|
"oxc.oxc-vscode",
|
||||||
"shakram02.bash-beautify",
|
"shakram02.bash-beautify",
|
||||||
"marshallofsound.gnls-electron"
|
"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
|
/lib/renderer/security-warnings.ts @electron/wg-security
|
||||||
|
|
||||||
# Infra WG
|
# Infra WG
|
||||||
|
/.claude/ @electron/wg-infra
|
||||||
/.github/actions/ @electron/wg-infra
|
/.github/actions/ @electron/wg-infra
|
||||||
/.github/workflows/*-publish.yml @electron/wg-infra
|
/.github/workflows/*-publish.yml @electron/wg-infra
|
||||||
/.github/workflows/build.yml @electron/wg-infra
|
/.github/workflows/build.yml @electron/wg-infra
|
||||||
|
|||||||
14
.github/ISSUE_TEMPLATE/maintainer_issue.yml
vendored
14
.github/ISSUE_TEMPLATE/maintainer_issue.yml
vendored
@@ -1,14 +0,0 @@
|
|||||||
name: Maintainer Issue (not for public use)
|
|
||||||
description: Only to be created by Electron maintainers
|
|
||||||
body:
|
|
||||||
- type: checkboxes
|
|
||||||
attributes:
|
|
||||||
label: Confirmation
|
|
||||||
options:
|
|
||||||
- label: I am a [maintainer](https://github.com/orgs/electron/people) of the Electron project. (If not, please create a [different issue type](https://github.com/electron/electron/issues/new/).)
|
|
||||||
required: true
|
|
||||||
- type: textarea
|
|
||||||
attributes:
|
|
||||||
label: Description
|
|
||||||
validations:
|
|
||||||
required: true
|
|
||||||
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -5,13 +5,18 @@ Thank you for your Pull Request. Please provide a description above and review
|
|||||||
the requirements below.
|
the requirements below.
|
||||||
|
|
||||||
Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
|
Contributors guide: https://github.com/electron/electron/blob/main/CONTRIBUTING.md
|
||||||
|
|
||||||
|
Using a coding agent / AI? Read the policy: https://github.com/electron/governance/blob/main/policy/ai.md
|
||||||
|
|
||||||
|
NOTE: PRs submitted that do not follow this template will be automatically closed.
|
||||||
-->
|
-->
|
||||||
|
|
||||||
#### Checklist
|
#### Checklist
|
||||||
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->
|
<!-- Remove items that do not apply. For completed items, change [ ] to [x]. -->
|
||||||
|
|
||||||
- [ ] PR description included
|
- [ ] I have built and tested this change
|
||||||
- [ ] I have built and tested this PR
|
- [ ] I have filled out the PR description
|
||||||
|
- [ ] [I have reviewed and verified the changes](https://github.com/electron/governance/blob/main/policy/ai.md)
|
||||||
- [ ] `npm test` passes
|
- [ ] `npm test` passes
|
||||||
- [ ] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
|
- [ ] tests are [changed or added](https://github.com/electron/electron/blob/main/docs/development/testing.md)
|
||||||
- [ ] relevant API documentation, tutorials, and examples are updated and follow the [documentation style guide](https://github.com/electron/electron/blob/main/docs/development/style-guide.md)
|
- [ ] relevant API documentation, tutorials, and examples are updated and follow the [documentation style guide](https://github.com/electron/electron/blob/main/docs/development/style-guide.md)
|
||||||
|
|||||||
60
.github/actions/build-electron/action.yml
vendored
60
.github/actions/build-electron/action.yml
vendored
@@ -47,6 +47,16 @@ runs:
|
|||||||
- name: Add Clang problem matcher
|
- name: Add Clang problem matcher
|
||||||
shell: bash
|
shell: bash
|
||||||
run: echo "::add-matcher::src/electron/.github/problem-matchers/clang.json"
|
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 }}
|
- name: Build Electron ${{ inputs.step-suffix }}
|
||||||
if: ${{ inputs.target-platform != 'win' }}
|
if: ${{ inputs.target-platform != 'win' }}
|
||||||
shell: bash
|
shell: bash
|
||||||
@@ -72,12 +82,17 @@ runs:
|
|||||||
cp out/Default/.ninja_log out/electron_ninja_log
|
cp out/Default/.ninja_log out/electron_ninja_log
|
||||||
node electron/script/check-symlinks.js
|
node electron/script/check-symlinks.js
|
||||||
|
|
||||||
# Upload build stats to Datadog
|
# Build stats and object checksums
|
||||||
if ! [ -z $DD_API_KEY ]; then
|
BUILD_STATS_ARGS="out/Default/siso.INFO --out-dir out/Default --output-object-checksums object-checksums.${{ inputs.artifact-platform }}_${{ inputs.target-arch }}.json"
|
||||||
npx node electron/script/build-stats.mjs out/Default/siso.INFO --upload-stats || true
|
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
|
else
|
||||||
echo "Skipping build-stats.mjs upload because DD_API_KEY is not set"
|
echo "Skipping build-stats.mjs upload because DD_API_KEY is not set"
|
||||||
fi
|
fi
|
||||||
|
node electron/script/build-stats.mjs $BUILD_STATS_ARGS || true
|
||||||
- name: Build Electron (Windows) ${{ inputs.step-suffix }}
|
- name: Build Electron (Windows) ${{ inputs.step-suffix }}
|
||||||
if: ${{ inputs.target-platform == 'win' }}
|
if: ${{ inputs.target-platform == 'win' }}
|
||||||
shell: powershell
|
shell: powershell
|
||||||
@@ -86,6 +101,21 @@ runs:
|
|||||||
git pack-refs
|
git pack-refs
|
||||||
cd ..
|
cd ..
|
||||||
|
|
||||||
|
# Pre-create the ThinLTO cache directory so lld-link does not need to
|
||||||
|
# call CreateDirectoryW through the bindflt filter driver, which can
|
||||||
|
# return ERROR_INVALID_PARAMETER under concurrent I/O on ARC runners.
|
||||||
|
# Discover the path from GN instead of hardcoding it so we stay in
|
||||||
|
# sync with `cache_dir` in build/config/compiler/BUILD.gn; skip the
|
||||||
|
# pre-create when ThinLTO is disabled (non-official builds).
|
||||||
|
$env:ELECTRON_DEPOT_TOOLS_DISABLE_LOG = "1"
|
||||||
|
$ltoFlag = e d gn desc out/Default //electron:electron_app ldflags 2>$null |
|
||||||
|
Select-String -Pattern '^/lldltocache:(.+)$' |
|
||||||
|
Select-Object -First 1
|
||||||
|
if ($ltoFlag) {
|
||||||
|
$cachePath = Join-Path 'out\Default' $ltoFlag.Matches[0].Groups[1].Value
|
||||||
|
New-Item -ItemType Directory -Force -Path $cachePath | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
$env:NINJA_SUMMARIZE_BUILD = 1
|
$env:NINJA_SUMMARIZE_BUILD = 1
|
||||||
if ("${{ inputs.is-release }}" -eq "true") {
|
if ("${{ inputs.is-release }}" -eq "true") {
|
||||||
e build --target electron:release_build
|
e build --target electron:release_build
|
||||||
@@ -99,16 +129,21 @@ runs:
|
|||||||
Copy-Item out\Default\.ninja_log out\electron_ninja_log
|
Copy-Item out\Default\.ninja_log out\electron_ninja_log
|
||||||
node electron\script\check-symlinks.js
|
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) {
|
if ($env:DD_API_KEY) {
|
||||||
try {
|
$statsArgs += "--upload-stats"
|
||||||
npx node electron\script\build-stats.mjs out\Default\siso.exe.INFO --upload-stats ; $LASTEXITCODE = 0
|
|
||||||
} catch {
|
|
||||||
Write-Host "Build stats upload failed, continuing..."
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
Write-Host "Skipping build-stats.mjs upload because DD_API_KEY is not set"
|
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 }}
|
- name: Verify dist.zip ${{ inputs.step-suffix }}
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
@@ -306,3 +341,10 @@ runs:
|
|||||||
with:
|
with:
|
||||||
name: out_gen_artifacts_${{ env.ARTIFACT_KEY }}
|
name: out_gen_artifacts_${{ env.ARTIFACT_KEY }}
|
||||||
path: ./src/out/Default/gen
|
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);
|
||||||
|
});
|
||||||
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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
4
.github/workflows/apply-patches.yml
vendored
4
.github/workflows/apply-patches.yml
vendored
@@ -27,7 +27,7 @@ jobs:
|
|||||||
# Use dorny/paths-filter instead of the path filter under the on: pull_request: block
|
# 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
|
# 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).
|
# 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
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
@@ -77,7 +77,7 @@ jobs:
|
|||||||
target-platform: linux
|
target-platform: linux
|
||||||
- name: Upload Patch Conflict Fix
|
- name: Upload Patch Conflict Fix
|
||||||
if: ${{ failure() }}
|
if: ${{ failure() }}
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: update-patches
|
name: update-patches
|
||||||
path: patches/update-patches.patch
|
path: patches/update-patches.patch
|
||||||
|
|||||||
4
.github/workflows/archaeologist-dig.yml
vendored
4
.github/workflows/archaeologist-dig.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Setup Node.js/npm
|
- name: Setup Node.js/npm
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||||
with:
|
with:
|
||||||
node-version: 24.12.x
|
node-version: 24.12.x
|
||||||
- name: Setting Up Dig Site
|
- name: Setting Up Dig Site
|
||||||
@@ -49,7 +49,7 @@ jobs:
|
|||||||
sha-file: .dig-old
|
sha-file: .dig-old
|
||||||
filename: electron.old.d.ts
|
filename: electron.old.d.ts
|
||||||
- name: Upload artifacts
|
- name: Upload artifacts
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
||||||
with:
|
with:
|
||||||
name: artifacts
|
name: artifacts
|
||||||
path: electron/artifacts
|
path: electron/artifacts
|
||||||
|
|||||||
9
.github/workflows/audit-branch-ci.yml
vendored
9
.github/workflows/audit-branch-ci.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
steps:
|
steps:
|
||||||
- name: Setup Node.js
|
- name: Setup Node.js
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
|
||||||
with:
|
with:
|
||||||
node-version: 22.17.x
|
node-version: 22.17.x
|
||||||
- name: Sparse checkout repository
|
- name: Sparse checkout repository
|
||||||
@@ -28,7 +28,7 @@ jobs:
|
|||||||
.github
|
.github
|
||||||
.yarn
|
.yarn
|
||||||
- run: yarn workspaces focus @electron/gha-workflows
|
- run: yarn workspaces focus @electron/gha-workflows
|
||||||
- uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
- uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
id: audit-errors
|
id: audit-errors
|
||||||
with:
|
with:
|
||||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
@@ -86,6 +86,9 @@ jobs:
|
|||||||
!message.startsWith("Response status code does not indicate success") &&
|
!message.startsWith("Response status code does not indicate success") &&
|
||||||
!message.startsWith("The hosted runner lost communication with the server") &&
|
!message.startsWith("The hosted runner lost communication with the server") &&
|
||||||
!message.startsWith("Dependabot encountered an error performing the update") &&
|
!message.startsWith("Dependabot encountered an error performing the update") &&
|
||||||
|
!message.startsWith("The action 'Run Electron Tests' has timed out") &&
|
||||||
|
!message.startsWith("The operation was canceled") &&
|
||||||
|
!message.startsWith("Canceling since") &&
|
||||||
!/Unable to make request/.test(message) &&
|
!/Unable to make request/.test(message) &&
|
||||||
!/The requested URL returned error/.test(message),
|
!/The requested URL returned error/.test(message),
|
||||||
)
|
)
|
||||||
@@ -154,7 +157,7 @@ jobs:
|
|||||||
await core.summary.write();
|
await core.summary.write();
|
||||||
- name: Send Slack message if errors
|
- name: Send Slack message if errors
|
||||||
if: ${{ always() && steps.audit-errors.outputs.errorsFound && github.ref == 'refs/heads/main' }}
|
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:
|
with:
|
||||||
payload: |
|
payload: |
|
||||||
link: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
link: "https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}"
|
||||||
|
|||||||
81
.github/workflows/branch-created.yml
vendored
81
.github/workflows/branch-created.yml
vendored
@@ -31,8 +31,8 @@ jobs:
|
|||||||
else
|
else
|
||||||
echo "Not a release branch: $BRANCH_NAME"
|
echo "Not a release branch: $BRANCH_NAME"
|
||||||
fi
|
fi
|
||||||
- name: Determine Unsupported Major Version
|
- name: Determine Next Unsupported Major Version
|
||||||
id: determine-unsupported-major
|
id: determine-next-unsupported-major
|
||||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
env:
|
env:
|
||||||
MAJOR: ${{ steps.check-major-version.outputs.MAJOR }}
|
MAJOR: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
@@ -50,26 +50,27 @@ jobs:
|
|||||||
|
|
||||||
# Find the oldest version where eolDate >= stableDate of the new major
|
# Find the oldest version where eolDate >= stableDate of the new major
|
||||||
# This gives us the oldest supported version when the new major goes stable
|
# 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]
|
[.[] | 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"
|
echo "Could not determine oldest supported version"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo "SCHEDULE=$SCHEDULE" >> "$GITHUB_OUTPUT"
|
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
|
- name: New Release Branch Tasks
|
||||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
GH_REPO: electron/electron
|
GH_REPO: electron/electron
|
||||||
MAJOR: ${{ steps.check-major-version.outputs.MAJOR }}
|
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: |
|
run: |
|
||||||
PREVIOUS_MAJOR=$((MAJOR - 1))
|
PREVIOUS_MAJOR=$((MAJOR - 1))
|
||||||
|
UNSUPPORTED_MAJOR=$((NEXT_UNSUPPORTED_MAJOR - 1))
|
||||||
|
|
||||||
# Create new labels
|
# Create new labels
|
||||||
gh label create $MAJOR-x-y --color 8d9ee8 || true
|
gh label create $MAJOR-x-y --color 8d9ee8 || true
|
||||||
@@ -104,12 +105,12 @@ jobs:
|
|||||||
org: electron
|
org: electron
|
||||||
- name: Generate Release Project Board Metadata
|
- name: Generate Release Project Board Metadata
|
||||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
id: generate-project-metadata
|
id: generate-project-metadata
|
||||||
env:
|
env:
|
||||||
MAJOR: ${{ steps.check-major-version.outputs.MAJOR }}
|
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 }}
|
||||||
SCHEDULE: ${{ steps.determine-unsupported-major.outputs.SCHEDULE }}
|
SCHEDULE: ${{ steps.determine-next-unsupported-major.outputs.SCHEDULE }}
|
||||||
with:
|
with:
|
||||||
script: |
|
script: |
|
||||||
const schedule = JSON.parse(process.env.SCHEDULE)
|
const schedule = JSON.parse(process.env.SCHEDULE)
|
||||||
@@ -144,7 +145,7 @@ jobs:
|
|||||||
major,
|
major,
|
||||||
"next-major": nextMajor,
|
"next-major": nextMajor,
|
||||||
"prev-major": prevMajor,
|
"prev-major": prevMajor,
|
||||||
"ending-support-major": parseInt(process.env.UNSUPPORTED_MAJOR),
|
"ending-support-major": parseInt(process.env.NEXT_UNSUPPORTED_MAJOR),
|
||||||
"beta-date": betaDate,
|
"beta-date": betaDate,
|
||||||
"beta-prep-week": betaPrepWeek.toISOString().split('T')[0],
|
"beta-prep-week": betaPrepWeek.toISOString().split('T')[0],
|
||||||
"beta-prep-week-end": betaPrepWeekEnd.toISOString().split('T')[0],
|
"beta-prep-week-end": betaPrepWeekEnd.toISOString().split('T')[0],
|
||||||
@@ -156,7 +157,7 @@ jobs:
|
|||||||
}))
|
}))
|
||||||
- name: Create Release Project Board
|
- name: Create Release Project Board
|
||||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
uses: dsanders11/project-actions/copy-project@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/copy-project@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
id: create-release-board
|
id: create-release-board
|
||||||
with:
|
with:
|
||||||
drafts: true
|
drafts: true
|
||||||
@@ -169,6 +170,60 @@ jobs:
|
|||||||
template-view: ${{ steps.generate-project-metadata.outputs.template-view }}
|
template-view: ${{ steps.generate-project-metadata.outputs.template-view }}
|
||||||
title: ${{ steps.generate-project-metadata.outputs.major }}-x-y
|
title: ${{ steps.generate-project-metadata.outputs.major }}-x-y
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
- name: Randomly Assign Draft Issues to Release WG Members
|
||||||
|
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
|
uses: dsanders11/project-actions/github-script@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
|
env:
|
||||||
|
PROJECT_ID: ${{ steps.create-release-board.outputs.id }}
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
script: |
|
||||||
|
const { data: members } = await github.rest.teams.listMembersInOrg({
|
||||||
|
org: 'electron',
|
||||||
|
team_slug: 'wg-releases',
|
||||||
|
});
|
||||||
|
|
||||||
|
const excludedLogins = ['nikwen'];
|
||||||
|
const memberLogins = new Set(members.map(m => m.login));
|
||||||
|
for (const login of excludedLogins) {
|
||||||
|
if (!memberLogins.has(login)) {
|
||||||
|
core.warning(`Excluded member "${login}" is not in @electron/wg-releases`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const eligible = members.filter(m => !excludedLogins.includes(m.login));
|
||||||
|
|
||||||
|
if (eligible.length === 0) {
|
||||||
|
core.warning('No eligible members found in @electron/wg-releases team');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectId = process.env.PROJECT_ID;
|
||||||
|
const draftIssues = await actions.getDraftIssues(projectId);
|
||||||
|
|
||||||
|
if (draftIssues.length === 0) {
|
||||||
|
core.info('No draft issues found in the project');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fisher-Yates shuffle for uniform random assignment
|
||||||
|
const shuffled = [...eligible];
|
||||||
|
for (let i = shuffled.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign draft issues round-robin across team members
|
||||||
|
for (let i = 0; i < draftIssues.length; i++) {
|
||||||
|
const member = shuffled[i % shuffled.length];
|
||||||
|
const draftIssue = draftIssues[i];
|
||||||
|
core.info(`Assigning "${draftIssue.content.title}" to ${member.login}`);
|
||||||
|
await actions.editItem(projectId, draftIssue.content.id, {
|
||||||
|
assignees: [member.login],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
core.info(`Assigned ${draftIssues.length} draft issues to ${eligible.length} team members`);
|
||||||
- name: Dump Release Project Board Contents
|
- name: Dump Release Project Board Contents
|
||||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
run: gh project item-list ${{ steps.create-release-board.outputs.number }} --owner electron --format json | jq
|
run: gh project item-list ${{ steps.create-release-board.outputs.number }} --owner electron --format json | jq
|
||||||
@@ -176,7 +231,7 @@ jobs:
|
|||||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
- name: Find Previous Release Project Board
|
- name: Find Previous Release Project Board
|
||||||
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
if: ${{ steps.check-major-version.outputs.MAJOR }}
|
||||||
uses: dsanders11/project-actions/find-project@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/find-project@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
id: find-prev-release-board
|
id: find-prev-release-board
|
||||||
with:
|
with:
|
||||||
fail-if-project-not-found: false
|
fail-if-project-not-found: false
|
||||||
@@ -184,7 +239,7 @@ jobs:
|
|||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
- name: Close Previous Release Project Board
|
- name: Close Previous Release Project Board
|
||||||
if: ${{ steps.find-prev-release-board.outputs.number }}
|
if: ${{ steps.find-prev-release-board.outputs.number }}
|
||||||
uses: dsanders11/project-actions/close-project@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/close-project@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
project-number: ${{ steps.find-prev-release-board.outputs.number }}
|
project-number: ${{ steps.find-prev-release-board.outputs.number }}
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|||||||
34
.github/workflows/build-git-cache.yml
vendored
34
.github/workflows/build-git-cache.yml
vendored
@@ -2,25 +2,38 @@ name: Build Git Cache
|
|||||||
# This workflow updates git cache on the cross-instance cache volumes
|
# This workflow updates git cache on the cross-instance cache volumes
|
||||||
# It runs daily at midnight.
|
# It runs daily at midnight.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: "0 0 * * *"
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-git-cache-linux:
|
setup:
|
||||||
if: github.repository == 'electron/electron'
|
if: github.repository == 'electron/electron'
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
outputs:
|
||||||
|
build-image-sha: ${{ steps.build-image-sha.outputs.build-image-sha }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||||
|
- name: Set Build Image SHA
|
||||||
|
id: build-image-sha
|
||||||
|
uses: ./.github/actions/build-image-sha
|
||||||
|
|
||||||
|
build-git-cache-linux:
|
||||||
|
needs: setup
|
||||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
|
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
|
||||||
options: --user root
|
options: --user root
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||||
env:
|
env:
|
||||||
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
CHROMIUM_GIT_COOKIE: ${{ secrets.CHROMIUM_GIT_COOKIE }}
|
||||||
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
GCLIENT_EXTRA_ARGS: '--custom-var=checkout_arm=True --custom-var=checkout_arm64=True'
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Electron
|
- name: Checkout Electron
|
||||||
@@ -34,12 +47,12 @@ jobs:
|
|||||||
target-platform: linux
|
target-platform: linux
|
||||||
|
|
||||||
build-git-cache-windows:
|
build-git-cache-windows:
|
||||||
if: github.repository == 'electron/electron'
|
needs: setup
|
||||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
|
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
|
||||||
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
|
options: --user root --device /dev/fuse --cap-add SYS_ADMIN
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/win-cache:/mnt/win-cache
|
- /mnt/win-cache:/mnt/win-cache
|
||||||
@@ -59,14 +72,13 @@ jobs:
|
|||||||
target-platform: win
|
target-platform: win
|
||||||
|
|
||||||
build-git-cache-macos:
|
build-git-cache-macos:
|
||||||
if: github.repository == 'electron/electron'
|
# This job updates the same git cache as linux, so it needs to run after the linux one.
|
||||||
|
needs: [setup, build-git-cache-linux]
|
||||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
# This job updates the same git cache as linux, so it needs to run after the linux one.
|
|
||||||
needs: build-git-cache-linux
|
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/electron/build:eac3529546ea8f3aa356d31e345715eef342233b
|
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
|
||||||
options: --user root
|
options: --user root
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||||
@@ -82,4 +94,4 @@ jobs:
|
|||||||
- name: Build Git Cache
|
- name: Build Git Cache
|
||||||
uses: ./src/electron/.github/actions/build-git-cache
|
uses: ./src/electron/.github/actions/build-git-cache
|
||||||
with:
|
with:
|
||||||
target-platform: macos
|
target-platform: macos
|
||||||
|
|||||||
18
.github/workflows/build.yml
vendored
18
.github/workflows/build.yml
vendored
@@ -61,18 +61,19 @@ jobs:
|
|||||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||||
with:
|
with:
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
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
|
id: filter
|
||||||
with:
|
with:
|
||||||
filters: |
|
filters: |
|
||||||
docs:
|
docs:
|
||||||
- 'docs/**'
|
- 'docs/**'
|
||||||
|
- '.claude/**'
|
||||||
- README.md
|
- README.md
|
||||||
- SECURITY.md
|
- SECURITY.md
|
||||||
- CONTRIBUTING.md
|
- CONTRIBUTING.md
|
||||||
- CODE_OF_CONDUCT.md
|
- CODE_OF_CONDUCT.md
|
||||||
src:
|
src:
|
||||||
- '!docs/**'
|
- '!{docs,.claude}/**'
|
||||||
- name: Set Build Image SHA
|
- name: Set Build Image SHA
|
||||||
id: build-image-sha
|
id: build-image-sha
|
||||||
uses: ./.github/actions/build-image-sha
|
uses: ./.github/actions/build-image-sha
|
||||||
@@ -274,10 +275,11 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
issues: read
|
issues: read
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
uses: ./.github/workflows/pipeline-electron-build-and-test.yml
|
uses: ./.github/workflows/pipeline-electron-build-and-tidy-and-test.yml
|
||||||
needs: checkout-macos
|
needs: checkout-macos
|
||||||
with:
|
with:
|
||||||
build-runs-on: macos-15-xlarge
|
build-runs-on: macos-15-xlarge
|
||||||
|
clang-tidy-runs-on: macos-15-large
|
||||||
test-runs-on: macos-15
|
test-runs-on: macos-15
|
||||||
target-platform: macos
|
target-platform: macos
|
||||||
target-arch: arm64
|
target-arch: arm64
|
||||||
@@ -392,12 +394,14 @@ jobs:
|
|||||||
contents: read
|
contents: read
|
||||||
issues: read
|
issues: read
|
||||||
pull-requests: read
|
pull-requests: read
|
||||||
uses: ./.github/workflows/pipeline-electron-build-and-test.yml
|
uses: ./.github/workflows/pipeline-electron-build-and-tidy-and-test.yml
|
||||||
needs: [checkout-windows, build-siso-windows]
|
needs: [checkout-windows, build-siso-windows]
|
||||||
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
|
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
|
||||||
with:
|
with:
|
||||||
build-runs-on: electron-arc-centralus-windows-amd64-16core
|
build-runs-on: electron-arc-centralus-windows-amd64-32core
|
||||||
|
clang-tidy-runs-on: electron-arc-centralus-linux-amd64-8core
|
||||||
test-runs-on: windows-latest
|
test-runs-on: windows-latest
|
||||||
|
clang-tidy-container: '{"image":"ghcr.io/electron/build:${{ needs.checkout-windows.outputs.build-image-sha }}","options":"--user root --device /dev/fuse --cap-add SYS_ADMIN","volumes":["/mnt/win-cache:/mnt/win-cache"]}'
|
||||||
target-platform: win
|
target-platform: win
|
||||||
target-arch: x64
|
target-arch: x64
|
||||||
is-release: false
|
is-release: false
|
||||||
@@ -415,7 +419,7 @@ jobs:
|
|||||||
needs: [checkout-windows, build-siso-windows]
|
needs: [checkout-windows, build-siso-windows]
|
||||||
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
|
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
|
||||||
with:
|
with:
|
||||||
build-runs-on: electron-arc-centralus-windows-amd64-16core
|
build-runs-on: electron-arc-centralus-windows-amd64-32core
|
||||||
test-runs-on: windows-latest
|
test-runs-on: windows-latest
|
||||||
target-platform: win
|
target-platform: win
|
||||||
target-arch: x86
|
target-arch: x86
|
||||||
@@ -434,7 +438,7 @@ jobs:
|
|||||||
needs: [checkout-windows, build-siso-windows]
|
needs: [checkout-windows, build-siso-windows]
|
||||||
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
|
if: ${{ needs.setup.outputs.src == 'true' && !inputs.skip-windows }}
|
||||||
with:
|
with:
|
||||||
build-runs-on: electron-arc-centralus-windows-amd64-16core
|
build-runs-on: electron-arc-centralus-windows-amd64-32core
|
||||||
test-runs-on: windows-11-arm
|
test-runs-on: windows-11-arm
|
||||||
target-platform: win
|
target-platform: win
|
||||||
target-arch: arm64
|
target-arch: arm64
|
||||||
|
|||||||
@@ -13,13 +13,26 @@ on:
|
|||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
clean-orphaned-uploads:
|
setup:
|
||||||
if: github.repository == 'electron/electron'
|
if: github.repository == 'electron/electron'
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
outputs:
|
||||||
|
build-image-sha: ${{ steps.build-image-sha.outputs.build-image-sha }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||||
|
- name: Set Build Image SHA
|
||||||
|
id: build-image-sha
|
||||||
|
uses: ./.github/actions/build-image-sha
|
||||||
|
|
||||||
|
clean-orphaned-uploads:
|
||||||
|
needs: setup
|
||||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
|
||||||
options: --user root
|
options: --user root
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||||
|
|||||||
144
.github/workflows/clean-src-cache.yml
vendored
144
.github/workflows/clean-src-cache.yml
vendored
@@ -7,28 +7,162 @@ name: Clean Source Cache
|
|||||||
on:
|
on:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 0 * * *"
|
- cron: "0 0 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
permissions: {}
|
permissions: {}
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
clean-src-cache:
|
setup:
|
||||||
if: github.repository == 'electron/electron'
|
if: github.repository == 'electron/electron'
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
outputs:
|
||||||
|
build-image-sha: ${{ steps.build-image-sha.outputs.build-image-sha }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
|
||||||
|
- name: Set Build Image SHA
|
||||||
|
id: build-image-sha
|
||||||
|
uses: ./.github/actions/build-image-sha
|
||||||
|
|
||||||
|
clean-src-cache:
|
||||||
|
needs: setup
|
||||||
runs-on: electron-arc-centralus-linux-amd64-32core
|
runs-on: electron-arc-centralus-linux-amd64-32core
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
env:
|
||||||
|
DD_API_KEY: ${{ secrets.DD_API_KEY }}
|
||||||
container:
|
container:
|
||||||
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
|
image: ghcr.io/electron/build:${{ needs.setup.outputs.build-image-sha }}
|
||||||
options: --user root
|
options: --user root
|
||||||
volumes:
|
volumes:
|
||||||
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
|
||||||
- /mnt/win-cache:/mnt/win-cache
|
- /mnt/win-cache:/mnt/win-cache
|
||||||
steps:
|
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
|
- name: Cleanup Source Cache
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
df -h /mnt/cross-instance-cache
|
|
||||||
find /mnt/cross-instance-cache -type f -mtime +15 -delete
|
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/cross-instance-cache
|
||||||
df -h /mnt/win-cache
|
df -h /mnt/win-cache
|
||||||
find /mnt/win-cache -type f -mtime +15 -delete
|
CROSS_FREE_AFTER=$(df -k /mnt/cross-instance-cache | tail -1 | awk '{print $4}')
|
||||||
df -h /mnt/win-cache
|
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 }}
|
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||||
org: electron
|
org: electron
|
||||||
- name: Set status
|
- name: Set status
|
||||||
uses: dsanders11/project-actions/edit-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/edit-item@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
project-number: 90
|
project-number: 90
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||||
org: electron
|
org: electron
|
||||||
- name: Set status
|
- name: Set status
|
||||||
uses: dsanders11/project-actions/edit-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/edit-item@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
project-number: 90
|
project-number: 90
|
||||||
@@ -76,7 +76,7 @@ jobs:
|
|||||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||||
- name: Create comment
|
- name: Create comment
|
||||||
if: ${{ steps.check-for-comment.outputs.SHOULD_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:
|
with:
|
||||||
actions: 'create-comment'
|
actions: 'create-comment'
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|||||||
6
.github/workflows/issue-opened.yml
vendored
6
.github/workflows/issue-opened.yml
vendored
@@ -20,7 +20,7 @@ jobs:
|
|||||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||||
org: electron
|
org: electron
|
||||||
- name: Add to Issue Triage
|
- name: Add to Issue Triage
|
||||||
uses: dsanders11/project-actions/add-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/add-item@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
field: Reporter
|
field: Reporter
|
||||||
field-value: ${{ github.event.issue.user.login }}
|
field-value: ${{ github.event.issue.user.login }}
|
||||||
@@ -46,7 +46,7 @@ jobs:
|
|||||||
.yarn
|
.yarn
|
||||||
- run: yarn workspaces focus @electron/gha-workflows
|
- run: yarn workspaces focus @electron/gha-workflows
|
||||||
- name: Add labels
|
- name: Add labels
|
||||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0
|
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0
|
||||||
id: add-labels
|
id: add-labels
|
||||||
env:
|
env:
|
||||||
ISSUE_BODY: ${{ github.event.issue.body }}
|
ISSUE_BODY: ${{ github.event.issue.body }}
|
||||||
@@ -146,7 +146,7 @@ jobs:
|
|||||||
}
|
}
|
||||||
- name: Create unsupported major comment
|
- name: Create unsupported major comment
|
||||||
if: ${{ steps.add-labels.outputs.unsupportedMajor }}
|
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:
|
with:
|
||||||
actions: 'create-comment'
|
actions: 'create-comment'
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
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 }}
|
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||||
org: electron
|
org: electron
|
||||||
- name: Remove from issue triage
|
- name: Remove from issue triage
|
||||||
uses: dsanders11/project-actions/delete-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/delete-item@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
project-number: 90
|
project-number: 90
|
||||||
|
|||||||
2
.github/workflows/issue-unlabeled.yml
vendored
2
.github/workflows/issue-unlabeled.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
|||||||
org: electron
|
org: electron
|
||||||
- name: Set status
|
- name: Set status
|
||||||
if: ${{ steps.check-for-blocked-labels.outputs.NOT_BLOCKED }}
|
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@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
project-number: 90
|
project-number: 90
|
||||||
|
|||||||
@@ -51,4 +51,21 @@ jobs:
|
|||||||
PR_URL: ${{ github.event.pull_request.html_url }}
|
PR_URL: ${{ github.event.pull_request.html_url }}
|
||||||
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
PR_AUTHOR: ${{ github.event.pull_request.user.login }}
|
||||||
run: |
|
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
|
- name: Add problem matchers
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "::add-matcher::src/electron/.github/problem-matchers/eslint-stylish.json"
|
|
||||||
echo "::add-matcher::src/electron/.github/problem-matchers/markdownlint.json"
|
echo "::add-matcher::src/electron/.github/problem-matchers/markdownlint.json"
|
||||||
- name: Run Lint
|
- name: Run Lint
|
||||||
shell: bash
|
shell: bash
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ jobs:
|
|||||||
run: df -h
|
run: df -h
|
||||||
- name: Setup Node.js/npm
|
- name: Setup Node.js/npm
|
||||||
if: ${{ inputs.target-platform == 'macos' }}
|
if: ${{ inputs.target-platform == 'macos' }}
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||||
with:
|
with:
|
||||||
node-version: 22.21.x
|
node-version: 22.21.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ jobs:
|
|||||||
cd src/electron
|
cd src/electron
|
||||||
git pack-refs
|
git pack-refs
|
||||||
- name: Download Out Gen Artifacts
|
- name: Download Out Gen Artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: out_gen_artifacts_${{ env.ARTIFACT_KEY }}
|
name: out_gen_artifacts_${{ env.ARTIFACT_KEY }}
|
||||||
path: ./src/out/${{ env.ELECTRON_OUT_DIR }}/gen
|
path: ./src/out/${{ env.ELECTRON_OUT_DIR }}/gen
|
||||||
@@ -137,13 +137,32 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
e init -f --root=$(pwd) --out=${ELECTRON_OUT_DIR} testing --target-cpu ${TARGET_ARCH} --remote-build none
|
e init -f --root=$(pwd) --out=${ELECTRON_OUT_DIR} testing --target-cpu ${TARGET_ARCH} --remote-build none
|
||||||
|
|
||||||
export GN_EXTRA_ARGS="target_cpu=\"${TARGET_ARCH}\""
|
# For macOS use_remoteexec=false will cause GN errors, so even though we're doing no remote build, set it
|
||||||
|
export GN_EXTRA_ARGS="use_remoteexec=true target_cpu=\"${TARGET_ARCH}\""
|
||||||
if [ "${{ inputs.target-platform }}" = "win" ]; then
|
if [ "${{ inputs.target-platform }}" = "win" ]; then
|
||||||
export GN_EXTRA_ARGS="$GN_EXTRA_ARGS use_v8_context_snapshot=true target_os=\"win\""
|
export GN_EXTRA_ARGS="$GN_EXTRA_ARGS use_v8_context_snapshot=true target_os=\"win\""
|
||||||
fi
|
fi
|
||||||
|
|
||||||
e build --only-gen
|
e build --only-gen
|
||||||
|
|
||||||
|
# Copy macOS framework headers so clang-tidy can find them via -F.
|
||||||
|
# This must happen after e build --only-gen since e init -f may
|
||||||
|
# recreate the output directory.
|
||||||
|
if [ "${{ inputs.target-platform }}" = "macos" ]; then
|
||||||
|
OUT=src/out/${ELECTRON_OUT_DIR}
|
||||||
|
SQRL=src/third_party/squirrel.mac
|
||||||
|
|
||||||
|
mkdir -p ${OUT}/{ReactiveObjC,Squirrel,Mantle}.framework/Headers
|
||||||
|
|
||||||
|
cp ${SQRL}/vendor/ReactiveObjC/ReactiveObjC/*.h ${OUT}/ReactiveObjC.framework/Headers/
|
||||||
|
cp ${SQRL}/vendor/ReactiveObjC/ReactiveObjC/extobjc/*.h ${OUT}/ReactiveObjC.framework/Headers/
|
||||||
|
|
||||||
|
cp ${SQRL}/Squirrel/*.h ${OUT}/Squirrel.framework/Headers/
|
||||||
|
|
||||||
|
cp ${SQRL}/vendor/Mantle/Mantle/include/*.h ${OUT}/Mantle.framework/Headers/
|
||||||
|
cp ${SQRL}/vendor/Mantle/Mantle/extobjc/include/*.h ${OUT}/Mantle.framework/Headers/
|
||||||
|
fi
|
||||||
|
|
||||||
cd src/electron
|
cd src/electron
|
||||||
node script/yarn.js lint:clang-tidy --jobs 8 --out-dir ../out/${ELECTRON_OUT_DIR}
|
node script/yarn.js lint:clang-tidy --jobs 8 --out-dir ../out/${ELECTRON_OUT_DIR}
|
||||||
- name: Remove Clang problem matcher
|
- name: Remove Clang problem matcher
|
||||||
|
|||||||
@@ -131,7 +131,7 @@ jobs:
|
|||||||
run: df -h
|
run: df -h
|
||||||
- name: Setup Node.js/npm
|
- name: Setup Node.js/npm
|
||||||
if: ${{ inputs.target-platform == 'macos' }}
|
if: ${{ inputs.target-platform == 'macos' }}
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||||
with:
|
with:
|
||||||
node-version: 22.21.x
|
node-version: 22.21.x
|
||||||
cache: yarn
|
cache: yarn
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ jobs:
|
|||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
ref: ${{ github.event.pull_request.head.sha }}
|
ref: ${{ github.event.pull_request.head.sha }}
|
||||||
- name: Download Generated Artifacts
|
- name: Download Generated Artifacts
|
||||||
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: generated_artifacts_linux_arm64
|
name: generated_artifacts_linux_arm64
|
||||||
path: ./generated_artifacts_linux_arm64
|
path: ./generated_artifacts_linux_arm64
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ jobs:
|
|||||||
cp $(which node) /mnt/runner-externals/node24/bin/
|
cp $(which node) /mnt/runner-externals/node24/bin/
|
||||||
- name: Setup Node.js/npm
|
- name: Setup Node.js/npm
|
||||||
if: ${{ inputs.target-platform == 'win' }}
|
if: ${{ inputs.target-platform == 'win' }}
|
||||||
uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f
|
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e
|
||||||
with:
|
with:
|
||||||
node-version: 22.21.x
|
node-version: 22.21.x
|
||||||
- name: Add TCC permissions on macOS
|
- name: Add TCC permissions on macOS
|
||||||
@@ -175,12 +175,12 @@ jobs:
|
|||||||
echo "DISABLE_CRASH_REPORTER_TESTS=true" >> $GITHUB_ENV
|
echo "DISABLE_CRASH_REPORTER_TESTS=true" >> $GITHUB_ENV
|
||||||
echo "IS_ASAN=true" >> $GITHUB_ENV
|
echo "IS_ASAN=true" >> $GITHUB_ENV
|
||||||
- name: Download Generated Artifacts
|
- name: Download Generated Artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: generated_artifacts_${{ env.ARTIFACT_KEY }}
|
name: generated_artifacts_${{ env.ARTIFACT_KEY }}
|
||||||
path: ./generated_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
path: ./generated_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||||
- name: Download Src Artifacts
|
- name: Download Src Artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: src_artifacts_${{ env.ARTIFACT_KEY }}
|
name: src_artifacts_${{ env.ARTIFACT_KEY }}
|
||||||
path: ./src_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
path: ./src_artifacts_${{ matrix.build-type }}_${{ inputs.target-arch }}
|
||||||
@@ -315,7 +315,7 @@ jobs:
|
|||||||
if: always() && !cancelled()
|
if: always() && !cancelled()
|
||||||
- name: Upload Test Artifacts
|
- name: Upload Test Artifacts
|
||||||
if: always()
|
if: always()
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f #v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a #v7.0.1
|
||||||
with:
|
with:
|
||||||
name: ${{ inputs.target-platform == 'linux' && format('test_artifacts_{0}_{1}_{2}', env.ARTIFACT_KEY, inputs.display-server, matrix.shard) || format('test_artifacts_{0}_{1}', env.ARTIFACT_KEY, matrix.shard) }}
|
name: ${{ inputs.target-platform == 'linux' && format('test_artifacts_{0}_{1}_{2}', env.ARTIFACT_KEY, inputs.display-server, matrix.shard) || format('test_artifacts_{0}_{1}', env.ARTIFACT_KEY, matrix.shard) }}
|
||||||
path: src/electron/spec/artifacts
|
path: src/electron/spec/artifacts
|
||||||
|
|||||||
@@ -67,12 +67,12 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
uses: ./src/electron/.github/actions/install-dependencies
|
uses: ./src/electron/.github/actions/install-dependencies
|
||||||
- name: Download Generated Artifacts
|
- name: Download Generated Artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||||
- name: Download Src Artifacts
|
- name: Download Src Artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||||
@@ -123,12 +123,12 @@ jobs:
|
|||||||
- name: Install Dependencies
|
- name: Install Dependencies
|
||||||
uses: ./src/electron/.github/actions/install-dependencies
|
uses: ./src/electron/.github/actions/install-dependencies
|
||||||
- name: Download Generated Artifacts
|
- name: Download Generated Artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
name: generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||||
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
path: ./generated_artifacts_${{ env.BUILD_TYPE }}_${{ env.TARGET_ARCH }}
|
||||||
- name: Download Src Artifacts
|
- name: Download Src Artifacts
|
||||||
uses: actions/download-artifact@70fc10c6e5e1ce46ad2ea6f2b72d43f7d47b13c3
|
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c
|
||||||
with:
|
with:
|
||||||
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
name: src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||||
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
path: ./src_artifacts_linux_${{ env.TARGET_ARCH }}
|
||||||
|
|||||||
64
.github/workflows/pr-template-check.yml
vendored
Normal file
64
.github/workflows/pr-template-check.yml
vendored
Normal file
@@ -0,0 +1,64 @@
|
|||||||
|
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@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.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 || '';
|
||||||
|
// Allow through if body contains a valid backport line
|
||||||
|
const backportRegex = /Backport of (?:#|https:\/\/github.com\/electron\/electron\/pull\/)\d+/i;
|
||||||
|
if (backportRegex.test(body)) {
|
||||||
|
console.log('Backport PR detected, skipping required section check.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
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',
|
||||||
|
});
|
||||||
|
}
|
||||||
56
.github/workflows/pr-triage-automation.yml
vendored
Normal file
56
.github/workflows/pr-triage-automation.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
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: Get project item status
|
||||||
|
uses: dsanders11/project-actions/get-item@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
|
id: get-item
|
||||||
|
with:
|
||||||
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
|
project-number: 118
|
||||||
|
fail-if-item-not-found: false
|
||||||
|
- name: Set status to Needs Review
|
||||||
|
if: >-
|
||||||
|
(steps.get-item.outputs.field-status == '🛑 Needs Submitter Response'
|
||||||
|
|| steps.get-item.outputs.field-status == '🟡 WIP')
|
||||||
|
uses: dsanders11/project-actions/edit-item@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
|
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: {}
|
permissions: {}
|
||||||
steps:
|
steps:
|
||||||
- name: Trigger Slack workflow
|
- name: Trigger Slack workflow
|
||||||
uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1
|
uses: slackapi/slack-github-action@af78098f536edbc4de71162a307590698245be95 # v3.0.1
|
||||||
with:
|
with:
|
||||||
webhook: ${{ secrets.BACKPORT_REQUESTED_SLACK_WEBHOOK_URL }}
|
webhook: ${{ secrets.BACKPORT_REQUESTED_SLACK_WEBHOOK_URL }}
|
||||||
webhook-type: webhook-trigger
|
webhook-type: webhook-trigger
|
||||||
@@ -42,7 +42,7 @@ jobs:
|
|||||||
creds: ${{ secrets.RELEASE_BOARD_GH_APP_CREDS }}
|
creds: ${{ secrets.RELEASE_BOARD_GH_APP_CREDS }}
|
||||||
org: electron
|
org: electron
|
||||||
- name: Set status
|
- name: Set status
|
||||||
uses: dsanders11/project-actions/edit-item@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/edit-item@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
token: ${{ steps.generate-token.outputs.token }}
|
||||||
project-number: 94
|
project-number: 94
|
||||||
@@ -50,7 +50,7 @@ jobs:
|
|||||||
field-value: ✅ Reviewed
|
field-value: ✅ Reviewed
|
||||||
pull-request-labeled-ai-pr:
|
pull-request-labeled-ai-pr:
|
||||||
name: ai-pr label added
|
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
|
runs-on: ubuntu-latest
|
||||||
permissions: {}
|
permissions: {}
|
||||||
steps:
|
steps:
|
||||||
@@ -60,7 +60,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
creds: ${{ secrets.ISSUE_TRIAGE_GH_APP_CREDS }}
|
||||||
- name: Create comment
|
- name: Create comment
|
||||||
uses: actions-cool/issues-helper@71b62d7da76e59ff7b193904feb6e77d4dbb2777 # v3.7.6
|
uses: actions-cool/issues-helper@200c78641dbf33838311e5a1e0c31bbdb92d7cf0 # v3.8.0
|
||||||
with:
|
with:
|
||||||
actions: 'create-comment'
|
actions: 'create-comment'
|
||||||
token: ${{ steps.generate-token.outputs.token }}
|
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.
|
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
|
- name: Close the pull request
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
GITHUB_TOKEN: ${{ steps.generate-token.outputs.token }}
|
||||||
|
|||||||
4
.github/workflows/scorecards.yml
vendored
4
.github/workflows/scorecards.yml
vendored
@@ -43,7 +43,7 @@ jobs:
|
|||||||
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
# Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF
|
||||||
# format to the repository Actions tab.
|
# format to the repository Actions tab.
|
||||||
- name: "Upload artifact"
|
- name: "Upload artifact"
|
||||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
||||||
with:
|
with:
|
||||||
name: SARIF file
|
name: SARIF file
|
||||||
path: results.sarif
|
path: results.sarif
|
||||||
@@ -51,6 +51,6 @@ jobs:
|
|||||||
|
|
||||||
# Upload the results to GitHub's code scanning dashboard.
|
# Upload the results to GitHub's code scanning dashboard.
|
||||||
- name: "Upload to code-scanning"
|
- name: "Upload to code-scanning"
|
||||||
uses: github/codeql-action/upload-sarif@0d579ffd059c29b07949a3cce3983f0780820c98 # v3.29.5
|
uses: github/codeql-action/upload-sarif@95e58e9a2cdfd71adc6e0353d5c52f41a045d225 # v3.29.5
|
||||||
with:
|
with:
|
||||||
sarif_file: results.sarif
|
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')
|
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"
|
echo "PROJECT_NUMBER=$PROJECT_NUMBER" >> "$GITHUB_OUTPUT"
|
||||||
- name: Update Completed Stable Prep Items
|
- name: Update Completed Stable Prep Items
|
||||||
uses: dsanders11/project-actions/completed-by@2134fe7cc71c58b7ae259c82a8e63c6058255678 # v1.7.0
|
uses: dsanders11/project-actions/completed-by@4b06452b0128cf601dac14399aa668a8eed2d684 # v2.0.1
|
||||||
with:
|
with:
|
||||||
field: Prep Status
|
field: Prep Status
|
||||||
field-value: ✅ Complete
|
field-value: ✅ Complete
|
||||||
|
|||||||
6
.github/workflows/windows-publish.yml
vendored
6
.github/workflows/windows-publish.yml
vendored
@@ -82,7 +82,7 @@ jobs:
|
|||||||
needs: [checkout-windows, build-siso-windows]
|
needs: [checkout-windows, build-siso-windows]
|
||||||
with:
|
with:
|
||||||
environment: production-release
|
environment: production-release
|
||||||
build-runs-on: electron-arc-centralus-windows-amd64-16core
|
build-runs-on: electron-arc-centralus-windows-amd64-32core
|
||||||
target-platform: win
|
target-platform: win
|
||||||
target-arch: x64
|
target-arch: x64
|
||||||
is-release: true
|
is-release: true
|
||||||
@@ -101,7 +101,7 @@ jobs:
|
|||||||
needs: [checkout-windows, build-siso-windows]
|
needs: [checkout-windows, build-siso-windows]
|
||||||
with:
|
with:
|
||||||
environment: production-release
|
environment: production-release
|
||||||
build-runs-on: electron-arc-centralus-windows-amd64-16core
|
build-runs-on: electron-arc-centralus-windows-amd64-32core
|
||||||
target-platform: win
|
target-platform: win
|
||||||
target-arch: arm64
|
target-arch: arm64
|
||||||
is-release: true
|
is-release: true
|
||||||
@@ -120,7 +120,7 @@ jobs:
|
|||||||
needs: [checkout-windows, build-siso-windows]
|
needs: [checkout-windows, build-siso-windows]
|
||||||
with:
|
with:
|
||||||
environment: production-release
|
environment: production-release
|
||||||
build-runs-on: electron-arc-centralus-windows-amd64-16core
|
build-runs-on: electron-arc-centralus-windows-amd64-32core
|
||||||
target-platform: win
|
target-platform: win
|
||||||
target-arch: x86
|
target-arch: x86
|
||||||
is-release: true
|
is-release: true
|
||||||
|
|||||||
47
.oxfmtrc.json
Normal file
47
.oxfmtrc.json
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||||
|
"printWidth": 120,
|
||||||
|
"tabWidth": 2,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "none",
|
||||||
|
"sortImports": {
|
||||||
|
"newlinesBetween": true,
|
||||||
|
"groups": [
|
||||||
|
"electron-internal",
|
||||||
|
"electron-scoped",
|
||||||
|
"electron",
|
||||||
|
"external",
|
||||||
|
"builtin",
|
||||||
|
["sibling", "parent"],
|
||||||
|
"index",
|
||||||
|
"type",
|
||||||
|
"unknown"
|
||||||
|
],
|
||||||
|
"customGroups": [
|
||||||
|
{
|
||||||
|
"groupName": "electron-internal",
|
||||||
|
"elementNamePattern": ["@electron/internal", "@electron/internal/**"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupName": "electron-scoped",
|
||||||
|
"elementNamePattern": ["@electron/**"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"groupName": "electron",
|
||||||
|
"elementNamePattern": ["electron", "electron/**"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"ignorePatterns": [
|
||||||
|
"node_modules",
|
||||||
|
"out",
|
||||||
|
"ts-gen",
|
||||||
|
"spec/node_modules",
|
||||||
|
"spec/fixtures/native-addon",
|
||||||
|
".github/workflows/node_modules",
|
||||||
|
"docs/fiddles",
|
||||||
|
"shell/browser/resources/win/resource.h",
|
||||||
|
"shell/common/node_includes.h",
|
||||||
|
"spec/fixtures/pages/jquery-3.6.0.min.js"
|
||||||
|
]
|
||||||
|
}
|
||||||
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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
19
BUILD.gn
19
BUILD.gn
@@ -151,6 +151,25 @@ config("branding") {
|
|||||||
|
|
||||||
config("electron_lib_config") {
|
config("electron_lib_config") {
|
||||||
include_dirs = [ "." ]
|
include_dirs = [ "." ]
|
||||||
|
cflags = []
|
||||||
|
if (is_clang && clang_use_chrome_plugins) {
|
||||||
|
# The plugin is built directly into clang, so there's no need to load it
|
||||||
|
# dynamically.
|
||||||
|
cflags += [
|
||||||
|
"-Xclang",
|
||||||
|
"-add-plugin",
|
||||||
|
"-Xclang",
|
||||||
|
"blink-gc-plugin",
|
||||||
|
"-Xclang",
|
||||||
|
"-plugin-arg-blink-gc-plugin",
|
||||||
|
"-Xclang",
|
||||||
|
"check-directory=electron/shell/",
|
||||||
|
"-Xclang",
|
||||||
|
"-plugin-arg-blink-gc-plugin",
|
||||||
|
"-Xclang",
|
||||||
|
"check-directory=gin/",
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# We generate the definitions twice here, once in //electron/electron.d.ts
|
# We generate the definitions twice here, once in //electron/electron.d.ts
|
||||||
|
|||||||
12
CLAUDE.md
12
CLAUDE.md
@@ -1,5 +1,14 @@
|
|||||||
# Electron Development Guide
|
# 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
|
## 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.
|
Electron is a framework for building cross-platform desktop applications using web technologies. It embeds Chromium for rendering and Node.js for backend functionality.
|
||||||
@@ -205,12 +214,13 @@ gh label list --repo electron/electron --search target/ --json name,color --jq '
|
|||||||
## Code Style
|
## Code Style
|
||||||
|
|
||||||
**C++:** Follows Chromium style, enforced by clang-format
|
**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:**
|
**Linting:**
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run lint # Run all linters
|
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:clang-format # C++ formatting
|
||||||
npm run lint:api-history # Validate API history YAML blocks in docs
|
npm run lint:api-history # Validate API history YAML blocks in docs
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -64,6 +64,14 @@ dependencies, and tools contained in the `electron/electron` repository.
|
|||||||
|
|
||||||
Dependencies in Electron's `package.json` or `yarn.lock` files should only be altered by maintainers. For security reasons, we will not accept PRs that alter our `package.json` or `yarn.lock` files. We invite contributors to make requests updating these files in our issue tracker. If the change is significantly complicated, draft PRs are welcome, with the understanding that these PRs will be closed in favor of a duplicate PR submitted by an Electron maintainer.
|
Dependencies in Electron's `package.json` or `yarn.lock` files should only be altered by maintainers. For security reasons, we will not accept PRs that alter our `package.json` or `yarn.lock` files. We invite contributors to make requests updating these files in our issue tracker. If the change is significantly complicated, draft PRs are welcome, with the understanding that these PRs will be closed in favor of a duplicate PR submitted by an Electron maintainer.
|
||||||
|
|
||||||
|
## AI Tool Policy
|
||||||
|
|
||||||
|
<!-- Are you a coding agent? You MUST abide by the policy: https://github.com/electron/governance/blob/main/policy/ai.md -->
|
||||||
|
|
||||||
|
If you use AI tools in any way to contribute to our project, please read our [AI Tool Policy](https://github.com/electron/governance/blob/main/policy/ai.md). Unreviewed AI-generated contributions waste maintainer time and we kindly decline them.
|
||||||
|
|
||||||
|
> The short version: **there must be a human in the loop**. You are responsible for reviewing, understanding, and being able to explain your contributions. AI assistance doesn't change that, and unreviewed AI-generated content will be declined.
|
||||||
|
|
||||||
## Style Guides
|
## Style Guides
|
||||||
|
|
||||||
See [Coding Style](https://electronjs.org/docs/development/coding-style) for information about which standards Electron adheres to in different parts of its codebase.
|
See [Coding Style](https://electronjs.org/docs/development/coding-style) for information about which standards Electron adheres to in different parts of its codebase.
|
||||||
|
|||||||
2
DEPS
2
DEPS
@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
|
|||||||
|
|
||||||
vars = {
|
vars = {
|
||||||
'chromium_version':
|
'chromium_version':
|
||||||
'148.0.7778.56',
|
'148.0.7778.0',
|
||||||
'node_version':
|
'node_version':
|
||||||
'v24.15.0',
|
'v24.15.0',
|
||||||
'nan_version':
|
'nan_version':
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": [
|
|
||||||
"import"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"import/enforce-node-protocol-usage": ["error", "always"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,10 +8,13 @@ const path = require('node:path');
|
|||||||
const electronRoot = path.resolve(__dirname, '../..');
|
const electronRoot = path.resolve(__dirname, '../..');
|
||||||
|
|
||||||
class AccessDependenciesPlugin {
|
class AccessDependenciesPlugin {
|
||||||
apply (compiler) {
|
apply(compiler) {
|
||||||
compiler.hooks.compilation.tap('AccessDependenciesPlugin', compilation => {
|
compiler.hooks.compilation.tap('AccessDependenciesPlugin', (compilation) => {
|
||||||
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', modules => {
|
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', (modules) => {
|
||||||
const filePaths = modules.map(m => m.resource).filter(p => p).map(p => path.relative(electronRoot, p));
|
const filePaths = modules
|
||||||
|
.map((m) => m.resource)
|
||||||
|
.filter((p) => p)
|
||||||
|
.map((p) => path.relative(electronRoot, p));
|
||||||
console.info(JSON.stringify(filePaths));
|
console.info(JSON.stringify(filePaths));
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -31,7 +34,14 @@ module.exports = ({
|
|||||||
entry = path.resolve(electronRoot, 'lib', target, 'init.js');
|
entry = path.resolve(electronRoot, 'lib', target, 'init.js');
|
||||||
}
|
}
|
||||||
|
|
||||||
const electronAPIFile = path.resolve(electronRoot, 'lib', loadElectronFromAlternateTarget || target, 'api', 'exports', 'electron.ts');
|
const electronAPIFile = path.resolve(
|
||||||
|
electronRoot,
|
||||||
|
'lib',
|
||||||
|
loadElectronFromAlternateTarget || target,
|
||||||
|
'api',
|
||||||
|
'exports',
|
||||||
|
'electron.ts'
|
||||||
|
);
|
||||||
|
|
||||||
return (env = {}, argv = {}) => {
|
return (env = {}, argv = {}) => {
|
||||||
const onlyPrintingGraph = !!env.PRINT_WEBPACK_GRAPH;
|
const onlyPrintingGraph = !!env.PRINT_WEBPACK_GRAPH;
|
||||||
@@ -61,49 +71,59 @@ module.exports = ({
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (targetDeletesNodeGlobals) {
|
if (targetDeletesNodeGlobals) {
|
||||||
plugins.push(new webpack.ProvidePlugin({
|
plugins.push(
|
||||||
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
|
new webpack.ProvidePlugin({
|
||||||
global: ['@electron/internal/common/webpack-provider', '_global'],
|
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
|
||||||
process: ['@electron/internal/common/webpack-provider', 'process']
|
global: ['@electron/internal/common/webpack-provider', '_global'],
|
||||||
}));
|
process: ['@electron/internal/common/webpack-provider', 'process']
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Webpack 5 no longer polyfills process or Buffer.
|
// Webpack 5 no longer polyfills process or Buffer.
|
||||||
if (!alwaysHasNode) {
|
if (!alwaysHasNode) {
|
||||||
plugins.push(new webpack.ProvidePlugin({
|
plugins.push(
|
||||||
Buffer: ['buffer', 'Buffer'],
|
new webpack.ProvidePlugin({
|
||||||
process: 'process/browser'
|
Buffer: ['buffer', 'Buffer'],
|
||||||
}));
|
process: 'process/browser'
|
||||||
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
plugins.push(new webpack.ProvidePlugin({
|
plugins.push(
|
||||||
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
|
new webpack.ProvidePlugin({
|
||||||
}));
|
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
plugins.push(new webpack.DefinePlugin(defines));
|
plugins.push(new webpack.DefinePlugin(defines));
|
||||||
|
|
||||||
if (wrapInitWithProfilingTimeout) {
|
if (wrapInitWithProfilingTimeout) {
|
||||||
plugins.push(new WrapperPlugin({
|
plugins.push(
|
||||||
header: 'function ___electron_webpack_init__() {',
|
new WrapperPlugin({
|
||||||
footer: `
|
header: 'function ___electron_webpack_init__() {',
|
||||||
|
footer: `
|
||||||
};
|
};
|
||||||
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
|
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
|
||||||
setTimeout(___electron_webpack_init__, 0);
|
setTimeout(___electron_webpack_init__, 0);
|
||||||
} else {
|
} else {
|
||||||
___electron_webpack_init__();
|
___electron_webpack_init__();
|
||||||
}`
|
}`
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wrapInitWithTryCatch) {
|
if (wrapInitWithTryCatch) {
|
||||||
plugins.push(new WrapperPlugin({
|
plugins.push(
|
||||||
header: 'try {',
|
new WrapperPlugin({
|
||||||
footer: `
|
header: 'try {',
|
||||||
|
footer: `
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Electron ${outputFilename} script failed to run');
|
console.error('Electron ${outputFilename} script failed to run');
|
||||||
console.error(err);
|
console.error(err);
|
||||||
}`
|
}`
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -133,23 +153,26 @@ if ((globalThis.process || binding.process).argv.includes("--profile-electron-in
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
module: {
|
module: {
|
||||||
rules: [{
|
rules: [
|
||||||
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
|
{
|
||||||
loader: 'null-loader'
|
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
|
||||||
}, {
|
loader: 'null-loader'
|
||||||
test: /\.ts$/,
|
},
|
||||||
loader: 'ts-loader',
|
{
|
||||||
options: {
|
test: /\.ts$/,
|
||||||
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
|
loader: 'ts-loader',
|
||||||
transpileOnly: onlyPrintingGraph,
|
options: {
|
||||||
ignoreDiagnostics: [
|
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
|
||||||
// File '{0}' is not under 'rootDir' '{1}'.
|
transpileOnly: onlyPrintingGraph,
|
||||||
6059,
|
ignoreDiagnostics: [
|
||||||
// Private field '{0}' must be declared in an enclosing class.
|
// File '{0}' is not under 'rootDir' '{1}'.
|
||||||
1111
|
6059,
|
||||||
]
|
// Private field '{0}' must be declared in an enclosing class.
|
||||||
|
1111
|
||||||
|
]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}]
|
]
|
||||||
},
|
},
|
||||||
node: {
|
node: {
|
||||||
__dirname: false,
|
__dirname: false,
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"plugins": [
|
|
||||||
"import"
|
|
||||||
],
|
|
||||||
"rules": {
|
|
||||||
"import/enforce-node-protocol-usage": ["error", "always"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -61,13 +61,12 @@ app.whenReady().then(() => {
|
|||||||
// Find the shortest path to the electron binary
|
// Find the shortest path to the electron binary
|
||||||
const absoluteElectronPath = process.execPath;
|
const absoluteElectronPath = process.execPath;
|
||||||
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
|
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
|
||||||
const electronPath = absoluteElectronPath.length < relativeElectronPath.length
|
const electronPath =
|
||||||
? absoluteElectronPath
|
absoluteElectronPath.length < relativeElectronPath.length ? absoluteElectronPath : relativeElectronPath;
|
||||||
: relativeElectronPath;
|
|
||||||
|
|
||||||
const indexPath = path.resolve(app.getAppPath(), 'index.html');
|
const indexPath = path.resolve(app.getAppPath(), 'index.html');
|
||||||
|
|
||||||
function isTrustedSender (webContents: Electron.WebContents) {
|
function isTrustedSender(webContents: Electron.WebContents) {
|
||||||
if (webContents !== (mainWindow && mainWindow.webContents)) {
|
if (webContents !== (mainWindow && mainWindow.webContents)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -83,7 +82,7 @@ ipcMain.handle('bootstrap', (event) => {
|
|||||||
return isTrustedSender(event.sender) ? electronPath : null;
|
return isTrustedSender(event.sender) ? electronPath : null;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function createWindow (backgroundColor?: string) {
|
async function createWindow(backgroundColor?: string) {
|
||||||
await app.whenReady();
|
await app.whenReady();
|
||||||
|
|
||||||
const options: Electron.BrowserWindowConstructorOptions = {
|
const options: Electron.BrowserWindowConstructorOptions = {
|
||||||
@@ -108,7 +107,7 @@ async function createWindow (backgroundColor?: string) {
|
|||||||
mainWindow = new BrowserWindow(options);
|
mainWindow = new BrowserWindow(options);
|
||||||
mainWindow.on('ready-to-show', () => mainWindow!.show());
|
mainWindow.on('ready-to-show', () => mainWindow!.show());
|
||||||
|
|
||||||
mainWindow.webContents.setWindowOpenHandler(details => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
shell.openExternal(details.url);
|
shell.openExternal(details.url);
|
||||||
return { action: 'deny' };
|
return { action: 'deny' };
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ type DefaultAppOptions = {
|
|||||||
interactive: boolean;
|
interactive: boolean;
|
||||||
abi: boolean;
|
abi: boolean;
|
||||||
modules: string[];
|
modules: string[];
|
||||||
}
|
};
|
||||||
|
|
||||||
// Parse command line options.
|
// Parse command line options.
|
||||||
const argv = process.argv.slice(1);
|
const argv = process.argv.slice(1);
|
||||||
@@ -74,7 +74,7 @@ if (option.modules.length > 0) {
|
|||||||
(Module as any)._preloadModules(option.modules);
|
(Module as any)._preloadModules(option.modules);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadApplicationPackage (packagePath: string) {
|
async function loadApplicationPackage(packagePath: string) {
|
||||||
// Add a flag indicating app is started from default app.
|
// Add a flag indicating app is started from default app.
|
||||||
Object.defineProperty(process, 'defaultApp', {
|
Object.defineProperty(process, 'defaultApp', {
|
||||||
configurable: false,
|
configurable: false,
|
||||||
@@ -92,9 +92,11 @@ async function loadApplicationPackage (packagePath: string) {
|
|||||||
const emitWarning = process.emitWarning;
|
const emitWarning = process.emitWarning;
|
||||||
try {
|
try {
|
||||||
process.emitWarning = () => {};
|
process.emitWarning = () => {};
|
||||||
packageJson = (await import(url.pathToFileURL(packageJsonPath).toString(), {
|
packageJson = (
|
||||||
with: { type: 'json' }
|
await import(url.pathToFileURL(packageJsonPath).toString(), {
|
||||||
})).default;
|
with: { type: 'json' }
|
||||||
|
})
|
||||||
|
).default;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${(e as Error).message}`);
|
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${(e as Error).message}`);
|
||||||
return;
|
return;
|
||||||
@@ -143,23 +145,23 @@ async function loadApplicationPackage (packagePath: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function showErrorMessage (message: string) {
|
function showErrorMessage(message: string) {
|
||||||
app.focus();
|
app.focus();
|
||||||
dialog.showErrorBox('Error launching app', message);
|
dialog.showErrorBox('Error launching app', message);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadApplicationByURL (appUrl: string) {
|
async function loadApplicationByURL(appUrl: string) {
|
||||||
const { loadURL } = await import('./default_app.js');
|
const { loadURL } = await import('./default_app.js');
|
||||||
loadURL(appUrl);
|
loadURL(appUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadApplicationByFile (appPath: string) {
|
async function loadApplicationByFile(appPath: string) {
|
||||||
const { loadFile } = await import('./default_app.js');
|
const { loadFile } = await import('./default_app.js');
|
||||||
loadFile(appPath);
|
loadFile(appPath);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function startRepl () {
|
async function startRepl() {
|
||||||
if (process.platform === 'win32') {
|
if (process.platform === 'win32') {
|
||||||
console.error('Electron REPL not currently supported on Windows');
|
console.error('Electron REPL not currently supported on Windows');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
@@ -187,7 +189,7 @@ async function startRepl () {
|
|||||||
process.exit(0);
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
function defineBuiltin (context: any, name: string, getter: Function) {
|
function defineBuiltin(context: any, name: string, getter: Function) {
|
||||||
const setReal = (val: any) => {
|
const setReal = (val: any) => {
|
||||||
// Deleting the property before re-assigning it disables the
|
// Deleting the property before re-assigning it disables the
|
||||||
// getter/setter mechanism.
|
// getter/setter mechanism.
|
||||||
@@ -225,11 +227,42 @@ async function startRepl () {
|
|||||||
// we only trigger custom tab-completion when no common words are
|
// we only trigger custom tab-completion when no common words are
|
||||||
// potentially matches.
|
// potentially matches.
|
||||||
const commonWords = [
|
const commonWords = [
|
||||||
'async', 'await', 'break', 'case', 'catch', 'const', 'continue',
|
'async',
|
||||||
'debugger', 'default', 'delete', 'do', 'else', 'export', 'false',
|
'await',
|
||||||
'finally', 'for', 'function', 'if', 'import', 'in', 'instanceof', 'let',
|
'break',
|
||||||
'new', 'null', 'return', 'switch', 'this', 'throw', 'true', 'try',
|
'case',
|
||||||
'typeof', 'var', 'void', 'while', 'with', 'yield'
|
'catch',
|
||||||
|
'const',
|
||||||
|
'continue',
|
||||||
|
'debugger',
|
||||||
|
'default',
|
||||||
|
'delete',
|
||||||
|
'do',
|
||||||
|
'else',
|
||||||
|
'export',
|
||||||
|
'false',
|
||||||
|
'finally',
|
||||||
|
'for',
|
||||||
|
'function',
|
||||||
|
'if',
|
||||||
|
'import',
|
||||||
|
'in',
|
||||||
|
'instanceof',
|
||||||
|
'let',
|
||||||
|
'new',
|
||||||
|
'null',
|
||||||
|
'return',
|
||||||
|
'switch',
|
||||||
|
'this',
|
||||||
|
'throw',
|
||||||
|
'true',
|
||||||
|
'try',
|
||||||
|
'typeof',
|
||||||
|
'var',
|
||||||
|
'void',
|
||||||
|
'while',
|
||||||
|
'with',
|
||||||
|
'yield'
|
||||||
];
|
];
|
||||||
|
|
||||||
const electronBuiltins = [...Object.keys(electron), 'original-fs', 'electron'];
|
const electronBuiltins = [...Object.keys(electron), 'original-fs', 'electron'];
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ const { ipcRenderer, contextBridge } = require('electron/renderer');
|
|||||||
|
|
||||||
const policy = window.trustedTypes.createPolicy('electron-default-app', {
|
const policy = window.trustedTypes.createPolicy('electron-default-app', {
|
||||||
// we trust the SVG contents
|
// we trust the SVG contents
|
||||||
createHTML: input => input
|
createHTML: (input) => input
|
||||||
});
|
});
|
||||||
|
|
||||||
async function getOcticonSvg (name: string) {
|
async function getOcticonSvg(name: string) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`octicon/${name}.svg`);
|
const response = await fetch(`octicon/${name}.svg`);
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
@@ -16,7 +16,7 @@ async function getOcticonSvg (name: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadSVG (element: HTMLSpanElement) {
|
async function loadSVG(element: HTMLSpanElement) {
|
||||||
for (const cssClass of element.classList) {
|
for (const cssClass of element.classList) {
|
||||||
if (cssClass.startsWith('octicon-')) {
|
if (cssClass.startsWith('octicon-')) {
|
||||||
const icon = await getOcticonSvg(cssClass.substr(8));
|
const icon = await getOcticonSvg(cssClass.substr(8));
|
||||||
@@ -32,9 +32,9 @@ async function loadSVG (element: HTMLSpanElement) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function initialize () {
|
async function initialize() {
|
||||||
const electronPath = await ipcRenderer.invoke('bootstrap');
|
const electronPath = await ipcRenderer.invoke('bootstrap');
|
||||||
function replaceText (selector: string, text: string, link?: string) {
|
function replaceText(selector: string, text: string, link?: string) {
|
||||||
const element = document.querySelector<HTMLElement>(selector);
|
const element = document.querySelector<HTMLElement>(selector);
|
||||||
if (element) {
|
if (element) {
|
||||||
if (link) {
|
if (link) {
|
||||||
@@ -51,7 +51,11 @@ async function initialize () {
|
|||||||
|
|
||||||
replaceText('.electron-version', `Electron v${process.versions.electron}`, 'https://electronjs.org/docs');
|
replaceText('.electron-version', `Electron v${process.versions.electron}`, 'https://electronjs.org/docs');
|
||||||
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`, 'https://developer.chrome.com/docs/chromium');
|
replaceText('.chrome-version', `Chromium v${process.versions.chrome}`, 'https://developer.chrome.com/docs/chromium');
|
||||||
replaceText('.node-version', `Node v${process.versions.node}`, `https://nodejs.org/docs/v${process.versions.node}/api`);
|
replaceText(
|
||||||
|
'.node-version',
|
||||||
|
`Node v${process.versions.node}`,
|
||||||
|
`https://nodejs.org/docs/v${process.versions.node}/api`
|
||||||
|
);
|
||||||
replaceText('.v8-version', `v8 v${process.versions.v8}`, 'https://v8.dev/docs');
|
replaceText('.v8-version', `v8 v${process.versions.v8}`, 'https://v8.dev/docs');
|
||||||
replaceText('.command-example', `${electronPath} path-to-app`);
|
replaceText('.command-example', `${electronPath} path-to-app`);
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
* `window` [BaseWindow](base-window.md) (optional)
|
||||||
* `options` Object
|
* `options` Object
|
||||||
* `title` string (optional)
|
* `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
|
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||||
left empty the default label will be used.
|
left empty the default label will be used.
|
||||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||||
@@ -109,7 +112,10 @@ changes:
|
|||||||
* `window` [BaseWindow](base-window.md) (optional)
|
* `window` [BaseWindow](base-window.md) (optional)
|
||||||
* `options` Object
|
* `options` Object
|
||||||
* `title` string (optional)
|
* `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
|
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||||
left empty the default label will be used.
|
left empty the default label will be used.
|
||||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||||
@@ -198,7 +204,9 @@ added:
|
|||||||
* `options` Object
|
* `options` Object
|
||||||
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
||||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
* `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
|
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||||
left empty the default label will be used.
|
left empty the default label will be used.
|
||||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||||
@@ -238,7 +246,9 @@ changes:
|
|||||||
* `options` Object
|
* `options` Object
|
||||||
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
* `title` string (optional) - The dialog title. Cannot be displayed on some _Linux_ desktop environments.
|
||||||
* `defaultPath` string (optional) - Absolute directory path, absolute file
|
* `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
|
* `buttonLabel` string (optional) - Custom label for the confirmation button, when
|
||||||
left empty the default label will be used.
|
left empty the default label will be used.
|
||||||
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
* `filters` [FileFilter[]](structures/file-filter.md) (optional)
|
||||||
|
|||||||
@@ -56,6 +56,9 @@ Returns `string` - The badge string of the dock.
|
|||||||
|
|
||||||
Hides the dock icon.
|
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_
|
#### `dock.show()` _macOS_
|
||||||
|
|
||||||
Returns `Promise<void>` - Resolves when the dock icon is shown.
|
Returns `Promise<void>` - Resolves when the dock icon is shown.
|
||||||
|
|||||||
@@ -203,13 +203,22 @@ the one downloaded by `npm install`. Usage:
|
|||||||
export ELECTRON_OVERRIDE_DIST_PATH=/Users/username/projects/electron/out/Testing
|
export ELECTRON_OVERRIDE_DIST_PATH=/Users/username/projects/electron/out/Testing
|
||||||
```
|
```
|
||||||
|
|
||||||
### `ELECTRON_SKIP_BINARY_DOWNLOAD`
|
### `ELECTRON_INSTALL_PLATFORM`
|
||||||
|
|
||||||
If you want to install your project's dependencies but don't need to use Electron functionality,
|
Manually overrides platform used by `electron` package during an install.
|
||||||
you can set the `ELECTRON_SKIP_BINARY_DOWNLOAD` environment variable to prevent the binary from being
|
This can be useful if you are on one platform (e.g macOS) but want to
|
||||||
downloaded. For instance, this feature can be useful in continuous integration environments when
|
download binaries for another platform (e.g Windows or Linux). Usage:
|
||||||
running unit tests that mock out the `electron` module.
|
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
ELECTRON_SKIP_BINARY_DOWNLOAD=1 npm install
|
ELECTRON_INSTALL_PLATFORM=darwin npm install
|
||||||
|
```
|
||||||
|
|
||||||
|
### `ELECTRON_INSTALL_ARCH`
|
||||||
|
|
||||||
|
Manually overrides architecture used by `electron` package during an install.
|
||||||
|
This can be useful if you are on one arch (e.g `arm64`) but want to download
|
||||||
|
binaries meant for another arch. Note that this will not work under Rosetta. Usage:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
ELECTRON_INSTALL_ARCH=arm64 npm install
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -59,7 +59,12 @@ On Windows, returns true once the app has emitted the `ready` event.
|
|||||||
|
|
||||||
### `safeStorage.isAsyncEncryptionAvailable()`
|
### `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)`
|
### `safeStorage.encryptString(plainText)`
|
||||||
|
|
||||||
|
|||||||
@@ -650,7 +650,7 @@ Clears the session’s HTTP cache.
|
|||||||
`scheme://host:port`.
|
`scheme://host:port`.
|
||||||
* `storages` string[] (optional) - The types of storages to clear, can be
|
* `storages` string[] (optional) - The types of storages to clear, can be
|
||||||
`cookies`, `filesystem`, `indexdb`, `localstorage`,
|
`cookies`, `filesystem`, `indexdb`, `localstorage`,
|
||||||
`shadercache`, `websql`, `serviceworkers`, `cachestorage`. If not
|
`shadercache`, `serviceworkers`, `cachestorage`. If not
|
||||||
specified, clear all storage types.
|
specified, clear all storage types.
|
||||||
|
|
||||||
Returns `Promise<void>` - resolves when the storage data has been cleared.
|
Returns `Promise<void>` - resolves when the storage data has been cleared.
|
||||||
|
|||||||
@@ -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.
|
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.
|
* `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.
|
* `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.
|
* `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
|
* `contextIsolation` boolean (optional) - Whether to run Electron APIs and
|
||||||
the specified `preload` script in a separate JavaScript context. Defaults
|
the specified `preload` script in a separate JavaScript context. Defaults
|
||||||
|
|||||||
@@ -226,16 +226,7 @@ Returns:
|
|||||||
Only defined when the window is being created by a form that set
|
Only defined when the window is being created by a form that set
|
||||||
`target=_blank`.
|
`target=_blank`.
|
||||||
* `disposition` string - Can be `default`, `foreground-tab`,
|
* `disposition` string - Can be `default`, `foreground-tab`,
|
||||||
`background-tab`, `new-window` or `other`. Corresponds to the manner
|
`background-tab`, `new-window` or `other`.
|
||||||
an associated link was clicked. See Chromium's
|
|
||||||
[WindowOpenDisposition](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/window_open_disposition.h).
|
|
||||||
* `default` - Indicates Chromium deems in-window navigation valid
|
|
||||||
for a window open call.
|
|
||||||
* `foreground-tab` - Corresponds to a left click or shift + middle click.
|
|
||||||
* `background-tab` - Corresponds to a middle click or ctrl/cmd + click.
|
|
||||||
* `new-window` - Corresponds to a shift + left click.
|
|
||||||
* `other` - A catch-all for the remaining Chromium dispositions not
|
|
||||||
handled by Electron.
|
|
||||||
|
|
||||||
Emitted _after_ successful creation of a window via `window.open` in the renderer.
|
Emitted _after_ successful creation of a window via `window.open` in the renderer.
|
||||||
Not emitted if the creation of the window is canceled from
|
Not emitted if the creation of the window is canceled from
|
||||||
@@ -1458,17 +1449,8 @@ Ignore application menu shortcuts while this web contents is focused.
|
|||||||
* `url` string - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
|
* `url` string - The _resolved_ version of the URL passed to `window.open()`. e.g. opening a window with `window.open('foo')` will yield something like `https://the-origin/the/current/path/foo`.
|
||||||
* `frameName` string - Name of the window provided in `window.open()`
|
* `frameName` string - Name of the window provided in `window.open()`
|
||||||
* `features` string - Comma separated list of window features provided to `window.open()`.
|
* `features` string - Comma separated list of window features provided to `window.open()`.
|
||||||
* `disposition` string - Can be `default`, `foreground-tab`,
|
* `disposition` string - Can be `default`, `foreground-tab`, `background-tab`,
|
||||||
`background-tab`, `new-window` or `other`. Corresponds to the manner
|
`new-window` or `other`.
|
||||||
an associated link was clicked. See Chromium's
|
|
||||||
[WindowOpenDisposition](https://source.chromium.org/chromium/chromium/src/+/main:ui/base/window_open_disposition.h).
|
|
||||||
* `default` - Indicates Chromium deems in-window navigation valid
|
|
||||||
for a window open call.
|
|
||||||
* `foreground-tab` - Corresponds to a left click or shift + middle click.
|
|
||||||
* `background-tab` - Corresponds to a middle click or ctrl/cmd + click.
|
|
||||||
* `new-window` - Corresponds to a shift + left click.
|
|
||||||
* `other` - A catch-all for the remaining Chromium dispositions not
|
|
||||||
handled by Electron.
|
|
||||||
* `referrer` [Referrer](structures/referrer.md) - The referrer that will be
|
* `referrer` [Referrer](structures/referrer.md) - The referrer that will be
|
||||||
passed to the new window. May or may not result in the `Referer` header being
|
passed to the new window. May or may not result in the `Referer` header being
|
||||||
sent, depending on the referrer policy.
|
sent, depending on the referrer policy.
|
||||||
@@ -1603,6 +1585,20 @@ Centers the current text selection in web page.
|
|||||||
|
|
||||||
Copy the image at the given position to the clipboard.
|
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()`
|
#### `contents.paste()`
|
||||||
|
|
||||||
Executes the editing command `paste` in web page.
|
Executes the editing command `paste` in web page.
|
||||||
@@ -2279,6 +2275,20 @@ Returns `Integer` - The Chromium internal `pid` of the associated renderer. Can
|
|||||||
be compared to the `frameProcessId` passed by frame specific navigation events
|
be compared to the `frameProcessId` passed by frame specific navigation events
|
||||||
(e.g. `did-frame-navigate`)
|
(e.g. `did-frame-navigate`)
|
||||||
|
|
||||||
|
#### `contents.clone()`
|
||||||
|
|
||||||
|
Returns `WebContents` - A cloned WebContents instance. This method creates a copy
|
||||||
|
of the WebContents with the following attributes:
|
||||||
|
|
||||||
|
* **WebPreferences** - All preferences from the original WebContents are copied
|
||||||
|
* **SiteInstance** - Uses the same SiteInstance as the original. This means the cloned WebContents will reuse the same render process as the original when loading same-origin pages, and only spawn a new render process for cross-origin navigations. This process allocation behavior is consistent with window.open and tab duplication in Chromium. For more details, see [Chromium's Site Isolation](https://www.chromium.org/developers/design-documents/site-isolation/) design document.
|
||||||
|
* **Opener relationship** - Inherits the opener (window.opener) relationship
|
||||||
|
* **Navigation state** - Copies the navigation history and controller state
|
||||||
|
|
||||||
|
The cloned WebContents is an independent instance with its own lifecycle that can be destroyed separately and will not contain any open web pages.
|
||||||
|
|
||||||
|
This API is useful for use cases where you want to create a new WebContents that shares the same render process with the original for same-origin content, while maintaining full lifecycle independence. Additionally, reusing the existing render process can help optimize memory usage and page load speed to a certain extent, as it eliminates the overhead of spawning and initializing a new render process from scratch.
|
||||||
|
|
||||||
#### `contents.takeHeapSnapshot(filePath)`
|
#### `contents.takeHeapSnapshot(filePath)`
|
||||||
|
|
||||||
* `filePath` string - Path to the output file.
|
* `filePath` string - Path to the output file.
|
||||||
|
|||||||
@@ -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
|
### Instance Properties
|
||||||
|
|
||||||
#### `frame.ipc` _Readonly_
|
#### `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.
|
* **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.
|
* **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)
|
## Planned Breaking API Changes (42.0)
|
||||||
|
|
||||||
### Behavior Changed: macOS notifications now use `UNNotification` API
|
### Behavior Changed: macOS notifications now use `UNNotification` API
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
These are the style guidelines for coding in Electron.
|
These are the style guidelines for coding in Electron.
|
||||||
|
|
||||||
You can run `npm run lint` to show any style issues detected by `cpplint` and
|
You can run `npm run lint` to show any style issues detected by `cpplint` and
|
||||||
`eslint`.
|
`oxlint`.
|
||||||
|
|
||||||
## General Code
|
## General Code
|
||||||
|
|
||||||
|
|||||||
@@ -87,6 +87,13 @@ if (!gotTheLock) {
|
|||||||
// Create mainWindow, load the rest of the app, etc...
|
// Create mainWindow, load the rest of the app, etc...
|
||||||
app.whenReady().then(() => {
|
app.whenReady().then(() => {
|
||||||
createWindow()
|
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}`)
|
||||||
|
}
|
||||||
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -107,7 +107,7 @@ When signing the app with `@electron/osx-sign`, it will automatically add the
|
|||||||
necessary entitlements to your app's entitlements.
|
necessary entitlements to your app's entitlements.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>Extra steps without `@electron/osx-sign`</summary>
|
<summary>Extra steps without `electron-osx-sign`</summary>
|
||||||
|
|
||||||
If you are signing your app without using `@electron/osx-sign`, you must ensure
|
If you are signing your app without using `@electron/osx-sign`, you must ensure
|
||||||
the app bundle's entitlements have at least following keys:
|
the app bundle's entitlements have at least following keys:
|
||||||
|
|||||||
@@ -180,6 +180,7 @@ auto_filenames = {
|
|||||||
"lib/common/define-properties.ts",
|
"lib/common/define-properties.ts",
|
||||||
"lib/common/deprecate.ts",
|
"lib/common/deprecate.ts",
|
||||||
"lib/common/ipc-messages.ts",
|
"lib/common/ipc-messages.ts",
|
||||||
|
"lib/common/timers-shim.ts",
|
||||||
"lib/common/web-view-methods.ts",
|
"lib/common/web-view-methods.ts",
|
||||||
"lib/common/webpack-globals-provider.ts",
|
"lib/common/webpack-globals-provider.ts",
|
||||||
"lib/renderer/api/context-bridge.ts",
|
"lib/renderer/api/context-bridge.ts",
|
||||||
|
|||||||
@@ -688,6 +688,7 @@ filenames = {
|
|||||||
"shell/common/gin_helper/wrappable.cc",
|
"shell/common/gin_helper/wrappable.cc",
|
||||||
"shell/common/gin_helper/wrappable.h",
|
"shell/common/gin_helper/wrappable.h",
|
||||||
"shell/common/gin_helper/wrappable_base.h",
|
"shell/common/gin_helper/wrappable_base.h",
|
||||||
|
"shell/common/gin_helper/wrappable_pointer_tags.h",
|
||||||
"shell/common/heap_snapshot.cc",
|
"shell/common/heap_snapshot.cc",
|
||||||
"shell/common/heap_snapshot.h",
|
"shell/common/heap_snapshot.h",
|
||||||
"shell/common/key_weak_map.h",
|
"shell/common/key_weak_map.h",
|
||||||
@@ -805,8 +806,6 @@ filenames = {
|
|||||||
"shell/browser/extensions/electron_kiosk_delegate.h",
|
"shell/browser/extensions/electron_kiosk_delegate.h",
|
||||||
"shell/browser/extensions/electron_messaging_delegate.cc",
|
"shell/browser/extensions/electron_messaging_delegate.cc",
|
||||||
"shell/browser/extensions/electron_messaging_delegate.h",
|
"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.cc",
|
||||||
"shell/browser/extensions/electron_process_manager_delegate.h",
|
"shell/browser/extensions/electron_process_manager_delegate.h",
|
||||||
"shell/common/extensions/electron_extensions_api_provider.cc",
|
"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/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -41,7 +41,8 @@ Object.assign(app, {
|
|||||||
commandLine: {
|
commandLine: {
|
||||||
hasSwitch: (theSwitch: string) => commandLine.hasSwitch(String(theSwitch)),
|
hasSwitch: (theSwitch: string) => commandLine.hasSwitch(String(theSwitch)),
|
||||||
getSwitchValue: (theSwitch: string) => commandLine.getSwitchValue(String(theSwitch)),
|
getSwitchValue: (theSwitch: string) => commandLine.getSwitchValue(String(theSwitch)),
|
||||||
appendSwitch: (theSwitch: string, value?: string) => commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
|
appendSwitch: (theSwitch: string, value?: string) =>
|
||||||
|
commandLine.appendSwitch(String(theSwitch), typeof value === 'undefined' ? value : String(value)),
|
||||||
appendArgument: (arg: string) => commandLine.appendArgument(String(arg)),
|
appendArgument: (arg: string) => commandLine.appendArgument(String(arg)),
|
||||||
removeSwitch: (theSwitch: string) => commandLine.removeSwitch(String(theSwitch))
|
removeSwitch: (theSwitch: string) => commandLine.removeSwitch(String(theSwitch))
|
||||||
} as Electron.CommandLine
|
} as Electron.CommandLine
|
||||||
@@ -50,10 +51,10 @@ Object.assign(app, {
|
|||||||
// we define this here because it'd be overly complicated to
|
// we define this here because it'd be overly complicated to
|
||||||
// do in native land
|
// do in native land
|
||||||
Object.defineProperty(app, 'applicationMenu', {
|
Object.defineProperty(app, 'applicationMenu', {
|
||||||
get () {
|
get() {
|
||||||
return Menu.getApplicationMenu();
|
return Menu.getApplicationMenu();
|
||||||
},
|
},
|
||||||
set (menu: Electron.Menu | null) {
|
set(menu: Electron.Menu | null) {
|
||||||
return Menu.setApplicationMenu(menu);
|
return Menu.setApplicationMenu(menu);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -116,17 +117,22 @@ for (const name of events) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
app._clientCertRequestPasswordHandler = null;
|
app._clientCertRequestPasswordHandler = null;
|
||||||
app.setClientCertRequestPasswordHandler = function (handler: (params: Electron.ClientCertRequestParams) => Promise<string>) {
|
app.setClientCertRequestPasswordHandler = function (
|
||||||
|
handler: (params: Electron.ClientCertRequestParams) => Promise<string>
|
||||||
|
) {
|
||||||
app._clientCertRequestPasswordHandler = handler;
|
app._clientCertRequestPasswordHandler = handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
app.on('-client-certificate-request-password', async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
|
app.on(
|
||||||
event.preventDefault();
|
'-client-certificate-request-password',
|
||||||
const { hostname, tokenName, isRetry } = event;
|
async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
|
||||||
if (!app._clientCertRequestPasswordHandler) {
|
event.preventDefault();
|
||||||
callback('');
|
const { hostname, tokenName, isRetry } = event;
|
||||||
return;
|
if (!app._clientCertRequestPasswordHandler) {
|
||||||
|
callback('');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
|
||||||
|
callback(password);
|
||||||
}
|
}
|
||||||
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
|
);
|
||||||
callback(password);
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -135,7 +135,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
allowAnyVersion: boolean = false;
|
allowAnyVersion: boolean = false;
|
||||||
|
|
||||||
// Private: Validate that the URL points to an MSIX file (following redirects)
|
// Private: Validate that the URL points to an MSIX file (following redirects)
|
||||||
private async validateMsixUrl (url: string): Promise<void> {
|
private async validateMsixUrl(url: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
// Make a HEAD request to follow redirects and get the final URL
|
// Make a HEAD request to follow redirects and get the final URL
|
||||||
const response = await net.fetch(url, {
|
const response = await net.fetch(url, {
|
||||||
@@ -153,7 +153,9 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
const hasMsixExtension = pathname.endsWith('.msix') || pathname.endsWith('.msixbundle');
|
const hasMsixExtension = pathname.endsWith('.msix') || pathname.endsWith('.msixbundle');
|
||||||
|
|
||||||
if (!hasMsixExtension) {
|
if (!hasMsixExtension) {
|
||||||
throw new Error(`Update URL does not point to an MSIX file. Expected .msix or .msixbundle extension, got final URL: ${finalUrl}`);
|
throw new Error(
|
||||||
|
`Update URL does not point to an MSIX file. Expected .msix or .msixbundle extension, got final URL: ${finalUrl}`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
if (error instanceof TypeError) {
|
if (error instanceof TypeError) {
|
||||||
@@ -164,7 +166,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Private: Check if URL is a direct MSIX file (following redirects)
|
// Private: Check if URL is a direct MSIX file (following redirects)
|
||||||
private async isDirectMsixUrl (url: string, emitError: boolean = false): Promise<boolean> {
|
private async isDirectMsixUrl(url: string, emitError: boolean = false): Promise<boolean> {
|
||||||
try {
|
try {
|
||||||
await this.validateMsixUrl(url);
|
await this.validateMsixUrl(url);
|
||||||
return true;
|
return true;
|
||||||
@@ -178,12 +180,12 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
|
|
||||||
// Supports both versioning (x.y.z) and Windows version format (x.y.z.a)
|
// Supports both versioning (x.y.z) and Windows version format (x.y.z.a)
|
||||||
// Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if v1 === v2
|
// Returns: 1 if v1 > v2, -1 if v1 < v2, 0 if v1 === v2
|
||||||
private compareVersions (v1: string, v2: string): number {
|
private compareVersions(v1: string, v2: string): number {
|
||||||
const parts1 = v1.split('.').map(part => {
|
const parts1 = v1.split('.').map((part) => {
|
||||||
const parsed = parseInt(part, 10);
|
const parsed = parseInt(part, 10);
|
||||||
return isNaN(parsed) ? 0 : parsed;
|
return isNaN(parsed) ? 0 : parsed;
|
||||||
});
|
});
|
||||||
const parts2 = v2.split('.').map(part => {
|
const parts2 = v2.split('.').map((part) => {
|
||||||
const parsed = parseInt(part, 10);
|
const parsed = parseInt(part, 10);
|
||||||
return isNaN(parsed) ? 0 : parsed;
|
return isNaN(parsed) ? 0 : parsed;
|
||||||
});
|
});
|
||||||
@@ -203,9 +205,12 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
|
|
||||||
// Private: Parse the static releases array format
|
// Private: Parse the static releases array format
|
||||||
// This is a static JSON file containing all releases
|
// This is a static JSON file containing all releases
|
||||||
private parseStaticReleasFile (json: any, currentVersion: string): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
private parseStaticReleasFile(
|
||||||
|
json: any,
|
||||||
|
currentVersion: string
|
||||||
|
): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
||||||
if (!Array.isArray(json.releases) || !json.currentRelease || typeof json.currentRelease !== 'string') {
|
if (!Array.isArray(json.releases) || !json.currentRelease || typeof json.currentRelease !== 'string') {
|
||||||
this.emitError(new Error('Invalid releases format. Expected \'releases\' array and \'currentRelease\' string.'));
|
this.emitError(new Error("Invalid releases format. Expected 'releases' array and 'currentRelease' string."));
|
||||||
return { ok: false, available: false };
|
return { ok: false, available: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -234,14 +239,18 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
const releaseEntry = json.releases.find((r: any) => r.version === currentReleaseVersion);
|
const releaseEntry = json.releases.find((r: any) => r.version === currentReleaseVersion);
|
||||||
|
|
||||||
if (!releaseEntry || !releaseEntry.updateTo) {
|
if (!releaseEntry || !releaseEntry.updateTo) {
|
||||||
this.emitError(new Error(`Release entry for version '${currentReleaseVersion}' not found or missing 'updateTo' property.`));
|
this.emitError(
|
||||||
|
new Error(`Release entry for version '${currentReleaseVersion}' not found or missing 'updateTo' property.`)
|
||||||
|
);
|
||||||
return { ok: false, available: false };
|
return { ok: false, available: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
const updateTo = releaseEntry.updateTo;
|
const updateTo = releaseEntry.updateTo;
|
||||||
|
|
||||||
if (!updateTo.url) {
|
if (!updateTo.url) {
|
||||||
this.emitError(new Error(`Invalid release entry. 'updateTo.url' is missing for version ${currentReleaseVersion}.`));
|
this.emitError(
|
||||||
|
new Error(`Invalid release entry. 'updateTo.url' is missing for version ${currentReleaseVersion}.`)
|
||||||
|
);
|
||||||
return { ok: false, available: false };
|
return { ok: false, available: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,15 +264,22 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private parseDynamicReleasFile (json: any): { ok: boolean; available: boolean; url?: string; name?: string; notes?: string; pub_date?: string } {
|
private parseDynamicReleasFile(json: any): {
|
||||||
|
ok: boolean;
|
||||||
|
available: boolean;
|
||||||
|
url?: string;
|
||||||
|
name?: string;
|
||||||
|
notes?: string;
|
||||||
|
pub_date?: string;
|
||||||
|
} {
|
||||||
if (!json.url) {
|
if (!json.url) {
|
||||||
this.emitError(new Error('Invalid releases format. Expected \'url\' string property.'));
|
this.emitError(new Error("Invalid releases format. Expected 'url' string property."));
|
||||||
return { ok: false, available: false };
|
return { ok: false, available: false };
|
||||||
}
|
}
|
||||||
return { ok: true, available: true, url: json.url, name: json.name, notes: json.notes, pub_date: json.pub_date };
|
return { ok: true, available: true, url: json.url, name: json.name, notes: json.notes, pub_date: json.pub_date };
|
||||||
}
|
}
|
||||||
|
|
||||||
private async fetchSquirrelJson (url: string) {
|
private async fetchSquirrelJson(url: string) {
|
||||||
const headers: Record<string, string> = {
|
const headers: Record<string, string> = {
|
||||||
...this.updateHeaders,
|
...this.updateHeaders,
|
||||||
Accept: 'application/json' // Always set Accept header, overriding any user-provided Accept
|
Accept: 'application/json' // Always set Accept header, overriding any user-provided Accept
|
||||||
@@ -299,8 +315,8 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getUpdateInfo (url: string): Promise<UpdateInfo> {
|
private async getUpdateInfo(url: string): Promise<UpdateInfo> {
|
||||||
if (url && await this.isDirectMsixUrl(url)) {
|
if (url && (await this.isDirectMsixUrl(url))) {
|
||||||
return { ok: true, available: true, updateUrl: url, releaseDate: new Date() };
|
return { ok: true, available: true, updateUrl: url, releaseDate: new Date() };
|
||||||
} else {
|
} else {
|
||||||
const updateJson = await this.fetchSquirrelJson(url);
|
const updateJson = await this.fetchSquirrelJson(url);
|
||||||
@@ -321,7 +337,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
const releaseName = updateJson.name ?? '';
|
const releaseName = updateJson.name ?? '';
|
||||||
releaseDate = releaseDate ?? new Date();
|
releaseDate = releaseDate ?? new Date();
|
||||||
|
|
||||||
if (!await this.isDirectMsixUrl(updateUrl, true)) {
|
if (!(await this.isDirectMsixUrl(updateUrl, true))) {
|
||||||
return { ok: false };
|
return { ok: false };
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {
|
||||||
@@ -337,11 +353,11 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
getFeedURL () {
|
getFeedURL() {
|
||||||
return this.updateURL ?? '';
|
return this.updateURL ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
setFeedURL (options: { url: string; headers?: Record<string, string>; allowAnyVersion?: boolean } | string) {
|
setFeedURL(options: { url: string; headers?: Record<string, string>; allowAnyVersion?: boolean } | string) {
|
||||||
let updateURL: string;
|
let updateURL: string;
|
||||||
let headers: Record<string, string> | undefined;
|
let headers: Record<string, string> | undefined;
|
||||||
let allowAnyVersion: boolean | undefined;
|
let allowAnyVersion: boolean | undefined;
|
||||||
@@ -351,23 +367,23 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
headers = options.headers;
|
headers = options.headers;
|
||||||
allowAnyVersion = options.allowAnyVersion;
|
allowAnyVersion = options.allowAnyVersion;
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Expected options object to contain a \'url\' string property in setFeedUrl call');
|
throw new TypeError("Expected options object to contain a 'url' string property in setFeedUrl call");
|
||||||
}
|
}
|
||||||
} else if (typeof options === 'string') {
|
} else if (typeof options === 'string') {
|
||||||
updateURL = options;
|
updateURL = options;
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Expected an options object with a \'url\' property to be provided');
|
throw new TypeError("Expected an options object with a 'url' property to be provided");
|
||||||
}
|
}
|
||||||
this.updateURL = updateURL;
|
this.updateURL = updateURL;
|
||||||
this.updateHeaders = headers ?? null;
|
this.updateHeaders = headers ?? null;
|
||||||
this.allowAnyVersion = allowAnyVersion ?? false;
|
this.allowAnyVersion = allowAnyVersion ?? false;
|
||||||
}
|
}
|
||||||
|
|
||||||
getPackageInfo (): MSIXPackageInfo {
|
getPackageInfo(): MSIXPackageInfo {
|
||||||
return msixUpdate.getPackageInfo() as MSIXPackageInfo;
|
return msixUpdate.getPackageInfo() as MSIXPackageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkForUpdates () {
|
async checkForUpdates() {
|
||||||
const url = this.updateURL;
|
const url = this.updateURL;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return this.emitError(new Error('Update URL is not set'));
|
return this.emitError(new Error('Update URL is not set'));
|
||||||
@@ -382,7 +398,11 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
// If appInstallerUri is set, Windows App Installer manages updates automatically
|
// If appInstallerUri is set, Windows App Installer manages updates automatically
|
||||||
// Prevent updates here to avoid conflicts
|
// Prevent updates here to avoid conflicts
|
||||||
if (packageInfo.appInstallerUri) {
|
if (packageInfo.appInstallerUri) {
|
||||||
return this.emitError(new Error('Auto-updates are managed by Windows App Installer. Updates are not allowed when installed via Application Manifest.'));
|
return this.emitError(
|
||||||
|
new Error(
|
||||||
|
'Auto-updates are managed by Windows App Installer. Updates are not allowed when installed via Application Manifest.'
|
||||||
|
)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.emit('checking-for-update');
|
this.emit('checking-for-update');
|
||||||
@@ -405,18 +425,26 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
forceUpdateFromAnyVersion: this.allowAnyVersion
|
forceUpdateFromAnyVersion: this.allowAnyVersion
|
||||||
} as UpdateMsixOptions);
|
} as UpdateMsixOptions);
|
||||||
|
|
||||||
this.emit('update-downloaded', {}, msixUrlInfo.releaseNotes, msixUrlInfo.releaseName, msixUrlInfo.releaseDate, msixUrlInfo.updateUrl, () => {
|
this.emit(
|
||||||
this.quitAndInstall();
|
'update-downloaded',
|
||||||
});
|
{},
|
||||||
|
msixUrlInfo.releaseNotes,
|
||||||
|
msixUrlInfo.releaseName,
|
||||||
|
msixUrlInfo.releaseDate,
|
||||||
|
msixUrlInfo.updateUrl,
|
||||||
|
() => {
|
||||||
|
this.quitAndInstall();
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.emitError(error as Error);
|
this.emitError(error as Error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async quitAndInstall () {
|
async quitAndInstall() {
|
||||||
if (!this.updateAvailable) {
|
if (!this.updateAvailable) {
|
||||||
this.emitError(new Error('No update available, can\'t quit and install'));
|
this.emitError(new Error("No update available, can't quit and install"));
|
||||||
app.quit();
|
app.quit();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -441,7 +469,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
|
|
||||||
// Private: Emit both error object and message, this is to keep compatibility
|
// Private: Emit both error object and message, this is to keep compatibility
|
||||||
// with Old APIs.
|
// with Old APIs.
|
||||||
emitError (error: Error) {
|
emitError(error: Error) {
|
||||||
this.emit('error', error, error.message);
|
this.emit('error', error, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,40 +8,40 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
updateAvailable: boolean = false;
|
updateAvailable: boolean = false;
|
||||||
updateURL: string | null = null;
|
updateURL: string | null = null;
|
||||||
|
|
||||||
quitAndInstall () {
|
quitAndInstall() {
|
||||||
if (!this.updateAvailable) {
|
if (!this.updateAvailable) {
|
||||||
return this.emitError(new Error('No update available, can\'t quit and install'));
|
return this.emitError(new Error("No update available, can't quit and install"));
|
||||||
}
|
}
|
||||||
squirrelUpdate.processStart();
|
squirrelUpdate.processStart();
|
||||||
app.quit();
|
app.quit();
|
||||||
}
|
}
|
||||||
|
|
||||||
getFeedURL () {
|
getFeedURL() {
|
||||||
return this.updateURL ?? '';
|
return this.updateURL ?? '';
|
||||||
}
|
}
|
||||||
|
|
||||||
getPackageInfo () {
|
getPackageInfo() {
|
||||||
// Squirrel-based Windows apps don't have MSIX package information
|
// Squirrel-based Windows apps don't have MSIX package information
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
setFeedURL (options: { url: string } | string) {
|
setFeedURL(options: { url: string } | string) {
|
||||||
let updateURL: string;
|
let updateURL: string;
|
||||||
if (typeof options === 'object') {
|
if (typeof options === 'object') {
|
||||||
if (typeof options.url === 'string') {
|
if (typeof options.url === 'string') {
|
||||||
updateURL = options.url;
|
updateURL = options.url;
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Expected options object to contain a \'url\' string property in setFeedUrl call');
|
throw new TypeError("Expected options object to contain a 'url' string property in setFeedUrl call");
|
||||||
}
|
}
|
||||||
} else if (typeof options === 'string') {
|
} else if (typeof options === 'string') {
|
||||||
updateURL = options;
|
updateURL = options;
|
||||||
} else {
|
} else {
|
||||||
throw new TypeError('Expected an options object with a \'url\' property to be provided');
|
throw new TypeError("Expected an options object with a 'url' property to be provided");
|
||||||
}
|
}
|
||||||
this.updateURL = updateURL;
|
this.updateURL = updateURL;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkForUpdates () {
|
async checkForUpdates() {
|
||||||
const url = this.updateURL;
|
const url = this.updateURL;
|
||||||
if (!url) {
|
if (!url) {
|
||||||
return this.emitError(new Error('Update URL is not set'));
|
return this.emitError(new Error('Update URL is not set'));
|
||||||
@@ -72,7 +72,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
|
|||||||
|
|
||||||
// Private: Emit both error object and message, this is to keep compatibility
|
// Private: Emit both error object and message, this is to keep compatibility
|
||||||
// with Old APIs.
|
// with Old APIs.
|
||||||
emitError (error: Error) {
|
emitError(error: Error) {
|
||||||
this.emit('error', error, error.message);
|
this.emit('error', error, error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } =
|
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } = process._linkedBinding(
|
||||||
process._linkedBinding('electron_browser_msix_updater');
|
'electron_browser_msix_updater'
|
||||||
|
);
|
||||||
|
|
||||||
export { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo };
|
export { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo };
|
||||||
|
|||||||
@@ -34,8 +34,12 @@ const spawnUpdate = async function (args: string[], options: { detached: boolean
|
|||||||
let stdout = '';
|
let stdout = '';
|
||||||
let stderr = '';
|
let stderr = '';
|
||||||
|
|
||||||
spawnedProcess.stdout.on('data', (data) => { stdout += data; });
|
spawnedProcess.stdout.on('data', (data) => {
|
||||||
spawnedProcess.stderr.on('data', (data) => { stderr += data; });
|
stdout += data;
|
||||||
|
});
|
||||||
|
spawnedProcess.stderr.on('data', (data) => {
|
||||||
|
stderr += data;
|
||||||
|
});
|
||||||
|
|
||||||
spawnedProcess.on('error', (error) => {
|
spawnedProcess.on('error', (error) => {
|
||||||
spawnedProcess = undefined;
|
spawnedProcess = undefined;
|
||||||
@@ -59,12 +63,12 @@ const spawnUpdate = async function (args: string[], options: { detached: boolean
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Start an instance of the installed app.
|
// Start an instance of the installed app.
|
||||||
export function processStart () {
|
export function processStart() {
|
||||||
spawnUpdate(['--processStartAndWait', exeName], { detached: true });
|
spawnUpdate(['--processStartAndWait', exeName], { detached: true });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Download the releases specified by the URL and write new results to stdout.
|
// Download the releases specified by the URL and write new results to stdout.
|
||||||
export async function checkForUpdate (updateURL: string): Promise<any> {
|
export async function checkForUpdate(updateURL: string): Promise<any> {
|
||||||
const stdout = await spawnUpdate(['--checkForUpdate', updateURL], { detached: false });
|
const stdout = await spawnUpdate(['--checkForUpdate', updateURL], { detached: false });
|
||||||
try {
|
try {
|
||||||
// Last line of output is the JSON details about the releases
|
// Last line of output is the JSON details about the releases
|
||||||
@@ -76,12 +80,12 @@ export async function checkForUpdate (updateURL: string): Promise<any> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update the application to the latest remote version specified by URL.
|
// Update the application to the latest remote version specified by URL.
|
||||||
export async function update (updateURL: string): Promise<void> {
|
export async function update(updateURL: string): Promise<void> {
|
||||||
await spawnUpdate(['--update', updateURL], { detached: false });
|
await spawnUpdate(['--update', updateURL], { detached: false });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Is the Update.exe installed with the current application?
|
// Is the Update.exe installed with the current application?
|
||||||
export function supported () {
|
export function supported() {
|
||||||
try {
|
try {
|
||||||
fs.accessSync(updateExe, fs.constants.R_OK);
|
fs.accessSync(updateExe, fs.constants.R_OK);
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -25,88 +25,156 @@ BaseWindow.prototype.setTouchBar = function (touchBar) {
|
|||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'autoHideMenuBar', {
|
Object.defineProperty(BaseWindow.prototype, 'autoHideMenuBar', {
|
||||||
get: function () { return this.isMenuBarAutoHide(); },
|
get: function () {
|
||||||
set: function (autoHide) { this.setAutoHideMenuBar(autoHide); }
|
return this.isMenuBarAutoHide();
|
||||||
|
},
|
||||||
|
set: function (autoHide) {
|
||||||
|
this.setAutoHideMenuBar(autoHide);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'visibleOnAllWorkspaces', {
|
Object.defineProperty(BaseWindow.prototype, 'visibleOnAllWorkspaces', {
|
||||||
get: function () { return this.isVisibleOnAllWorkspaces(); },
|
get: function () {
|
||||||
set: function (visible) { this.setVisibleOnAllWorkspaces(visible); }
|
return this.isVisibleOnAllWorkspaces();
|
||||||
|
},
|
||||||
|
set: function (visible) {
|
||||||
|
this.setVisibleOnAllWorkspaces(visible);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'fullScreen', {
|
Object.defineProperty(BaseWindow.prototype, 'fullScreen', {
|
||||||
get: function () { return this.isFullScreen(); },
|
get: function () {
|
||||||
set: function (full) { this.setFullScreen(full); }
|
return this.isFullScreen();
|
||||||
|
},
|
||||||
|
set: function (full) {
|
||||||
|
this.setFullScreen(full);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'simpleFullScreen', {
|
Object.defineProperty(BaseWindow.prototype, 'simpleFullScreen', {
|
||||||
get: function () { return this.isSimpleFullScreen(); },
|
get: function () {
|
||||||
set: function (simple) { this.setSimpleFullScreen(simple); }
|
return this.isSimpleFullScreen();
|
||||||
|
},
|
||||||
|
set: function (simple) {
|
||||||
|
this.setSimpleFullScreen(simple);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'focusable', {
|
Object.defineProperty(BaseWindow.prototype, 'focusable', {
|
||||||
get: function () { return this.isFocusable(); },
|
get: function () {
|
||||||
set: function (focusable) { this.setFocusable(focusable); }
|
return this.isFocusable();
|
||||||
|
},
|
||||||
|
set: function (focusable) {
|
||||||
|
this.setFocusable(focusable);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'kiosk', {
|
Object.defineProperty(BaseWindow.prototype, 'kiosk', {
|
||||||
get: function () { return this.isKiosk(); },
|
get: function () {
|
||||||
set: function (kiosk) { this.setKiosk(kiosk); }
|
return this.isKiosk();
|
||||||
|
},
|
||||||
|
set: function (kiosk) {
|
||||||
|
this.setKiosk(kiosk);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'documentEdited', {
|
Object.defineProperty(BaseWindow.prototype, 'documentEdited', {
|
||||||
get: function () { return this.isDocumentEdited(); },
|
get: function () {
|
||||||
set: function (edited) { this.setDocumentEdited(edited); }
|
return this.isDocumentEdited();
|
||||||
|
},
|
||||||
|
set: function (edited) {
|
||||||
|
this.setDocumentEdited(edited);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'shadow', {
|
Object.defineProperty(BaseWindow.prototype, 'shadow', {
|
||||||
get: function () { return this.hasShadow(); },
|
get: function () {
|
||||||
set: function (shadow) { this.setHasShadow(shadow); }
|
return this.hasShadow();
|
||||||
|
},
|
||||||
|
set: function (shadow) {
|
||||||
|
this.setHasShadow(shadow);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'representedFilename', {
|
Object.defineProperty(BaseWindow.prototype, 'representedFilename', {
|
||||||
get: function () { return this.getRepresentedFilename(); },
|
get: function () {
|
||||||
set: function (filename) { this.setRepresentedFilename(filename); }
|
return this.getRepresentedFilename();
|
||||||
|
},
|
||||||
|
set: function (filename) {
|
||||||
|
this.setRepresentedFilename(filename);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'minimizable', {
|
Object.defineProperty(BaseWindow.prototype, 'minimizable', {
|
||||||
get: function () { return this.isMinimizable(); },
|
get: function () {
|
||||||
set: function (min) { this.setMinimizable(min); }
|
return this.isMinimizable();
|
||||||
|
},
|
||||||
|
set: function (min) {
|
||||||
|
this.setMinimizable(min);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'title', {
|
Object.defineProperty(BaseWindow.prototype, 'title', {
|
||||||
get: function () { return this.getTitle(); },
|
get: function () {
|
||||||
set: function (title) { this.setTitle(title); }
|
return this.getTitle();
|
||||||
|
},
|
||||||
|
set: function (title) {
|
||||||
|
this.setTitle(title);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'maximizable', {
|
Object.defineProperty(BaseWindow.prototype, 'maximizable', {
|
||||||
get: function () { return this.isMaximizable(); },
|
get: function () {
|
||||||
set: function (max) { this.setMaximizable(max); }
|
return this.isMaximizable();
|
||||||
|
},
|
||||||
|
set: function (max) {
|
||||||
|
this.setMaximizable(max);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'resizable', {
|
Object.defineProperty(BaseWindow.prototype, 'resizable', {
|
||||||
get: function () { return this.isResizable(); },
|
get: function () {
|
||||||
set: function (res) { this.setResizable(res); }
|
return this.isResizable();
|
||||||
|
},
|
||||||
|
set: function (res) {
|
||||||
|
this.setResizable(res);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'menuBarVisible', {
|
Object.defineProperty(BaseWindow.prototype, 'menuBarVisible', {
|
||||||
get: function () { return this.isMenuBarVisible(); },
|
get: function () {
|
||||||
set: function (visible) { this.setMenuBarVisibility(visible); }
|
return this.isMenuBarVisible();
|
||||||
|
},
|
||||||
|
set: function (visible) {
|
||||||
|
this.setMenuBarVisibility(visible);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'fullScreenable', {
|
Object.defineProperty(BaseWindow.prototype, 'fullScreenable', {
|
||||||
get: function () { return this.isFullScreenable(); },
|
get: function () {
|
||||||
set: function (full) { this.setFullScreenable(full); }
|
return this.isFullScreenable();
|
||||||
|
},
|
||||||
|
set: function (full) {
|
||||||
|
this.setFullScreenable(full);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'closable', {
|
Object.defineProperty(BaseWindow.prototype, 'closable', {
|
||||||
get: function () { return this.isClosable(); },
|
get: function () {
|
||||||
set: function (close) { this.setClosable(close); }
|
return this.isClosable();
|
||||||
|
},
|
||||||
|
set: function (close) {
|
||||||
|
this.setClosable(close);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
Object.defineProperty(BaseWindow.prototype, 'movable', {
|
Object.defineProperty(BaseWindow.prototype, 'movable', {
|
||||||
get: function () { return this.isMovable(); },
|
get: function () {
|
||||||
set: function (move) { this.setMovable(move); }
|
return this.isMovable();
|
||||||
|
},
|
||||||
|
set: function (move) {
|
||||||
|
this.setMovable(move);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
BaseWindow.getFocusedWindow = () => {
|
BaseWindow.getFocusedWindow = () => {
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { BrowserWindow, AutoResizeOptions, Rectangle, WebContentsView, WebPreferences, WebContents } from 'electron/main';
|
import {
|
||||||
|
BrowserWindow,
|
||||||
|
AutoResizeOptions,
|
||||||
|
Rectangle,
|
||||||
|
WebContentsView,
|
||||||
|
WebPreferences,
|
||||||
|
WebContents
|
||||||
|
} from 'electron/main';
|
||||||
|
|
||||||
const v8Util = process._linkedBinding('electron_common_v8_util');
|
const v8Util = process._linkedBinding('electron_common_v8_util');
|
||||||
|
|
||||||
@@ -10,10 +17,10 @@ export default class BrowserView {
|
|||||||
|
|
||||||
// AutoResize state
|
// AutoResize state
|
||||||
#resizeListener: ((...args: any[]) => void) | null = null;
|
#resizeListener: ((...args: any[]) => void) | null = null;
|
||||||
#lastWindowSize: {width: number, height: number} = { width: 0, height: 0 };
|
#lastWindowSize: { width: number; height: number } = { width: 0, height: 0 };
|
||||||
#autoResizeFlags: AutoResizeOptions = {};
|
#autoResizeFlags: AutoResizeOptions = {};
|
||||||
|
|
||||||
constructor (options: {webPreferences: WebPreferences, webContents?: WebContents} = { webPreferences: {} }) {
|
constructor(options: { webPreferences: WebPreferences; webContents?: WebContents } = { webPreferences: {} }) {
|
||||||
const { webPreferences = {}, webContents } = options;
|
const { webPreferences = {}, webContents } = options;
|
||||||
if (webContents) {
|
if (webContents) {
|
||||||
v8Util.setHiddenValue(webPreferences, 'webContents', webContents);
|
v8Util.setHiddenValue(webPreferences, 'webContents', webContents);
|
||||||
@@ -25,21 +32,21 @@ export default class BrowserView {
|
|||||||
this.#webContentsView.webContents.once('destroyed', this.#destroyListener);
|
this.#webContentsView.webContents.once('destroyed', this.#destroyListener);
|
||||||
}
|
}
|
||||||
|
|
||||||
get webContents () {
|
get webContents() {
|
||||||
return this.#webContentsView.webContents;
|
return this.#webContentsView.webContents;
|
||||||
}
|
}
|
||||||
|
|
||||||
setBounds (bounds: Rectangle) {
|
setBounds(bounds: Rectangle) {
|
||||||
this.#webContentsView.setBounds(bounds);
|
this.#webContentsView.setBounds(bounds);
|
||||||
this.#autoHorizontalProportion = null;
|
this.#autoHorizontalProportion = null;
|
||||||
this.#autoVerticalProportion = null;
|
this.#autoVerticalProportion = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getBounds () {
|
getBounds() {
|
||||||
return this.#webContentsView.getBounds();
|
return this.#webContentsView.getBounds();
|
||||||
}
|
}
|
||||||
|
|
||||||
setAutoResize (options: AutoResizeOptions) {
|
setAutoResize(options: AutoResizeOptions) {
|
||||||
if (options == null || typeof options !== 'object') {
|
if (options == null || typeof options !== 'object') {
|
||||||
throw new Error('Invalid auto resize options');
|
throw new Error('Invalid auto resize options');
|
||||||
}
|
}
|
||||||
@@ -55,19 +62,19 @@ export default class BrowserView {
|
|||||||
this.#autoVerticalProportion = null;
|
this.#autoVerticalProportion = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
setBackgroundColor (color: string) {
|
setBackgroundColor(color: string) {
|
||||||
this.#webContentsView.setBackgroundColor(color);
|
this.#webContentsView.setBackgroundColor(color);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Internal methods
|
// Internal methods
|
||||||
get ownerWindow (): BrowserWindow | null {
|
get ownerWindow(): BrowserWindow | null {
|
||||||
return this.#ownerWindow;
|
return this.#ownerWindow;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can't rely solely on the webContents' owner window because
|
// We can't rely solely on the webContents' owner window because
|
||||||
// a webContents can be closed by the user while the BrowserView
|
// a webContents can be closed by the user while the BrowserView
|
||||||
// remains alive and attached to a BrowserWindow.
|
// remains alive and attached to a BrowserWindow.
|
||||||
set ownerWindow (w: BrowserWindow | null) {
|
set ownerWindow(w: BrowserWindow | null) {
|
||||||
this.#removeResizeListener();
|
this.#removeResizeListener();
|
||||||
|
|
||||||
if (this.webContents && !this.webContents.isDestroyed()) {
|
if (this.webContents && !this.webContents.isDestroyed()) {
|
||||||
@@ -77,7 +84,7 @@ export default class BrowserView {
|
|||||||
this.#ownerWindow = w;
|
this.#ownerWindow = w;
|
||||||
if (w) {
|
if (w) {
|
||||||
this.#lastWindowSize = w.getBounds();
|
this.#lastWindowSize = w.getBounds();
|
||||||
w.on('resize', this.#resizeListener = this.#autoResize.bind(this));
|
w.on('resize', (this.#resizeListener = this.#autoResize.bind(this)));
|
||||||
w.on('closed', () => {
|
w.on('closed', () => {
|
||||||
this.#removeResizeListener();
|
this.#removeResizeListener();
|
||||||
this.#ownerWindow = null;
|
this.#ownerWindow = null;
|
||||||
@@ -86,25 +93,25 @@ export default class BrowserView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#onDestroy () {
|
#onDestroy() {
|
||||||
// Ensure that if #webContentsView's webContents is destroyed,
|
// Ensure that if #webContentsView's webContents is destroyed,
|
||||||
// the WebContentsView is removed from the view hierarchy.
|
// the WebContentsView is removed from the view hierarchy.
|
||||||
this.#ownerWindow?.contentView.removeChildView(this.webContentsView);
|
this.#ownerWindow?.contentView.removeChildView(this.webContentsView);
|
||||||
}
|
}
|
||||||
|
|
||||||
#removeResizeListener () {
|
#removeResizeListener() {
|
||||||
if (this.#ownerWindow && this.#resizeListener) {
|
if (this.#ownerWindow && this.#resizeListener) {
|
||||||
this.#ownerWindow.off('resize', this.#resizeListener);
|
this.#ownerWindow.off('resize', this.#resizeListener);
|
||||||
this.#resizeListener = null;
|
this.#resizeListener = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#autoHorizontalProportion: {width: number, left: number} | null = null;
|
#autoHorizontalProportion: { width: number; left: number } | null = null;
|
||||||
#autoVerticalProportion: {height: number, top: number} | null = null;
|
#autoVerticalProportion: { height: number; top: number } | null = null;
|
||||||
#autoResize () {
|
#autoResize() {
|
||||||
if (!this.ownerWindow) {
|
if (!this.ownerWindow) {
|
||||||
throw new Error('Electron bug: #autoResize called without owner window');
|
throw new Error('Electron bug: #autoResize called without owner window');
|
||||||
};
|
}
|
||||||
|
|
||||||
if (this.#autoResizeFlags.horizontal && this.#autoHorizontalProportion == null) {
|
if (this.#autoResizeFlags.horizontal && this.#autoHorizontalProportion == null) {
|
||||||
const viewBounds = this.#webContentsView.getBounds();
|
const viewBounds = this.#webContentsView.getBounds();
|
||||||
@@ -158,7 +165,7 @@ export default class BrowserView {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get webContentsView () {
|
get webContentsView() {
|
||||||
return this.#webContentsView;
|
return this.#webContentsView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,10 +40,14 @@ BrowserWindow.prototype._init = function (this: BWT) {
|
|||||||
let unresponsiveEvent: NodeJS.Timeout | null = null;
|
let unresponsiveEvent: NodeJS.Timeout | null = null;
|
||||||
const emitUnresponsiveEvent = () => {
|
const emitUnresponsiveEvent = () => {
|
||||||
unresponsiveEvent = null;
|
unresponsiveEvent = null;
|
||||||
if (!this.isDestroyed() && this.isEnabled()) { this.emit('unresponsive'); }
|
if (!this.isDestroyed() && this.isEnabled()) {
|
||||||
|
this.emit('unresponsive');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
this.webContents.on('unresponsive', () => {
|
this.webContents.on('unresponsive', () => {
|
||||||
if (!unresponsiveEvent) { unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50); }
|
if (!unresponsiveEvent) {
|
||||||
|
unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
this.webContents.on('responsive', () => {
|
this.webContents.on('responsive', () => {
|
||||||
if (unresponsiveEvent) {
|
if (unresponsiveEvent) {
|
||||||
@@ -83,16 +87,16 @@ BrowserWindow.prototype._init = function (this: BWT) {
|
|||||||
this._browserViews = [];
|
this._browserViews = [];
|
||||||
|
|
||||||
this.on('closed', () => {
|
this.on('closed', () => {
|
||||||
this._browserViews.forEach(b => b.webContents?.close({ waitForBeforeUnload: true }));
|
this._browserViews.forEach((b) => b.webContents?.close({ waitForBeforeUnload: true }));
|
||||||
});
|
});
|
||||||
|
|
||||||
// Notify the creation of the window.
|
// Notify the creation of the window.
|
||||||
app.emit('browser-window-created', { preventDefault () {} }, this);
|
app.emit('browser-window-created', { preventDefault() {} }, this);
|
||||||
|
|
||||||
Object.defineProperty(this, 'devToolsWebContents', {
|
Object.defineProperty(this, 'devToolsWebContents', {
|
||||||
enumerable: true,
|
enumerable: true,
|
||||||
configurable: false,
|
configurable: false,
|
||||||
get () {
|
get() {
|
||||||
return this.webContents.devToolsWebContents;
|
return this.webContents.devToolsWebContents;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -104,7 +108,7 @@ const isBrowserWindow = (win: any) => {
|
|||||||
|
|
||||||
BrowserWindow.fromId = (id: number) => {
|
BrowserWindow.fromId = (id: number) => {
|
||||||
const win = BaseWindow.fromId(id);
|
const win = BaseWindow.fromId(id);
|
||||||
return isBrowserWindow(win) ? win as any as BWT : null;
|
return isBrowserWindow(win) ? (win as any as BWT) : null;
|
||||||
};
|
};
|
||||||
|
|
||||||
BrowserWindow.getAllWindows = () => {
|
BrowserWindow.getAllWindows = () => {
|
||||||
@@ -214,10 +218,12 @@ BrowserWindow.prototype.addBrowserView = function (browserView: BrowserView) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
BrowserWindow.prototype.setBrowserView = function (browserView: BrowserView) {
|
BrowserWindow.prototype.setBrowserView = function (browserView: BrowserView) {
|
||||||
this._browserViews.forEach(bv => {
|
this._browserViews.forEach((bv) => {
|
||||||
this.removeBrowserView(bv);
|
this.removeBrowserView(bv);
|
||||||
});
|
});
|
||||||
if (browserView) { this.addBrowserView(browserView); }
|
if (browserView) {
|
||||||
|
this.addBrowserView(browserView);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
BrowserWindow.prototype.removeBrowserView = function (browserView: BrowserView) {
|
BrowserWindow.prototype.removeBrowserView = function (browserView: BrowserView) {
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { app } from 'electron/main';
|
|||||||
const binding = process._linkedBinding('electron_browser_crash_reporter');
|
const binding = process._linkedBinding('electron_browser_crash_reporter');
|
||||||
|
|
||||||
class CrashReporter implements Electron.CrashReporter {
|
class CrashReporter implements Electron.CrashReporter {
|
||||||
start (options: Electron.CrashReporterStartOptions) {
|
start(options: Electron.CrashReporterStartOptions) {
|
||||||
const {
|
const {
|
||||||
productName = app.name,
|
productName = app.name,
|
||||||
companyName,
|
companyName,
|
||||||
@@ -21,7 +21,9 @@ class CrashReporter implements Electron.CrashReporter {
|
|||||||
if (uploadToServer && !submitURL) throw new Error('submitURL must be specified when uploadToServer is true');
|
if (uploadToServer && !submitURL) throw new Error('submitURL must be specified when uploadToServer is true');
|
||||||
|
|
||||||
if (!compress && uploadToServer) {
|
if (!compress && uploadToServer) {
|
||||||
deprecate.log('Sending uncompressed crash reports is deprecated and will be removed in a future version of Electron. Set { compress: true } to opt-in to the new behavior. Crash reports will be uploaded gzipped, which most crash reporting servers support.');
|
deprecate.log(
|
||||||
|
'Sending uncompressed crash reports is deprecated and will be removed in a future version of Electron. Set { compress: true } to opt-in to the new behavior. Crash reports will be uploaded gzipped, which most crash reporting servers support.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const appVersion = app.getVersion();
|
const appVersion = app.getVersion();
|
||||||
@@ -34,26 +36,33 @@ class CrashReporter implements Electron.CrashReporter {
|
|||||||
...globalExtra
|
...globalExtra
|
||||||
};
|
};
|
||||||
|
|
||||||
binding.start(submitURL, uploadToServer,
|
binding.start(
|
||||||
ignoreSystemCrashHandler, rateLimit, compress, globalExtraAmended, extra, false);
|
submitURL,
|
||||||
|
uploadToServer,
|
||||||
|
ignoreSystemCrashHandler,
|
||||||
|
rateLimit,
|
||||||
|
compress,
|
||||||
|
globalExtraAmended,
|
||||||
|
extra,
|
||||||
|
false
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getLastCrashReport () {
|
getLastCrashReport() {
|
||||||
const reports = this.getUploadedReports()
|
const reports = this.getUploadedReports().sort((a, b) => {
|
||||||
.sort((a, b) => {
|
const ats = a && a.date ? new Date(a.date).getTime() : 0;
|
||||||
const ats = (a && a.date) ? new Date(a.date).getTime() : 0;
|
const bts = b && b.date ? new Date(b.date).getTime() : 0;
|
||||||
const bts = (b && b.date) ? new Date(b.date).getTime() : 0;
|
return bts - ats;
|
||||||
return bts - ats;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
return (reports.length > 0) ? reports[0] : null;
|
return reports.length > 0 ? reports[0] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getUploadedReports (): Electron.CrashReport[] {
|
getUploadedReports(): Electron.CrashReport[] {
|
||||||
return binding.getUploadedReports();
|
return binding.getUploadedReports();
|
||||||
}
|
}
|
||||||
|
|
||||||
getUploadToServer () {
|
getUploadToServer() {
|
||||||
if (process.type === 'browser') {
|
if (process.type === 'browser') {
|
||||||
return binding.getUploadToServer();
|
return binding.getUploadToServer();
|
||||||
} else {
|
} else {
|
||||||
@@ -61,7 +70,7 @@ class CrashReporter implements Electron.CrashReporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setUploadToServer (uploadToServer: boolean) {
|
setUploadToServer(uploadToServer: boolean) {
|
||||||
if (process.type === 'browser') {
|
if (process.type === 'browser') {
|
||||||
return binding.setUploadToServer(uploadToServer);
|
return binding.setUploadToServer(uploadToServer);
|
||||||
} else {
|
} else {
|
||||||
@@ -69,15 +78,15 @@ class CrashReporter implements Electron.CrashReporter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
addExtraParameter (key: string, value: string) {
|
addExtraParameter(key: string, value: string) {
|
||||||
binding.addExtraParameter(key, value);
|
binding.addExtraParameter(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
removeExtraParameter (key: string) {
|
removeExtraParameter(key: string) {
|
||||||
binding.removeExtraParameter(key);
|
binding.removeExtraParameter(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
getParameters () {
|
getParameters() {
|
||||||
return binding.getParameters();
|
return binding.getParameters();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,11 @@
|
|||||||
import { BrowserWindow } from 'electron/main';
|
import { BrowserWindow } from 'electron/main';
|
||||||
|
|
||||||
const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding('electron_browser_desktop_capturer');
|
const { createDesktopCapturer, isDisplayMediaSystemPickerAvailable } = process._linkedBinding(
|
||||||
|
'electron_browser_desktop_capturer'
|
||||||
|
);
|
||||||
|
|
||||||
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) => JSON.stringify(a) === JSON.stringify(b);
|
const deepEqual = (a: ElectronInternal.GetSourcesOptions, b: ElectronInternal.GetSourcesOptions) =>
|
||||||
|
JSON.stringify(a) === JSON.stringify(b);
|
||||||
|
|
||||||
let currentlyRunning: {
|
let currentlyRunning: {
|
||||||
options: ElectronInternal.GetSourcesOptions;
|
options: ElectronInternal.GetSourcesOptions;
|
||||||
@@ -10,13 +13,13 @@ let currentlyRunning: {
|
|||||||
}[] = [];
|
}[] = [];
|
||||||
|
|
||||||
// |options.types| can't be empty and must be an array
|
// |options.types| can't be empty and must be an array
|
||||||
function isValid (options: Electron.SourcesOptions) {
|
function isValid(options: Electron.SourcesOptions) {
|
||||||
return Array.isArray(options?.types);
|
return Array.isArray(options?.types);
|
||||||
}
|
}
|
||||||
|
|
||||||
export { isDisplayMediaSystemPickerAvailable };
|
export { isDisplayMediaSystemPickerAvailable };
|
||||||
|
|
||||||
export async function getSources (args: Electron.SourcesOptions) {
|
export async function getSources(args: Electron.SourcesOptions) {
|
||||||
if (!isValid(args)) throw new Error('Invalid options');
|
if (!isValid(args)) throw new Error('Invalid options');
|
||||||
|
|
||||||
const resizableValues = new Map();
|
const resizableValues = new Map();
|
||||||
@@ -50,44 +53,50 @@ export async function getSources (args: Electron.SourcesOptions) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let resolveGetSources!: (value: ElectronInternal.GetSourcesResult[]) => void;
|
||||||
|
let rejectGetSources!: (reason?: any) => void;
|
||||||
|
|
||||||
const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
|
const getSources = new Promise<ElectronInternal.GetSourcesResult[]>((resolve, reject) => {
|
||||||
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
|
resolveGetSources = resolve;
|
||||||
|
rejectGetSources = reject;
|
||||||
|
});
|
||||||
|
|
||||||
const stopRunning = () => {
|
// Register in currentlyRunning BEFORE startHandling so that synchronous
|
||||||
if (capturer) {
|
// completion (e.g. when no capturers are created) can properly clean up.
|
||||||
delete capturer._onerror;
|
currentlyRunning.push({ options, getSources });
|
||||||
delete capturer._onfinished;
|
|
||||||
capturer = null;
|
|
||||||
|
|
||||||
if (process.platform === 'darwin') {
|
let capturer: ElectronInternal.DesktopCapturer | null = createDesktopCapturer();
|
||||||
for (const win of BrowserWindow.getAllWindows()) {
|
|
||||||
if (resizableValues.has(win.id)) {
|
const stopRunning = () => {
|
||||||
win.resizable = resizableValues.get(win.id);
|
if (capturer) {
|
||||||
}
|
delete capturer._onerror;
|
||||||
};
|
delete capturer._onfinished;
|
||||||
|
capturer = null;
|
||||||
|
|
||||||
|
if (process.platform === 'darwin') {
|
||||||
|
for (const win of BrowserWindow.getAllWindows()) {
|
||||||
|
if (resizableValues.has(win.id)) {
|
||||||
|
win.resizable = resizableValues.get(win.id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Remove from currentlyRunning once we resolve or reject
|
}
|
||||||
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
|
// Remove from currentlyRunning once we resolve or reject
|
||||||
};
|
currentlyRunning = currentlyRunning.filter((running) => running.options !== options);
|
||||||
|
};
|
||||||
|
|
||||||
capturer._onerror = (error: string) => {
|
capturer._onerror = (error: string) => {
|
||||||
stopRunning();
|
stopRunning();
|
||||||
reject(error);
|
// eslint-disable-next-line prefer-promise-reject-errors
|
||||||
};
|
rejectGetSources(error);
|
||||||
|
};
|
||||||
|
|
||||||
capturer._onfinished = (sources: Electron.DesktopCapturerSource[]) => {
|
capturer._onfinished = (sources: Electron.DesktopCapturerSource[]) => {
|
||||||
stopRunning();
|
stopRunning();
|
||||||
resolve(sources);
|
resolveGetSources(sources);
|
||||||
};
|
};
|
||||||
|
|
||||||
capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
|
capturer.startHandling(captureWindow, captureScreen, thumbnailSize, fetchWindowIcons);
|
||||||
});
|
|
||||||
|
|
||||||
currentlyRunning.push({
|
|
||||||
options,
|
|
||||||
getSources
|
|
||||||
});
|
|
||||||
|
|
||||||
return getSources;
|
return getSources;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,13 @@
|
|||||||
import { app, BaseWindow } from 'electron/main';
|
import { app, BaseWindow } from 'electron/main';
|
||||||
import type { OpenDialogOptions, OpenDialogReturnValue, MessageBoxOptions, SaveDialogOptions, SaveDialogReturnValue, MessageBoxReturnValue, CertificateTrustDialogOptions } from 'electron/main';
|
import type {
|
||||||
|
OpenDialogOptions,
|
||||||
|
OpenDialogReturnValue,
|
||||||
|
MessageBoxOptions,
|
||||||
|
SaveDialogOptions,
|
||||||
|
SaveDialogReturnValue,
|
||||||
|
MessageBoxReturnValue,
|
||||||
|
CertificateTrustDialogOptions
|
||||||
|
} from 'electron/main';
|
||||||
|
|
||||||
const dialogBinding = process._linkedBinding('electron_browser_dialog');
|
const dialogBinding = process._linkedBinding('electron_browser_dialog');
|
||||||
|
|
||||||
@@ -60,7 +68,9 @@ const checkAppInitialized = function () {
|
|||||||
const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogProperties)[]): number => {
|
const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogProperties)[]): number => {
|
||||||
let dialogProperties = 0;
|
let dialogProperties = 0;
|
||||||
for (const property of properties) {
|
for (const property of properties) {
|
||||||
if (Object.hasOwn(OpenFileDialogProperties, property)) { dialogProperties |= OpenFileDialogProperties[property]; }
|
if (Object.hasOwn(OpenFileDialogProperties, property)) {
|
||||||
|
dialogProperties |= OpenFileDialogProperties[property];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return dialogProperties;
|
return dialogProperties;
|
||||||
};
|
};
|
||||||
@@ -68,7 +78,9 @@ const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogPrope
|
|||||||
const setupSaveDialogProperties = (properties: (keyof typeof SaveFileDialogProperties)[]): number => {
|
const setupSaveDialogProperties = (properties: (keyof typeof SaveFileDialogProperties)[]): number => {
|
||||||
let dialogProperties = 0;
|
let dialogProperties = 0;
|
||||||
for (const property of properties) {
|
for (const property of properties) {
|
||||||
if (Object.hasOwn(SaveFileDialogProperties, property)) { dialogProperties |= SaveFileDialogProperties[property]; }
|
if (Object.hasOwn(SaveFileDialogProperties, property)) {
|
||||||
|
dialogProperties |= SaveFileDialogProperties[property];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return dialogProperties;
|
return dialogProperties;
|
||||||
};
|
};
|
||||||
@@ -150,7 +162,7 @@ const openDialog = (sync: boolean, window: BaseWindow | null, options?: OpenDial
|
|||||||
properties: setupOpenDialogProperties(properties)
|
properties: setupOpenDialogProperties(properties)
|
||||||
};
|
};
|
||||||
|
|
||||||
return (sync) ? dialogBinding.showOpenDialogSync(settings) : dialogBinding.showOpenDialog(settings);
|
return sync ? dialogBinding.showOpenDialogSync(settings) : dialogBinding.showOpenDialog(settings);
|
||||||
};
|
};
|
||||||
|
|
||||||
const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageBoxOptions) => {
|
const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageBoxOptions) => {
|
||||||
@@ -194,7 +206,7 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
|
|||||||
// Choose a default button to get selected when dialog is cancelled.
|
// Choose a default button to get selected when dialog is cancelled.
|
||||||
if (cancelId == null) {
|
if (cancelId == null) {
|
||||||
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
|
// If the defaultId is set to 0, ensure the cancel button is a different index (1)
|
||||||
cancelId = (defaultId === 0 && buttons.length > 1) ? 1 : 0;
|
cancelId = defaultId === 0 && buttons.length > 1 ? 1 : 0;
|
||||||
for (const [i, button] of buttons.entries()) {
|
for (const [i, button] of buttons.entries()) {
|
||||||
const text = button.toLowerCase();
|
const text = button.toLowerCase();
|
||||||
if (text === 'cancel' || text === 'no') {
|
if (text === 'cancel' || text === 'no') {
|
||||||
@@ -210,7 +222,9 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
|
|||||||
// Generate an ID used for closing the message box.
|
// Generate an ID used for closing the message box.
|
||||||
id = getNextId();
|
id = getNextId();
|
||||||
// Close the message box when signal is aborted.
|
// Close the message box when signal is aborted.
|
||||||
if (signal.aborted) { return Promise.resolve({ cancelId, checkboxChecked }); }
|
if (signal.aborted) {
|
||||||
|
return Promise.resolve({ cancelId, checkboxChecked });
|
||||||
|
}
|
||||||
signal.addEventListener('abort', () => dialogBinding._closeMessageBox(id));
|
signal.addEventListener('abort', () => dialogBinding._closeMessageBox(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,59 +254,80 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
|
|||||||
|
|
||||||
export function showOpenDialog(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
|
export function showOpenDialog(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
|
||||||
export function showOpenDialog(options: OpenDialogOptions): OpenDialogReturnValue;
|
export function showOpenDialog(options: OpenDialogOptions): OpenDialogReturnValue;
|
||||||
export function showOpenDialog (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
|
export function showOpenDialog(
|
||||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
windowOrOptions: BaseWindow | OpenDialogOptions,
|
||||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
maybeOptions?: OpenDialogOptions
|
||||||
|
): OpenDialogReturnValue {
|
||||||
|
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||||
|
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||||
return openDialog(false, window, options);
|
return openDialog(false, window, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showOpenDialogSync(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
|
export function showOpenDialogSync(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
|
||||||
export function showOpenDialogSync(options: OpenDialogOptions): OpenDialogReturnValue;
|
export function showOpenDialogSync(options: OpenDialogOptions): OpenDialogReturnValue;
|
||||||
export function showOpenDialogSync (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
|
export function showOpenDialogSync(
|
||||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
windowOrOptions: BaseWindow | OpenDialogOptions,
|
||||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
maybeOptions?: OpenDialogOptions
|
||||||
|
): OpenDialogReturnValue {
|
||||||
|
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||||
|
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||||
return openDialog(true, window, options);
|
return openDialog(true, window, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showSaveDialog(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
|
export function showSaveDialog(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
|
||||||
export function showSaveDialog(options: SaveDialogOptions): SaveDialogReturnValue;
|
export function showSaveDialog(options: SaveDialogOptions): SaveDialogReturnValue;
|
||||||
export function showSaveDialog (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
|
export function showSaveDialog(
|
||||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
windowOrOptions: BaseWindow | SaveDialogOptions,
|
||||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
maybeOptions?: SaveDialogOptions
|
||||||
|
): SaveDialogReturnValue {
|
||||||
|
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||||
|
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||||
return saveDialog(false, window, options);
|
return saveDialog(false, window, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showSaveDialogSync(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
|
export function showSaveDialogSync(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
|
||||||
export function showSaveDialogSync(options: SaveDialogOptions): SaveDialogReturnValue;
|
export function showSaveDialogSync(options: SaveDialogOptions): SaveDialogReturnValue;
|
||||||
export function showSaveDialogSync (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
|
export function showSaveDialogSync(
|
||||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
windowOrOptions: BaseWindow | SaveDialogOptions,
|
||||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
maybeOptions?: SaveDialogOptions
|
||||||
|
): SaveDialogReturnValue {
|
||||||
|
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||||
|
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||||
return saveDialog(true, window, options);
|
return saveDialog(true, window, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showMessageBox(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
|
export function showMessageBox(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
|
||||||
export function showMessageBox(options: MessageBoxOptions): MessageBoxReturnValue;
|
export function showMessageBox(options: MessageBoxOptions): MessageBoxReturnValue;
|
||||||
export function showMessageBox (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
|
export function showMessageBox(
|
||||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
windowOrOptions: BaseWindow | MessageBoxOptions,
|
||||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
maybeOptions?: MessageBoxOptions
|
||||||
|
): MessageBoxReturnValue {
|
||||||
|
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||||
|
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||||
return messageBox(false, window, options);
|
return messageBox(false, window, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showMessageBoxSync(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
|
export function showMessageBoxSync(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
|
||||||
export function showMessageBoxSync(options: MessageBoxOptions): MessageBoxReturnValue;
|
export function showMessageBoxSync(options: MessageBoxOptions): MessageBoxReturnValue;
|
||||||
export function showMessageBoxSync (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
|
export function showMessageBoxSync(
|
||||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
windowOrOptions: BaseWindow | MessageBoxOptions,
|
||||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
maybeOptions?: MessageBoxOptions
|
||||||
|
): MessageBoxReturnValue {
|
||||||
|
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||||
|
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||||
return messageBox(true, window, options);
|
return messageBox(true, window, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showErrorBox (...args: any[]) {
|
export function showErrorBox(...args: any[]) {
|
||||||
return dialogBinding.showErrorBox(...args);
|
return dialogBinding.showErrorBox(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function showCertificateTrustDialog (windowOrOptions: BaseWindow | CertificateTrustDialogOptions, maybeOptions?: CertificateTrustDialogOptions) {
|
export function showCertificateTrustDialog(
|
||||||
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
|
windowOrOptions: BaseWindow | CertificateTrustDialogOptions,
|
||||||
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
|
maybeOptions?: CertificateTrustDialogOptions
|
||||||
|
) {
|
||||||
|
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
|
||||||
|
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
|
||||||
|
|
||||||
if (options == null || typeof options !== 'object') {
|
if (options == null || typeof options !== 'object') {
|
||||||
throw new TypeError('options must be an object');
|
throw new TypeError('options must be an object');
|
||||||
|
|||||||
@@ -1,2 +1,39 @@
|
|||||||
const { globalShortcut } = process._linkedBinding('electron_browser_global_shortcut');
|
const { createGlobalShortcut } = process._linkedBinding('electron_browser_global_shortcut');
|
||||||
export default globalShortcut;
|
|
||||||
|
let globalShortcut: Electron.GlobalShortcut;
|
||||||
|
|
||||||
|
const createGlobalShortcutIfNeeded = () => {
|
||||||
|
if (globalShortcut === undefined) {
|
||||||
|
globalShortcut = createGlobalShortcut();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default new Proxy(
|
||||||
|
{},
|
||||||
|
{
|
||||||
|
get: (_target, property: keyof Electron.GlobalShortcut) => {
|
||||||
|
createGlobalShortcutIfNeeded();
|
||||||
|
const value = globalShortcut[property];
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
return value.bind(globalShortcut);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
set: (_target, property: string, value: unknown) => {
|
||||||
|
createGlobalShortcutIfNeeded();
|
||||||
|
return Reflect.set(globalShortcut, property, value);
|
||||||
|
},
|
||||||
|
ownKeys: () => {
|
||||||
|
createGlobalShortcutIfNeeded();
|
||||||
|
return Reflect.ownKeys(globalShortcut);
|
||||||
|
},
|
||||||
|
has: (_target, property: string) => {
|
||||||
|
createGlobalShortcutIfNeeded();
|
||||||
|
return property in globalShortcut;
|
||||||
|
},
|
||||||
|
getOwnPropertyDescriptor: (_target, property: string) => {
|
||||||
|
createGlobalShortcutIfNeeded();
|
||||||
|
return Reflect.getOwnPropertyDescriptor(globalShortcut, property);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -4,8 +4,12 @@ let _inAppPurchase;
|
|||||||
|
|
||||||
if (process.platform === 'darwin') {
|
if (process.platform === 'darwin') {
|
||||||
const { inAppPurchase } = process._linkedBinding('electron_browser_in_app_purchase');
|
const { inAppPurchase } = process._linkedBinding('electron_browser_in_app_purchase');
|
||||||
const _purchase = inAppPurchase.purchaseProduct as (productID: string, quantity?: number, username?: string) => Promise<boolean>;
|
const _purchase = inAppPurchase.purchaseProduct as (
|
||||||
inAppPurchase.purchaseProduct = (productID: string, opts?: number | { quantity?: number, username?: string }) => {
|
productID: string,
|
||||||
|
quantity?: number,
|
||||||
|
username?: string
|
||||||
|
) => Promise<boolean>;
|
||||||
|
inAppPurchase.purchaseProduct = (productID: string, opts?: number | { quantity?: number; username?: string }) => {
|
||||||
const quantity = typeof opts === 'object' ? opts.quantity : opts;
|
const quantity = typeof opts === 'object' ? opts.quantity : opts;
|
||||||
const username = typeof opts === 'object' ? opts.username : undefined;
|
const username = typeof opts === 'object' ? opts.username : undefined;
|
||||||
return _purchase.apply(inAppPurchase, [productID, quantity, username]);
|
return _purchase.apply(inAppPurchase, [productID, quantity, username]);
|
||||||
|
|||||||
@@ -1,20 +1,66 @@
|
|||||||
import { app, BaseWindow, BrowserWindow, session, webContents, WebContents, MenuItemConstructorOptions } from 'electron/main';
|
import {
|
||||||
|
app,
|
||||||
|
BaseWindow,
|
||||||
|
BrowserWindow,
|
||||||
|
session,
|
||||||
|
webContents,
|
||||||
|
WebContents,
|
||||||
|
MenuItemConstructorOptions
|
||||||
|
} from 'electron/main';
|
||||||
|
|
||||||
const isMac = process.platform === 'darwin';
|
const isMac = process.platform === 'darwin';
|
||||||
const isWindows = process.platform === 'win32';
|
const isWindows = process.platform === 'win32';
|
||||||
const isLinux = process.platform === 'linux';
|
const isLinux = process.platform === 'linux';
|
||||||
|
|
||||||
type RoleId = 'about' | 'close' | 'copy' | 'cut' | 'delete' | 'forcereload' | 'front' | 'help' | 'hide' | 'hideothers' | 'minimize' |
|
type RoleId =
|
||||||
'paste' | 'pasteandmatchstyle' | 'quit' | 'redo' | 'reload' | 'resetzoom' | 'selectall' | 'services' | 'recentdocuments' | 'clearrecentdocuments' |
|
| 'about'
|
||||||
'showsubstitutions' | 'togglesmartquotes' | 'togglesmartdashes' | 'toggletextreplacement' | 'startspeaking' | 'stopspeaking' |
|
| 'close'
|
||||||
'toggledevtools' | 'togglefullscreen' | 'undo' | 'unhide' | 'window' | 'zoom' | 'zoomin' | 'zoomout' | 'togglespellchecker' |
|
| 'copy'
|
||||||
'appmenu' | 'filemenu' | 'editmenu' | 'viewmenu' | 'windowmenu' | 'sharemenu'
|
| 'cut'
|
||||||
|
| 'delete'
|
||||||
|
| 'forcereload'
|
||||||
|
| 'front'
|
||||||
|
| 'help'
|
||||||
|
| 'hide'
|
||||||
|
| 'hideothers'
|
||||||
|
| 'minimize'
|
||||||
|
| 'paste'
|
||||||
|
| 'pasteandmatchstyle'
|
||||||
|
| 'quit'
|
||||||
|
| 'redo'
|
||||||
|
| 'reload'
|
||||||
|
| 'resetzoom'
|
||||||
|
| 'selectall'
|
||||||
|
| 'services'
|
||||||
|
| 'recentdocuments'
|
||||||
|
| 'clearrecentdocuments'
|
||||||
|
| 'showsubstitutions'
|
||||||
|
| 'togglesmartquotes'
|
||||||
|
| 'togglesmartdashes'
|
||||||
|
| 'toggletextreplacement'
|
||||||
|
| 'startspeaking'
|
||||||
|
| 'stopspeaking'
|
||||||
|
| 'toggledevtools'
|
||||||
|
| 'togglefullscreen'
|
||||||
|
| 'undo'
|
||||||
|
| 'unhide'
|
||||||
|
| 'window'
|
||||||
|
| 'zoom'
|
||||||
|
| 'zoomin'
|
||||||
|
| 'zoomout'
|
||||||
|
| 'togglespellchecker'
|
||||||
|
| 'appmenu'
|
||||||
|
| 'filemenu'
|
||||||
|
| 'editmenu'
|
||||||
|
| 'viewmenu'
|
||||||
|
| 'windowmenu'
|
||||||
|
| 'sharemenu';
|
||||||
interface Role {
|
interface Role {
|
||||||
label: string;
|
label: string;
|
||||||
accelerator?: string;
|
accelerator?: string;
|
||||||
checked?: boolean;
|
checked?: boolean;
|
||||||
windowMethod?: ((window: BaseWindow) => void);
|
windowMethod?: (window: BaseWindow) => void;
|
||||||
webContentsMethod?: ((webContents: WebContents) => void);
|
webContentsMethod?: (webContents: WebContents) => void;
|
||||||
appMethod?: () => void;
|
appMethod?: () => void;
|
||||||
registerAccelerator?: boolean;
|
registerAccelerator?: boolean;
|
||||||
nonNativeMacOSRole?: boolean;
|
nonNativeMacOSRole?: boolean;
|
||||||
@@ -23,7 +69,7 @@ interface Role {
|
|||||||
|
|
||||||
export const roleList: Record<RoleId, Role> = {
|
export const roleList: Record<RoleId, Role> = {
|
||||||
about: {
|
about: {
|
||||||
get label () {
|
get label() {
|
||||||
return isLinux ? 'About' : `About ${app.name}`;
|
return isLinux ? 'About' : `About ${app.name}`;
|
||||||
},
|
},
|
||||||
...((isWindows || isLinux) && { appMethod: () => app.showAboutPanel() })
|
...((isWindows || isLinux) && { appMethod: () => app.showAboutPanel() })
|
||||||
@@ -31,23 +77,23 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
close: {
|
close: {
|
||||||
label: isMac ? 'Close Window' : 'Close',
|
label: isMac ? 'Close Window' : 'Close',
|
||||||
accelerator: 'CommandOrControl+W',
|
accelerator: 'CommandOrControl+W',
|
||||||
windowMethod: w => w.close()
|
windowMethod: (w) => w.close()
|
||||||
},
|
},
|
||||||
copy: {
|
copy: {
|
||||||
label: 'Copy',
|
label: 'Copy',
|
||||||
accelerator: 'CommandOrControl+C',
|
accelerator: 'CommandOrControl+C',
|
||||||
webContentsMethod: wc => wc.copy(),
|
webContentsMethod: (wc) => wc.copy(),
|
||||||
registerAccelerator: false
|
registerAccelerator: false
|
||||||
},
|
},
|
||||||
cut: {
|
cut: {
|
||||||
label: 'Cut',
|
label: 'Cut',
|
||||||
accelerator: 'CommandOrControl+X',
|
accelerator: 'CommandOrControl+X',
|
||||||
webContentsMethod: wc => wc.cut(),
|
webContentsMethod: (wc) => wc.cut(),
|
||||||
registerAccelerator: false
|
registerAccelerator: false
|
||||||
},
|
},
|
||||||
delete: {
|
delete: {
|
||||||
label: 'Delete',
|
label: 'Delete',
|
||||||
webContentsMethod: wc => wc.delete()
|
webContentsMethod: (wc) => wc.delete()
|
||||||
},
|
},
|
||||||
forcereload: {
|
forcereload: {
|
||||||
label: 'Force Reload',
|
label: 'Force Reload',
|
||||||
@@ -66,7 +112,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
label: 'Help'
|
label: 'Help'
|
||||||
},
|
},
|
||||||
hide: {
|
hide: {
|
||||||
get label () {
|
get label() {
|
||||||
return `Hide ${app.name}`;
|
return `Hide ${app.name}`;
|
||||||
},
|
},
|
||||||
accelerator: 'Command+H'
|
accelerator: 'Command+H'
|
||||||
@@ -78,28 +124,31 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
minimize: {
|
minimize: {
|
||||||
label: 'Minimize',
|
label: 'Minimize',
|
||||||
accelerator: 'CommandOrControl+M',
|
accelerator: 'CommandOrControl+M',
|
||||||
windowMethod: w => {
|
windowMethod: (w) => {
|
||||||
if (w.minimizable) w.minimize();
|
if (w.minimizable) w.minimize();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
paste: {
|
paste: {
|
||||||
label: 'Paste',
|
label: 'Paste',
|
||||||
accelerator: 'CommandOrControl+V',
|
accelerator: 'CommandOrControl+V',
|
||||||
webContentsMethod: wc => wc.paste(),
|
webContentsMethod: (wc) => wc.paste(),
|
||||||
registerAccelerator: false
|
registerAccelerator: false
|
||||||
},
|
},
|
||||||
pasteandmatchstyle: {
|
pasteandmatchstyle: {
|
||||||
label: 'Paste and Match Style',
|
label: 'Paste and Match Style',
|
||||||
accelerator: isMac ? 'Cmd+Option+Shift+V' : 'Shift+CommandOrControl+V',
|
accelerator: isMac ? 'Cmd+Option+Shift+V' : 'Shift+CommandOrControl+V',
|
||||||
webContentsMethod: wc => wc.pasteAndMatchStyle(),
|
webContentsMethod: (wc) => wc.pasteAndMatchStyle(),
|
||||||
registerAccelerator: false
|
registerAccelerator: false
|
||||||
},
|
},
|
||||||
quit: {
|
quit: {
|
||||||
get label () {
|
get label() {
|
||||||
switch (process.platform) {
|
switch (process.platform) {
|
||||||
case 'darwin': return `Quit ${app.name}`;
|
case 'darwin':
|
||||||
case 'win32': return 'Exit';
|
return `Quit ${app.name}`;
|
||||||
default: return 'Quit';
|
case 'win32':
|
||||||
|
return 'Exit';
|
||||||
|
default:
|
||||||
|
return 'Quit';
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
accelerator: isWindows ? undefined : 'CommandOrControl+Q',
|
accelerator: isWindows ? undefined : 'CommandOrControl+Q',
|
||||||
@@ -108,7 +157,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
redo: {
|
redo: {
|
||||||
label: 'Redo',
|
label: 'Redo',
|
||||||
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
|
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
|
||||||
webContentsMethod: wc => wc.redo()
|
webContentsMethod: (wc) => wc.redo()
|
||||||
},
|
},
|
||||||
reload: {
|
reload: {
|
||||||
label: 'Reload',
|
label: 'Reload',
|
||||||
@@ -131,7 +180,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
selectall: {
|
selectall: {
|
||||||
label: 'Select All',
|
label: 'Select All',
|
||||||
accelerator: 'CommandOrControl+A',
|
accelerator: 'CommandOrControl+A',
|
||||||
webContentsMethod: wc => wc.selectAll()
|
webContentsMethod: (wc) => wc.selectAll()
|
||||||
},
|
},
|
||||||
services: {
|
services: {
|
||||||
label: 'Services'
|
label: 'Services'
|
||||||
@@ -164,7 +213,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
label: 'Toggle Developer Tools',
|
label: 'Toggle Developer Tools',
|
||||||
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
|
||||||
nonNativeMacOSRole: true,
|
nonNativeMacOSRole: true,
|
||||||
webContentsMethod: wc => {
|
webContentsMethod: (wc) => {
|
||||||
const bw = wc.getOwnerBrowserWindow();
|
const bw = wc.getOwnerBrowserWindow();
|
||||||
if (bw) bw.webContents.toggleDevTools();
|
if (bw) bw.webContents.toggleDevTools();
|
||||||
}
|
}
|
||||||
@@ -179,7 +228,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
undo: {
|
undo: {
|
||||||
label: 'Undo',
|
label: 'Undo',
|
||||||
accelerator: 'CommandOrControl+Z',
|
accelerator: 'CommandOrControl+Z',
|
||||||
webContentsMethod: wc => wc.undo()
|
webContentsMethod: (wc) => wc.undo()
|
||||||
},
|
},
|
||||||
unhide: {
|
unhide: {
|
||||||
label: 'Show All'
|
label: 'Show All'
|
||||||
@@ -208,7 +257,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
},
|
},
|
||||||
togglespellchecker: {
|
togglespellchecker: {
|
||||||
label: 'Check Spelling While Typing',
|
label: 'Check Spelling While Typing',
|
||||||
get checked () {
|
get checked() {
|
||||||
const wc = webContents.getFocusedWebContents();
|
const wc = webContents.getFocusedWebContents();
|
||||||
const ses = wc ? wc.session : session.defaultSession;
|
const ses = wc ? wc.session : session.defaultSession;
|
||||||
return ses.spellCheckerEnabled;
|
return ses.spellCheckerEnabled;
|
||||||
@@ -221,7 +270,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
},
|
},
|
||||||
// App submenu should be used for Mac only
|
// App submenu should be used for Mac only
|
||||||
appmenu: {
|
appmenu: {
|
||||||
get label () {
|
get label() {
|
||||||
return app.name;
|
return app.name;
|
||||||
},
|
},
|
||||||
submenu: [
|
submenu: [
|
||||||
@@ -239,9 +288,7 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
// File submenu
|
// File submenu
|
||||||
filemenu: {
|
filemenu: {
|
||||||
label: 'File',
|
label: 'File',
|
||||||
submenu: [
|
submenu: [isMac ? { role: 'close' } : { role: 'quit' }]
|
||||||
isMac ? { role: 'close' } : { role: 'quit' }
|
|
||||||
]
|
|
||||||
},
|
},
|
||||||
// Edit submenu
|
// Edit submenu
|
||||||
editmenu: {
|
editmenu: {
|
||||||
@@ -254,34 +301,27 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
{ role: 'copy' },
|
{ role: 'copy' },
|
||||||
{ role: 'paste' },
|
{ role: 'paste' },
|
||||||
...(isMac
|
...(isMac
|
||||||
? [
|
? ([
|
||||||
{ role: 'pasteAndMatchStyle' },
|
{ role: 'pasteAndMatchStyle' },
|
||||||
{ role: 'delete' },
|
{ role: 'delete' },
|
||||||
{ role: 'selectAll' },
|
{ role: 'selectAll' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{
|
{
|
||||||
label: 'Substitutions',
|
label: 'Substitutions',
|
||||||
submenu: [
|
submenu: [
|
||||||
{ role: 'showSubstitutions' },
|
{ role: 'showSubstitutions' },
|
||||||
{ type: 'separator' },
|
{ type: 'separator' },
|
||||||
{ role: 'toggleSmartQuotes' },
|
{ role: 'toggleSmartQuotes' },
|
||||||
{ role: 'toggleSmartDashes' },
|
{ role: 'toggleSmartDashes' },
|
||||||
{ role: 'toggleTextReplacement' }
|
{ role: 'toggleTextReplacement' }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Speech',
|
label: 'Speech',
|
||||||
submenu: [
|
submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }]
|
||||||
{ role: 'startSpeaking' },
|
}
|
||||||
{ role: 'stopSpeaking' }
|
] as MenuItemConstructorOptions[])
|
||||||
]
|
: ([{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }] as MenuItemConstructorOptions[]))
|
||||||
}
|
|
||||||
] as MenuItemConstructorOptions[]
|
|
||||||
: [
|
|
||||||
{ role: 'delete' },
|
|
||||||
{ type: 'separator' },
|
|
||||||
{ role: 'selectAll' }
|
|
||||||
] as MenuItemConstructorOptions[])
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// View submenu
|
// View submenu
|
||||||
@@ -306,13 +346,8 @@ export const roleList: Record<RoleId, Role> = {
|
|||||||
{ role: 'minimize' },
|
{ role: 'minimize' },
|
||||||
{ role: 'zoom' },
|
{ role: 'zoom' },
|
||||||
...(isMac
|
...(isMac
|
||||||
? [
|
? ([{ type: 'separator' }, { role: 'front' }] as MenuItemConstructorOptions[])
|
||||||
{ type: 'separator' },
|
: ([{ role: 'close' }] as MenuItemConstructorOptions[]))
|
||||||
{ role: 'front' }
|
|
||||||
] as MenuItemConstructorOptions[]
|
|
||||||
: [
|
|
||||||
{ role: 'close' }
|
|
||||||
] as MenuItemConstructorOptions[])
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
// Share submenu
|
// Share submenu
|
||||||
@@ -334,34 +369,34 @@ const canExecuteRole = (role: keyof typeof roleList) => {
|
|||||||
return roleList[role].nonNativeMacOSRole;
|
return roleList[role].nonNativeMacOSRole;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function getDefaultType (role: RoleId) {
|
export function getDefaultType(role: RoleId) {
|
||||||
if (shouldOverrideCheckStatus(role)) return 'checkbox';
|
if (shouldOverrideCheckStatus(role)) return 'checkbox';
|
||||||
return 'normal';
|
return 'normal';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultLabel (role: RoleId) {
|
export function getDefaultLabel(role: RoleId) {
|
||||||
return hasRole(role) ? roleList[role].label : '';
|
return hasRole(role) ? roleList[role].label : '';
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCheckStatus (role: RoleId) {
|
export function getCheckStatus(role: RoleId) {
|
||||||
if (hasRole(role)) return roleList[role].checked;
|
if (hasRole(role)) return roleList[role].checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldOverrideCheckStatus (role: RoleId) {
|
export function shouldOverrideCheckStatus(role: RoleId) {
|
||||||
return hasRole(role) && Object.hasOwn(roleList[role], 'checked');
|
return hasRole(role) && Object.hasOwn(roleList[role], 'checked');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultAccelerator (role: RoleId) {
|
export function getDefaultAccelerator(role: RoleId) {
|
||||||
if (hasRole(role)) return roleList[role].accelerator;
|
if (hasRole(role)) return roleList[role].accelerator;
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function shouldRegisterAccelerator (role: RoleId) {
|
export function shouldRegisterAccelerator(role: RoleId) {
|
||||||
const hasRoleRegister = hasRole(role) && roleList[role].registerAccelerator !== undefined;
|
const hasRoleRegister = hasRole(role) && roleList[role].registerAccelerator !== undefined;
|
||||||
return hasRoleRegister ? roleList[role].registerAccelerator : true;
|
return hasRoleRegister ? roleList[role].registerAccelerator : true;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getDefaultSubmenu (role: RoleId) {
|
export function getDefaultSubmenu(role: RoleId) {
|
||||||
if (!hasRole(role)) return;
|
if (!hasRole(role)) return;
|
||||||
|
|
||||||
let { submenu } = roleList[role];
|
let { submenu } = roleList[role];
|
||||||
@@ -374,7 +409,7 @@ export function getDefaultSubmenu (role: RoleId) {
|
|||||||
return submenu;
|
return submenu;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function execute (role: RoleId, focusedWindow: BaseWindow, focusedWebContents: WebContents) {
|
export function execute(role: RoleId, focusedWindow: BaseWindow, focusedWebContents: WebContents) {
|
||||||
if (!canExecuteRole(role)) return false;
|
if (!canExecuteRole(role)) return false;
|
||||||
|
|
||||||
const { appMethod, webContentsMethod, windowMethod } = roleList[role];
|
const { appMethod, webContentsMethod, windowMethod } = roleList[role];
|
||||||
|
|||||||
@@ -56,8 +56,7 @@ const MenuItem = function (this: any, options: any) {
|
|||||||
const click = options.click;
|
const click = options.click;
|
||||||
this.click = (event: KeyboardEvent, focusedWindow: BaseWindow, focusedWebContents: WebContents) => {
|
this.click = (event: KeyboardEvent, focusedWindow: BaseWindow, focusedWebContents: WebContents) => {
|
||||||
// Manually flip the checked flags when clicked.
|
// Manually flip the checked flags when clicked.
|
||||||
if (!roles.shouldOverrideCheckStatus(this.role) &&
|
if (!roles.shouldOverrideCheckStatus(this.role) && (this.type === 'checkbox' || this.type === 'radio')) {
|
||||||
(this.type === 'checkbox' || this.type === 'radio')) {
|
|
||||||
this.checked = !this.checked;
|
this.checked = !this.checked;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,16 @@
|
|||||||
function splitArray<T> (arr: T[], predicate: (x: T) => boolean) {
|
function splitArray<T>(arr: T[], predicate: (x: T) => boolean) {
|
||||||
const result = arr.reduce((multi, item) => {
|
const result = arr.reduce(
|
||||||
const current = multi[multi.length - 1];
|
(multi, item) => {
|
||||||
if (predicate(item)) {
|
const current = multi[multi.length - 1];
|
||||||
if (current.length > 0) multi.push([]);
|
if (predicate(item)) {
|
||||||
} else {
|
if (current.length > 0) multi.push([]);
|
||||||
current.push(item);
|
} else {
|
||||||
}
|
current.push(item);
|
||||||
return multi;
|
}
|
||||||
}, [[]] as T[][]);
|
return multi;
|
||||||
|
},
|
||||||
|
[[]] as T[][]
|
||||||
|
);
|
||||||
|
|
||||||
if (result[result.length - 1].length === 0) {
|
if (result[result.length - 1].length === 0) {
|
||||||
return result.slice(0, result.length - 1);
|
return result.slice(0, result.length - 1);
|
||||||
@@ -15,7 +18,7 @@ function splitArray<T> (arr: T[], predicate: (x: T) => boolean) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
function joinArrays (arrays: any[][], joinIDs: any[]) {
|
function joinArrays(arrays: any[][], joinIDs: any[]) {
|
||||||
return arrays.reduce((joined, arr, i) => {
|
return arrays.reduce((joined, arr, i) => {
|
||||||
if (i > 0 && arr.length) {
|
if (i > 0 && arr.length) {
|
||||||
if (joinIDs.length > 0) {
|
if (joinIDs.length > 0) {
|
||||||
@@ -29,26 +32,23 @@ function joinArrays (arrays: any[][], joinIDs: any[]) {
|
|||||||
}, []);
|
}, []);
|
||||||
}
|
}
|
||||||
|
|
||||||
function pushOntoMultiMap<K, V> (map: Map<K, V[]>, key: K, value: V) {
|
function pushOntoMultiMap<K, V>(map: Map<K, V[]>, key: K, value: V) {
|
||||||
if (!map.has(key)) {
|
if (!map.has(key)) {
|
||||||
map.set(key, []);
|
map.set(key, []);
|
||||||
}
|
}
|
||||||
map.get(key)!.push(value);
|
map.get(key)!.push(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
function indexOfGroupContainingID<T> (groups: {id?: T}[][], id: T, ignoreGroup: {id?: T}[]) {
|
function indexOfGroupContainingID<T>(groups: { id?: T }[][], id: T, ignoreGroup: { id?: T }[]) {
|
||||||
return groups.findIndex(
|
return groups.findIndex(
|
||||||
candidateGroup =>
|
(candidateGroup) =>
|
||||||
candidateGroup !== ignoreGroup &&
|
candidateGroup !== ignoreGroup && candidateGroup.some((candidateItem) => candidateItem.id === id)
|
||||||
candidateGroup.some(
|
|
||||||
candidateItem => candidateItem.id === id
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sort nodes topologically using a depth-first approach. Encountered cycles
|
// Sort nodes topologically using a depth-first approach. Encountered cycles
|
||||||
// are broken.
|
// are broken.
|
||||||
function sortTopologically<T> (originalOrder: T[], edgesById: Map<T, T[]>) {
|
function sortTopologically<T>(originalOrder: T[], edgesById: Map<T, T[]>) {
|
||||||
const sorted = [] as T[];
|
const sorted = [] as T[];
|
||||||
const marked = new Set<T>();
|
const marked = new Set<T>();
|
||||||
|
|
||||||
@@ -71,7 +71,7 @@ function sortTopologically<T> (originalOrder: T[], edgesById: Map<T, T[]>) {
|
|||||||
return sorted;
|
return sorted;
|
||||||
}
|
}
|
||||||
|
|
||||||
function attemptToMergeAGroup<T> (groups: {before?: T[], after?: T[], id?: T}[][]) {
|
function attemptToMergeAGroup<T>(groups: { before?: T[]; after?: T[]; id?: T }[][]) {
|
||||||
for (let i = 0; i < groups.length; i++) {
|
for (let i = 0; i < groups.length; i++) {
|
||||||
const group = groups[i];
|
const group = groups[i];
|
||||||
for (const item of group) {
|
for (const item of group) {
|
||||||
@@ -90,7 +90,7 @@ function attemptToMergeAGroup<T> (groups: {before?: T[], after?: T[], id?: T}[][
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
function mergeGroups<T> (groups: {before?: T[], after?: T[], id?: T}[][]) {
|
function mergeGroups<T>(groups: { before?: T[]; after?: T[]; id?: T }[][]) {
|
||||||
let merged = true;
|
let merged = true;
|
||||||
while (merged) {
|
while (merged) {
|
||||||
merged = attemptToMergeAGroup(groups);
|
merged = attemptToMergeAGroup(groups);
|
||||||
@@ -98,7 +98,7 @@ function mergeGroups<T> (groups: {before?: T[], after?: T[], id?: T}[][]) {
|
|||||||
return groups;
|
return groups;
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortItemsInGroup<T> (group: {before?: T[], after?: T[], id?: T}[]) {
|
function sortItemsInGroup<T>(group: { before?: T[]; after?: T[]; id?: T }[]) {
|
||||||
const originalOrder = group.map((node, i) => i);
|
const originalOrder = group.map((node, i) => i);
|
||||||
const edges = new Map();
|
const edges = new Map();
|
||||||
const idToIndex = new Map(group.map((item, i) => [item.id, i]));
|
const idToIndex = new Map(group.map((item, i) => [item.id, i]));
|
||||||
@@ -123,10 +123,14 @@ function sortItemsInGroup<T> (group: {before?: T[], after?: T[], id?: T}[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sortedNodes = sortTopologically(originalOrder, edges);
|
const sortedNodes = sortTopologically(originalOrder, edges);
|
||||||
return sortedNodes.map(i => group[i]);
|
return sortedNodes.map((i) => group[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function findEdgesInGroup<T> (groups: {beforeGroupContaining?: T[], afterGroupContaining?: T[], id?: T}[][], i: number, edges: Map<any, any>) {
|
function findEdgesInGroup<T>(
|
||||||
|
groups: { beforeGroupContaining?: T[]; afterGroupContaining?: T[]; id?: T }[][],
|
||||||
|
i: number,
|
||||||
|
edges: Map<any, any>
|
||||||
|
) {
|
||||||
const group = groups[i];
|
const group = groups[i];
|
||||||
for (const item of group) {
|
for (const item of group) {
|
||||||
if (item.beforeGroupContaining) {
|
if (item.beforeGroupContaining) {
|
||||||
@@ -150,7 +154,7 @@ function findEdgesInGroup<T> (groups: {beforeGroupContaining?: T[], afterGroupCo
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortGroups<T> (groups: {id?: T}[][]) {
|
function sortGroups<T>(groups: { id?: T }[][]) {
|
||||||
const originalOrder = groups.map((item, i) => i);
|
const originalOrder = groups.map((item, i) => i);
|
||||||
const edges = new Map();
|
const edges = new Map();
|
||||||
|
|
||||||
@@ -159,13 +163,15 @@ function sortGroups<T> (groups: {id?: T}[][]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sortedGroupIndexes = sortTopologically(originalOrder, edges);
|
const sortedGroupIndexes = sortTopologically(originalOrder, edges);
|
||||||
return sortedGroupIndexes.map(i => groups[i]);
|
return sortedGroupIndexes.map((i) => groups[i]);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortMenuItems (menuItems: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[]) {
|
export function sortMenuItems(menuItems: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[]) {
|
||||||
const isSeparator = (i: Electron.MenuItemConstructorOptions | Electron.MenuItem) => {
|
const isSeparator = (i: Electron.MenuItemConstructorOptions | Electron.MenuItem) => {
|
||||||
const opts = i as Electron.MenuItemConstructorOptions;
|
const opts = i as Electron.MenuItemConstructorOptions;
|
||||||
return i.type === 'separator' && !opts.before && !opts.after && !opts.beforeGroupContaining && !opts.afterGroupContaining;
|
return (
|
||||||
|
i.type === 'separator' && !opts.before && !opts.after && !opts.beforeGroupContaining && !opts.afterGroupContaining
|
||||||
|
);
|
||||||
};
|
};
|
||||||
const separators = menuItems.filter(isSeparator);
|
const separators = menuItems.filter(isSeparator);
|
||||||
|
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ Menu.prototype._executeCommand = function (event, id) {
|
|||||||
Menu.prototype._menuWillShow = function () {
|
Menu.prototype._menuWillShow = function () {
|
||||||
// Ensure radio groups have at least one menu item selected
|
// Ensure radio groups have at least one menu item selected
|
||||||
for (const id of Object.keys(this.groupsMap)) {
|
for (const id of Object.keys(this.groupsMap)) {
|
||||||
const found = this.groupsMap[id].find(item => item.checked) || null;
|
const found = this.groupsMap[id].find((item) => item.checked) || null;
|
||||||
if (!found) checked.set(this.groupsMap[id][0], true);
|
if (!found) checked.set(this.groupsMap[id][0], true);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -141,7 +141,7 @@ Menu.prototype.closePopup = function (window) {
|
|||||||
Menu.prototype.getMenuItemById = function (id) {
|
Menu.prototype.getMenuItemById = function (id) {
|
||||||
const items = this.items;
|
const items = this.items;
|
||||||
|
|
||||||
let found = items.find(item => item.id === id) || null;
|
let found = items.find((item) => item.id === id) || null;
|
||||||
for (let i = 0; !found && i < items.length; i++) {
|
for (let i = 0; !found && i < items.length; i++) {
|
||||||
const { submenu } = items[i];
|
const { submenu } = items[i];
|
||||||
if (submenu) {
|
if (submenu) {
|
||||||
@@ -213,7 +213,7 @@ Menu.setApplicationMenu = function (menu: MenuType) {
|
|||||||
bindings.setApplicationMenu(menu);
|
bindings.setApplicationMenu(menu);
|
||||||
} else {
|
} else {
|
||||||
const windows = BaseWindow.getAllWindows();
|
const windows = BaseWindow.getAllWindows();
|
||||||
windows.map(w => w.setMenu(menu));
|
windows.map((w) => w.setMenu(menu));
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -244,14 +244,16 @@ Menu.buildFromTemplate = function (template) {
|
|||||||
/* Helper Functions */
|
/* Helper Functions */
|
||||||
|
|
||||||
// validate the template against having the wrong attribute
|
// validate the template against having the wrong attribute
|
||||||
function areValidTemplateItems (template: (MenuItemConstructorOptions | MenuItem)[]) {
|
function areValidTemplateItems(template: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||||
return template.every(item =>
|
return template.every(
|
||||||
item != null &&
|
(item) =>
|
||||||
typeof item === 'object' &&
|
item != null &&
|
||||||
(Object.hasOwn(item, 'label') || Object.hasOwn(item, 'role') || item.type === 'separator'));
|
typeof item === 'object' &&
|
||||||
|
(Object.hasOwn(item, 'label') || Object.hasOwn(item, 'role') || item.type === 'separator')
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function sortTemplate (template: (MenuItemConstructorOptions | MenuItem)[]) {
|
function sortTemplate(template: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||||
const sorted = sortMenuItems(template);
|
const sorted = sortMenuItems(template);
|
||||||
for (const item of sorted) {
|
for (const item of sorted) {
|
||||||
if (Array.isArray(item.submenu)) {
|
if (Array.isArray(item.submenu)) {
|
||||||
@@ -262,7 +264,7 @@ function sortTemplate (template: (MenuItemConstructorOptions | MenuItem)[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Search between separators to find a radio menu item and return its group id
|
// Search between separators to find a radio menu item and return its group id
|
||||||
function generateGroupId (items: (MenuItemConstructorOptions | MenuItem)[], pos: number) {
|
function generateGroupId(items: (MenuItemConstructorOptions | MenuItem)[], pos: number) {
|
||||||
if (pos > 0) {
|
if (pos > 0) {
|
||||||
for (let idx = pos - 1; idx >= 0; idx--) {
|
for (let idx = pos - 1; idx >= 0; idx--) {
|
||||||
if (items[idx].type === 'radio') return (items[idx] as MenuItem).groupId;
|
if (items[idx].type === 'radio') return (items[idx] as MenuItem).groupId;
|
||||||
@@ -278,7 +280,7 @@ function generateGroupId (items: (MenuItemConstructorOptions | MenuItem)[], pos:
|
|||||||
return groupIdIndex;
|
return groupIdIndex;
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]) {
|
function removeExtraSeparators(items: (MenuItemConstructorOptions | MenuItem)[]) {
|
||||||
// fold adjacent separators together
|
// fold adjacent separators together
|
||||||
let ret = items.filter((e, idx, arr) => {
|
let ret = items.filter((e, idx, arr) => {
|
||||||
if (e.visible === false) return true;
|
if (e.visible === false) return true;
|
||||||
@@ -294,7 +296,7 @@ function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
|
function insertItemByType(this: MenuType, item: MenuItem, pos: number) {
|
||||||
const types = {
|
const types = {
|
||||||
normal: () => this.insertItem(pos, item.commandId, item.label),
|
normal: () => this.insertItem(pos, item.commandId, item.label),
|
||||||
header: () => this.insertItem(pos, item.commandId, item.label),
|
header: () => this.insertItem(pos, item.commandId, item.label),
|
||||||
|
|||||||
@@ -1,14 +1,11 @@
|
|||||||
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
|
||||||
|
|
||||||
const { createPair } = process._linkedBinding('electron_browser_message_port');
|
const { createPair } = process._linkedBinding('electron_browser_message_port');
|
||||||
|
|
||||||
export default class MessageChannelMain extends EventEmitter implements Electron.MessageChannelMain {
|
export default class MessageChannelMain implements Electron.MessageChannelMain {
|
||||||
port1: MessagePortMain;
|
port1: MessagePortMain;
|
||||||
port2: MessagePortMain;
|
port2: MessagePortMain;
|
||||||
constructor () {
|
constructor() {
|
||||||
super();
|
|
||||||
const { port1, port2 } = createPair();
|
const { port1, port2 } = createPair();
|
||||||
this.port1 = new MessagePortMain(port1);
|
this.port1 = new MessagePortMain(port1);
|
||||||
this.port2 = new MessagePortMain(port2);
|
this.port2 = new MessagePortMain(port2);
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ import { ClientRequestConstructorOptions, ClientRequest, IncomingMessage, Sessio
|
|||||||
|
|
||||||
import { Readable, Writable, isReadable } from 'stream';
|
import { Readable, Writable, isReadable } from 'stream';
|
||||||
|
|
||||||
function createDeferredPromise<T, E extends Error = Error> (): { promise: Promise<T>; resolve: (x: T) => void; reject: (e: E) => void; } {
|
function createDeferredPromise<T, E extends Error = Error>(): {
|
||||||
|
promise: Promise<T>;
|
||||||
|
resolve: (x: T) => void;
|
||||||
|
reject: (e: E) => void;
|
||||||
|
} {
|
||||||
let res: (x: T) => void;
|
let res: (x: T) => void;
|
||||||
let rej: (e: E) => void;
|
let rej: (e: E) => void;
|
||||||
const promise = new Promise<T>((resolve, reject) => {
|
const promise = new Promise<T>((resolve, reject) => {
|
||||||
@@ -15,8 +19,12 @@ function createDeferredPromise<T, E extends Error = Error> (): { promise: Promis
|
|||||||
return { promise, resolve: res!, reject: rej! };
|
return { promise, resolve: res!, reject: rej! };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypassCustomProtocolHandlers?: boolean}) | undefined, session: SessionT | undefined,
|
export function fetchWithSession(
|
||||||
request: (options: ClientRequestConstructorOptions | string) => ClientRequest) {
|
input: RequestInfo,
|
||||||
|
init: (RequestInit & { bypassCustomProtocolHandlers?: boolean }) | undefined,
|
||||||
|
session: SessionT | undefined,
|
||||||
|
request: (options: ClientRequestConstructorOptions | string) => ClientRequest
|
||||||
|
) {
|
||||||
const p = createDeferredPromise<Response>();
|
const p = createDeferredPromise<Response>();
|
||||||
let req: Request;
|
let req: Request;
|
||||||
try {
|
try {
|
||||||
@@ -76,16 +84,18 @@ export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypa
|
|||||||
// We can't set credentials to same-origin unless there's an origin set.
|
// We can't set credentials to same-origin unless there's an origin set.
|
||||||
const credentials = req.credentials === 'same-origin' && !origin ? 'include' : req.credentials;
|
const credentials = req.credentials === 'same-origin' && !origin ? 'include' : req.credentials;
|
||||||
|
|
||||||
const r = request(allowAnyProtocol({
|
const r = request(
|
||||||
session,
|
allowAnyProtocol({
|
||||||
method: req.method,
|
session,
|
||||||
url: req.url,
|
method: req.method,
|
||||||
origin,
|
url: req.url,
|
||||||
credentials,
|
origin,
|
||||||
cache: req.cache,
|
credentials,
|
||||||
referrerPolicy: req.referrerPolicy,
|
cache: req.cache,
|
||||||
redirect: req.redirect
|
referrerPolicy: req.referrerPolicy,
|
||||||
}));
|
redirect: req.redirect
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
(r as any)._urlLoaderOptions.bypassCustomProtocolHandlers = !!init?.bypassCustomProtocolHandlers;
|
(r as any)._urlLoaderOptions.bypassCustomProtocolHandlers = !!init?.bypassCustomProtocolHandlers;
|
||||||
|
|
||||||
@@ -105,7 +115,10 @@ export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypa
|
|||||||
headers.set(k, Array.isArray(v) ? v.join(', ') : v);
|
headers.set(k, Array.isArray(v) ? v.join(', ') : v);
|
||||||
}
|
}
|
||||||
const nullBodyStatus = [101, 204, 205, 304];
|
const nullBodyStatus = [101, 204, 205, 304];
|
||||||
const body = nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD' ? null : Readable.toWeb(resp as unknown as Readable) as ReadableStream;
|
const body =
|
||||||
|
nullBodyStatus.includes(resp.statusCode) || req.method === 'HEAD'
|
||||||
|
? null
|
||||||
|
: (Readable.toWeb(resp as unknown as Readable) as ReadableStream);
|
||||||
const rResp = new Response(body, {
|
const rResp = new Response(body, {
|
||||||
headers,
|
headers,
|
||||||
status: resp.statusCode,
|
status: resp.statusCode,
|
||||||
@@ -122,7 +135,9 @@ export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypa
|
|||||||
// pipeTo expects a WritableStream<Uint8Array>. Node.js' Writable.toWeb returns WritableStream<any>,
|
// pipeTo expects a WritableStream<Uint8Array>. Node.js' Writable.toWeb returns WritableStream<any>,
|
||||||
// which causes a TS structural mismatch.
|
// which causes a TS structural mismatch.
|
||||||
const writable = Writable.toWeb(r as unknown as Writable) as unknown as WritableStream<Uint8Array>;
|
const writable = Writable.toWeb(r as unknown as Writable) as unknown as WritableStream<Uint8Array>;
|
||||||
if (!req.body?.pipeTo(writable).then(() => r.end())) { r.end(); }
|
if (!req.body?.pipeTo(writable).then(() => r.end())) {
|
||||||
|
r.end();
|
||||||
|
}
|
||||||
|
|
||||||
return p.promise;
|
return p.promise;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ const stopLogging: typeof session.defaultSession.netLog.stopLogging = async () =
|
|||||||
export default {
|
export default {
|
||||||
startLogging,
|
startLogging,
|
||||||
stopLogging,
|
stopLogging,
|
||||||
get currentlyLogging (): boolean {
|
get currentlyLogging(): boolean {
|
||||||
if (!app.isReady()) return false;
|
if (!app.isReady()) return false;
|
||||||
return session.defaultSession.netLog.currentlyLogging;
|
return session.defaultSession.netLog.currentlyLogging;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,18 +5,21 @@ import type { ClientRequestConstructorOptions } from 'electron/main';
|
|||||||
|
|
||||||
const { isOnline } = process._linkedBinding('electron_common_net');
|
const { isOnline } = process._linkedBinding('electron_common_net');
|
||||||
|
|
||||||
export function request (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
|
export function request(
|
||||||
|
options: ClientRequestConstructorOptions | string,
|
||||||
|
callback?: (message: IncomingMessage) => void
|
||||||
|
) {
|
||||||
if (!app.isReady()) {
|
if (!app.isReady()) {
|
||||||
throw new Error('net module can only be used after app is ready');
|
throw new Error('net module can only be used after app is ready');
|
||||||
}
|
}
|
||||||
return new ClientRequest(options, callback);
|
return new ClientRequest(options, callback);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
|
export function fetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
|
||||||
return session.defaultSession.fetch(input, init);
|
return session.defaultSession.fetch(input, init);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function resolveHost (host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
|
export function resolveHost(host: string, options?: Electron.ResolveHostOptions): Promise<Electron.ResolvedHost> {
|
||||||
return session.defaultSession.resolveHost(host, options);
|
return session.defaultSession.resolveHost(host, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,12 +1,7 @@
|
|||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
const {
|
const { createPowerMonitor, getSystemIdleState, getSystemIdleTime, getCurrentThermalState, isOnBatteryPower } =
|
||||||
createPowerMonitor,
|
process._linkedBinding('electron_browser_power_monitor');
|
||||||
getSystemIdleState,
|
|
||||||
getSystemIdleTime,
|
|
||||||
getCurrentThermalState,
|
|
||||||
isOnBatteryPower
|
|
||||||
} = process._linkedBinding('electron_browser_power_monitor');
|
|
||||||
|
|
||||||
// Hold the native PowerMonitor at module level so it is never garbage-collected
|
// Hold the native PowerMonitor at module level so it is never garbage-collected
|
||||||
// while this module is alive. The C++ side registers OS-level callbacks (HWND
|
// while this module is alive. The C++ side registers OS-level callbacks (HWND
|
||||||
@@ -15,7 +10,7 @@ const {
|
|||||||
let pm: any;
|
let pm: any;
|
||||||
|
|
||||||
class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
|
class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
|
||||||
constructor () {
|
constructor() {
|
||||||
super();
|
super();
|
||||||
// Don't start the event source until both a) the app is ready and b)
|
// Don't start the event source until both a) the app is ready and b)
|
||||||
// there's a listener registered for a powerMonitor event.
|
// there's a listener registered for a powerMonitor event.
|
||||||
@@ -43,23 +38,23 @@ class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
getSystemIdleState (idleThreshold: number) {
|
getSystemIdleState(idleThreshold: number) {
|
||||||
return getSystemIdleState(idleThreshold);
|
return getSystemIdleState(idleThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
getCurrentThermalState () {
|
getCurrentThermalState() {
|
||||||
return getCurrentThermalState();
|
return getCurrentThermalState();
|
||||||
}
|
}
|
||||||
|
|
||||||
getSystemIdleTime () {
|
getSystemIdleTime() {
|
||||||
return getSystemIdleTime();
|
return getSystemIdleTime();
|
||||||
}
|
}
|
||||||
|
|
||||||
isOnBatteryPower () {
|
isOnBatteryPower() {
|
||||||
return isOnBatteryPower();
|
return isOnBatteryPower();
|
||||||
}
|
}
|
||||||
|
|
||||||
get onBatteryPower () {
|
get onBatteryPower() {
|
||||||
return this.isOnBatteryPower();
|
return this.isOnBatteryPower();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,17 +7,18 @@ import { ReadableStream } from 'stream/web';
|
|||||||
import type { ReadableStreamDefaultReader } from 'stream/web';
|
import type { ReadableStreamDefaultReader } from 'stream/web';
|
||||||
|
|
||||||
// Global protocol APIs.
|
// Global protocol APIs.
|
||||||
const { registerSchemesAsPrivileged, getStandardSchemes, Protocol } = process._linkedBinding('electron_browser_protocol');
|
const { registerSchemesAsPrivileged, getStandardSchemes, Protocol } =
|
||||||
|
process._linkedBinding('electron_browser_protocol');
|
||||||
|
|
||||||
const ERR_FAILED = -2;
|
const ERR_FAILED = -2;
|
||||||
const ERR_UNEXPECTED = -9;
|
const ERR_UNEXPECTED = -9;
|
||||||
|
|
||||||
const isBuiltInScheme = (scheme: string) => ['http', 'https', 'file'].includes(scheme);
|
const isBuiltInScheme = (scheme: string) => ['http', 'https', 'file'].includes(scheme);
|
||||||
|
|
||||||
function makeStreamFromPipe (pipe: any): ReadableStream<Uint8Array> {
|
function makeStreamFromPipe(pipe: any): ReadableStream<Uint8Array> {
|
||||||
const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
|
const buf = new Uint8Array(1024 * 1024 /* 1 MB */);
|
||||||
return new ReadableStream({
|
return new ReadableStream({
|
||||||
async pull (controller) {
|
async pull(controller) {
|
||||||
try {
|
try {
|
||||||
const rv = await pipe.read(buf);
|
const rv = await pipe.read(buf);
|
||||||
if (rv > 0) {
|
if (rv > 0) {
|
||||||
@@ -32,7 +33,7 @@ function makeStreamFromPipe (pipe: any): ReadableStream<Uint8Array> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeStreamFromFileInfo ({
|
function makeStreamFromFileInfo({
|
||||||
filePath,
|
filePath,
|
||||||
offset = 0,
|
offset = 0,
|
||||||
length = -1
|
length = -1
|
||||||
@@ -42,13 +43,15 @@ function makeStreamFromFileInfo ({
|
|||||||
length?: number;
|
length?: number;
|
||||||
}): ReadableStream<Uint8Array> {
|
}): ReadableStream<Uint8Array> {
|
||||||
// Node's Readable.toWeb produces a WHATWG ReadableStream whose chunks are Uint8Array.
|
// Node's Readable.toWeb produces a WHATWG ReadableStream whose chunks are Uint8Array.
|
||||||
return Readable.toWeb(createReadStream(filePath, {
|
return Readable.toWeb(
|
||||||
start: offset,
|
createReadStream(filePath, {
|
||||||
end: length >= 0 ? offset + length : undefined
|
start: offset,
|
||||||
})) as ReadableStream<Uint8Array>;
|
end: length >= 0 ? offset + length : undefined
|
||||||
|
})
|
||||||
|
) as ReadableStream<Uint8Array>;
|
||||||
}
|
}
|
||||||
|
|
||||||
function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): RequestInit['body'] {
|
function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): RequestInit['body'] {
|
||||||
if (!uploadData) return null;
|
if (!uploadData) return null;
|
||||||
// Optimization: skip creating a stream if the request is just a single buffer.
|
// Optimization: skip creating a stream if the request is just a single buffer.
|
||||||
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') {
|
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') {
|
||||||
@@ -60,7 +63,7 @@ function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): Reque
|
|||||||
// Generic <Uint8Array> ensures reader.read() returns value?: Uint8Array consistent with enqueue.
|
// Generic <Uint8Array> ensures reader.read() returns value?: Uint8Array consistent with enqueue.
|
||||||
let current: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
let current: ReadableStreamDefaultReader<Uint8Array> | null = null;
|
||||||
return new ReadableStream<Uint8Array>({
|
return new ReadableStream<Uint8Array>({
|
||||||
async pull (controller) {
|
async pull(controller) {
|
||||||
if (current) {
|
if (current) {
|
||||||
const { done, value } = await current.read();
|
const { done, value } = await current.read();
|
||||||
// (done => value === undefined) as per WHATWG spec
|
// (done => value === undefined) as per WHATWG spec
|
||||||
@@ -71,7 +74,9 @@ function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): Reque
|
|||||||
controller.enqueue(value);
|
controller.enqueue(value);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!chunks.length) { return controller.close(); }
|
if (!chunks.length) {
|
||||||
|
return controller.close();
|
||||||
|
}
|
||||||
const chunk = chunks.shift()!;
|
const chunk = chunks.shift()!;
|
||||||
if (chunk.type === 'rawData') {
|
if (chunk.type === 'rawData') {
|
||||||
controller.enqueue(chunk.bytes as Uint8Array);
|
controller.enqueue(chunk.bytes as Uint8Array);
|
||||||
@@ -96,7 +101,7 @@ function convertToRequestBody (uploadData: ProtocolRequest['uploadData']): Reque
|
|||||||
}) as RequestInit['body'];
|
}) as RequestInit['body'];
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateResponse (res: Response) {
|
function validateResponse(res: Response) {
|
||||||
if (!res || typeof res !== 'object') return false;
|
if (!res || typeof res !== 'object') return false;
|
||||||
|
|
||||||
if (res.type === 'error') return true;
|
if (res.type === 'error') return true;
|
||||||
@@ -115,7 +120,11 @@ function validateResponse (res: Response) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
Protocol.prototype.handle = function (this: Electron.Protocol, scheme: string, handler: (req: Request) => Response | Promise<Response>) {
|
Protocol.prototype.handle = function (
|
||||||
|
this: Electron.Protocol,
|
||||||
|
scheme: string,
|
||||||
|
handler: (req: Request) => Response | Promise<Response>
|
||||||
|
) {
|
||||||
const register = isBuiltInScheme(scheme) ? this.interceptProtocol : this.registerProtocol;
|
const register = isBuiltInScheme(scheme) ? this.interceptProtocol : this.registerProtocol;
|
||||||
const success = register.call(this, scheme, async (preq: ProtocolRequest, cb: any) => {
|
const success = register.call(this, scheme, async (preq: ProtocolRequest, cb: any) => {
|
||||||
try {
|
try {
|
||||||
@@ -155,7 +164,9 @@ Protocol.prototype.handle = function (this: Electron.Protocol, scheme: string, h
|
|||||||
|
|
||||||
Protocol.prototype.unhandle = function (this: Electron.Protocol, scheme: string) {
|
Protocol.prototype.unhandle = function (this: Electron.Protocol, scheme: string) {
|
||||||
const unregister = isBuiltInScheme(scheme) ? this.uninterceptProtocol : this.unregisterProtocol;
|
const unregister = isBuiltInScheme(scheme) ? this.uninterceptProtocol : this.unregisterProtocol;
|
||||||
if (!unregister.call(this, scheme)) { throw new Error(`Failed to unhandle protocol: ${scheme}`); }
|
if (!unregister.call(this, scheme)) {
|
||||||
|
throw new Error(`Failed to unhandle protocol: ${scheme}`);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Protocol.prototype.isProtocolHandled = function (this: Electron.Protocol, scheme: string) {
|
Protocol.prototype.isProtocolHandled = function (this: Electron.Protocol, scheme: string) {
|
||||||
|
|||||||
@@ -14,35 +14,38 @@ const createScreenIfNeeded = () => {
|
|||||||
// exposes an instance created by createScreen. In order to avoid
|
// exposes an instance created by createScreen. In order to avoid
|
||||||
// side-effecting and calling createScreen upon import of this module, instead
|
// side-effecting and calling createScreen upon import of this module, instead
|
||||||
// we export a proxy which lazily calls createScreen on first access.
|
// we export a proxy which lazily calls createScreen on first access.
|
||||||
export default new Proxy({}, {
|
export default new Proxy(
|
||||||
get: (target, property: keyof Electron.Screen) => {
|
{},
|
||||||
createScreenIfNeeded();
|
{
|
||||||
const value = _screen[property];
|
get: (target, property: keyof Electron.Screen) => {
|
||||||
if (typeof value === 'function') {
|
createScreenIfNeeded();
|
||||||
return value.bind(_screen);
|
const value = _screen[property];
|
||||||
|
if (typeof value === 'function') {
|
||||||
|
return value.bind(_screen);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
},
|
||||||
|
set: (target, property: string, value: unknown) => {
|
||||||
|
createScreenIfNeeded();
|
||||||
|
return Reflect.set(_screen, property, value);
|
||||||
|
},
|
||||||
|
ownKeys: () => {
|
||||||
|
createScreenIfNeeded();
|
||||||
|
return Reflect.ownKeys(_screen);
|
||||||
|
},
|
||||||
|
has: (target, property: string) => {
|
||||||
|
createScreenIfNeeded();
|
||||||
|
return property in _screen;
|
||||||
|
},
|
||||||
|
getOwnPropertyDescriptor: (target, property: string) => {
|
||||||
|
createScreenIfNeeded();
|
||||||
|
return Reflect.getOwnPropertyDescriptor(_screen, property);
|
||||||
|
},
|
||||||
|
getPrototypeOf: () => {
|
||||||
|
// This is necessary as a result of weirdness with EventEmitterMixin
|
||||||
|
// and FunctionTemplate - we need to explicitly ensure it's returned
|
||||||
|
// in the prototype.
|
||||||
|
return EventEmitter.prototype;
|
||||||
}
|
}
|
||||||
return value;
|
|
||||||
},
|
|
||||||
set: (target, property: string, value: unknown) => {
|
|
||||||
createScreenIfNeeded();
|
|
||||||
return Reflect.set(_screen, property, value);
|
|
||||||
},
|
|
||||||
ownKeys: () => {
|
|
||||||
createScreenIfNeeded();
|
|
||||||
return Reflect.ownKeys(_screen);
|
|
||||||
},
|
|
||||||
has: (target, property: string) => {
|
|
||||||
createScreenIfNeeded();
|
|
||||||
return property in _screen;
|
|
||||||
},
|
|
||||||
getOwnPropertyDescriptor: (target, property: string) => {
|
|
||||||
createScreenIfNeeded();
|
|
||||||
return Reflect.getOwnPropertyDescriptor(_screen, property);
|
|
||||||
},
|
|
||||||
getPrototypeOf: () => {
|
|
||||||
// This is necessary as a result of weirdness with EventEmitterMixin
|
|
||||||
// and FunctionTemplate - we need to explicitly ensure it's returned
|
|
||||||
// in the prototype.
|
|
||||||
return EventEmitter.prototype;
|
|
||||||
}
|
}
|
||||||
});
|
);
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
|||||||
const { ServiceWorkerMain } = process._linkedBinding('electron_browser_service_worker_main');
|
const { ServiceWorkerMain } = process._linkedBinding('electron_browser_service_worker_main');
|
||||||
|
|
||||||
Object.defineProperty(ServiceWorkerMain.prototype, 'ipc', {
|
Object.defineProperty(ServiceWorkerMain.prototype, 'ipc', {
|
||||||
get () {
|
get() {
|
||||||
const ipc = new IpcMainImpl();
|
const ipc = new IpcMainImpl();
|
||||||
Object.defineProperty(this, 'ipc', { value: ipc });
|
Object.defineProperty(this, 'ipc', { value: ipc });
|
||||||
return ipc;
|
return ipc;
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ let fakeVideoWindowId = -1;
|
|||||||
const kMacOsNativePickerId = -4;
|
const kMacOsNativePickerId = -4;
|
||||||
const systemPickerVideoSource = Object.create(null);
|
const systemPickerVideoSource = Object.create(null);
|
||||||
Object.defineProperty(systemPickerVideoSource, 'id', {
|
Object.defineProperty(systemPickerVideoSource, 'id', {
|
||||||
get () {
|
get() {
|
||||||
return `window:${kMacOsNativePickerId}:${fakeVideoWindowId--}`;
|
return `window:${kMacOsNativePickerId}:${fakeVideoWindowId--}`;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -73,13 +73,18 @@ Session.prototype.setPreloads = function (preloads) {
|
|||||||
.forEach((script) => {
|
.forEach((script) => {
|
||||||
this.unregisterPreloadScript(script.id);
|
this.unregisterPreloadScript(script.id);
|
||||||
});
|
});
|
||||||
preloads.map(filePath => ({
|
preloads
|
||||||
type: 'frame',
|
.map(
|
||||||
filePath,
|
(filePath) =>
|
||||||
_deprecated: true
|
({
|
||||||
}) as Electron.PreloadScriptRegistration).forEach(script => {
|
type: 'frame',
|
||||||
this.registerPreloadScript(script);
|
filePath,
|
||||||
});
|
_deprecated: true
|
||||||
|
}) as Electron.PreloadScriptRegistration
|
||||||
|
)
|
||||||
|
.forEach((script) => {
|
||||||
|
this.registerPreloadScript(script);
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
Session.prototype.getAllExtensions = deprecate.moveAPI(
|
Session.prototype.getAllExtensions = deprecate.moveAPI(
|
||||||
@@ -114,7 +119,7 @@ Session.prototype.removeExtension = deprecate.moveAPI(
|
|||||||
export default {
|
export default {
|
||||||
fromPartition,
|
fromPartition,
|
||||||
fromPath,
|
fromPath,
|
||||||
get defaultSession () {
|
get defaultSession() {
|
||||||
return fromPartition('');
|
return fromPartition('');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
import { BrowserWindow, Menu, SharingItem, PopupOptions } from 'electron/main';
|
import { BrowserWindow, Menu, SharingItem, PopupOptions } from 'electron/main';
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
class ShareMenu implements Electron.ShareMenu {
|
||||||
|
|
||||||
class ShareMenu extends EventEmitter implements Electron.ShareMenu {
|
|
||||||
private menu: Menu;
|
private menu: Menu;
|
||||||
|
|
||||||
constructor (sharingItem: SharingItem) {
|
constructor(sharingItem: SharingItem) {
|
||||||
super();
|
|
||||||
this.menu = new (Menu as any)({ sharingItem });
|
this.menu = new (Menu as any)({ sharingItem });
|
||||||
}
|
}
|
||||||
|
|
||||||
popup (options?: PopupOptions) {
|
popup(options?: PopupOptions) {
|
||||||
this.menu.popup(options);
|
this.menu.popup(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
closePopup (browserWindow?: BrowserWindow) {
|
closePopup(browserWindow?: BrowserWindow) {
|
||||||
this.menu.closePopup(browserWindow);
|
this.menu.closePopup(browserWindow);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,36 +14,41 @@ type SharedTextureImportedWrapper = {
|
|||||||
texture: Electron.SharedTextureImported;
|
texture: Electron.SharedTextureImported;
|
||||||
allReferencesReleased: AllReleasedCallback | undefined;
|
allReferencesReleased: AllReleasedCallback | undefined;
|
||||||
mainReference: boolean;
|
mainReference: boolean;
|
||||||
rendererFrameReferences: Map<number, { count: number, reference: Electron.WebFrameMain }>;
|
rendererFrameReferences: Map<number, { count: number; reference: Electron.WebFrameMain }>;
|
||||||
}
|
};
|
||||||
|
|
||||||
ipcMain.handle(IPC_MESSAGES.IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN, (event: Electron.IpcMainInvokeEvent, textureId: string) => {
|
ipcMain.handle(
|
||||||
const frameTreeNodeId = event.frameTreeNodeId ?? event.sender.mainFrame.frameTreeNodeId;
|
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN,
|
||||||
wrapperReleaseFromRenderer(textureId, frameTreeNodeId);
|
(event: Electron.IpcMainInvokeEvent, textureId: string) => {
|
||||||
});
|
const frameTreeNodeId = event.frameTreeNodeId ?? event.sender.mainFrame.frameTreeNodeId;
|
||||||
|
wrapperReleaseFromRenderer(textureId, frameTreeNodeId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
let checkManagedSharedTexturesInterval: NodeJS.Timeout | null = null;
|
let checkManagedSharedTexturesInterval: NodeJS.Timeout | null = null;
|
||||||
|
|
||||||
function scheduleCheckManagedSharedTextures () {
|
function scheduleCheckManagedSharedTextures() {
|
||||||
if (checkManagedSharedTexturesInterval === null) {
|
if (checkManagedSharedTexturesInterval === null) {
|
||||||
checkManagedSharedTexturesInterval = setInterval(checkManagedSharedTextures, 1000);
|
checkManagedSharedTexturesInterval = setInterval(checkManagedSharedTextures, 1000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function unscheduleCheckManagedSharedTextures () {
|
function unscheduleCheckManagedSharedTextures() {
|
||||||
if (checkManagedSharedTexturesInterval !== null) {
|
if (checkManagedSharedTexturesInterval !== null) {
|
||||||
clearInterval(checkManagedSharedTexturesInterval);
|
clearInterval(checkManagedSharedTexturesInterval);
|
||||||
checkManagedSharedTexturesInterval = null;
|
checkManagedSharedTexturesInterval = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkManagedSharedTextures () {
|
function checkManagedSharedTextures() {
|
||||||
const texturesToRemoveTracking = new Set<string>();
|
const texturesToRemoveTracking = new Set<string>();
|
||||||
for (const [, wrapper] of managedSharedTextures) {
|
for (const [, wrapper] of managedSharedTextures) {
|
||||||
for (const [frameTreeNodeId, entry] of wrapper.rendererFrameReferences) {
|
for (const [frameTreeNodeId, entry] of wrapper.rendererFrameReferences) {
|
||||||
const frame = entry.reference;
|
const frame = entry.reference;
|
||||||
if (!frame || frame.isDestroyed()) {
|
if (!frame || frame.isDestroyed()) {
|
||||||
console.error(`The imported shared texture ${wrapper.texture.textureId} is referenced by a destroyed webContent/webFrameMain, this means a imported shared texture in renderer process is not released before the process is exited. Releasing that dangling reference now.`);
|
console.error(
|
||||||
|
`The imported shared texture ${wrapper.texture.textureId} is referenced by a destroyed webContent/webFrameMain, this means a imported shared texture in renderer process is not released before the process is exited. Releasing that dangling reference now.`
|
||||||
|
);
|
||||||
wrapper.rendererFrameReferences.delete(frameTreeNodeId);
|
wrapper.rendererFrameReferences.delete(frameTreeNodeId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -65,7 +70,7 @@ function checkManagedSharedTextures () {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapperReleaseFromRenderer (id: string, frameTreeNodeId: number) {
|
function wrapperReleaseFromRenderer(id: string, frameTreeNodeId: number) {
|
||||||
const wrapper = managedSharedTextures.get(id);
|
const wrapper = managedSharedTextures.get(id);
|
||||||
if (!wrapper) {
|
if (!wrapper) {
|
||||||
throw new Error(`Shared texture with id ${id} not found`);
|
throw new Error(`Shared texture with id ${id} not found`);
|
||||||
@@ -92,7 +97,7 @@ function wrapperReleaseFromRenderer (id: string, frameTreeNodeId: number) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function wrapperReleaseFromMain (id: string) {
|
function wrapperReleaseFromMain(id: string) {
|
||||||
const wrapper = managedSharedTextures.get(id);
|
const wrapper = managedSharedTextures.get(id);
|
||||||
if (!wrapper) {
|
if (!wrapper) {
|
||||||
throw new Error(`Shared texture with id ${id} not found`);
|
throw new Error(`Shared texture with id ${id} not found`);
|
||||||
@@ -108,14 +113,18 @@ function wrapperReleaseFromMain (id: string) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function sendSharedTexture (options: Electron.SendSharedTextureOptions, ...args: any[]) {
|
async function sendSharedTexture(options: Electron.SendSharedTextureOptions, ...args: any[]) {
|
||||||
const imported = options.importedSharedTexture;
|
const imported = options.importedSharedTexture;
|
||||||
const transfer = imported.subtle.startTransferSharedTexture();
|
const transfer = imported.subtle.startTransferSharedTexture();
|
||||||
|
|
||||||
let timeoutHandle: NodeJS.Timeout | null = null;
|
let timeoutHandle: NodeJS.Timeout | null = null;
|
||||||
const timeoutPromise = new Promise<never>((resolve, reject) => {
|
const timeoutPromise = new Promise<never>((resolve, reject) => {
|
||||||
timeoutHandle = setTimeout(() => {
|
timeoutHandle = setTimeout(() => {
|
||||||
reject(new Error(`transfer shared texture timed out after ${transferTimeout}ms, ensure you have registered receiver at renderer process.`));
|
reject(
|
||||||
|
new Error(
|
||||||
|
`transfer shared texture timed out after ${transferTimeout}ms, ensure you have registered receiver at renderer process.`
|
||||||
|
)
|
||||||
|
);
|
||||||
}, transferTimeout);
|
}, transferTimeout);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -124,13 +133,14 @@ async function sendSharedTexture (options: Electron.SendSharedTextureOptions, ..
|
|||||||
throw new Error('`frame` should be provided');
|
throw new Error('`frame` should be provided');
|
||||||
}
|
}
|
||||||
|
|
||||||
const invokePromise: Promise<Electron.SharedTextureSyncToken> = ipcMainInternalUtils.invokeInWebFrameMain<Electron.SharedTextureSyncToken>(
|
const invokePromise: Promise<Electron.SharedTextureSyncToken> =
|
||||||
targetFrame,
|
ipcMainInternalUtils.invokeInWebFrameMain<Electron.SharedTextureSyncToken>(
|
||||||
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER,
|
targetFrame,
|
||||||
transfer,
|
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER,
|
||||||
imported.textureId,
|
transfer,
|
||||||
...args
|
imported.textureId,
|
||||||
);
|
...args
|
||||||
|
);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const syncToken = await Promise.race([invokePromise, timeoutPromise]);
|
const syncToken = await Promise.race([invokePromise, timeoutPromise]);
|
||||||
@@ -159,7 +169,7 @@ async function sendSharedTexture (options: Electron.SendSharedTextureOptions, ..
|
|||||||
scheduleCheckManagedSharedTextures();
|
scheduleCheckManagedSharedTextures();
|
||||||
}
|
}
|
||||||
|
|
||||||
function importSharedTexture (options: Electron.ImportSharedTextureOptions) {
|
function importSharedTexture(options: Electron.ImportSharedTextureOptions) {
|
||||||
const id = randomUUID();
|
const id = randomUUID();
|
||||||
const imported = sharedTextureNative.importSharedTexture(Object.assign(options.textureInfo, { id }));
|
const imported = sharedTextureNative.importSharedTexture(Object.assign(options.textureInfo, { id }));
|
||||||
const ret: Electron.SharedTextureImported = {
|
const ret: Electron.SharedTextureImported = {
|
||||||
|
|||||||
@@ -10,7 +10,10 @@ if ('getEffectiveAppearance' in systemPreferences) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if ('accessibilityDisplayShouldReduceTransparency' in systemPreferences) {
|
if ('accessibilityDisplayShouldReduceTransparency' in systemPreferences) {
|
||||||
const reduceTransparencyDeprecated = deprecate.warnOnce('systemPreferences.accessibilityDisplayShouldReduceTransparency', 'nativeTheme.prefersReducedTransparency');
|
const reduceTransparencyDeprecated = deprecate.warnOnce(
|
||||||
|
'systemPreferences.accessibilityDisplayShouldReduceTransparency',
|
||||||
|
'nativeTheme.prefersReducedTransparency'
|
||||||
|
);
|
||||||
const nativeReduceTransparency = systemPreferences.accessibilityDisplayShouldReduceTransparency;
|
const nativeReduceTransparency = systemPreferences.accessibilityDisplayShouldReduceTransparency;
|
||||||
Object.defineProperty(systemPreferences, 'accessibilityDisplayShouldReduceTransparency', {
|
Object.defineProperty(systemPreferences, 'accessibilityDisplayShouldReduceTransparency', {
|
||||||
get: () => {
|
get: () => {
|
||||||
|
|||||||
@@ -12,41 +12,53 @@ const extendConstructHook = (target: any, hook: Function) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const ImmutableProperty = <T extends TouchBarItem<any>>(def: (config: T extends TouchBarItem<infer C> ? C : never, setInternalProp: <K extends keyof T>(k: K, v: T[K]) => void) => any) => (target: T, propertyKey: keyof T) => {
|
const ImmutableProperty =
|
||||||
extendConstructHook(target, function (this: T) {
|
<T extends TouchBarItem<any>>(
|
||||||
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config, (k, v) => {
|
def: (
|
||||||
(this as any)[hiddenProperties][k] = v;
|
config: T extends TouchBarItem<infer C> ? C : never,
|
||||||
|
setInternalProp: <K extends keyof T>(k: K, v: T[K]) => void
|
||||||
|
) => any
|
||||||
|
) =>
|
||||||
|
(target: T, propertyKey: keyof T) => {
|
||||||
|
extendConstructHook(target, function (this: T) {
|
||||||
|
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config, (k, v) => {
|
||||||
|
(this as any)[hiddenProperties][k] = v;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
Object.defineProperty(target, propertyKey, {
|
||||||
Object.defineProperty(target, propertyKey, {
|
get: function () {
|
||||||
get: function () {
|
return this[hiddenProperties][propertyKey];
|
||||||
return this[hiddenProperties][propertyKey];
|
},
|
||||||
},
|
set: function () {
|
||||||
set: function () {
|
throw new Error(`Cannot override property ${name}`);
|
||||||
throw new Error(`Cannot override property ${name}`);
|
},
|
||||||
},
|
enumerable: true,
|
||||||
enumerable: true,
|
configurable: false
|
||||||
configurable: false
|
});
|
||||||
});
|
};
|
||||||
};
|
|
||||||
|
|
||||||
const LiveProperty = <T extends TouchBarItem<any>>(def: (config: T extends TouchBarItem<infer C> ? C : never) => any, onMutate?: (self: T, newValue: any) => void) => (target: T, propertyKey: keyof T) => {
|
const LiveProperty =
|
||||||
extendConstructHook(target, function (this: T) {
|
<T extends TouchBarItem<any>>(
|
||||||
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config);
|
def: (config: T extends TouchBarItem<infer C> ? C : never) => any,
|
||||||
if (onMutate) onMutate((this as any), (this as any)[hiddenProperties][propertyKey]);
|
onMutate?: (self: T, newValue: any) => void
|
||||||
});
|
) =>
|
||||||
Object.defineProperty(target, propertyKey, {
|
(target: T, propertyKey: keyof T) => {
|
||||||
get: function () {
|
extendConstructHook(target, function (this: T) {
|
||||||
return this[hiddenProperties][propertyKey];
|
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config);
|
||||||
},
|
if (onMutate) onMutate(this as any, (this as any)[hiddenProperties][propertyKey]);
|
||||||
set: function (value) {
|
});
|
||||||
if (onMutate) onMutate((this as any), value);
|
Object.defineProperty(target, propertyKey, {
|
||||||
this[hiddenProperties][propertyKey] = value;
|
get: function () {
|
||||||
this.emit('change', this);
|
return this[hiddenProperties][propertyKey];
|
||||||
},
|
},
|
||||||
enumerable: true
|
set: function (value) {
|
||||||
});
|
if (onMutate) onMutate(this as any, value);
|
||||||
};
|
this[hiddenProperties][propertyKey] = value;
|
||||||
|
this.emit('change', this);
|
||||||
|
},
|
||||||
|
enumerable: true
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
abstract class TouchBarItem<ConfigType> extends EventEmitter {
|
abstract class TouchBarItem<ConfigType> extends EventEmitter {
|
||||||
@ImmutableProperty(() => `${nextItemID++}`) id!: string;
|
@ImmutableProperty(() => `${nextItemID++}`) id!: string;
|
||||||
@@ -57,17 +69,17 @@ abstract class TouchBarItem<ConfigType> extends EventEmitter {
|
|||||||
private _parents: { id: string; type: string }[] = [];
|
private _parents: { id: string; type: string }[] = [];
|
||||||
private _config!: ConfigType;
|
private _config!: ConfigType;
|
||||||
|
|
||||||
constructor (config: ConfigType) {
|
constructor(config: ConfigType) {
|
||||||
super();
|
super();
|
||||||
this._config = this._config || config || {} as ConfigType;
|
this._config = this._config || config || ({} as ConfigType);
|
||||||
(this as any)[hiddenProperties] = {};
|
(this as any)[hiddenProperties] = {};
|
||||||
const hook = (this as any)._hook;
|
const hook = (this as any)._hook;
|
||||||
if (hook) hook.call(this);
|
if (hook) hook.call(this);
|
||||||
delete (this as any)._hook;
|
delete (this as any)._hook;
|
||||||
}
|
}
|
||||||
|
|
||||||
public _addParent (item: TouchBarItem<any>) {
|
public _addParent(item: TouchBarItem<any>) {
|
||||||
const existing = this._parents.some(test => test.id === item.id);
|
const existing = this._parents.some((test) => test.id === item.id);
|
||||||
if (!existing) {
|
if (!existing) {
|
||||||
this._parents.push({
|
this._parents.push({
|
||||||
id: item.id,
|
id: item.id,
|
||||||
@@ -76,211 +88,246 @@ abstract class TouchBarItem<ConfigType> extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public _removeParent (item: TouchBarItem<any>) {
|
public _removeParent(item: TouchBarItem<any>) {
|
||||||
this._parents = this._parents.filter(test => test.id !== item.id);
|
this._parents = this._parents.filter((test) => test.id !== item.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class TouchBarButton extends TouchBarItem<Electron.TouchBarButtonConstructorOptions> implements Electron.TouchBarButton {
|
class TouchBarButton
|
||||||
|
extends TouchBarItem<Electron.TouchBarButtonConstructorOptions>
|
||||||
|
implements Electron.TouchBarButton
|
||||||
|
{
|
||||||
@ImmutableProperty(() => 'button')
|
@ImmutableProperty(() => 'button')
|
||||||
type!: string;
|
type!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarButton>(config => config.label)
|
@LiveProperty<TouchBarButton>((config) => config.label)
|
||||||
label!: string;
|
label!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarButton>(config => config.accessibilityLabel)
|
@LiveProperty<TouchBarButton>((config) => config.accessibilityLabel)
|
||||||
accessibilityLabel!: string;
|
accessibilityLabel!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarButton>(config => config.backgroundColor)
|
@LiveProperty<TouchBarButton>((config) => config.backgroundColor)
|
||||||
backgroundColor!: string;
|
backgroundColor!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarButton>(config => config.icon)
|
@LiveProperty<TouchBarButton>((config) => config.icon)
|
||||||
icon!: Electron.NativeImage;
|
icon!: Electron.NativeImage;
|
||||||
|
|
||||||
@LiveProperty<TouchBarButton>(config => config.iconPosition)
|
@LiveProperty<TouchBarButton>((config) => config.iconPosition)
|
||||||
iconPosition!: Electron.TouchBarButton['iconPosition'];
|
iconPosition!: Electron.TouchBarButton['iconPosition'];
|
||||||
|
|
||||||
@LiveProperty<TouchBarButton>(config => typeof config.enabled !== 'boolean' ? true : config.enabled)
|
@LiveProperty<TouchBarButton>((config) => (typeof config.enabled !== 'boolean' ? true : config.enabled))
|
||||||
enabled!: boolean;
|
enabled!: boolean;
|
||||||
|
|
||||||
@ImmutableProperty<TouchBarButton>(({ click: onClick }) => typeof onClick === 'function' ? () => onClick() : null)
|
@ImmutableProperty<TouchBarButton>(({ click: onClick }) => (typeof onClick === 'function' ? () => onClick() : null))
|
||||||
onInteraction!: Function | null;
|
onInteraction!: Function | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TouchBarColorPicker extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions> implements Electron.TouchBarColorPicker {
|
class TouchBarColorPicker
|
||||||
|
extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions>
|
||||||
|
implements Electron.TouchBarColorPicker
|
||||||
|
{
|
||||||
@ImmutableProperty(() => 'colorpicker')
|
@ImmutableProperty(() => 'colorpicker')
|
||||||
type!: string;
|
type!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarColorPicker>(config => config.availableColors)
|
@LiveProperty<TouchBarColorPicker>((config) => config.availableColors)
|
||||||
availableColors!: string[];
|
availableColors!: string[];
|
||||||
|
|
||||||
@LiveProperty<TouchBarColorPicker>(config => config.selectedColor)
|
@LiveProperty<TouchBarColorPicker>((config) => config.selectedColor)
|
||||||
selectedColor!: string;
|
selectedColor!: string;
|
||||||
|
|
||||||
@ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) => typeof onChange === 'function'
|
@ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) =>
|
||||||
? (details: { color: string }) => {
|
typeof onChange === 'function'
|
||||||
setInternalProp('selectedColor', details.color);
|
? (details: { color: string }) => {
|
||||||
onChange(details.color);
|
setInternalProp('selectedColor', details.color);
|
||||||
}
|
onChange(details.color);
|
||||||
: null)
|
}
|
||||||
onInteraction!: Function | null;
|
: null
|
||||||
|
)
|
||||||
|
onInteraction!: Function | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TouchBarGroup extends TouchBarItem<Electron.TouchBarGroupConstructorOptions> implements Electron.TouchBarGroup {
|
class TouchBarGroup extends TouchBarItem<Electron.TouchBarGroupConstructorOptions> implements Electron.TouchBarGroup {
|
||||||
@ImmutableProperty(() => 'group')
|
@ImmutableProperty(() => 'group')
|
||||||
type!: string;
|
type!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarGroup>(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => {
|
@LiveProperty<TouchBarGroup>(
|
||||||
if (self.child) {
|
(config) => (config.items instanceof TouchBar ? config.items : new TouchBar(config.items)),
|
||||||
for (const item of self.child.orderedItems) {
|
(self, newChild: TouchBar) => {
|
||||||
item._removeParent(self);
|
if (self.child) {
|
||||||
|
for (const item of self.child.orderedItems) {
|
||||||
|
item._removeParent(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const item of newChild.orderedItems) {
|
||||||
|
item._addParent(self);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const item of newChild.orderedItems) {
|
)
|
||||||
item._addParent(self);
|
child!: TouchBar;
|
||||||
}
|
|
||||||
})
|
|
||||||
child!: TouchBar;
|
|
||||||
|
|
||||||
onInteraction = null;
|
onInteraction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TouchBarLabel extends TouchBarItem<Electron.TouchBarLabelConstructorOptions> implements Electron.TouchBarLabel {
|
class TouchBarLabel extends TouchBarItem<Electron.TouchBarLabelConstructorOptions> implements Electron.TouchBarLabel {
|
||||||
@ImmutableProperty(() => 'label')
|
@ImmutableProperty(() => 'label')
|
||||||
type!: string;
|
type!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarLabel>(config => config.label)
|
@LiveProperty<TouchBarLabel>((config) => config.label)
|
||||||
label!: string;
|
label!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarLabel>(config => config.accessibilityLabel)
|
@LiveProperty<TouchBarLabel>((config) => config.accessibilityLabel)
|
||||||
accessibilityLabel!: string;
|
accessibilityLabel!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarLabel>(config => config.textColor)
|
@LiveProperty<TouchBarLabel>((config) => config.textColor)
|
||||||
textColor!: string;
|
textColor!: string;
|
||||||
|
|
||||||
onInteraction = null;
|
onInteraction = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TouchBarPopover extends TouchBarItem<Electron.TouchBarPopoverConstructorOptions> implements Electron.TouchBarPopover {
|
class TouchBarPopover
|
||||||
|
extends TouchBarItem<Electron.TouchBarPopoverConstructorOptions>
|
||||||
|
implements Electron.TouchBarPopover
|
||||||
|
{
|
||||||
@ImmutableProperty(() => 'popover')
|
@ImmutableProperty(() => 'popover')
|
||||||
type!: string;
|
type!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarPopover>(config => config.label)
|
@LiveProperty<TouchBarPopover>((config) => config.label)
|
||||||
label!: string;
|
label!: string;
|
||||||
|
|
||||||
@LiveProperty<TouchBarPopover>(config => config.icon)
|
@LiveProperty<TouchBarPopover>((config) => config.icon)
|
||||||
icon!: Electron.NativeImage;
|
icon!: Electron.NativeImage;
|
||||||
|
|
||||||
@LiveProperty<TouchBarPopover>(config => config.showCloseButton)
|
@LiveProperty<TouchBarPopover>((config) => config.showCloseButton)
|
||||||
showCloseButton!: boolean;
|
showCloseButton!: boolean;
|
||||||
|
|
||||||
@LiveProperty<TouchBarPopover>(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => {
|
@LiveProperty<TouchBarPopover>(
|
||||||
if (self.child) {
|
(config) => (config.items instanceof TouchBar ? config.items : new TouchBar(config.items)),
|
||||||
for (const item of self.child.orderedItems) {
|
(self, newChild: TouchBar) => {
|
||||||
item._removeParent(self);
|
if (self.child) {
|
||||||
}
|
for (const item of self.child.orderedItems) {
|
||||||
}
|
item._removeParent(self);
|
||||||
for (const item of newChild.orderedItems) {
|
|
||||||
item._addParent(self);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
child!: TouchBar;
|
|
||||||
|
|
||||||
onInteraction = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TouchBarSlider extends TouchBarItem<Electron.TouchBarSliderConstructorOptions> implements Electron.TouchBarSlider {
|
|
||||||
@ImmutableProperty(() => 'slider')
|
|
||||||
type!: string;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSlider>(config => config.label)
|
|
||||||
label!: string;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSlider>(config => config.minValue)
|
|
||||||
minValue!: number;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSlider>(config => config.maxValue)
|
|
||||||
maxValue!: number;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSlider>(config => config.value)
|
|
||||||
value!: number;
|
|
||||||
|
|
||||||
@ImmutableProperty<TouchBarSlider>(({ change: onChange }, setInternalProp) => typeof onChange === 'function'
|
|
||||||
? (details: { value: number }) => {
|
|
||||||
setInternalProp('value', details.value);
|
|
||||||
onChange(details.value);
|
|
||||||
}
|
|
||||||
: null)
|
|
||||||
onInteraction!: Function | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TouchBarSpacer extends TouchBarItem<Electron.TouchBarSpacerConstructorOptions> implements Electron.TouchBarSpacer {
|
|
||||||
@ImmutableProperty(() => 'spacer')
|
|
||||||
type!: string;
|
|
||||||
|
|
||||||
@ImmutableProperty<TouchBarSpacer>(config => config.size)
|
|
||||||
size!: Electron.TouchBarSpacer['size'];
|
|
||||||
|
|
||||||
onInteraction = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TouchBarSegmentedControl extends TouchBarItem<Electron.TouchBarSegmentedControlConstructorOptions> implements Electron.TouchBarSegmentedControl {
|
|
||||||
@ImmutableProperty(() => 'segmented_control')
|
|
||||||
type!: string;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSegmentedControl>(config => config.segmentStyle)
|
|
||||||
segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle'];
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSegmentedControl>(config => config.segments || [])
|
|
||||||
segments!: Electron.SegmentedControlSegment[];
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSegmentedControl>(config => config.selectedIndex)
|
|
||||||
selectedIndex!: number;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarSegmentedControl>(config => config.mode)
|
|
||||||
mode!: Electron.TouchBarSegmentedControl['mode'];
|
|
||||||
|
|
||||||
@ImmutableProperty<TouchBarSegmentedControl>(({ change: onChange }, setInternalProp) => typeof onChange === 'function'
|
|
||||||
? (details: { selectedIndex: number, isSelected: boolean }) => {
|
|
||||||
setInternalProp('selectedIndex', details.selectedIndex);
|
|
||||||
onChange(details.selectedIndex, details.isSelected);
|
|
||||||
}
|
|
||||||
: null)
|
|
||||||
onInteraction!: Function | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
class TouchBarScrubber extends TouchBarItem<Electron.TouchBarScrubberConstructorOptions> implements Electron.TouchBarScrubber {
|
|
||||||
@ImmutableProperty(() => 'scrubber')
|
|
||||||
type!: string;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarScrubber>(config => config.items)
|
|
||||||
items!: Electron.ScrubberItem[];
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarScrubber>(config => config.selectedStyle || null)
|
|
||||||
selectedStyle!: Electron.TouchBarScrubber['selectedStyle'];
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarScrubber>(config => config.overlayStyle || null)
|
|
||||||
overlayStyle!: Electron.TouchBarScrubber['overlayStyle'];
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarScrubber>(config => config.showArrowButtons || false)
|
|
||||||
showArrowButtons!: boolean;
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarScrubber>(config => config.mode || 'free')
|
|
||||||
mode!: Electron.TouchBarScrubber['mode'];
|
|
||||||
|
|
||||||
@LiveProperty<TouchBarScrubber>(config => typeof config.continuous === 'undefined' ? true : config.continuous)
|
|
||||||
continuous!: boolean;
|
|
||||||
|
|
||||||
@ImmutableProperty<TouchBarScrubber>(({ select: onSelect, highlight: onHighlight }) => typeof onSelect === 'function' || typeof onHighlight === 'function'
|
|
||||||
? (details: { type: 'select'; selectedIndex: number } | { type: 'highlight'; highlightedIndex: number }) => {
|
|
||||||
if (details.type === 'select') {
|
|
||||||
if (onSelect) onSelect(details.selectedIndex);
|
|
||||||
} else {
|
|
||||||
if (onHighlight) onHighlight(details.highlightedIndex);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
: null)
|
for (const item of newChild.orderedItems) {
|
||||||
onInteraction!: Function | null;
|
item._addParent(self);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
child!: TouchBar;
|
||||||
|
|
||||||
|
onInteraction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TouchBarSlider
|
||||||
|
extends TouchBarItem<Electron.TouchBarSliderConstructorOptions>
|
||||||
|
implements Electron.TouchBarSlider
|
||||||
|
{
|
||||||
|
@ImmutableProperty(() => 'slider')
|
||||||
|
type!: string;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSlider>((config) => config.label)
|
||||||
|
label!: string;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSlider>((config) => config.minValue)
|
||||||
|
minValue!: number;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSlider>((config) => config.maxValue)
|
||||||
|
maxValue!: number;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSlider>((config) => config.value)
|
||||||
|
value!: number;
|
||||||
|
|
||||||
|
@ImmutableProperty<TouchBarSlider>(({ change: onChange }, setInternalProp) =>
|
||||||
|
typeof onChange === 'function'
|
||||||
|
? (details: { value: number }) => {
|
||||||
|
setInternalProp('value', details.value);
|
||||||
|
onChange(details.value);
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
onInteraction!: Function | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TouchBarSpacer
|
||||||
|
extends TouchBarItem<Electron.TouchBarSpacerConstructorOptions>
|
||||||
|
implements Electron.TouchBarSpacer
|
||||||
|
{
|
||||||
|
@ImmutableProperty(() => 'spacer')
|
||||||
|
type!: string;
|
||||||
|
|
||||||
|
@ImmutableProperty<TouchBarSpacer>((config) => config.size)
|
||||||
|
size!: Electron.TouchBarSpacer['size'];
|
||||||
|
|
||||||
|
onInteraction = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TouchBarSegmentedControl
|
||||||
|
extends TouchBarItem<Electron.TouchBarSegmentedControlConstructorOptions>
|
||||||
|
implements Electron.TouchBarSegmentedControl
|
||||||
|
{
|
||||||
|
@ImmutableProperty(() => 'segmented_control')
|
||||||
|
type!: string;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSegmentedControl>((config) => config.segmentStyle)
|
||||||
|
segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle'];
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSegmentedControl>((config) => config.segments || [])
|
||||||
|
segments!: Electron.SegmentedControlSegment[];
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSegmentedControl>((config) => config.selectedIndex)
|
||||||
|
selectedIndex!: number;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarSegmentedControl>((config) => config.mode)
|
||||||
|
mode!: Electron.TouchBarSegmentedControl['mode'];
|
||||||
|
|
||||||
|
@ImmutableProperty<TouchBarSegmentedControl>(({ change: onChange }, setInternalProp) =>
|
||||||
|
typeof onChange === 'function'
|
||||||
|
? (details: { selectedIndex: number; isSelected: boolean }) => {
|
||||||
|
setInternalProp('selectedIndex', details.selectedIndex);
|
||||||
|
onChange(details.selectedIndex, details.isSelected);
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
onInteraction!: Function | null;
|
||||||
|
}
|
||||||
|
|
||||||
|
class TouchBarScrubber
|
||||||
|
extends TouchBarItem<Electron.TouchBarScrubberConstructorOptions>
|
||||||
|
implements Electron.TouchBarScrubber
|
||||||
|
{
|
||||||
|
@ImmutableProperty(() => 'scrubber')
|
||||||
|
type!: string;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarScrubber>((config) => config.items)
|
||||||
|
items!: Electron.ScrubberItem[];
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarScrubber>((config) => config.selectedStyle || null)
|
||||||
|
selectedStyle!: Electron.TouchBarScrubber['selectedStyle'];
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarScrubber>((config) => config.overlayStyle || null)
|
||||||
|
overlayStyle!: Electron.TouchBarScrubber['overlayStyle'];
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarScrubber>((config) => config.showArrowButtons || false)
|
||||||
|
showArrowButtons!: boolean;
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarScrubber>((config) => config.mode || 'free')
|
||||||
|
mode!: Electron.TouchBarScrubber['mode'];
|
||||||
|
|
||||||
|
@LiveProperty<TouchBarScrubber>((config) => (typeof config.continuous === 'undefined' ? true : config.continuous))
|
||||||
|
continuous!: boolean;
|
||||||
|
|
||||||
|
@ImmutableProperty<TouchBarScrubber>(({ select: onSelect, highlight: onHighlight }) =>
|
||||||
|
typeof onSelect === 'function' || typeof onHighlight === 'function'
|
||||||
|
? (details: { type: 'select'; selectedIndex: number } | { type: 'highlight'; highlightedIndex: number }) => {
|
||||||
|
if (details.type === 'select') {
|
||||||
|
if (onSelect) onSelect(details.selectedIndex);
|
||||||
|
} else {
|
||||||
|
if (onHighlight) onHighlight(details.highlightedIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
: null
|
||||||
|
)
|
||||||
|
onInteraction!: Function | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
class TouchBarOtherItemsProxy extends TouchBarItem<null> implements Electron.TouchBarOtherItemsProxy {
|
class TouchBarOtherItemsProxy extends TouchBarItem<null> implements Electron.TouchBarOtherItemsProxy {
|
||||||
@@ -292,7 +339,7 @@ const escapeItemSymbol = Symbol('escape item');
|
|||||||
|
|
||||||
class TouchBar extends EventEmitter implements Electron.TouchBar {
|
class TouchBar extends EventEmitter implements Electron.TouchBar {
|
||||||
// Bind a touch bar to a window
|
// Bind a touch bar to a window
|
||||||
static _setOnWindow (touchBar: TouchBar | Electron.TouchBarConstructorOptions['items'], window: Electron.BaseWindow) {
|
static _setOnWindow(touchBar: TouchBar | Electron.TouchBarConstructorOptions['items'], window: Electron.BaseWindow) {
|
||||||
if (window._touchBar != null) {
|
if (window._touchBar != null) {
|
||||||
window._touchBar._removeFromWindow(window);
|
window._touchBar._removeFromWindow(window);
|
||||||
}
|
}
|
||||||
@@ -312,7 +359,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
|||||||
private items = new Map<string, TouchBarItem<any>>();
|
private items = new Map<string, TouchBarItem<any>>();
|
||||||
orderedItems: TouchBarItem<any>[] = [];
|
orderedItems: TouchBarItem<any>[] = [];
|
||||||
|
|
||||||
constructor (options: Electron.TouchBarConstructorOptions) {
|
constructor(options: Electron.TouchBarConstructorOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (options == null) {
|
if (options == null) {
|
||||||
@@ -360,7 +407,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// register in separate loop after all items are validated
|
// register in separate loop after all items are validated
|
||||||
for (const item of (items as TouchBarItem<any>[])) {
|
for (const item of items as TouchBarItem<any>[]) {
|
||||||
this.orderedItems.push(item);
|
this.orderedItems.push(item);
|
||||||
registerItem(item);
|
registerItem(item);
|
||||||
}
|
}
|
||||||
@@ -372,7 +419,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
|||||||
|
|
||||||
private [escapeItemSymbol]: TouchBarItem<unknown> | null = null;
|
private [escapeItemSymbol]: TouchBarItem<unknown> | null = null;
|
||||||
|
|
||||||
set escapeItem (item: TouchBarItem<unknown> | null) {
|
set escapeItem(item: TouchBarItem<unknown> | null) {
|
||||||
if (item != null && !(item instanceof TouchBarItem)) {
|
if (item != null && !(item instanceof TouchBarItem)) {
|
||||||
throw new Error('Escape item must be an instance of TouchBarItem');
|
throw new Error('Escape item must be an instance of TouchBarItem');
|
||||||
}
|
}
|
||||||
@@ -387,11 +434,11 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
|||||||
this.emit('escape-item-change', item);
|
this.emit('escape-item-change', item);
|
||||||
}
|
}
|
||||||
|
|
||||||
get escapeItem (): TouchBarItem<unknown> | null {
|
get escapeItem(): TouchBarItem<unknown> | null {
|
||||||
return this[escapeItemSymbol];
|
return this[escapeItemSymbol];
|
||||||
}
|
}
|
||||||
|
|
||||||
_addToWindow (window: Electron.BaseWindow) {
|
_addToWindow(window: Electron.BaseWindow) {
|
||||||
const { id } = window;
|
const { id } = window;
|
||||||
|
|
||||||
// Already added to window
|
// Already added to window
|
||||||
@@ -447,7 +494,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
|
|||||||
escapeItemListener(this.escapeItem);
|
escapeItemListener(this.escapeItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
_removeFromWindow (window: Electron.BaseWindow) {
|
_removeFromWindow(window: Electron.BaseWindow) {
|
||||||
const removeListeners = this.windowListeners.get(window.id);
|
const removeListeners = this.windowListeners.get(window.id);
|
||||||
if (removeListeners != null) removeListeners();
|
if (removeListeners != null) removeListeners();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
|||||||
#handle: ElectronInternal.UtilityProcessWrapper | null;
|
#handle: ElectronInternal.UtilityProcessWrapper | null;
|
||||||
#stdout: Duplex | null = null;
|
#stdout: Duplex | null = null;
|
||||||
#stderr: Duplex | null = null;
|
#stderr: Duplex | null = null;
|
||||||
constructor (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
constructor(modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
if (!modulePath) {
|
if (!modulePath) {
|
||||||
@@ -53,7 +53,7 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (typeof options.stdio === 'string') {
|
if (typeof options.stdio === 'string') {
|
||||||
const stdio : Array<'pipe' | 'ignore' | 'inherit'> = [];
|
const stdio: Array<'pipe' | 'ignore' | 'inherit'> = [];
|
||||||
switch (options.stdio) {
|
switch (options.stdio) {
|
||||||
case 'inherit':
|
case 'inherit':
|
||||||
case 'ignore':
|
case 'ignore':
|
||||||
@@ -119,27 +119,27 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
get pid () {
|
get pid() {
|
||||||
return this.#handle?.pid;
|
return this.#handle?.pid;
|
||||||
}
|
}
|
||||||
|
|
||||||
get stdout () {
|
get stdout() {
|
||||||
return this.#stdout;
|
return this.#stdout;
|
||||||
}
|
}
|
||||||
|
|
||||||
get stderr () {
|
get stderr() {
|
||||||
return this.#stderr;
|
return this.#stderr;
|
||||||
}
|
}
|
||||||
|
|
||||||
postMessage (message: any, transfer?: MessagePortMain[]) {
|
postMessage(message: any, transfer?: MessagePortMain[]) {
|
||||||
if (Array.isArray(transfer)) {
|
if (Array.isArray(transfer)) {
|
||||||
transfer = transfer.map((o: any) => o instanceof MessagePortMain ? o._internalPort : o);
|
transfer = transfer.map((o: any) => (o instanceof MessagePortMain ? o._internalPort : o));
|
||||||
return this.#handle?.postMessage(message, transfer);
|
return this.#handle?.postMessage(message, transfer);
|
||||||
}
|
}
|
||||||
return this.#handle?.postMessage(message);
|
return this.#handle?.postMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
kill () : boolean {
|
kill(): boolean {
|
||||||
if (this.#handle === null) {
|
if (this.#handle === null) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -147,6 +147,6 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fork (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
export function fork(modulePath: string, args?: string[], options?: Electron.ForkOptions) {
|
||||||
return new ForkUtilityProcess(modulePath, args, options);
|
return new ForkUtilityProcess(modulePath, args, options);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import { openGuestWindow, makeWebPreferences, parseContentTypeFormat } from '@electron/internal/browser/guest-window-manager';
|
import {
|
||||||
|
openGuestWindow,
|
||||||
|
makeWebPreferences,
|
||||||
|
parseContentTypeFormat
|
||||||
|
} from '@electron/internal/browser/guest-window-manager';
|
||||||
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
|
||||||
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
|
||||||
import { parseFeatures } from '@electron/internal/browser/parse-features-string';
|
import { parseFeatures } from '@electron/internal/browser/parse-features-string';
|
||||||
@@ -110,7 +114,7 @@ const paperFormats: Record<string, ElectronInternal.PageSize> = {
|
|||||||
// Practically, this means microns need to be > 352 microns.
|
// Practically, this means microns need to be > 352 microns.
|
||||||
// We therefore need to verify this or it will silently fail.
|
// We therefore need to verify this or it will silently fail.
|
||||||
const isValidCustomPageSize = (width: number, height: number) => {
|
const isValidCustomPageSize = (width: number, height: number) => {
|
||||||
return [width, height].every(x => x > 352);
|
return [width, height].every((x) => x > 352);
|
||||||
};
|
};
|
||||||
|
|
||||||
// JavaScript implementations of WebContents.
|
// JavaScript implementations of WebContents.
|
||||||
@@ -130,10 +134,10 @@ WebContents.prototype._sendInternal = function (channel, ...args) {
|
|||||||
return this.mainFrame._sendInternal(channel, ...args);
|
return this.mainFrame._sendInternal(channel, ...args);
|
||||||
};
|
};
|
||||||
|
|
||||||
function getWebFrame (contents: Electron.WebContents, frame: number | [number, number]) {
|
function getWebFrame(contents: Electron.WebContents, frame: number | [number, number]) {
|
||||||
if (typeof frame === 'number') {
|
if (typeof frame === 'number') {
|
||||||
return webFrameMain.fromId(contents.mainFrame.processId, frame);
|
return webFrameMain.fromId(contents.mainFrame.processId, frame);
|
||||||
} else if (Array.isArray(frame) && frame.length === 2 && frame.every(value => typeof value === 'number')) {
|
} else if (Array.isArray(frame) && frame.length === 2 && frame.every((value) => typeof value === 'number')) {
|
||||||
return webFrameMain.fromId(frame[0], frame[1]);
|
return webFrameMain.fromId(frame[0], frame[1]);
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Missing required frame argument (must be number or [processId, frameId])');
|
throw new Error('Missing required frame argument (must be number or [processId, frameId])');
|
||||||
@@ -148,12 +152,12 @@ WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Following methods are mapped to webFrame.
|
// Following methods are mapped to webFrame.
|
||||||
const webFrameMethods = [
|
const webFrameMethods = ['insertCSS', 'insertText', 'removeInsertedCSS', 'setVisualZoomLevelLimits'] as (
|
||||||
'insertCSS',
|
| 'insertCSS'
|
||||||
'insertText',
|
| 'insertText'
|
||||||
'removeInsertedCSS',
|
| 'removeInsertedCSS'
|
||||||
'setVisualZoomLevelLimits'
|
| 'setVisualZoomLevelLimits'
|
||||||
] as ('insertCSS' | 'insertText' | 'removeInsertedCSS' | 'setVisualZoomLevelLimits')[];
|
)[];
|
||||||
|
|
||||||
for (const method of webFrameMethods) {
|
for (const method of webFrameMethods) {
|
||||||
WebContents.prototype[method] = function (...args: any[]): Promise<any> {
|
WebContents.prototype[method] = function (...args: any[]): Promise<any> {
|
||||||
@@ -175,14 +179,27 @@ const waitTillCanExecuteJavaScript = async (webContents: Electron.WebContents) =
|
|||||||
// WebContents has been loaded.
|
// WebContents has been loaded.
|
||||||
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
|
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
|
||||||
await waitTillCanExecuteJavaScript(this);
|
await waitTillCanExecuteJavaScript(this);
|
||||||
return ipcMainUtils.invokeInWebContents(this, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScript', String(code), !!hasUserGesture);
|
return ipcMainUtils.invokeInWebContents(
|
||||||
|
this,
|
||||||
|
IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD,
|
||||||
|
'executeJavaScript',
|
||||||
|
String(code),
|
||||||
|
!!hasUserGesture
|
||||||
|
);
|
||||||
};
|
};
|
||||||
WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (worldId, code, hasUserGesture) {
|
WebContents.prototype.executeJavaScriptInIsolatedWorld = async function (worldId, code, hasUserGesture) {
|
||||||
await waitTillCanExecuteJavaScript(this);
|
await waitTillCanExecuteJavaScript(this);
|
||||||
return ipcMainUtils.invokeInWebContents(this, IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, 'executeJavaScriptInIsolatedWorld', worldId, code, !!hasUserGesture);
|
return ipcMainUtils.invokeInWebContents(
|
||||||
|
this,
|
||||||
|
IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD,
|
||||||
|
'executeJavaScriptInIsolatedWorld',
|
||||||
|
worldId,
|
||||||
|
code,
|
||||||
|
!!hasUserGesture
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
function checkType<T> (value: T, type: 'number' | 'boolean' | 'string' | 'object', name: string): T {
|
function checkType<T>(value: T, type: 'number' | 'boolean' | 'string' | 'object', name: string): T {
|
||||||
// eslint-disable-next-line valid-typeof
|
// eslint-disable-next-line valid-typeof
|
||||||
if (typeof value !== type) {
|
if (typeof value !== type) {
|
||||||
throw new TypeError(`${name} must be a ${type}`);
|
throw new TypeError(`${name} must be a ${type}`);
|
||||||
@@ -191,7 +208,7 @@ function checkType<T> (value: T, type: 'number' | 'boolean' | 'string' | 'object
|
|||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function parsePageSize (pageSize: string | ElectronInternal.PageSize) {
|
function parsePageSize(pageSize: string | ElectronInternal.PageSize) {
|
||||||
if (typeof pageSize === 'string') {
|
if (typeof pageSize === 'string') {
|
||||||
const format = paperFormats[pageSize.toLowerCase()];
|
const format = paperFormats[pageSize.toLowerCase()];
|
||||||
if (!format) {
|
if (!format) {
|
||||||
@@ -218,8 +235,8 @@ WebContents.prototype.printToPDF = async function (options) {
|
|||||||
const pageSize = parsePageSize(options.pageSize ?? 'letter');
|
const pageSize = parsePageSize(options.pageSize ?? 'letter');
|
||||||
|
|
||||||
const { top, bottom, left, right } = margins;
|
const { top, bottom, left, right } = margins;
|
||||||
const validHeight = [top, bottom].every(u => u === undefined || u <= pageSize.paperHeight);
|
const validHeight = [top, bottom].every((u) => u === undefined || u <= pageSize.paperHeight);
|
||||||
const validWidth = [left, right].every(u => u === undefined || u <= pageSize.paperWidth);
|
const validWidth = [left, right].every((u) => u === undefined || u <= pageSize.paperWidth);
|
||||||
|
|
||||||
if (!validHeight || !validWidth) {
|
if (!validHeight || !validWidth) {
|
||||||
throw new Error('margins must be less than or equal to pageSize');
|
throw new Error('margins must be less than or equal to pageSize');
|
||||||
@@ -335,19 +352,21 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
|
|||||||
}
|
}
|
||||||
const { query, search, hash } = options;
|
const { query, search, hash } = options;
|
||||||
|
|
||||||
return this.loadURL(url.format({
|
return this.loadURL(
|
||||||
protocol: 'file',
|
url.format({
|
||||||
slashes: true,
|
protocol: 'file',
|
||||||
pathname: path.resolve(app.getAppPath(), filePath),
|
slashes: true,
|
||||||
query,
|
pathname: path.resolve(app.getAppPath(), filePath),
|
||||||
search,
|
query,
|
||||||
hash
|
search,
|
||||||
}));
|
hash
|
||||||
|
})
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
type LoadError = { errorCode: number, errorDescription: string, url: string };
|
type LoadError = { errorCode: number; errorDescription: string; url: string };
|
||||||
|
|
||||||
function _awaitNextLoad (this: Electron.WebContents, navigationUrl: string) {
|
function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
const resolveAndCleanup = () => {
|
const resolveAndCleanup = () => {
|
||||||
removeListeners();
|
removeListeners();
|
||||||
@@ -355,7 +374,9 @@ function _awaitNextLoad (this: Electron.WebContents, navigationUrl: string) {
|
|||||||
};
|
};
|
||||||
let error: LoadError | undefined;
|
let error: LoadError | undefined;
|
||||||
const rejectAndCleanup = ({ errorCode, errorDescription, url }: LoadError) => {
|
const rejectAndCleanup = ({ errorCode, errorDescription, url }: LoadError) => {
|
||||||
const err = new Error(`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`);
|
const err = new Error(
|
||||||
|
`${errorDescription} (${errorCode}) loading '${typeof url === 'string' ? url.substr(0, 2048) : url}'`
|
||||||
|
);
|
||||||
Object.assign(err, { errno: errorCode, code: errorDescription, url });
|
Object.assign(err, { errno: errorCode, code: errorDescription, url });
|
||||||
removeListeners();
|
removeListeners();
|
||||||
reject(err);
|
reject(err);
|
||||||
@@ -388,7 +409,13 @@ function _awaitNextLoad (this: Electron.WebContents, navigationUrl: string) {
|
|||||||
navigationStarted = true;
|
navigationStarted = true;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const failListener = (event: Electron.Event, errorCode: number, errorDescription: string, validatedURL: string, isMainFrame: boolean) => {
|
const failListener = (
|
||||||
|
event: Electron.Event,
|
||||||
|
errorCode: number,
|
||||||
|
errorDescription: string,
|
||||||
|
validatedURL: string,
|
||||||
|
isMainFrame: boolean
|
||||||
|
) => {
|
||||||
if (!error && isMainFrame) {
|
if (!error && isMainFrame) {
|
||||||
error = { errorCode, errorDescription, url: validatedURL };
|
error = { errorCode, errorDescription, url: validatedURL };
|
||||||
}
|
}
|
||||||
@@ -430,7 +457,7 @@ function _awaitNextLoad (this: Electron.WebContents, navigationUrl: string) {
|
|||||||
this.on('did-stop-loading', stopLoadingListener);
|
this.on('did-stop-loading', stopLoadingListener);
|
||||||
this.on('destroyed', stopLoadingListener);
|
this.on('destroyed', stopLoadingListener);
|
||||||
});
|
});
|
||||||
};
|
}
|
||||||
|
|
||||||
WebContents.prototype.loadURL = function (url, options) {
|
WebContents.prototype.loadURL = function (url, options) {
|
||||||
const p = _awaitNextLoad.call(this, url);
|
const p = _awaitNextLoad.call(this, url);
|
||||||
@@ -440,11 +467,28 @@ WebContents.prototype.loadURL = function (url, options) {
|
|||||||
return p;
|
return p;
|
||||||
};
|
};
|
||||||
|
|
||||||
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => Electron.WindowOpenHandlerResponse) {
|
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;
|
this._windowOpenHandler = handler;
|
||||||
};
|
};
|
||||||
|
|
||||||
WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event, details: Electron.HandlerDetails): {browserWindowConstructorOptions: BrowserWindowConstructorOptions | null, outlivesOpener: boolean, createWindow?: Electron.CreateWindowFunction} {
|
WebContents.prototype._callWindowOpenHandler = function (
|
||||||
|
event: Electron.Event,
|
||||||
|
details: Electron.HandlerDetails
|
||||||
|
): {
|
||||||
|
browserWindowConstructorOptions: BrowserWindowConstructorOptions | null;
|
||||||
|
outlivesOpener: boolean;
|
||||||
|
createWindow?: Electron.CreateWindowFunction;
|
||||||
|
} {
|
||||||
const defaultResponse = {
|
const defaultResponse = {
|
||||||
browserWindowConstructorOptions: null,
|
browserWindowConstructorOptions: null,
|
||||||
outlivesOpener: false,
|
outlivesOpener: false,
|
||||||
@@ -473,13 +517,14 @@ WebContents.prototype._callWindowOpenHandler = function (event: Electron.Event,
|
|||||||
return defaultResponse;
|
return defaultResponse;
|
||||||
} else if (response.action === 'allow') {
|
} else if (response.action === 'allow') {
|
||||||
return {
|
return {
|
||||||
browserWindowConstructorOptions: typeof response.overrideBrowserWindowOptions === 'object' ? response.overrideBrowserWindowOptions : null,
|
browserWindowConstructorOptions:
|
||||||
|
typeof response.overrideBrowserWindowOptions === 'object' ? response.overrideBrowserWindowOptions : null,
|
||||||
outlivesOpener: typeof response.outlivesOpener === 'boolean' ? response.outlivesOpener : false,
|
outlivesOpener: typeof response.outlivesOpener === 'boolean' ? response.outlivesOpener : false,
|
||||||
createWindow: typeof response.createWindow === 'function' ? response.createWindow : undefined
|
createWindow: typeof response.createWindow === 'function' ? response.createWindow : undefined
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
console.error('The window open handler response must be an object with an \'action\' property of \'allow\' or \'deny\'.');
|
console.error("The window open handler response must be an object with an 'action' property of 'allow' or 'deny'.");
|
||||||
return defaultResponse;
|
return defaultResponse;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -497,13 +542,19 @@ WebContents.prototype.canGoBack = function () {
|
|||||||
return this._canGoBack();
|
return this._canGoBack();
|
||||||
};
|
};
|
||||||
|
|
||||||
const canGoForwardDeprecated = deprecate.warnOnce('webContents.canGoForward', 'webContents.navigationHistory.canGoForward');
|
const canGoForwardDeprecated = deprecate.warnOnce(
|
||||||
|
'webContents.canGoForward',
|
||||||
|
'webContents.navigationHistory.canGoForward'
|
||||||
|
);
|
||||||
WebContents.prototype.canGoForward = function () {
|
WebContents.prototype.canGoForward = function () {
|
||||||
canGoForwardDeprecated();
|
canGoForwardDeprecated();
|
||||||
return this._canGoForward();
|
return this._canGoForward();
|
||||||
};
|
};
|
||||||
|
|
||||||
const canGoToOffsetDeprecated = deprecate.warnOnce('webContents.canGoToOffset', 'webContents.navigationHistory.canGoToOffset');
|
const canGoToOffsetDeprecated = deprecate.warnOnce(
|
||||||
|
'webContents.canGoToOffset',
|
||||||
|
'webContents.navigationHistory.canGoToOffset'
|
||||||
|
);
|
||||||
WebContents.prototype.canGoToOffset = function (index: number) {
|
WebContents.prototype.canGoToOffset = function (index: number) {
|
||||||
canGoToOffsetDeprecated();
|
canGoToOffsetDeprecated();
|
||||||
return this._canGoToOffset(index);
|
return this._canGoToOffset(index);
|
||||||
@@ -539,13 +590,17 @@ WebContents.prototype.goToOffset = function (index: number) {
|
|||||||
return this._goToOffset(index);
|
return this._goToOffset(index);
|
||||||
};
|
};
|
||||||
|
|
||||||
const consoleMessageDeprecated = deprecate.warnOnceMessage('\'console-message\' arguments are deprecated and will be removed. Please use Event<WebContentsConsoleMessageEventParams> object instead.');
|
const consoleMessageDeprecated = deprecate.warnOnceMessage(
|
||||||
|
"'console-message' arguments are deprecated and will be removed. Please use Event<WebContentsConsoleMessageEventParams> object instead."
|
||||||
|
);
|
||||||
|
|
||||||
// Add JavaScript wrappers for WebContents class.
|
// Add JavaScript wrappers for WebContents class.
|
||||||
WebContents.prototype._init = function () {
|
WebContents.prototype._init = function () {
|
||||||
const prefs = this.getLastWebPreferences() || {};
|
const prefs = this.getLastWebPreferences() || {};
|
||||||
if (!prefs.nodeIntegration && prefs.preload != null && prefs.sandbox == null) {
|
if (!prefs.nodeIntegration && prefs.preload != null && prefs.sandbox == null) {
|
||||||
deprecate.log('The default sandbox option for windows without nodeIntegration is changing. Presently, by default, when a window has a preload script, it defaults to being unsandboxed. In Electron 20, this default will be changing, and all windows that have nodeIntegration: false (which is the default) will be sandboxed by default. If your preload script doesn\'t use Node, no action is needed. If your preload script does use Node, either refactor it to move Node usage to the main process, or specify sandbox: false in your WebPreferences.');
|
deprecate.log(
|
||||||
|
"The default sandbox option for windows without nodeIntegration is changing. Presently, by default, when a window has a preload script, it defaults to being unsandboxed. In Electron 20, this default will be changing, and all windows that have nodeIntegration: false (which is the default) will be sandboxed by default. If your preload script doesn't use Node, no action is needed. If your preload script does use Node, either refactor it to move Node usage to the main process, or specify sandbox: false in your WebPreferences."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
// Read off the ID at construction time, so that it's accessible even after
|
// Read off the ID at construction time, so that it's accessible even after
|
||||||
// the underlying C++ WebContents is destroyed.
|
// the underlying C++ WebContents is destroyed.
|
||||||
@@ -559,7 +614,9 @@ WebContents.prototype._init = function () {
|
|||||||
|
|
||||||
const ipc = new IpcMainImpl();
|
const ipc = new IpcMainImpl();
|
||||||
Object.defineProperty(this, 'ipc', {
|
Object.defineProperty(this, 'ipc', {
|
||||||
get () { return ipc; },
|
get() {
|
||||||
|
return ipc;
|
||||||
|
},
|
||||||
enumerable: true
|
enumerable: true
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -580,13 +637,15 @@ WebContents.prototype._init = function () {
|
|||||||
getEntryAtIndex: this._getNavigationEntryAtIndex.bind(this),
|
getEntryAtIndex: this._getNavigationEntryAtIndex.bind(this),
|
||||||
removeEntryAtIndex: this._removeNavigationEntryAtIndex.bind(this),
|
removeEntryAtIndex: this._removeNavigationEntryAtIndex.bind(this),
|
||||||
getAllEntries: this._getHistory.bind(this),
|
getAllEntries: this._getHistory.bind(this),
|
||||||
restore: ({ index, entries }: { index?: number, entries: NavigationEntry[] }) => {
|
restore: ({ index, entries }: { index?: number; entries: NavigationEntry[] }) => {
|
||||||
if (index === undefined) {
|
if (index === undefined) {
|
||||||
index = entries.length - 1;
|
index = entries.length - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (index < 0 || !entries[index]) {
|
if (index < 0 || !entries[index]) {
|
||||||
throw new Error('Invalid index. Index must be a positive integer and within the bounds of the entries length.');
|
throw new Error(
|
||||||
|
'Invalid index. Index must be a positive integer and within the bounds of the entries length.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const p = _awaitNextLoad.call(this, entries[index].url);
|
const p = _awaitNextLoad.call(this, entries[index].url);
|
||||||
@@ -610,7 +669,9 @@ WebContents.prototype._init = function () {
|
|||||||
|
|
||||||
// Log out a hint to help users better debug renderer crashes.
|
// Log out a hint to help users better debug renderer crashes.
|
||||||
if (loggingEnabled()) {
|
if (loggingEnabled()) {
|
||||||
console.info(`Renderer process ${details.reason} - see https://www.electronjs.org/docs/tutorial/application-debugging for potential debugging information.`);
|
console.info(
|
||||||
|
`Renderer process ${details.reason} - see https://www.electronjs.org/docs/tutorial/application-debugging for potential debugging information.`
|
||||||
|
);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -620,7 +681,9 @@ WebContents.prototype._init = function () {
|
|||||||
// All other types should ignore the "proceed" signal and unload
|
// All other types should ignore the "proceed" signal and unload
|
||||||
// regardless.
|
// regardless.
|
||||||
if (type === 'window' || type === 'offscreen' || type === 'browserView') {
|
if (type === 'window' || type === 'offscreen' || type === 'browserView') {
|
||||||
if (!proceed) { return event.preventDefault(); }
|
if (!proceed) {
|
||||||
|
return event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -705,8 +768,8 @@ WebContents.prototype._init = function () {
|
|||||||
const secureOverrideWebPreferences = windowOpenOverriddenOptions
|
const secureOverrideWebPreferences = windowOpenOverriddenOptions
|
||||||
? {
|
? {
|
||||||
// Allow setting of backgroundColor as a webPreference even though
|
// Allow setting of backgroundColor as a webPreference even though
|
||||||
// it's technically a BrowserWindowConstructorOptions option because
|
// it's technically a BrowserWindowConstructorOptions option because
|
||||||
// we need to access it in the renderer at init time.
|
// we need to access it in the renderer at init time.
|
||||||
backgroundColor: windowOpenOverriddenOptions.backgroundColor,
|
backgroundColor: windowOpenOverriddenOptions.backgroundColor,
|
||||||
transparent: windowOpenOverriddenOptions.transparent,
|
transparent: windowOpenOverriddenOptions.transparent,
|
||||||
...windowOpenOverriddenOptions.webPreferences
|
...windowOpenOverriddenOptions.webPreferences
|
||||||
@@ -727,38 +790,54 @@ WebContents.prototype._init = function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Create a new browser window for "window.open"
|
// Create a new browser window for "window.open"
|
||||||
this.on('-add-new-contents', (event, webContents, disposition, _userGesture, _left, _top, _width, _height, url, frameName, referrer, rawFeatures, postData) => {
|
this.on(
|
||||||
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
'-add-new-contents',
|
||||||
const outlivesOpener = windowOpenOutlivesOpenerOption;
|
(
|
||||||
const windowOpenFunction = createWindow;
|
event,
|
||||||
|
webContents,
|
||||||
createWindow = undefined;
|
|
||||||
windowOpenOverriddenOptions = null;
|
|
||||||
// false is the default
|
|
||||||
windowOpenOutlivesOpenerOption = false;
|
|
||||||
|
|
||||||
if ((disposition !== 'foreground-tab' && disposition !== 'new-window' &&
|
|
||||||
disposition !== 'background-tab')) {
|
|
||||||
event.preventDefault();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
openGuestWindow({
|
|
||||||
embedder: this,
|
|
||||||
guest: webContents,
|
|
||||||
overrideBrowserWindowOptions: overriddenOptions,
|
|
||||||
disposition,
|
disposition,
|
||||||
|
_userGesture,
|
||||||
|
_left,
|
||||||
|
_top,
|
||||||
|
_width,
|
||||||
|
_height,
|
||||||
|
url,
|
||||||
|
frameName,
|
||||||
referrer,
|
referrer,
|
||||||
postData,
|
rawFeatures,
|
||||||
windowOpenArgs: {
|
postData
|
||||||
url,
|
) => {
|
||||||
frameName,
|
const overriddenOptions = windowOpenOverriddenOptions || undefined;
|
||||||
features: rawFeatures
|
const outlivesOpener = windowOpenOutlivesOpenerOption;
|
||||||
},
|
const windowOpenFunction = createWindow;
|
||||||
outlivesOpener,
|
|
||||||
createWindow: windowOpenFunction
|
createWindow = undefined;
|
||||||
});
|
windowOpenOverriddenOptions = null;
|
||||||
});
|
// false is the default
|
||||||
|
windowOpenOutlivesOpenerOption = false;
|
||||||
|
|
||||||
|
if (disposition !== 'foreground-tab' && disposition !== 'new-window' && disposition !== 'background-tab') {
|
||||||
|
event.preventDefault();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
openGuestWindow({
|
||||||
|
embedder: this,
|
||||||
|
guest: webContents,
|
||||||
|
overrideBrowserWindowOptions: overriddenOptions,
|
||||||
|
disposition,
|
||||||
|
referrer,
|
||||||
|
postData,
|
||||||
|
windowOpenArgs: {
|
||||||
|
url,
|
||||||
|
frameName,
|
||||||
|
features: rawFeatures
|
||||||
|
},
|
||||||
|
outlivesOpener,
|
||||||
|
createWindow: windowOpenFunction
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.on('login', (event, ...args) => {
|
this.on('login', (event, ...args) => {
|
||||||
@@ -797,14 +876,17 @@ WebContents.prototype._init = function () {
|
|||||||
originCounts.set(origin, (originCounts.get(origin) ?? 0) + 1);
|
originCounts.set(origin, (originCounts.get(origin) ?? 0) + 1);
|
||||||
|
|
||||||
// TODO: translate?
|
// TODO: translate?
|
||||||
const checkbox = originCounts.get(origin)! > 1 && prefs.safeDialogs ? prefs.safeDialogsMessage || 'Prevent this app from creating additional dialogs' : '';
|
const checkbox =
|
||||||
|
originCounts.get(origin)! > 1 && prefs.safeDialogs
|
||||||
|
? prefs.safeDialogsMessage || 'Prevent this app from creating additional dialogs'
|
||||||
|
: '';
|
||||||
const parent = this.getOwnerBrowserWindow();
|
const parent = this.getOwnerBrowserWindow();
|
||||||
const abortController = new AbortController();
|
const abortController = new AbortController();
|
||||||
const options: MessageBoxOptions = {
|
const options: MessageBoxOptions = {
|
||||||
message: info.messageText,
|
message: info.messageText,
|
||||||
checkboxLabel: checkbox,
|
checkboxLabel: checkbox,
|
||||||
signal: abortController.signal,
|
signal: abortController.signal,
|
||||||
...(info.dialogType === 'confirm')
|
...(info.dialogType === 'confirm'
|
||||||
? {
|
? {
|
||||||
buttons: ['OK', 'Cancel'],
|
buttons: ['OK', 'Cancel'],
|
||||||
defaultId: 0,
|
defaultId: 0,
|
||||||
@@ -814,10 +896,11 @@ WebContents.prototype._init = function () {
|
|||||||
buttons: ['OK'],
|
buttons: ['OK'],
|
||||||
defaultId: -1, // No default button
|
defaultId: -1, // No default button
|
||||||
cancelId: 0
|
cancelId: 0
|
||||||
}
|
})
|
||||||
};
|
};
|
||||||
openDialogs.add(abortController);
|
openDialogs.add(abortController);
|
||||||
const promise = parent && !prefs.offscreen ? dialog.showMessageBox(parent, options) : dialog.showMessageBox(options);
|
const promise =
|
||||||
|
parent && !prefs.offscreen ? dialog.showMessageBox(parent, options) : dialog.showMessageBox(options);
|
||||||
try {
|
try {
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
if (abortController.signal.aborted || this.isDestroyed()) return;
|
if (abortController.signal.aborted || this.isDestroyed()) return;
|
||||||
@@ -829,13 +912,15 @@ WebContents.prototype._init = function () {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.on('-cancel-dialogs', () => {
|
this.on('-cancel-dialogs', () => {
|
||||||
for (const controller of openDialogs) { controller.abort(); }
|
for (const controller of openDialogs) {
|
||||||
|
controller.abort();
|
||||||
|
}
|
||||||
openDialogs.clear();
|
openDialogs.clear();
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(samuelmaddock): remove deprecated 'console-message' arguments
|
// TODO(samuelmaddock): remove deprecated 'console-message' arguments
|
||||||
this.on('-console-message' as any, (event: Electron.Event<Electron.WebContentsConsoleMessageEventParams>) => {
|
this.on('-console-message' as any, (event: Electron.Event<Electron.WebContentsConsoleMessageEventParams>) => {
|
||||||
const hasDeprecatedListener = this.listeners('console-message').some(listener => listener.length > 1);
|
const hasDeprecatedListener = this.listeners('console-message').some((listener) => listener.length > 1);
|
||||||
if (hasDeprecatedListener) {
|
if (hasDeprecatedListener) {
|
||||||
consoleMessageDeprecated();
|
consoleMessageDeprecated();
|
||||||
}
|
}
|
||||||
@@ -849,7 +934,17 @@ WebContents.prototype._init = function () {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.emit('web-contents-created', { sender: this, preventDefault () {}, get defaultPrevented () { return false; } }, this);
|
app.emit(
|
||||||
|
'web-contents-created',
|
||||||
|
{
|
||||||
|
sender: this,
|
||||||
|
preventDefault() {},
|
||||||
|
get defaultPrevented() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this
|
||||||
|
);
|
||||||
|
|
||||||
// Properties
|
// Properties
|
||||||
|
|
||||||
@@ -885,23 +980,23 @@ WebContents.prototype._init = function () {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Public APIs.
|
// Public APIs.
|
||||||
export function create (options = {}): Electron.WebContents {
|
export function create(options = {}): Electron.WebContents {
|
||||||
return new (WebContents as any)(options);
|
return new (WebContents as any)(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromId (id: number) {
|
export function fromId(id: number) {
|
||||||
return binding.fromId(id);
|
return binding.fromId(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromFrame (frame: Electron.WebFrameMain) {
|
export function fromFrame(frame: Electron.WebFrameMain) {
|
||||||
return binding.fromFrame(frame);
|
return binding.fromFrame(frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fromDevToolsTargetId (targetId: string) {
|
export function fromDevToolsTargetId(targetId: string) {
|
||||||
return binding.fromDevToolsTargetId(targetId);
|
return binding.fromDevToolsTargetId(targetId);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getFocusedWebContents () {
|
export function getFocusedWebContents() {
|
||||||
let focused = null;
|
let focused = null;
|
||||||
for (const contents of binding.getAllWebContents()) {
|
for (const contents of binding.getAllWebContents()) {
|
||||||
if (!contents.isFocused()) continue;
|
if (!contents.isFocused()) continue;
|
||||||
@@ -912,6 +1007,6 @@ export function getFocusedWebContents () {
|
|||||||
}
|
}
|
||||||
return focused;
|
return focused;
|
||||||
}
|
}
|
||||||
export function getAllWebContents () {
|
export function getAllWebContents() {
|
||||||
return binding.getAllWebContents();
|
return binding.getAllWebContents();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { MessagePortMain } from '@electron/internal/browser/message-port-main';
|
|||||||
const { WebFrameMain, fromId, fromFrameToken } = process._linkedBinding('electron_browser_web_frame_main');
|
const { WebFrameMain, fromId, fromFrameToken } = process._linkedBinding('electron_browser_web_frame_main');
|
||||||
|
|
||||||
Object.defineProperty(WebFrameMain.prototype, 'ipc', {
|
Object.defineProperty(WebFrameMain.prototype, 'ipc', {
|
||||||
get () {
|
get() {
|
||||||
const ipc = new IpcMainImpl();
|
const ipc = new IpcMainImpl();
|
||||||
Object.defineProperty(this, 'ipc', { value: ipc });
|
Object.defineProperty(this, 'ipc', { value: ipc });
|
||||||
return ipc;
|
return ipc;
|
||||||
@@ -37,7 +37,7 @@ WebFrameMain.prototype._sendInternal = function (channel, ...args) {
|
|||||||
|
|
||||||
WebFrameMain.prototype.postMessage = function (...args) {
|
WebFrameMain.prototype.postMessage = function (...args) {
|
||||||
if (Array.isArray(args[2])) {
|
if (Array.isArray(args[2])) {
|
||||||
args[2] = args[2].map(o => o instanceof MessagePortMain ? o._internalPort : o);
|
args[2] = args[2].map((o) => (o instanceof MessagePortMain ? o._internalPort : o));
|
||||||
}
|
}
|
||||||
this._postMessage(...args);
|
this._postMessage(...args);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -8,29 +8,30 @@ import * as fs from 'fs';
|
|||||||
|
|
||||||
const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
|
const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
|
||||||
return items.map(function (item) {
|
return items.map(function (item) {
|
||||||
const transformed: Electron.MenuItemConstructorOptions = item.type === 'subMenu'
|
const transformed: Electron.MenuItemConstructorOptions =
|
||||||
? {
|
item.type === 'subMenu'
|
||||||
type: 'submenu',
|
|
||||||
label: item.label,
|
|
||||||
enabled: item.enabled,
|
|
||||||
submenu: convertToMenuTemplate(item.subItems, handler)
|
|
||||||
}
|
|
||||||
: item.type === 'separator'
|
|
||||||
? {
|
? {
|
||||||
type: 'separator'
|
type: 'submenu',
|
||||||
|
label: item.label,
|
||||||
|
enabled: item.enabled,
|
||||||
|
submenu: convertToMenuTemplate(item.subItems, handler)
|
||||||
}
|
}
|
||||||
: item.type === 'checkbox'
|
: item.type === 'separator'
|
||||||
? {
|
? {
|
||||||
type: 'checkbox',
|
type: 'separator'
|
||||||
label: item.label,
|
|
||||||
enabled: item.enabled,
|
|
||||||
checked: item.checked
|
|
||||||
}
|
}
|
||||||
: {
|
: item.type === 'checkbox'
|
||||||
type: 'normal',
|
? {
|
||||||
label: item.label,
|
type: 'checkbox',
|
||||||
enabled: item.enabled
|
label: item.label,
|
||||||
};
|
enabled: item.enabled,
|
||||||
|
checked: item.checked
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
type: 'normal',
|
||||||
|
label: item.label,
|
||||||
|
enabled: item.enabled
|
||||||
|
};
|
||||||
|
|
||||||
if (item.id != null) {
|
if (item.id != null) {
|
||||||
transformed.click = () => handler(item.id);
|
transformed.click = () => handler(item.id);
|
||||||
@@ -67,18 +68,21 @@ const assertChromeDevTools = function (contents: Electron.WebContents, api: stri
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, function (event, items: ContextMenuItem[], isEditMenu: boolean) {
|
ipcMainInternal.handle(
|
||||||
return new Promise<number | void>(resolve => {
|
IPC_MESSAGES.INSPECTOR_CONTEXT_MENU,
|
||||||
if (event.type !== 'frame') return;
|
function (event, items: ContextMenuItem[], isEditMenu: boolean) {
|
||||||
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
|
return new Promise<number | void>((resolve) => {
|
||||||
|
if (event.type !== 'frame') return;
|
||||||
|
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
|
||||||
|
|
||||||
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
|
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
|
||||||
const menu = Menu.buildFromTemplate(template);
|
const menu = Menu.buildFromTemplate(template);
|
||||||
const window = event.sender.getOwnerBrowserWindow()!;
|
const window = event.sender.getOwnerBrowserWindow()!;
|
||||||
|
|
||||||
menu.popup({ window, callback: () => resolve() });
|
menu.popup({ window, callback: () => resolve() });
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
||||||
ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_SELECT_FILE, async function (event) {
|
ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_SELECT_FILE, async function (event) {
|
||||||
if (event.type !== 'frame') return [];
|
if (event.type !== 'frame') return [];
|
||||||
@@ -93,17 +97,20 @@ ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_SELECT_FILE, async function (event
|
|||||||
return [path, data];
|
return [path, data];
|
||||||
});
|
});
|
||||||
|
|
||||||
ipcMainUtils.handleSync(IPC_MESSAGES.INSPECTOR_CONFIRM, async function (event, message: string = '', title: string = '') {
|
ipcMainUtils.handleSync(
|
||||||
if (event.type !== 'frame') return;
|
IPC_MESSAGES.INSPECTOR_CONFIRM,
|
||||||
assertChromeDevTools(event.sender, 'window.confirm()');
|
async function (event, message: string = '', title: string = '') {
|
||||||
|
if (event.type !== 'frame') return;
|
||||||
|
assertChromeDevTools(event.sender, 'window.confirm()');
|
||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
message: String(message),
|
message: String(message),
|
||||||
title: String(title),
|
title: String(title),
|
||||||
buttons: ['OK', 'Cancel'],
|
buttons: ['OK', 'Cancel'],
|
||||||
cancelId: 1
|
cancelId: 1
|
||||||
};
|
};
|
||||||
const window = event.sender.getOwnerBrowserWindow()!;
|
const window = event.sender.getOwnerBrowserWindow()!;
|
||||||
const { response } = await dialog.showMessageBox(window, options);
|
const { response } = await dialog.showMessageBox(window, options);
|
||||||
return response === 0;
|
return response === 0;
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|||||||
@@ -3,7 +3,12 @@ import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-util
|
|||||||
import { parseWebViewWebPreferences } from '@electron/internal/browser/parse-features-string';
|
import { parseWebViewWebPreferences } from '@electron/internal/browser/parse-features-string';
|
||||||
import { webViewEvents } from '@electron/internal/browser/web-view-events';
|
import { webViewEvents } from '@electron/internal/browser/web-view-events';
|
||||||
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
|
||||||
import { syncMethods, asyncMethods, properties, navigationHistorySyncMethods } from '@electron/internal/common/web-view-methods';
|
import {
|
||||||
|
syncMethods,
|
||||||
|
asyncMethods,
|
||||||
|
properties,
|
||||||
|
navigationHistorySyncMethods
|
||||||
|
} from '@electron/internal/common/web-view-methods';
|
||||||
|
|
||||||
import { webContents } from 'electron/main';
|
import { webContents } from 'electron/main';
|
||||||
|
|
||||||
@@ -22,13 +27,11 @@ const supportedWebViewEvents = Object.keys(webViewEvents);
|
|||||||
const guestInstances = new Map<number, GuestInstance>();
|
const guestInstances = new Map<number, GuestInstance>();
|
||||||
const embedderElementsMap = new Map<string, number>();
|
const embedderElementsMap = new Map<string, number>();
|
||||||
|
|
||||||
function makeWebPreferences (embedder: Electron.WebContents, params: Record<string, any>) {
|
function makeWebPreferences(embedder: Electron.WebContents, params: Record<string, any>) {
|
||||||
// parse the 'webpreferences' attribute string, if set
|
// parse the 'webpreferences' attribute string, if set
|
||||||
// this uses the same parsing rules as window.open uses for its features
|
// this uses the same parsing rules as window.open uses for its features
|
||||||
const parsedWebPreferences =
|
const parsedWebPreferences =
|
||||||
typeof params.webpreferences === 'string'
|
typeof params.webpreferences === 'string' ? parseWebViewWebPreferences(params.webpreferences) : null;
|
||||||
? parseWebViewWebPreferences(params.webpreferences)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
const webPreferences: Electron.WebPreferences = {
|
const webPreferences: Electron.WebPreferences = {
|
||||||
nodeIntegration: params.nodeintegration ?? false,
|
nodeIntegration: params.nodeintegration ?? false,
|
||||||
@@ -68,7 +71,7 @@ function makeWebPreferences (embedder: Electron.WebContents, params: Record<stri
|
|||||||
return webPreferences;
|
return webPreferences;
|
||||||
}
|
}
|
||||||
|
|
||||||
function makeLoadURLOptions (params: Record<string, any>) {
|
function makeLoadURLOptions(params: Record<string, any>) {
|
||||||
const opts: Electron.LoadURLOptions = {};
|
const opts: Electron.LoadURLOptions = {};
|
||||||
if (params.httpreferrer) {
|
if (params.httpreferrer) {
|
||||||
opts.httpReferrer = params.httpreferrer;
|
opts.httpReferrer = params.httpreferrer;
|
||||||
@@ -80,11 +83,16 @@ function makeLoadURLOptions (params: Record<string, any>) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a new guest instance.
|
// Create a new guest instance.
|
||||||
const createGuest = function (embedder: Electron.WebContents, embedderFrameToken: string, elementInstanceId: number, params: Record<string, any>) {
|
const createGuest = function (
|
||||||
|
embedder: Electron.WebContents,
|
||||||
|
embedderFrameToken: string,
|
||||||
|
elementInstanceId: number,
|
||||||
|
params: Record<string, any>
|
||||||
|
) {
|
||||||
const webPreferences = makeWebPreferences(embedder, params);
|
const webPreferences = makeWebPreferences(embedder, params);
|
||||||
const event = {
|
const event = {
|
||||||
sender: embedder,
|
sender: embedder,
|
||||||
preventDefault () {
|
preventDefault() {
|
||||||
this.defaultPrevented = true;
|
this.defaultPrevented = true;
|
||||||
},
|
},
|
||||||
defaultPrevented: false
|
defaultPrevented: false
|
||||||
@@ -266,13 +274,18 @@ const isWebViewTagEnabled = function (contents: Electron.WebContents) {
|
|||||||
return isWebViewTagEnabledCache.get(contents);
|
return isWebViewTagEnabledCache.get(contents);
|
||||||
};
|
};
|
||||||
|
|
||||||
const makeSafeHandler = function<Event extends { sender: Electron.WebContents }> (channel: string, handler: (event: Event, ...args: any[]) => any) {
|
const makeSafeHandler = function <Event extends { sender: Electron.WebContents }>(
|
||||||
|
channel: string,
|
||||||
|
handler: (event: Event, ...args: any[]) => any
|
||||||
|
) {
|
||||||
return (event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent, ...args: any[]) => {
|
return (event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent, ...args: any[]) => {
|
||||||
if (event.type !== 'frame') return;
|
if (event.type !== 'frame') return;
|
||||||
if (isWebViewTagEnabled(event.sender)) {
|
if (isWebViewTagEnabled(event.sender)) {
|
||||||
return handler(event as unknown as Event, ...args);
|
return handler(event as unknown as Event, ...args);
|
||||||
} else {
|
} else {
|
||||||
console.error(`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`);
|
console.error(
|
||||||
|
`<webview> IPC message ${channel} sent by WebContents with <webview> disabled (${event.sender.id})`
|
||||||
|
);
|
||||||
throw new Error('<webview> disabled');
|
throw new Error('<webview> disabled');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -282,13 +295,19 @@ const handleMessage = function (channel: string, handler: (event: Electron.IpcMa
|
|||||||
ipcMainInternal.handle(channel, makeSafeHandler(channel, handler));
|
ipcMainInternal.handle(channel, makeSafeHandler(channel, handler));
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleMessageSync = function (channel: string, handler: (event: { sender: Electron.WebContents }, ...args: any[]) => any) {
|
const handleMessageSync = function (
|
||||||
|
channel: string,
|
||||||
|
handler: (event: { sender: Electron.WebContents }, ...args: any[]) => any
|
||||||
|
) {
|
||||||
ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler));
|
ipcMainUtils.handleSync(channel, makeSafeHandler(channel, handler));
|
||||||
};
|
};
|
||||||
|
|
||||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST, function (event, embedderFrameToken: string, elementInstanceId: number, params) {
|
handleMessage(
|
||||||
return createGuest(event.sender, embedderFrameToken, elementInstanceId, params);
|
IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST,
|
||||||
});
|
function (event, embedderFrameToken: string, elementInstanceId: number, params) {
|
||||||
|
return createGuest(event.sender, embedderFrameToken, elementInstanceId, params);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, function (event, guestInstanceId: number) {
|
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, function (event, guestInstanceId: number) {
|
||||||
return detachGuest(event.sender, guestInstanceId);
|
return detachGuest(event.sender, guestInstanceId);
|
||||||
@@ -301,49 +320,61 @@ ipcMainInternal.on(IPC_MESSAGES.GUEST_VIEW_MANAGER_FOCUS_CHANGE, function (event
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, function (event, guestInstanceId: number, method: string, args: any[]) {
|
handleMessage(
|
||||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL,
|
||||||
if (!asyncMethods.has(method)) {
|
function (event, guestInstanceId: number, method: string, args: any[]) {
|
||||||
throw new Error(`Invalid method: ${method}`);
|
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||||
}
|
if (!asyncMethods.has(method)) {
|
||||||
|
throw new Error(`Invalid method: ${method}`);
|
||||||
return (guest as any)[method](...args);
|
|
||||||
});
|
|
||||||
|
|
||||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, function (event, guestInstanceId: number, method: string, args: any[]) {
|
|
||||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
|
||||||
if (!syncMethods.has(method)) {
|
|
||||||
throw new Error(`Invalid method: ${method}`);
|
|
||||||
}
|
|
||||||
// Redirect history methods to updated navigationHistory property on webContents. See issue #42879.
|
|
||||||
if (navigationHistorySyncMethods.has(method)) {
|
|
||||||
let navigationMethod = method;
|
|
||||||
if (method === 'clearHistory') {
|
|
||||||
navigationMethod = 'clear';
|
|
||||||
}
|
}
|
||||||
return (guest as any).navigationHistory[navigationMethod](...args);
|
|
||||||
|
return (guest as any)[method](...args);
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (guest as any)[method](...args);
|
handleMessageSync(
|
||||||
});
|
IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL,
|
||||||
|
function (event, guestInstanceId: number, method: string, args: any[]) {
|
||||||
|
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||||
|
if (!syncMethods.has(method)) {
|
||||||
|
throw new Error(`Invalid method: ${method}`);
|
||||||
|
}
|
||||||
|
// Redirect history methods to updated navigationHistory property on webContents. See issue #42879.
|
||||||
|
if (navigationHistorySyncMethods.has(method)) {
|
||||||
|
let navigationMethod = method;
|
||||||
|
if (method === 'clearHistory') {
|
||||||
|
navigationMethod = 'clear';
|
||||||
|
}
|
||||||
|
return (guest as any).navigationHistory[navigationMethod](...args);
|
||||||
|
}
|
||||||
|
|
||||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET, function (event, guestInstanceId: number, property: string) {
|
return (guest as any)[method](...args);
|
||||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
|
||||||
if (!properties.has(property)) {
|
|
||||||
throw new Error(`Invalid property: ${property}`);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
return (guest as any)[property];
|
handleMessageSync(
|
||||||
});
|
IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET,
|
||||||
|
function (event, guestInstanceId: number, property: string) {
|
||||||
|
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||||
|
if (!properties.has(property)) {
|
||||||
|
throw new Error(`Invalid property: ${property}`);
|
||||||
|
}
|
||||||
|
|
||||||
handleMessageSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET, function (event, guestInstanceId: number, property: string, val: any) {
|
return (guest as any)[property];
|
||||||
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
|
||||||
if (!properties.has(property)) {
|
|
||||||
throw new Error(`Invalid property: ${property}`);
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
|
|
||||||
(guest as any)[property] = val;
|
handleMessageSync(
|
||||||
});
|
IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET,
|
||||||
|
function (event, guestInstanceId: number, property: string, val: any) {
|
||||||
|
const guest = getGuestForWebContents(guestInstanceId, event.sender);
|
||||||
|
if (!properties.has(property)) {
|
||||||
|
throw new Error(`Invalid property: ${property}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
(guest as any)[property] = val;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
// Returns WebContents from its guest id hosted in given webContents.
|
// Returns WebContents from its guest id hosted in given webContents.
|
||||||
const getGuestForWebContents = function (guestInstanceId: number, contents: Electron.WebContents) {
|
const getGuestForWebContents = function (guestInstanceId: number, contents: Electron.WebContents) {
|
||||||
|
|||||||
@@ -10,27 +10,37 @@ import { parseFeatures } from '@electron/internal/browser/parse-features-string'
|
|||||||
import { BrowserWindow } from 'electron/main';
|
import { BrowserWindow } from 'electron/main';
|
||||||
import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main';
|
import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main';
|
||||||
|
|
||||||
type PostData = LoadURLOptions['postData']
|
type PostData = LoadURLOptions['postData'];
|
||||||
export type WindowOpenArgs = {
|
export type WindowOpenArgs = {
|
||||||
url: string,
|
url: string;
|
||||||
frameName: string,
|
frameName: string;
|
||||||
features: string,
|
features: string;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `openGuestWindow` is called to create and setup event handling for the new
|
* `openGuestWindow` is called to create and setup event handling for the new
|
||||||
* window.
|
* window.
|
||||||
*/
|
*/
|
||||||
export function openGuestWindow ({ embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs, outlivesOpener, createWindow }: {
|
export function openGuestWindow({
|
||||||
embedder: WebContents,
|
embedder,
|
||||||
guest?: WebContents,
|
guest,
|
||||||
referrer: Referrer,
|
referrer,
|
||||||
disposition: string,
|
disposition,
|
||||||
postData?: PostData,
|
postData,
|
||||||
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions,
|
overrideBrowserWindowOptions,
|
||||||
windowOpenArgs: WindowOpenArgs,
|
windowOpenArgs,
|
||||||
outlivesOpener: boolean,
|
outlivesOpener,
|
||||||
createWindow?: Electron.CreateWindowFunction
|
createWindow
|
||||||
|
}: {
|
||||||
|
embedder: WebContents;
|
||||||
|
guest?: WebContents;
|
||||||
|
referrer: Referrer;
|
||||||
|
disposition: string;
|
||||||
|
postData?: PostData;
|
||||||
|
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions;
|
||||||
|
windowOpenArgs: WindowOpenArgs;
|
||||||
|
outlivesOpener: boolean;
|
||||||
|
createWindow?: Electron.CreateWindowFunction;
|
||||||
}): void {
|
}): void {
|
||||||
const { url, frameName, features } = windowOpenArgs;
|
const { url, frameName, features } = windowOpenArgs;
|
||||||
const { options: parsedOptions } = parseFeatures(features);
|
const { options: parsedOptions } = parseFeatures(features);
|
||||||
@@ -50,7 +60,9 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
|||||||
|
|
||||||
if (guest != null) {
|
if (guest != null) {
|
||||||
if (webContents !== guest) {
|
if (webContents !== guest) {
|
||||||
throw new Error('Invalid webContents. Created window should be connected to webContents passed with options object.');
|
throw new Error(
|
||||||
|
'Invalid webContents. Created window should be connected to webContents passed with options object.'
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
handleWindowLifecycleEvents({ embedder, guest, outlivesOpener });
|
handleWindowLifecycleEvents({ embedder, guest, outlivesOpener });
|
||||||
@@ -79,7 +91,14 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
|||||||
|
|
||||||
handleWindowLifecycleEvents({ embedder, guest: window.webContents, outlivesOpener });
|
handleWindowLifecycleEvents({ embedder, guest: window.webContents, outlivesOpener });
|
||||||
|
|
||||||
embedder.emit('did-create-window', window, { url, frameName, options: browserWindowOptions, disposition, referrer, postData });
|
embedder.emit('did-create-window', window, {
|
||||||
|
url,
|
||||||
|
frameName,
|
||||||
|
options: browserWindowOptions,
|
||||||
|
disposition,
|
||||||
|
referrer,
|
||||||
|
postData
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -88,10 +107,14 @@ export function openGuestWindow ({ embedder, guest, referrer, disposition, postD
|
|||||||
* too is the guest destroyed; this is Electron convention and isn't based in
|
* too is the guest destroyed; this is Electron convention and isn't based in
|
||||||
* browser behavior.
|
* browser behavior.
|
||||||
*/
|
*/
|
||||||
const handleWindowLifecycleEvents = function ({ embedder, guest, outlivesOpener }: {
|
const handleWindowLifecycleEvents = function ({
|
||||||
embedder: WebContents,
|
embedder,
|
||||||
guest: WebContents,
|
guest,
|
||||||
outlivesOpener: boolean
|
outlivesOpener
|
||||||
|
}: {
|
||||||
|
embedder: WebContents;
|
||||||
|
guest: WebContents;
|
||||||
|
outlivesOpener: boolean;
|
||||||
}) {
|
}) {
|
||||||
const closedByEmbedder = function () {
|
const closedByEmbedder = function () {
|
||||||
guest.removeListener('destroyed', closedByUser);
|
guest.removeListener('destroyed', closedByUser);
|
||||||
@@ -121,21 +144,25 @@ const securityWebPreferences: { [key: string]: boolean } = {
|
|||||||
enableWebSQL: false
|
enableWebSQL: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {}, insecureParsedWebPreferences: parsedWebPreferences = {} }: {
|
export function makeWebPreferences({
|
||||||
embedder: WebContents,
|
embedder,
|
||||||
insecureParsedWebPreferences?: ReturnType<typeof parseFeatures>['webPreferences'],
|
secureOverrideWebPreferences = {},
|
||||||
|
insecureParsedWebPreferences: parsedWebPreferences = {}
|
||||||
|
}: {
|
||||||
|
embedder: WebContents;
|
||||||
|
insecureParsedWebPreferences?: ReturnType<typeof parseFeatures>['webPreferences'];
|
||||||
// Note that override preferences are considered elevated, and should only be
|
// Note that override preferences are considered elevated, and should only be
|
||||||
// sourced from the main process, as they override security defaults. If you
|
// sourced from the main process, as they override security defaults. If you
|
||||||
// have unvetted prefs, use parsedWebPreferences.
|
// have unvetted prefs, use parsedWebPreferences.
|
||||||
secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'],
|
secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'];
|
||||||
}) {
|
}) {
|
||||||
const parentWebPreferences = embedder.getLastWebPreferences()!;
|
const parentWebPreferences = embedder.getLastWebPreferences()!;
|
||||||
const securityWebPreferencesFromParent = (Object.keys(securityWebPreferences).reduce((map, key) => {
|
const securityWebPreferencesFromParent = Object.keys(securityWebPreferences).reduce((map, key) => {
|
||||||
if (securityWebPreferences[key] === parentWebPreferences[key as keyof Electron.WebPreferences]) {
|
if (securityWebPreferences[key] === parentWebPreferences[key as keyof Electron.WebPreferences]) {
|
||||||
(map as any)[key] = parentWebPreferences[key as keyof Electron.WebPreferences];
|
(map as any)[key] = parentWebPreferences[key as keyof Electron.WebPreferences];
|
||||||
}
|
}
|
||||||
return map;
|
return map;
|
||||||
}, {} as Electron.WebPreferences));
|
}, {} as Electron.WebPreferences);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...parsedWebPreferences,
|
...parsedWebPreferences,
|
||||||
@@ -147,11 +174,13 @@ export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function formatPostDataHeaders (postData: PostData) {
|
function formatPostDataHeaders(postData: PostData) {
|
||||||
if (!postData) return;
|
if (!postData) return;
|
||||||
|
|
||||||
const { contentType, boundary } = parseContentTypeFormat(postData);
|
const { contentType, boundary } = parseContentTypeFormat(postData);
|
||||||
if (boundary != null) { return `content-type: ${contentType}; boundary=${boundary}`; }
|
if (boundary != null) {
|
||||||
|
return `content-type: ${contentType}; boundary=${boundary}`;
|
||||||
|
}
|
||||||
|
|
||||||
return `content-type: ${contentType}`;
|
return `content-type: ${contentType}`;
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user