Compare commits

..

39 Commits

Author SHA1 Message Date
Charles Kerr
b417696d6b refactor: migrate electron::api::Protocol to cppgc (#50857)
refactor: migrate api::Protocol to cppgc
2026-04-10 15:58:33 +09:00
Mitchell Cohen
4203d7688f fix: external resize hit targets for frameless windows on Windows (#50706) 2026-04-09 18:13:13 -05:00
Zeenat Lawal
62e637275a fix: move Electron help menu links to default app only (#50629)
* fix: remove Electron links from default help menu

* fix: remove help menu entirely from default menu

* fix: move Electron help menu links to default app

* docs: update default menu items list in menu.md
2026-04-09 12:14:22 -07:00
Shelley Vohr
28c0eb29df fix: webContents.print() ignoring mediaSize when silent (#50808)
fix: webContents.print() ignoring mediaSize when silent

PR #49523 moved the default media size fallback into OnGetDeviceNameToUse,
but the new code unconditionally writes kSettingMediaSize — clobbering
any mediaSize the caller had already set in WebContents::Print() from
options.mediaSize / pageSize. As a result, silent prints with an
explicit pageSize (e.g. "Letter") fell back to A4 with tiny content.

Only populate the default/printer media size when the caller hasn't
already supplied one, preserving the precedence:
  1. user-supplied mediaSize / pageSize
  2. printer default (when usePrinterDefaultPageSize is true)
  3. A4 fallback
2026-04-09 12:16:40 -05:00
Charles Kerr
8a730e2aec fix: remove dangling raw_ptr api::WebContents::zoom_controller_ (#50812)
fix: remove dangling raw_ptr api::WebContents::zoom_controller_
2026-04-09 12:16:17 -05:00
Shelley Vohr
044be7ce40 fix: avoid crash in window.print() when prefilling native print dialog (#50843)
fix: avoid crash in window.print() when prefilling native print dialog

When UpdatePrinterSettings() fails (e.g. the printer rejects the
requested resolution), OnError() nullifies print_info_ via
ReleaseContext(). The return value was not checked, so
AskUserForSettings() passed nil to [NSPrintPanel runModalWithPrintInfo:],
crashing in PJCSessionHasApplicationSetPrinter with a null PMPrintSession.

Check the return value and fall back to UseDefaultSettings() on failure
so the dialog opens with defaults instead of crashing.
2026-04-09 13:14:36 -04:00
Shelley Vohr
7245c6a3f0 ci: re-check signed commits on every PR synchronize (#50811)
The needs-signed-commits label was previously added by the lightweight
synchronize workflow but only removed by a job in build.yml gated on
`gha-done`, which requires every macOS/Linux/Windows build to finish
green. That made label removal both slow (waits on the full pipeline)
and fragile (any unrelated build failure leaves the label pinned even
after commits are properly signed).

Drop the `if` guard on the synchronize job so it re-evaluates signing
on every push, and add a removal step that runs on success when the
label is present. Force-pushing signed commits now clears the label as
soon as the check completes, with no dependency on the build pipeline.
2026-04-09 11:02:01 -04:00
Charles Kerr
b484b0bde9 fix: fix inset and stop using gfx::ToFlooredRectDeprecated() (#50809)
fix: fix inset and stop using ToFlooredRectDeprecated()
2026-04-09 09:55:11 -05:00
Charles Kerr
6c8a910232 refactor: remove unnecessary raw_ptr SavePageHandler::web_contents_ (#50810)
refactor: remove unnecessary field raw_ptr<content::WebContents> SavePageHandler::web_contents_
2026-04-09 09:54:44 -05:00
Noah Gregory
cc3d4f5f58 fix: PDF support when site isolation trials disabled (#50689)
* fix: use proper OOPIF PDF check in `StreamsPrivateAPI`

* fix: add `ShouldEnableSubframeZoom` override to `ElectronBrowserClient` for upstream parity

* fix: add `MaybeOverrideLocalURLCrossOriginEmbedderPolicy` override to `ElectronBrowserClient` for upstream parity

* fix: add `DoesSiteRequireDedicatedProcess` override to `ElectronBrowserClient` for upstream parity

* style: move `DoesSiteRequireDedicatedProcess` to correct override section
2026-04-09 15:35:26 +02:00
Shelley Vohr
b711ce7b04 chore: remove window enlargement revert patch (#50612)
* chore: remove window enlargement revert patch

Chromium removed the `window_enlargement_` system from
DesktopWindowTreeHostWin (1771dbae), which was a workaround for an AMD
driver bug from 2013 (crbug.com/286609) where translucent HWNDs smaller
than 64x64 caused graphical glitches. Chromium confirmed this is no
longer needed and shipped the removal.

This removes the revert patch and all Electron-side code that depended
on the `kEnableTransparentHwndEnlargement` feature flag, including the
`GetExpandedWindowSize` helper and max size constraint expansion in
`NativeWindow::GetContentMaximumSize`.

* test: remove obsolete <64x64 transparent window test

The test was added in 2018 (#12904) to verify the AMD driver
workaround that artificially enlarged translucent HWNDs smaller than
64x64 (crbug.com/286609). The workaround set the real HWND to 64x64
and subtracted a stored window_enlargement_ from every client/window
bounds query, so getContentSize() reported the originally-requested
size even though the actual HWND was larger.

With both the Chromium window_enlargement_ system and Electron's
GetExpandedWindowSize gone, setContentSize on a transparent
thickFrame window calls SetWindowPos directly. WS_THICKFRAME windows
are subject to DefWindowProc's MINMAXINFO.ptMinTrackSize clamp on
programmatic resizes (Chromium's OnGetMinMaxInfo ends with
SetMsgHandled(FALSE), so DefWindowProc overwrites the zeroed
min-track with system defaults), which on Windows Server 2025
floors at 32x39 — hence the failing [32, 39] vs [30, 30].

The removed feature_list.cc comment explicitly flagged this test as
the blocker for retiring kEnableTransparentHwndEnlargement, so
delete it alongside the workaround it was validating.
2026-04-09 15:34:10 +02:00
Alexey
adf9a6e303 fix: restore std::deque for dynamic crash key storage (#50795)
#47171 migrated `std::deque` to `base::circular_deque` in
`shell/common/crash_keys.cc`. However, `CrashKeyString` wraps a
`crashpad::Annotation` that holds self-referential pointers and
registers itself in a process-global linked list. `circular_deque`
relocates elements on growth (via `VectorBuffer::MoveConstructRange`),
leaving those pointers dangling — causing missing crash keys or a hung
crashpad handler (especially on macOS). The `base/containers/README.md`
warns: "Since `base::deque` does not have stable iterators and it will
move the objects it contains, it may not be appropriate for all uses."

Reverts to `std::deque`, whose block-based layout never relocates
existing elements. Adds a regression test that registers 50 dynamic
crash keys and verifies they all survive a renderer crash.

Notes: Fixed crash keys being lost and the crash reporter hanging on
macOS when many dynamic crash keys were registered.

Made-with: Cursor
2026-04-09 10:50:32 +02:00
Calvin
6744293e96 fix: account for extraSize in aspect ratio min/max clamping on macOS (#50794)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-09 10:50:17 +02:00
dependabot[bot]
0d3342debf build(deps-dev): bump @xmldom/xmldom from 0.8.11 to 0.8.12 in the npm_and_yarn group across 1 directory (#50824)
build(deps-dev): bump @xmldom/xmldom

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


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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-09 09:54:13 +02:00
Calvin
157cdac4b9 test: use shared get_out_dir() in generate_node_headers.py (#50828)
The local get_out_dir() defaulted to 'Testing' instead of 'Default',
causing e test to fail when using a non-Testing build config. Replace
it with the canonical version from script/lib/util.py.
2026-04-09 09:52:14 +02:00
Shelley Vohr
4dfada86ce fix: menu items not cleaned up after rebuild (#50806)
Menu was holding a SelfKeepAlive to itself from construction, so any
Menu that was never opened (e.g. an application menu replaced before
being shown) stayed pinned in cppgc forever. Repeated calls to
Menu.setApplicationMenu leaked every prior Menu along with its model
and items.

Restore the original Pin/Unpin lifecycle: start keep_alive_ empty and
only assign `this` in OnMenuWillShow. OnMenuWillClose already clears
it.
2026-04-09 11:56:39 +09:00
Kanishk Ranjan
df81a1d4ac test: add desktopCapturer icon validation (#50261)
* chore: testing of desktopCapturer can run on arm

* fix: DesktopMediaListCaptureThread crash

Fixed a crash when Windows calls ::CoCreateInstance() in the
DesktopMediaListCaptureThread before COM is initialized.

* test: added test for desktopCapturer fetchWindowIcons

* chore: updating Chromium patch hash

---------

Co-authored-by: Charles Kerr <charles@charleskerr.com>
2026-04-08 14:56:27 -04:00
Shelley Vohr
c3e3958668 fix: devtools re-attaches on open when previously detached (#50807)
PR #50646 added a dock state allowlist in SetDockState() that collapsed any
non-matching value to "right". WebContents::OpenDevTools passes an empty
string when no `mode` option is given, which is the sentinel LoadCompleted()
uses to restore `currentDockState` from prefs. The allowlist clobbered that
sentinel to "right", so previously-undocked devtools would flash detached
and then snap back to the right dock.

Preserve the empty string through SetDockState() so the pref-restore path
runs; still reject any non-empty invalid value to keep the JS-injection
guard from #50646 intact.
2026-04-08 13:36:47 -04:00
electron-roller[bot]
afd5fb4a60 chore: bump chromium to 148.0.7778.0 (main) (#50769)
* chore: bump chromium in DEPS to 148.0.7776.0

* chore: bump chromium in DEPS to 148.0.7778.0

* fix(patch): buffered_data_source_host_impl include added upstream

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7712714

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

* fix(patch): ASan process info callback added upstream

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7724018

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

* fix(patch): ServiceProcessHost per-instance observer migration

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7700794

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

* fix(patch): FSA BlockPath factory method refactor

Upstream refactored BlockPath initialization to use factory methods
(CreateRelative, CreateAbsolute, CreateSuffix) and a switch statement.
Updated the exposed code in the header to match.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7665590

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

* fix(patch): service process tracker per-instance observer refactor

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7700794

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

* chore: update patches (trivial only)

* 7723958: Rename blink::WebString::FromUTF16() to FromUtf16()

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7723958

Co-Authored-By: Claude <svc-devxp-claude@slack-corp.com>

* fixup! fix(patch): ASan process info callback added upstream

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Samuel Maddock <samuelmaddock@electronjs.org>
Co-authored-by: Claude <svc-devxp-claude@slack-corp.com>
2026-04-08 13:34:24 -04:00
Charles Kerr
8679522922 chore: iwyu commonly-included headers in shell/ (#50778)
* chore: iwyu in shell/browser/api/electron_api_web_contents.h

* chore: iwyu in shell/browser/browser.h

* chore: iwyu in shell/browser/javascript_environment.h

* chore: iwyu in shell/common/gin_hhelper/function_template.h

* chore: do not include node_includes.h if we are not using it

* chore: fix transitive include
2026-04-08 09:33:42 -05:00
David Sanders
0828de3ccd ci: include .obj checksums when calculating object change rate (#50772) 2026-04-08 14:57:40 +02:00
Michaela Laurencin
6b5a4ff66c ci: allow ai-pr label without comment (#50792) 2026-04-08 13:09:23 +02:00
Charles Kerr
ca28023d4d chore: remove unused enum classes (#50782)
chore: remove unused FileSystemAccessPermissionContext::Access enum class

chore: remove unused FileSystemAccessPermissionContext::RequestType enum class

declared in 344aba08 but never used
2026-04-08 09:41:38 +02:00
Samuel Attard
e60441ad60 build: update build-tools to latest (#50786) 2026-04-08 09:31:12 +02:00
Charles Kerr
a189425373 fix: dangling raw_ptr api::Session::browser_context_ (#50784)
* fix: dangling raw_ptr api::Session::browser_context_

* fix: address code review feedback
2026-04-08 15:04:29 +09:00
Charles Kerr
7eccea1315 refactor: remove use of deprecated class base::MemoryPressureListener (#50763) 2026-04-07 20:11:02 -05:00
Shelley Vohr
2e74ad2c68 feat: add setSuspended and isSuspended to globalShortcut (#50425)
Adds the ability to temporarily suspend and resume global shortcut
handling via `globalShortcut.setSuspended()` and query the current
state via `globalShortcut.isSuspended()`. When suspended, registered
shortcuts stop listening and new registrations are rejected. When
resumed, previously registered shortcuts are automatically restored.
2026-04-07 15:21:43 +02:00
dependabot[bot]
9ba299afff build(deps-dev): bump @octokit/rest from 20.1.2 to 22.0.1 (#50759)
Bumps [@octokit/rest](https://github.com/octokit/rest.js) from 20.1.2 to 22.0.1.
- [Release notes](https://github.com/octokit/rest.js/releases)
- [Commits](https://github.com/octokit/rest.js/compare/v20.1.2...v22.0.1)

---
updated-dependencies:
- dependency-name: "@octokit/rest"
  dependency-version: 22.0.1
  dependency-type: direct:development
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-07 12:52:52 +02:00
Charles Kerr
6df2228ea0 refactor: remove more unused menu api (#50661)
* chore: do not expose menu.isItemCheckedAt() to JS

Not used, documented, or typed. Added in dae98fa43f.

* chore: do not expose menu.isEnabledAt() to JS

Nto used, documented, or typed. Added in dae98fa43f.

* chore: do not expose menu.isVisibleAt() to JS

Not used, documented, or typed. Added in dae98fa43f.

* chore: remove unused undocumented API `getOjectHash`

Not used, documented, or typed. Added in ddad3e4846.

Appears to never have been used.
2026-04-07 10:05:12 +02:00
Charles Kerr
a29674e4cf fix: dangling raw_ptr JavascriptEnvironment::isolate_ (#50738) 2026-04-07 10:03:11 +02:00
David Sanders
81dd0f42e1 ci: don't set needs review status on PR that isn't open (#50762) 2026-04-06 23:12:53 -07:00
electron-roller[bot]
6aaf490aa5 chore: bump chromium to 148.0.7768.0 (main) (#50599)
* chore: bump chromium in DEPS to 148.0.7765.0

* chore: bump chromium in DEPS to 148.0.7766.0

* fix(patch-conflict): update packed_resources dep name after upstream rename

Upstream renamed //chrome:packed_resources_integrity_header to
//chrome:packed_resources. Updated the patch to guard the new dependency
name with !is_electron_build while preserving the same intent.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7714543

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(patch-conflict): update code_cache_host_impl.cc for upstream includes and TODO

Upstream added #include <stdint.h> and a TODO comment in
code_cache_host_impl.cc which conflicted with the Electron code cache
custom schemes patch. Resolved by keeping both upstream additions and
the Electron ProcessLockURLIsCodeCacheScheme function.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7615151

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: update patch hunk headers

Co-Authored-By: Claude <noreply@anthropic.com>

* 7700837: update RecordContentToVisibleTimeRequest from mojom to native struct

Upstream typemapped RecordContentToVisibleTimeRequest from a Mojo
struct to a native C++ struct. Updated OSR virtual method signatures
from blink::mojom::RecordContentToVisibleTimeRequestPtr to
std::optional<blink::RecordContentToVisibleTimeRequest> and
blink::RecordContentToVisibleTimeRequest to match.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7700837

Co-Authored-By: Claude <noreply@anthropic.com>

* 7714579: update WebString::FromASCII to FromUTF8

Upstream renamed blink::WebString::FromASCII to FromAscii. Updated
Electron's usage to FromUTF8 which is equivalent for ASCII scheme
strings and avoids a dependency on the renamed method. Also fixed
blink::String::FromUTF8 to use the String constructor directly.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7714579

Co-Authored-By: Claude <noreply@anthropic.com>

* 7696480: add stream_info dep after StreamInfo extraction

Upstream extracted extensions::StreamInfo from PdfViewerStreamManager
to a standalone class in extensions/browser/mime_handler/stream_info.h.
Added the new target as a dependency since Electron's streams_private
and pdf_viewer_private APIs use PdfViewerStreamManager which now
depends on the separate StreamInfo target.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7696480

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: bump chromium in DEPS to 148.0.7768.0

* fix(patch-conflict): update PiP patch for new toggle_mute_button in overlay window

Upstream added a toggle_mute_button to the live caption dialog controls
in VideoOverlayWindowViews::SetLiveCaptionDialogVisibility. Extended the
existing #if 0 guard to include the new button handling since Electron
disables live caption dialog functionality.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7682308

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(patch-conflict): update packed_resource_integrity patch after upstream dep removal

Upstream removed the deps += [ "//chrome:packed_resources" ] line from
the if (!is_win) block in chrome/browser/BUILD.gn. The Electron patch
no longer needs to guard this dep with !is_electron_build in this
location since the dep was already relocated by an earlier upstream CL.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7714543

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(patch-conflict): update WebSocket throttling revert for DisconnectWebSocketOnBFCache guard

Upstream added a DisconnectWebSocketOnBFCacheEnabled() runtime feature
check that wraps the WebSocket BFCache feature registration. Updated the
Electron revert patch to place the kAllowAggressiveThrottlingWithWebSocket
ternary inside the new conditional guard.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7698838

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(patch-conflict): update SCContentSharingPicker patch for upstream native picker refactor

Upstream added is_native_picker and filter_ based native picker session
validation to ScreenCaptureKitDeviceMac. Electron's patch uses its own
native picker approach (active_streams_ counter + direct SCContentSharingPicker
API), so marked the new upstream parameters as [[maybe_unused]] and kept
Electron's implementation.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7713560

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: update patch hunk headers

Co-Authored-By: Claude <noreply@anthropic.com>

* 7708800: update StartDragging signature to use RenderFrameHost

Upstream refactored StartDragging to take a RenderFrameHost& instead of
separate source_origin and source_rwh parameters. Updated
OffScreenWebContentsView to match the new signature and derive the
RenderWidgetHostImpl from the RenderFrameHost internally.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7708800

Co-Authored-By: Claude <noreply@anthropic.com>

* 7682308: add toggle_mute_button to chromium_src build sources

Upstream added a ToggleMuteButton to the PiP overlay window controls.
Added the new toggle_mute_button.cc/h source files to Electron's
chromium_src/BUILD.gn to resolve linker errors.

Ref: https://chromium-review.googlesource.com/c/chromium/src/+/7682308

Co-Authored-By: Claude <noreply@anthropic.com>

* chore: update patches after main rebase

* fixup! 7708800: update StartDragging signature to use RenderFrameHost

fix linting

* 7705541: [trap-handler] Track individual Wasm memories | https://chromium-review.googlesource.com/c/v8/v8/+/7705541

Moved the SetUpWebAssemblyTrapHandler() call to before the V8 isolate is created

* fixup! fix utility process tests

---------

Co-authored-by: electron-roller[bot] <84116207+electron-roller[bot]@users.noreply.github.com>
Co-authored-by: Keeley Hammond <khammond@slack-corp.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Keeley Hammond <vertedinde@electronjs.org>
2026-04-06 20:38:46 -07:00
Samuel Attard
b8f25c4ced fix: resolve target bundle path once at start of install (#50745)
Resolve the Squirrel.Mac target bundle URL to a canonical path once at the
top of -[SQRLInstaller installRequest:] and use it for every step of the
install chain, rejecting requests whose path is not already canonical. When
running with elevated privileges, additionally require the target to be the
application bundle that contains the installer. SQRLUpdater now writes the
resolved bundle URL so the serialized request is canonical by construction.
2026-04-06 22:10:52 -04:00
Samuel Attard
9fafc81e88 ci: use hermetic mac SDK for the release ffmpeg build (#50746)
* ci: use hermetic mac SDK for the release ffmpeg build

gn gen out/ffmpeg runs as a raw gn invocation, so it never receives the
mac_sdk_path arg that e build injects for out/Default. On macOS runners
that means out/Default builds against the hermetic build-tools SDK while
out/ffmpeg falls through to the runner's system Xcode SDK. Reuse the
value e build already wrote so both builds share the same sysroot.

* ci: copy hermetic SDK symlink into out/ffmpeg and rewrite path

mac_sdk_path must live under root_build_dir, so pointing out/ffmpeg at
//out/Default/... doesn't work. Copy the xcode_links symlink tree into
out/ffmpeg and rewrite the path. Gate on Darwin so Windows/Linux don't
run the sed/cp at all.
2026-04-06 18:26:38 -04:00
Mitchell Cohen
4d05010945 fix: enforce size constraints on window creation on Windows and Linux (#49906)
* enforce size constraints on window creation

* set constraints after resizing on init

* restore conditional centering
2026-04-06 16:38:23 -04:00
LiRongWan
c3189e9886 docs: link menu type references (#50414)
* docs: link menu type references

* docs: trigger CI re-run for signed commits verification
2026-04-06 16:36:06 -04:00
Samuel Attard
983ebdd6de ci: make src-cache upload atomic (#50743)
ci: make src-cache upload atomic and sweep orphaned temp files

The checkout action's cp of the ~6GB zstd archive directly to the final
path on the cache share is non-atomic; an interrupted copy or a
concurrent reader produces zstd "Read error (39): premature end" on
restore, and the truncated file then satisfies the existence check so
no later run repairs it.

Upload to a run-unique *.tar.upload-<run_id>-<attempt> temp name on the
share and mv to the final path, discarding our temp if a concurrent run
got there first. A new clean-orphaned-cache-uploads workflow removes
temp files older than 4h every 4 hours.
2026-04-06 16:04:49 -04:00
dependabot[bot]
b9c08ef9c2 build(deps): bump @electron/get from 2.0.3 to 4.0.3 in /npm (#50553)
Bumps [@electron/get](https://github.com/electron/get) from 2.0.3 to 4.0.3.
- [Release notes](https://github.com/electron/get/releases)
- [Commits](https://github.com/electron/get/compare/v2.0.3...v4.0.3)

---
updated-dependencies:
- dependency-name: "@electron/get"
  dependency-version: 4.0.3
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-04-06 10:32:35 -07:00
Samuel Attard
9f3cc9122c build: derive patches upstream-head ref from script path (#50727)
* build: derive patches upstream-head ref from script path

gclient-new-workdir.py symlinks each repo's .git/refs back to the source
checkout, so the fixed refs/patches/upstream-head was shared across all
worktrees. Parallel `e sync` runs in different worktrees clobbered each
other's upstream-head, breaking `e patches` and check-patch-diff.

Suffix the ref with an md5 of the script directory so each worktree writes
a distinct ref into the shared refs dir. Fall back to the legacy ref name
in guess_base_commit so existing checkouts keep working until next sync.

* fixup: also write legacy upstream-head ref and note it in docs
2026-04-06 09:42:08 -07:00
444 changed files with 11538 additions and 14775 deletions

View File

@@ -228,7 +228,17 @@ runs:
if: ${{ inputs.is-release == 'true' }}
run: |
cd src
gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") use_remoteexec=true use_siso=true $GN_EXTRA_ARGS"
# Reuse the hermetic mac_sdk_path that `e build` wrote for out/Default so
# out/ffmpeg builds against the same SDK instead of the runner's system Xcode.
# The path has to live under root_build_dir, so copy the symlink tree and
# rewrite Default -> ffmpeg.
MAC_SDK_ARG=""
if [ "$(uname)" = "Darwin" ]; then
mkdir -p out/ffmpeg
cp -a out/Default/xcode_links out/ffmpeg/
MAC_SDK_ARG=$(sed -n 's|^\(mac_sdk_path = "//out/\)Default/|\1ffmpeg/|p' out/Default/args.gn)
fi
gn gen out/ffmpeg --args="import(\"//electron/build/args/ffmpeg.gn\") use_remoteexec=true use_siso=true $MAC_SDK_ARG $GN_EXTRA_ARGS"
e build --target electron:electron_ffmpeg_zip -C ../../out/ffmpeg
- name: Remove Clang problem matcher
shell: bash

View File

@@ -191,19 +191,31 @@ runs:
# only permits .tar/.tgz so we keep the extension and decode on restore.
tar -cf - src | zstd -T0 --long=30 -f -o $CACHE_FILE
echo "Compressed src to $(du -sh $CACHE_FILE | cut -f1 -d' ')"
cp ./$CACHE_FILE $CACHE_DRIVE/
- name: Persist Src Cache
if: ${{ steps.check-cache.outputs.cache_exists == 'false' && inputs.use-cache == 'true' }}
shell: bash
run: |
final_cache_path=$CACHE_DRIVE/$CACHE_FILE
# Upload to a run-unique temp name first so concurrent readers never
# observe a partially-written file, and an interrupted copy can't leave
# a truncated file at the final path. Orphaned temp files get swept by
# the clean-orphaned-cache-uploads workflow.
tmp_cache_path=$final_cache_path.upload-${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}
echo "Uploading to temp path: $tmp_cache_path"
cp ./$CACHE_FILE $tmp_cache_path
echo "Using cache key: $DEPSHASH"
echo "Checking path: $final_cache_path"
if [ -f "$final_cache_path" ]; then
echo "Cache already persisted at $final_cache_path by a concurrent run; discarding ours"
rm -f $tmp_cache_path
else
mv -f $tmp_cache_path $final_cache_path
echo "Cache key persisted in $final_cache_path"
fi
if [ ! -f "$final_cache_path" ]; then
echo "Cache key not found"
exit 1
else
echo "Cache key persisted in $final_cache_path"
fi
- name: Wait for active SSH sessions
shell: bash

View File

@@ -15,7 +15,7 @@ runs:
git config --global core.preloadindex true
git config --global core.longpaths true
fi
export BUILD_TOOLS_SHA=a0cc95a1884a631559bcca0c948465b725d9295a
export BUILD_TOOLS_SHA=1b7bd25dae4a780bb3170fff56c9327b53aaf7eb
npm i -g @electron/build-tools
# Update depot_tools to ensure python
e d update_depot_tools
@@ -29,4 +29,4 @@ runs:
else
echo "$HOME/.electron_build_tools/third_party/depot_tools" >> $GITHUB_PATH
echo "$HOME/.electron_build_tools/third_party/depot_tools/python-bin" >> $GITHUB_PATH
fi
fi

View File

@@ -442,34 +442,7 @@ jobs:
contents: read
needs: [docs-only, macos-x64, macos-arm64, linux-x64, linux-x64-asan, linux-arm, linux-arm64, windows-x64, windows-x86, windows-arm64]
if: always() && github.repository == 'electron/electron' && !contains(needs.*.result, 'failure')
steps:
steps:
- name: GitHub Actions Jobs Done
run: |
echo "All GitHub Actions Jobs are done"
check-signed-commits:
name: Check signed commits in green PR
needs: gha-done
if: ${{ contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
runs-on: ubuntu-slim
permissions:
contents: read
pull-requests: write
steps:
- name: Check signed commits in PR
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
with:
comment: |
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
for all incoming PRs. To get your PR merged, please sign those commits
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
(`git push --force-with-lease`)
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
- name: Remove needs-signed-commits label
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh pr edit $PR_URL --remove-label needs-signed-commits

View File

@@ -0,0 +1,32 @@
name: Clean Orphaned Cache Uploads
# Description:
# Sweeps orphaned in-flight upload temp files left on the src-cache volumes
# by checkout/action.yml when its cp-to-share step dies before the rename.
# A successful upload finishes in minutes, so anything older than 4h is dead.
on:
schedule:
- cron: "0 */4 * * *"
workflow_dispatch:
permissions: {}
jobs:
clean-orphaned-uploads:
if: github.repository == 'electron/electron'
runs-on: electron-arc-centralus-linux-amd64-32core
permissions:
contents: read
container:
image: ghcr.io/electron/build:bc2f48b2415a670de18d13605b1cf0eb5fdbaae1
options: --user root
volumes:
- /mnt/cross-instance-cache:/mnt/cross-instance-cache
- /mnt/win-cache:/mnt/win-cache
steps:
- name: Remove Orphaned Upload Temp Files
shell: bash
run: |
find /mnt/cross-instance-cache -maxdepth 1 -type f -name '*.tar.upload-*' -mmin +240 -print -delete
find /mnt/win-cache -maxdepth 1 -type f -name '*.tar.upload-*' -mmin +240 -print -delete

View File

@@ -17,11 +17,13 @@ jobs:
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

View File

@@ -50,7 +50,7 @@ jobs:
field-value: ✅ Reviewed
pull-request-labeled-ai-pr:
name: ai-pr label added
if: github.event.label.name == 'ai-pr'
if: github.event.label.name == 'ai-pr' && github.event.pull_request.state != 'closed'
runs-on: ubuntu-latest
permissions: {}
steps:

View File

@@ -13,7 +13,6 @@ permissions: {}
jobs:
check-signed-commits:
name: Check signed commits in PR
if: ${{ !contains(github.event.pull_request.labels.*.name, 'needs-signed-commits')}}
runs-on: ubuntu-slim
permissions:
contents: read
@@ -23,9 +22,9 @@ jobs:
uses: 1Password/check-signed-commits-action@ed2885f3ed2577a4f5d3c3fe895432a557d23d52 # v1
with:
comment: |
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
for all incoming PRs. To get your PR merged, please sign those commits
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
⚠️ This PR contains unsigned commits. This repository enforces [commit signatures](https://docs.github.com/en/authentication/managing-commit-signature-verification)
for all incoming PRs. To get your PR merged, please sign those commits
(`git rebase --exec 'git commit -S --amend --no-edit -n' @{upstream}`) and force push them to this branch
(`git push --force-with-lease`)
For more information on signing commits, see GitHub's documentation on [Telling Git about your signing key](https://docs.github.com/en/authentication/managing-commit-signature-verification/telling-git-about-your-signing-key).
@@ -37,3 +36,11 @@ jobs:
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh pr edit $PR_URL --add-label needs-signed-commits
- name: Remove needs-signed-commits label
if: ${{ success() && contains(github.event.pull_request.labels.*.name, 'needs-signed-commits') }}
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
PR_URL: ${{ github.event.pull_request.html_url }}
run: |
gh pr edit $PR_URL --remove-label needs-signed-commits

View File

@@ -1,52 +0,0 @@
{
"$schema": "./node_modules/oxfmt/configuration_schema.json",
"printWidth": 120,
"tabWidth": 2,
"useTabs": false,
"semi": true,
"singleQuote": true,
"trailingComma": "none",
"arrowParens": "always",
"bracketSpacing": true,
"endOfLine": "lf",
"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"
]
}

View File

@@ -775,6 +775,7 @@ source_set("electron_lib") {
"//components/zoom",
"//extensions/browser",
"//extensions/browser/api:api_provider",
"//extensions/browser/mime_handler:stream_info",
"//extensions/browser/updater",
"//extensions/common",
"//extensions/common:core_api_provider",

2
DEPS
View File

@@ -2,7 +2,7 @@ gclient_gn_args_from = 'src'
vars = {
'chromium_version':
'148.0.7763.0',
'148.0.7778.0',
'node_version':
'v24.14.1',
'nan_version':

View File

@@ -8,13 +8,10 @@ const path = require('node:path');
const electronRoot = path.resolve(__dirname, '../..');
class AccessDependenciesPlugin {
apply(compiler) {
compiler.hooks.compilation.tap('AccessDependenciesPlugin', (compilation) => {
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', (modules) => {
const filePaths = modules
.map((m) => m.resource)
.filter((p) => p)
.map((p) => path.relative(electronRoot, p));
apply (compiler) {
compiler.hooks.compilation.tap('AccessDependenciesPlugin', compilation => {
compilation.hooks.finishModules.tap('AccessDependenciesPlugin', modules => {
const filePaths = modules.map(m => m.resource).filter(p => p).map(p => path.relative(electronRoot, p));
console.info(JSON.stringify(filePaths));
});
});
@@ -34,14 +31,7 @@ module.exports = ({
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 = {}) => {
const onlyPrintingGraph = !!env.PRINT_WEBPACK_GRAPH;
@@ -71,59 +61,49 @@ module.exports = ({
}
if (targetDeletesNodeGlobals) {
plugins.push(
new webpack.ProvidePlugin({
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
global: ['@electron/internal/common/webpack-provider', '_global'],
process: ['@electron/internal/common/webpack-provider', 'process']
})
);
plugins.push(new webpack.ProvidePlugin({
Buffer: ['@electron/internal/common/webpack-provider', 'Buffer'],
global: ['@electron/internal/common/webpack-provider', '_global'],
process: ['@electron/internal/common/webpack-provider', 'process']
}));
}
// Webpack 5 no longer polyfills process or Buffer.
if (!alwaysHasNode) {
plugins.push(
new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser'
})
);
plugins.push(new webpack.ProvidePlugin({
Buffer: ['buffer', 'Buffer'],
process: 'process/browser'
}));
}
plugins.push(
new webpack.ProvidePlugin({
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
})
);
plugins.push(new webpack.ProvidePlugin({
Promise: ['@electron/internal/common/webpack-globals-provider', 'Promise']
}));
plugins.push(new webpack.DefinePlugin(defines));
if (wrapInitWithProfilingTimeout) {
plugins.push(
new WrapperPlugin({
header: 'function ___electron_webpack_init__() {',
footer: `
plugins.push(new WrapperPlugin({
header: 'function ___electron_webpack_init__() {',
footer: `
};
if ((globalThis.process || binding.process).argv.includes("--profile-electron-init")) {
setTimeout(___electron_webpack_init__, 0);
} else {
___electron_webpack_init__();
}`
})
);
}));
}
if (wrapInitWithTryCatch) {
plugins.push(
new WrapperPlugin({
header: 'try {',
footer: `
plugins.push(new WrapperPlugin({
header: 'try {',
footer: `
} catch (err) {
console.error('Electron ${outputFilename} script failed to run');
console.error(err);
}`
})
);
}));
}
return {
@@ -153,26 +133,23 @@ if ((globalThis.process || binding.process).argv.includes("--profile-electron-in
}
},
module: {
rules: [
{
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
loader: 'null-loader'
},
{
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
transpileOnly: onlyPrintingGraph,
ignoreDiagnostics: [
// File '{0}' is not under 'rootDir' '{1}'.
6059,
// Private field '{0}' must be declared in an enclosing class.
1111
]
}
rules: [{
test: (moduleName) => !onlyPrintingGraph && ignoredModules.includes(moduleName),
loader: 'null-loader'
}, {
test: /\.ts$/,
loader: 'ts-loader',
options: {
configFile: path.resolve(electronRoot, 'tsconfig.electron.json'),
transpileOnly: onlyPrintingGraph,
ignoreDiagnostics: [
// File '{0}' is not under 'rootDir' '{1}'.
6059,
// Private field '{0}' must be declared in an enclosing class.
1111
]
}
]
}]
},
node: {
__dirname: false,

View File

@@ -144,6 +144,8 @@ static_library("chrome") {
"//chrome/browser/ui/views/overlay/toggle_camera_button.h",
"//chrome/browser/ui/views/overlay/toggle_microphone_button.cc",
"//chrome/browser/ui/views/overlay/toggle_microphone_button.h",
"//chrome/browser/ui/views/overlay/toggle_mute_button.cc",
"//chrome/browser/ui/views/overlay/toggle_mute_button.h",
"//chrome/browser/ui/views/overlay/video_overlay_window_views.cc",
"//chrome/browser/ui/views/overlay/video_overlay_window_views.h",
"//chrome/browser/ui/views/picture_in_picture/picture_in_picture_bounds_change_animation.cc",

View File

@@ -1,5 +1,5 @@
import { shell } from 'electron/common';
import { app, dialog, BrowserWindow, ipcMain } from 'electron/main';
import { app, dialog, BrowserWindow, ipcMain, Menu } from 'electron/main';
import * as path from 'node:path';
import * as url from 'node:url';
@@ -11,7 +11,54 @@ app.on('window-all-closed', () => {
app.quit();
});
function decorateURL(url: string) {
const isMac = process.platform === 'darwin';
app.whenReady().then(() => {
const helpMenu: Electron.MenuItemConstructorOptions = {
role: 'help',
submenu: [
{
label: 'Learn More',
click: async () => {
await shell.openExternal('https://electronjs.org');
}
},
{
label: 'Documentation',
click: async () => {
const version = process.versions.electron;
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
}
},
{
label: 'Community Discussions',
click: async () => {
await shell.openExternal('https://discord.gg/electronjs');
}
},
{
label: 'Search Issues',
click: async () => {
await shell.openExternal('https://github.com/electron/electron/issues');
}
}
]
};
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
const template: Electron.MenuItemConstructorOptions[] = [
...(isMac ? [macAppMenu] : []),
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
helpMenu
];
Menu.setApplicationMenu(Menu.buildFromTemplate(template));
});
function decorateURL (url: string) {
// safely add `?utm_source=default_app
const parsedUrl = new URL(url);
parsedUrl.searchParams.append('utm_source', 'default_app');
@@ -21,12 +68,13 @@ function decorateURL(url: string) {
// Find the shortest path to the electron binary
const absoluteElectronPath = process.execPath;
const relativeElectronPath = path.relative(process.cwd(), absoluteElectronPath);
const electronPath =
absoluteElectronPath.length < relativeElectronPath.length ? absoluteElectronPath : relativeElectronPath;
const electronPath = absoluteElectronPath.length < relativeElectronPath.length
? absoluteElectronPath
: relativeElectronPath;
const indexPath = path.resolve(app.getAppPath(), 'index.html');
function isTrustedSender(webContents: Electron.WebContents) {
function isTrustedSender (webContents: Electron.WebContents) {
if (webContents !== (mainWindow && mainWindow.webContents)) {
return false;
}
@@ -42,7 +90,7 @@ ipcMain.handle('bootstrap', (event) => {
return isTrustedSender(event.sender) ? electronPath : null;
});
async function createWindow(backgroundColor?: string) {
async function createWindow (backgroundColor?: string) {
await app.whenReady();
const options: Electron.BrowserWindowConstructorOptions = {
@@ -67,7 +115,7 @@ async function createWindow(backgroundColor?: string) {
mainWindow = new BrowserWindow(options);
mainWindow.on('ready-to-show', () => mainWindow!.show());
mainWindow.webContents.setWindowOpenHandler((details) => {
mainWindow.webContents.setWindowOpenHandler(details => {
shell.openExternal(decorateURL(details.url));
return { action: 'deny' };
});

View File

@@ -15,7 +15,7 @@ type DefaultAppOptions = {
interactive: boolean;
abi: boolean;
modules: string[];
};
}
// Parse command line options.
const argv = process.argv.slice(1);
@@ -74,7 +74,7 @@ if (option.modules.length > 0) {
(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.
Object.defineProperty(process, 'defaultApp', {
configurable: false,
@@ -92,11 +92,9 @@ async function loadApplicationPackage(packagePath: string) {
const emitWarning = process.emitWarning;
try {
process.emitWarning = () => {};
packageJson = (
await import(url.pathToFileURL(packageJsonPath).toString(), {
with: { type: 'json' }
})
).default;
packageJson = (await import(url.pathToFileURL(packageJsonPath).toString(), {
with: { type: 'json' }
})).default;
} catch (e) {
showErrorMessage(`Unable to parse ${packageJsonPath}\n\n${(e as Error).message}`);
return;
@@ -145,23 +143,23 @@ async function loadApplicationPackage(packagePath: string) {
}
}
function showErrorMessage(message: string) {
function showErrorMessage (message: string) {
app.focus();
dialog.showErrorBox('Error launching app', message);
process.exit(1);
}
async function loadApplicationByURL(appUrl: string) {
async function loadApplicationByURL (appUrl: string) {
const { loadURL } = await import('./default_app.js');
loadURL(appUrl);
}
async function loadApplicationByFile(appPath: string) {
async function loadApplicationByFile (appPath: string) {
const { loadFile } = await import('./default_app.js');
loadFile(appPath);
}
async function startRepl() {
async function startRepl () {
if (process.platform === 'win32') {
console.error('Electron REPL not currently supported on Windows');
process.exit(1);
@@ -189,7 +187,7 @@ async function startRepl() {
process.exit(0);
});
function defineBuiltin(context: any, name: string, getter: Function) {
function defineBuiltin (context: any, name: string, getter: Function) {
const setReal = (val: any) => {
// Deleting the property before re-assigning it disables the
// getter/setter mechanism.
@@ -227,42 +225,11 @@ async function startRepl() {
// we only trigger custom tab-completion when no common words are
// potentially matches.
const commonWords = [
'async',
'await',
'break',
'case',
'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'
'async', 'await', 'break', 'case', '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'];

View File

@@ -2,10 +2,10 @@ const { ipcRenderer, contextBridge } = require('electron/renderer');
const policy = window.trustedTypes.createPolicy('electron-default-app', {
// we trust the SVG contents
createHTML: (input) => input
createHTML: input => input
});
async function getOcticonSvg(name: string) {
async function getOcticonSvg (name: string) {
try {
const response = await fetch(`octicon/${name}.svg`);
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) {
if (cssClass.startsWith('octicon-')) {
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');
function replaceText(selector: string, text: string, link?: string) {
function replaceText (selector: string, text: string, link?: string) {
const element = document.querySelector<HTMLElement>(selector);
if (element) {
if (link) {
@@ -51,11 +51,7 @@ async function initialize() {
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(
'.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('.command-example', `${electronPath} path-to-app`);

View File

@@ -148,3 +148,34 @@ added:
-->
Unregisters all of the global shortcuts.
### `globalShortcut.setSuspended(suspended)`
<!--
```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/50425
```
-->
* `suspended` boolean - Whether global shortcut handling should be suspended.
Suspends or resumes global shortcut handling. When suspended, all registered
global shortcuts will stop listening for key presses. When resumed, all
previously registered shortcuts will begin listening again. New shortcut
registrations will fail while handling is suspended.
This can be useful when you want to temporarily allow the user to press key
combinations without your application intercepting them, for example while
displaying a UI to rebind shortcuts.
### `globalShortcut.isSuspended()`
<!--
```YAML history
added:
- pr-url: https://github.com/electron/electron/pull/50425
```
-->
Returns `boolean` - Whether global shortcut handling is currently suspended.

View File

@@ -44,8 +44,8 @@ See [`Menu`](menu.md) for examples.
menu items.
* `registerAccelerator` boolean (optional) _Linux_ _Windows_ - If false, the accelerator won't be registered
with the system, but it will still be displayed. Defaults to true.
* `sharingItem` SharingItem (optional) _macOS_ - The item to share when the `role` is `shareMenu`.
* `submenu` (MenuItemConstructorOptions[] | [Menu](menu.md)) (optional) - Should be specified
* `sharingItem` [SharingItem](structures/sharing-item.md) (optional) _macOS_ - The item to share when the `role` is `shareMenu`.
* `submenu` ([MenuItemConstructorOptions](#new-menuitemoptions)[] | [Menu](menu.md)) (optional) - Should be specified
for `submenu` type menu items. If `submenu` is specified, the `type: 'submenu'` can be omitted.
If the value is not a [`Menu`](menu.md) then it will be automatically converted to one using
`Menu.buildFromTemplate`.
@@ -89,7 +89,7 @@ A `Function` that is fired when the MenuItem receives a click event.
It can be called with `menuItem.click(event, focusedWindow, focusedWebContents)`.
* `event` [KeyboardEvent](structures/keyboard-event.md)
* `focusedWindow` [BaseWindow](browser-window.md)
* `focusedWindow` [BaseWindow](base-window.md)
* `focusedWebContents` [WebContents](web-contents.md)
#### `menuItem.submenu`
@@ -110,11 +110,11 @@ A `string` (optional) indicating the item's role, if set. Can be `undo`, `redo`,
#### `menuItem.accelerator`
An `Accelerator | null` indicating the item's accelerator, if set.
An [`Accelerator | null`](../tutorial/keyboard-shortcuts.md#accelerators) indicating the item's accelerator, if set.
#### `menuItem.userAccelerator` _Readonly_ _macOS_
An `Accelerator | null` indicating the item's [user-assigned accelerator](https://developer.apple.com/documentation/appkit/nsmenuitem/1514850-userkeyequivalent?language=objc) for the menu item.
An [`Accelerator | null`](../tutorial/keyboard-shortcuts.md#accelerators) indicating the item's [user-assigned accelerator](https://developer.apple.com/documentation/appkit/nsmenuitem/1514850-userkeyequivalent?language=objc) for the menu item.
> [!NOTE]
> This property is only initialized after the `MenuItem` has been added to a `Menu`. Either via `Menu.buildFromTemplate` or via `Menu.append()/insert()`. Accessing before initialization will just return `null`.
@@ -170,7 +170,7 @@ This property can be dynamically changed.
#### `menuItem.sharingItem` _macOS_
A `SharingItem` indicating the item to share when the `role` is `shareMenu`.
A [`SharingItem`](structures/sharing-item.md) indicating the item to share when the `role` is `shareMenu`.
This property can be dynamically changed.

View File

@@ -46,7 +46,7 @@ this has the additional effect of removing the menu bar from the window.
> [!NOTE]
> The default menu will be created automatically if the app does not set one.
> It contains standard items such as `File`, `Edit`, `View`, `Window` and `Help`.
> It contains standard items such as `File`, `Edit`, `View`, and `Window`.
#### `Menu.getApplicationMenu()`
@@ -70,7 +70,7 @@ for more information on macOS' native actions.
#### `Menu.buildFromTemplate(template)`
- `template` (MenuItemConstructorOptions | [MenuItem](menu-item.md))[]
- `template` ([MenuItemConstructorOptions](menu-item.md#new-menuitemoptions) | [MenuItem](menu-item.md))[]
Returns [`Menu`](menu.md)
@@ -162,7 +162,7 @@ Emitted when a popup is closed either manually or with `menu.closePopup()`.
#### `menu.items`
A `MenuItem[]` array containing the menu's items.
A [`MenuItem[]`](menu-item.md) array containing the menu's items.
Each `Menu` consists of multiple [`MenuItem`](menu-item.md) instances and each `MenuItem`
can nest a `Menu` into its `submenu` property.

View File

@@ -79,7 +79,7 @@ $ ../../electron/script/git-import-patches ../../electron/patches/node
$ ../../electron/script/git-export-patches -o ../../electron/patches/node
```
Note that `git-import-patches` will mark the commit that was `HEAD` when it was run as `refs/patches/upstream-head`. This lets you keep track of which commits are from Electron patches (those that come after `refs/patches/upstream-head`) and which commits are in upstream (those before `refs/patches/upstream-head`).
Note that `git-import-patches` will mark the commit that was `HEAD` when it was run as `refs/patches/upstream-head` (and a checkout-specific `refs/patches/upstream-head-<hash>` so that gclient worktrees sharing a `.git/refs` directory don't clobber each other). This lets you keep track of which commits are from Electron patches (those that come after `refs/patches/upstream-head`) and which commits are in upstream (those before `refs/patches/upstream-head`).
#### Resolving conflicts

View File

@@ -41,8 +41,7 @@ Object.assign(app, {
commandLine: {
hasSwitch: (theSwitch: string) => commandLine.hasSwitch(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)),
removeSwitch: (theSwitch: string) => commandLine.removeSwitch(String(theSwitch))
} as Electron.CommandLine
@@ -51,10 +50,10 @@ Object.assign(app, {
// we define this here because it'd be overly complicated to
// do in native land
Object.defineProperty(app, 'applicationMenu', {
get() {
get () {
return Menu.getApplicationMenu();
},
set(menu: Electron.Menu | null) {
set (menu: Electron.Menu | null) {
return Menu.setApplicationMenu(menu);
}
});
@@ -117,22 +116,17 @@ for (const name of events) {
}
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.on(
'-client-certificate-request-password',
async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
event.preventDefault();
const { hostname, tokenName, isRetry } = event;
if (!app._clientCertRequestPasswordHandler) {
callback('');
return;
}
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
callback(password);
app.on('-client-certificate-request-password', async (event: Electron.Event<Electron.ClientCertRequestParams>, callback: (password: string) => void) => {
event.preventDefault();
const { hostname, tokenName, isRetry } = event;
if (!app._clientCertRequestPasswordHandler) {
callback('');
return;
}
);
const password = await app._clientCertRequestPasswordHandler({ hostname, tokenName, isRetry });
callback(password);
});

View File

@@ -135,7 +135,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
allowAnyVersion: boolean = false;
// 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 {
// Make a HEAD request to follow redirects and get the final URL
const response = await net.fetch(url, {
@@ -153,9 +153,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
const hasMsixExtension = pathname.endsWith('.msix') || pathname.endsWith('.msixbundle');
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) {
if (error instanceof TypeError) {
@@ -166,7 +164,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
}
// 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 {
await this.validateMsixUrl(url);
return true;
@@ -180,12 +178,12 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
// 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
private compareVersions(v1: string, v2: string): number {
const parts1 = v1.split('.').map((part) => {
private compareVersions (v1: string, v2: string): number {
const parts1 = v1.split('.').map(part => {
const parsed = parseInt(part, 10);
return isNaN(parsed) ? 0 : parsed;
});
const parts2 = v2.split('.').map((part) => {
const parts2 = v2.split('.').map(part => {
const parsed = parseInt(part, 10);
return isNaN(parsed) ? 0 : parsed;
});
@@ -205,12 +203,9 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
// Private: Parse the static releases array format
// 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') {
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 };
}
@@ -239,18 +234,14 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
const releaseEntry = json.releases.find((r: any) => r.version === currentReleaseVersion);
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 };
}
const updateTo = releaseEntry.updateTo;
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 };
}
@@ -264,22 +255,15 @@ 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) {
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: 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> = {
...this.updateHeaders,
Accept: 'application/json' // Always set Accept header, overriding any user-provided Accept
@@ -315,8 +299,8 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
}
}
private async getUpdateInfo(url: string): Promise<UpdateInfo> {
if (url && (await this.isDirectMsixUrl(url))) {
private async getUpdateInfo (url: string): Promise<UpdateInfo> {
if (url && await this.isDirectMsixUrl(url)) {
return { ok: true, available: true, updateUrl: url, releaseDate: new Date() };
} else {
const updateJson = await this.fetchSquirrelJson(url);
@@ -337,7 +321,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
const releaseName = updateJson.name ?? '';
releaseDate = releaseDate ?? new Date();
if (!(await this.isDirectMsixUrl(updateUrl, true))) {
if (!await this.isDirectMsixUrl(updateUrl, true)) {
return { ok: false };
} else {
return {
@@ -353,11 +337,11 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
}
}
getFeedURL() {
getFeedURL () {
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 headers: Record<string, string> | undefined;
let allowAnyVersion: boolean | undefined;
@@ -367,23 +351,23 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
headers = options.headers;
allowAnyVersion = options.allowAnyVersion;
} 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') {
updateURL = options;
} 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.updateHeaders = headers ?? null;
this.allowAnyVersion = allowAnyVersion ?? false;
}
getPackageInfo(): MSIXPackageInfo {
getPackageInfo (): MSIXPackageInfo {
return msixUpdate.getPackageInfo() as MSIXPackageInfo;
}
async checkForUpdates() {
async checkForUpdates () {
const url = this.updateURL;
if (!url) {
return this.emitError(new Error('Update URL is not set'));
@@ -398,11 +382,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
// If appInstallerUri is set, Windows App Installer manages updates automatically
// Prevent updates here to avoid conflicts
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');
@@ -425,26 +405,18 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
forceUpdateFromAnyVersion: this.allowAnyVersion
} as UpdateMsixOptions);
this.emit(
'update-downloaded',
{},
msixUrlInfo.releaseNotes,
msixUrlInfo.releaseName,
msixUrlInfo.releaseDate,
msixUrlInfo.updateUrl,
() => {
this.quitAndInstall();
}
);
this.emit('update-downloaded', {}, msixUrlInfo.releaseNotes, msixUrlInfo.releaseName, msixUrlInfo.releaseDate, msixUrlInfo.updateUrl, () => {
this.quitAndInstall();
});
}
} catch (error) {
this.emitError(error as Error);
}
}
async quitAndInstall() {
async quitAndInstall () {
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();
return;
}
@@ -469,7 +441,7 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
// Private: Emit both error object and message, this is to keep compatibility
// with Old APIs.
emitError(error: Error) {
emitError (error: Error) {
this.emit('error', error, error.message);
}
}

View File

@@ -8,40 +8,40 @@ class AutoUpdater extends EventEmitter implements Electron.AutoUpdater {
updateAvailable: boolean = false;
updateURL: string | null = null;
quitAndInstall() {
quitAndInstall () {
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();
app.quit();
}
getFeedURL() {
getFeedURL () {
return this.updateURL ?? '';
}
getPackageInfo() {
getPackageInfo () {
// Squirrel-based Windows apps don't have MSIX package information
return undefined;
}
setFeedURL(options: { url: string } | string) {
setFeedURL (options: { url: string } | string) {
let updateURL: string;
if (typeof options === 'object') {
if (typeof options.url === 'string') {
updateURL = options.url;
} 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') {
updateURL = options;
} 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;
}
async checkForUpdates() {
async checkForUpdates () {
const url = this.updateURL;
if (!url) {
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
// with Old APIs.
emitError(error: Error) {
emitError (error: Error) {
this.emit('error', error, error.message);
}
}

View File

@@ -1,5 +1,4 @@
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } = process._linkedBinding(
'electron_browser_msix_updater'
);
const { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo } =
process._linkedBinding('electron_browser_msix_updater');
export { updateMsix, registerPackage, registerRestartOnUpdate, getPackageInfo };

View File

@@ -34,12 +34,8 @@ const spawnUpdate = async function (args: string[], options: { detached: boolean
let stdout = '';
let stderr = '';
spawnedProcess.stdout.on('data', (data) => {
stdout += data;
});
spawnedProcess.stderr.on('data', (data) => {
stderr += data;
});
spawnedProcess.stdout.on('data', (data) => { stdout += data; });
spawnedProcess.stderr.on('data', (data) => { stderr += data; });
spawnedProcess.on('error', (error) => {
spawnedProcess = undefined;
@@ -63,12 +59,12 @@ const spawnUpdate = async function (args: string[], options: { detached: boolean
};
// Start an instance of the installed app.
export function processStart() {
export function processStart () {
spawnUpdate(['--processStartAndWait', exeName], { detached: true });
}
// 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 });
try {
// Last line of output is the JSON details about the releases
@@ -80,12 +76,12 @@ export async function checkForUpdate(updateURL: string): Promise<any> {
}
// 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 });
}
// Is the Update.exe installed with the current application?
export function supported() {
export function supported () {
try {
fs.accessSync(updateExe, fs.constants.R_OK);
return true;

View File

@@ -25,156 +25,88 @@ BaseWindow.prototype.setTouchBar = function (touchBar) {
// Properties
Object.defineProperty(BaseWindow.prototype, 'autoHideMenuBar', {
get: function () {
return this.isMenuBarAutoHide();
},
set: function (autoHide) {
this.setAutoHideMenuBar(autoHide);
}
get: function () { return this.isMenuBarAutoHide(); },
set: function (autoHide) { this.setAutoHideMenuBar(autoHide); }
});
Object.defineProperty(BaseWindow.prototype, 'visibleOnAllWorkspaces', {
get: function () {
return this.isVisibleOnAllWorkspaces();
},
set: function (visible) {
this.setVisibleOnAllWorkspaces(visible);
}
get: function () { return this.isVisibleOnAllWorkspaces(); },
set: function (visible) { this.setVisibleOnAllWorkspaces(visible); }
});
Object.defineProperty(BaseWindow.prototype, 'fullScreen', {
get: function () {
return this.isFullScreen();
},
set: function (full) {
this.setFullScreen(full);
}
get: function () { return this.isFullScreen(); },
set: function (full) { this.setFullScreen(full); }
});
Object.defineProperty(BaseWindow.prototype, 'simpleFullScreen', {
get: function () {
return this.isSimpleFullScreen();
},
set: function (simple) {
this.setSimpleFullScreen(simple);
}
get: function () { return this.isSimpleFullScreen(); },
set: function (simple) { this.setSimpleFullScreen(simple); }
});
Object.defineProperty(BaseWindow.prototype, 'focusable', {
get: function () {
return this.isFocusable();
},
set: function (focusable) {
this.setFocusable(focusable);
}
get: function () { return this.isFocusable(); },
set: function (focusable) { this.setFocusable(focusable); }
});
Object.defineProperty(BaseWindow.prototype, 'kiosk', {
get: function () {
return this.isKiosk();
},
set: function (kiosk) {
this.setKiosk(kiosk);
}
get: function () { return this.isKiosk(); },
set: function (kiosk) { this.setKiosk(kiosk); }
});
Object.defineProperty(BaseWindow.prototype, 'documentEdited', {
get: function () {
return this.isDocumentEdited();
},
set: function (edited) {
this.setDocumentEdited(edited);
}
get: function () { return this.isDocumentEdited(); },
set: function (edited) { this.setDocumentEdited(edited); }
});
Object.defineProperty(BaseWindow.prototype, 'shadow', {
get: function () {
return this.hasShadow();
},
set: function (shadow) {
this.setHasShadow(shadow);
}
get: function () { return this.hasShadow(); },
set: function (shadow) { this.setHasShadow(shadow); }
});
Object.defineProperty(BaseWindow.prototype, 'representedFilename', {
get: function () {
return this.getRepresentedFilename();
},
set: function (filename) {
this.setRepresentedFilename(filename);
}
get: function () { return this.getRepresentedFilename(); },
set: function (filename) { this.setRepresentedFilename(filename); }
});
Object.defineProperty(BaseWindow.prototype, 'minimizable', {
get: function () {
return this.isMinimizable();
},
set: function (min) {
this.setMinimizable(min);
}
get: function () { return this.isMinimizable(); },
set: function (min) { this.setMinimizable(min); }
});
Object.defineProperty(BaseWindow.prototype, 'title', {
get: function () {
return this.getTitle();
},
set: function (title) {
this.setTitle(title);
}
get: function () { return this.getTitle(); },
set: function (title) { this.setTitle(title); }
});
Object.defineProperty(BaseWindow.prototype, 'maximizable', {
get: function () {
return this.isMaximizable();
},
set: function (max) {
this.setMaximizable(max);
}
get: function () { return this.isMaximizable(); },
set: function (max) { this.setMaximizable(max); }
});
Object.defineProperty(BaseWindow.prototype, 'resizable', {
get: function () {
return this.isResizable();
},
set: function (res) {
this.setResizable(res);
}
get: function () { return this.isResizable(); },
set: function (res) { this.setResizable(res); }
});
Object.defineProperty(BaseWindow.prototype, 'menuBarVisible', {
get: function () {
return this.isMenuBarVisible();
},
set: function (visible) {
this.setMenuBarVisibility(visible);
}
get: function () { return this.isMenuBarVisible(); },
set: function (visible) { this.setMenuBarVisibility(visible); }
});
Object.defineProperty(BaseWindow.prototype, 'fullScreenable', {
get: function () {
return this.isFullScreenable();
},
set: function (full) {
this.setFullScreenable(full);
}
get: function () { return this.isFullScreenable(); },
set: function (full) { this.setFullScreenable(full); }
});
Object.defineProperty(BaseWindow.prototype, 'closable', {
get: function () {
return this.isClosable();
},
set: function (close) {
this.setClosable(close);
}
get: function () { return this.isClosable(); },
set: function (close) { this.setClosable(close); }
});
Object.defineProperty(BaseWindow.prototype, 'movable', {
get: function () {
return this.isMovable();
},
set: function (move) {
this.setMovable(move);
}
get: function () { return this.isMovable(); },
set: function (move) { this.setMovable(move); }
});
BaseWindow.getFocusedWindow = () => {

View File

@@ -1,11 +1,4 @@
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');
@@ -17,10 +10,10 @@ export default class BrowserView {
// AutoResize state
#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 = {};
constructor(options: { webPreferences: WebPreferences; webContents?: WebContents } = { webPreferences: {} }) {
constructor (options: {webPreferences: WebPreferences, webContents?: WebContents} = { webPreferences: {} }) {
const { webPreferences = {}, webContents } = options;
if (webContents) {
v8Util.setHiddenValue(webPreferences, 'webContents', webContents);
@@ -32,21 +25,21 @@ export default class BrowserView {
this.#webContentsView.webContents.once('destroyed', this.#destroyListener);
}
get webContents() {
get webContents () {
return this.#webContentsView.webContents;
}
setBounds(bounds: Rectangle) {
setBounds (bounds: Rectangle) {
this.#webContentsView.setBounds(bounds);
this.#autoHorizontalProportion = null;
this.#autoVerticalProportion = null;
}
getBounds() {
getBounds () {
return this.#webContentsView.getBounds();
}
setAutoResize(options: AutoResizeOptions) {
setAutoResize (options: AutoResizeOptions) {
if (options == null || typeof options !== 'object') {
throw new Error('Invalid auto resize options');
}
@@ -62,19 +55,19 @@ export default class BrowserView {
this.#autoVerticalProportion = null;
}
setBackgroundColor(color: string) {
setBackgroundColor (color: string) {
this.#webContentsView.setBackgroundColor(color);
}
// Internal methods
get ownerWindow(): BrowserWindow | null {
get ownerWindow (): BrowserWindow | null {
return this.#ownerWindow;
}
// We can't rely solely on the webContents' owner window because
// a webContents can be closed by the user while the BrowserView
// remains alive and attached to a BrowserWindow.
set ownerWindow(w: BrowserWindow | null) {
set ownerWindow (w: BrowserWindow | null) {
this.#removeResizeListener();
if (this.webContents && !this.webContents.isDestroyed()) {
@@ -84,7 +77,7 @@ export default class BrowserView {
this.#ownerWindow = w;
if (w) {
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', () => {
this.#removeResizeListener();
this.#ownerWindow = null;
@@ -93,25 +86,25 @@ export default class BrowserView {
}
}
#onDestroy() {
#onDestroy () {
// Ensure that if #webContentsView's webContents is destroyed,
// the WebContentsView is removed from the view hierarchy.
this.#ownerWindow?.contentView.removeChildView(this.webContentsView);
}
#removeResizeListener() {
#removeResizeListener () {
if (this.#ownerWindow && this.#resizeListener) {
this.#ownerWindow.off('resize', this.#resizeListener);
this.#resizeListener = null;
}
}
#autoHorizontalProportion: { width: number; left: number } | null = null;
#autoVerticalProportion: { height: number; top: number } | null = null;
#autoResize() {
#autoHorizontalProportion: {width: number, left: number} | null = null;
#autoVerticalProportion: {height: number, top: number} | null = null;
#autoResize () {
if (!this.ownerWindow) {
throw new Error('Electron bug: #autoResize called without owner window');
}
};
if (this.#autoResizeFlags.horizontal && this.#autoHorizontalProportion == null) {
const viewBounds = this.#webContentsView.getBounds();
@@ -165,7 +158,7 @@ export default class BrowserView {
};
}
get webContentsView() {
get webContentsView () {
return this.#webContentsView;
}
}

View File

@@ -40,14 +40,10 @@ BrowserWindow.prototype._init = function (this: BWT) {
let unresponsiveEvent: NodeJS.Timeout | null = null;
const emitUnresponsiveEvent = () => {
unresponsiveEvent = null;
if (!this.isDestroyed() && this.isEnabled()) {
this.emit('unresponsive');
}
if (!this.isDestroyed() && this.isEnabled()) { this.emit('unresponsive'); }
};
this.webContents.on('unresponsive', () => {
if (!unresponsiveEvent) {
unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50);
}
if (!unresponsiveEvent) { unresponsiveEvent = setTimeout(emitUnresponsiveEvent, 50); }
});
this.webContents.on('responsive', () => {
if (unresponsiveEvent) {
@@ -87,16 +83,16 @@ BrowserWindow.prototype._init = function (this: BWT) {
this._browserViews = [];
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.
app.emit('browser-window-created', { preventDefault() {} }, this);
app.emit('browser-window-created', { preventDefault () {} }, this);
Object.defineProperty(this, 'devToolsWebContents', {
enumerable: true,
configurable: false,
get() {
get () {
return this.webContents.devToolsWebContents;
}
});
@@ -108,7 +104,7 @@ const isBrowserWindow = (win: any) => {
BrowserWindow.fromId = (id: number) => {
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 = () => {
@@ -218,12 +214,10 @@ BrowserWindow.prototype.addBrowserView = function (browserView: BrowserView) {
};
BrowserWindow.prototype.setBrowserView = function (browserView: BrowserView) {
this._browserViews.forEach((bv) => {
this._browserViews.forEach(bv => {
this.removeBrowserView(bv);
});
if (browserView) {
this.addBrowserView(browserView);
}
if (browserView) { this.addBrowserView(browserView); }
};
BrowserWindow.prototype.removeBrowserView = function (browserView: BrowserView) {

View File

@@ -5,7 +5,7 @@ import { app } from 'electron/main';
const binding = process._linkedBinding('electron_browser_crash_reporter');
class CrashReporter implements Electron.CrashReporter {
start(options: Electron.CrashReporterStartOptions) {
start (options: Electron.CrashReporterStartOptions) {
const {
productName = app.name,
companyName,
@@ -21,9 +21,7 @@ class CrashReporter implements Electron.CrashReporter {
if (uploadToServer && !submitURL) throw new Error('submitURL must be specified when uploadToServer is true');
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();
@@ -36,33 +34,26 @@ class CrashReporter implements Electron.CrashReporter {
...globalExtra
};
binding.start(
submitURL,
uploadToServer,
ignoreSystemCrashHandler,
rateLimit,
compress,
globalExtraAmended,
extra,
false
);
binding.start(submitURL, uploadToServer,
ignoreSystemCrashHandler, rateLimit, compress, globalExtraAmended, extra, false);
}
getLastCrashReport() {
const reports = this.getUploadedReports().sort((a, b) => {
const ats = a && a.date ? new Date(a.date).getTime() : 0;
const bts = b && b.date ? new Date(b.date).getTime() : 0;
return bts - ats;
});
getLastCrashReport () {
const reports = this.getUploadedReports()
.sort((a, b) => {
const ats = (a && a.date) ? new Date(a.date).getTime() : 0;
const bts = (b && b.date) ? new Date(b.date).getTime() : 0;
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();
}
getUploadToServer() {
getUploadToServer () {
if (process.type === 'browser') {
return binding.getUploadToServer();
} else {
@@ -70,7 +61,7 @@ class CrashReporter implements Electron.CrashReporter {
}
}
setUploadToServer(uploadToServer: boolean) {
setUploadToServer (uploadToServer: boolean) {
if (process.type === 'browser') {
return binding.setUploadToServer(uploadToServer);
} else {
@@ -78,15 +69,15 @@ class CrashReporter implements Electron.CrashReporter {
}
}
addExtraParameter(key: string, value: string) {
addExtraParameter (key: string, value: string) {
binding.addExtraParameter(key, value);
}
removeExtraParameter(key: string) {
removeExtraParameter (key: string) {
binding.removeExtraParameter(key);
}
getParameters() {
getParameters () {
return binding.getParameters();
}
}

View File

@@ -1,11 +1,8 @@
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: {
options: ElectronInternal.GetSourcesOptions;
@@ -13,13 +10,13 @@ let currentlyRunning: {
}[] = [];
// |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);
}
export { isDisplayMediaSystemPickerAvailable };
export async function getSources(args: Electron.SourcesOptions) {
export async function getSources (args: Electron.SourcesOptions) {
if (!isValid(args)) throw new Error('Invalid options');
const resizableValues = new Map();
@@ -67,11 +64,11 @@ export async function getSources(args: Electron.SourcesOptions) {
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);
currentlyRunning = currentlyRunning.filter(running => running.options !== options);
};
capturer._onerror = (error: string) => {

View File

@@ -1,13 +1,5 @@
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');
@@ -68,9 +60,7 @@ const checkAppInitialized = function () {
const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogProperties)[]): number => {
let dialogProperties = 0;
for (const property of properties) {
if (Object.hasOwn(OpenFileDialogProperties, property)) {
dialogProperties |= OpenFileDialogProperties[property];
}
if (Object.hasOwn(OpenFileDialogProperties, property)) { dialogProperties |= OpenFileDialogProperties[property]; }
}
return dialogProperties;
};
@@ -78,9 +68,7 @@ const setupOpenDialogProperties = (properties: (keyof typeof OpenFileDialogPrope
const setupSaveDialogProperties = (properties: (keyof typeof SaveFileDialogProperties)[]): number => {
let dialogProperties = 0;
for (const property of properties) {
if (Object.hasOwn(SaveFileDialogProperties, property)) {
dialogProperties |= SaveFileDialogProperties[property];
}
if (Object.hasOwn(SaveFileDialogProperties, property)) { dialogProperties |= SaveFileDialogProperties[property]; }
}
return dialogProperties;
};
@@ -162,7 +150,7 @@ const openDialog = (sync: boolean, window: BaseWindow | null, options?: OpenDial
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) => {
@@ -206,7 +194,7 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
// Choose a default button to get selected when dialog is cancelled.
if (cancelId == null) {
// 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()) {
const text = button.toLowerCase();
if (text === 'cancel' || text === 'no') {
@@ -222,9 +210,7 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
// Generate an ID used for closing the message box.
id = getNextId();
// 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));
}
@@ -254,80 +240,59 @@ const messageBox = (sync: boolean, window: BaseWindow | null, options?: MessageB
export function showOpenDialog(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialog(options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialog(
windowOrOptions: BaseWindow | OpenDialogOptions,
maybeOptions?: OpenDialogOptions
): OpenDialogReturnValue {
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
export function showOpenDialog (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
return openDialog(false, window, options);
}
export function showOpenDialogSync(window: BaseWindow, options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialogSync(options: OpenDialogOptions): OpenDialogReturnValue;
export function showOpenDialogSync(
windowOrOptions: BaseWindow | OpenDialogOptions,
maybeOptions?: OpenDialogOptions
): OpenDialogReturnValue {
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
export function showOpenDialogSync (windowOrOptions: BaseWindow | OpenDialogOptions, maybeOptions?: OpenDialogOptions): OpenDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
return openDialog(true, window, options);
}
export function showSaveDialog(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialog(options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialog(
windowOrOptions: BaseWindow | SaveDialogOptions,
maybeOptions?: SaveDialogOptions
): SaveDialogReturnValue {
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
export function showSaveDialog (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
return saveDialog(false, window, options);
}
export function showSaveDialogSync(window: BaseWindow, options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialogSync(options: SaveDialogOptions): SaveDialogReturnValue;
export function showSaveDialogSync(
windowOrOptions: BaseWindow | SaveDialogOptions,
maybeOptions?: SaveDialogOptions
): SaveDialogReturnValue {
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
export function showSaveDialogSync (windowOrOptions: BaseWindow | SaveDialogOptions, maybeOptions?: SaveDialogOptions): SaveDialogReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
return saveDialog(true, window, options);
}
export function showMessageBox(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBox(options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBox(
windowOrOptions: BaseWindow | MessageBoxOptions,
maybeOptions?: MessageBoxOptions
): MessageBoxReturnValue {
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
export function showMessageBox (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
return messageBox(false, window, options);
}
export function showMessageBoxSync(window: BaseWindow, options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBoxSync(options: MessageBoxOptions): MessageBoxReturnValue;
export function showMessageBoxSync(
windowOrOptions: BaseWindow | MessageBoxOptions,
maybeOptions?: MessageBoxOptions
): MessageBoxReturnValue {
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
export function showMessageBoxSync (windowOrOptions: BaseWindow | MessageBoxOptions, maybeOptions?: MessageBoxOptions): MessageBoxReturnValue {
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
return messageBox(true, window, options);
}
export function showErrorBox(...args: any[]) {
export function showErrorBox (...args: any[]) {
return dialogBinding.showErrorBox(...args);
}
export function showCertificateTrustDialog(
windowOrOptions: BaseWindow | CertificateTrustDialogOptions,
maybeOptions?: CertificateTrustDialogOptions
) {
const window = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions;
const options = windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions;
export function showCertificateTrustDialog (windowOrOptions: BaseWindow | CertificateTrustDialogOptions, maybeOptions?: CertificateTrustDialogOptions) {
const window = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? null : windowOrOptions);
const options = (windowOrOptions && !(windowOrOptions instanceof BaseWindow) ? windowOrOptions : maybeOptions);
if (options == null || typeof options !== 'object') {
throw new TypeError('options must be an object');

View File

@@ -4,12 +4,8 @@ let _inAppPurchase;
if (process.platform === 'darwin') {
const { inAppPurchase } = process._linkedBinding('electron_browser_in_app_purchase');
const _purchase = inAppPurchase.purchaseProduct as (
productID: string,
quantity?: number,
username?: string
) => Promise<boolean>;
inAppPurchase.purchaseProduct = (productID: string, opts?: number | { quantity?: number; username?: string }) => {
const _purchase = inAppPurchase.purchaseProduct as (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 username = typeof opts === 'object' ? opts.username : undefined;
return _purchase.apply(inAppPurchase, [productID, quantity, username]);

View File

@@ -1,66 +1,20 @@
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 isWindows = process.platform === 'win32';
const isLinux = process.platform === 'linux';
type RoleId =
| 'about'
| 'close'
| 'copy'
| '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';
type RoleId = 'about' | 'close' | 'copy' | '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 {
label: string;
accelerator?: string;
checked?: boolean;
windowMethod?: (window: BaseWindow) => void;
webContentsMethod?: (webContents: WebContents) => void;
windowMethod?: ((window: BaseWindow) => void);
webContentsMethod?: ((webContents: WebContents) => void);
appMethod?: () => void;
registerAccelerator?: boolean;
nonNativeMacOSRole?: boolean;
@@ -69,7 +23,7 @@ interface Role {
export const roleList: Record<RoleId, Role> = {
about: {
get label() {
get label () {
return isLinux ? 'About' : `About ${app.name}`;
},
...((isWindows || isLinux) && { appMethod: () => app.showAboutPanel() })
@@ -77,23 +31,23 @@ export const roleList: Record<RoleId, Role> = {
close: {
label: isMac ? 'Close Window' : 'Close',
accelerator: 'CommandOrControl+W',
windowMethod: (w) => w.close()
windowMethod: w => w.close()
},
copy: {
label: 'Copy',
accelerator: 'CommandOrControl+C',
webContentsMethod: (wc) => wc.copy(),
webContentsMethod: wc => wc.copy(),
registerAccelerator: false
},
cut: {
label: 'Cut',
accelerator: 'CommandOrControl+X',
webContentsMethod: (wc) => wc.cut(),
webContentsMethod: wc => wc.cut(),
registerAccelerator: false
},
delete: {
label: 'Delete',
webContentsMethod: (wc) => wc.delete()
webContentsMethod: wc => wc.delete()
},
forcereload: {
label: 'Force Reload',
@@ -112,7 +66,7 @@ export const roleList: Record<RoleId, Role> = {
label: 'Help'
},
hide: {
get label() {
get label () {
return `Hide ${app.name}`;
},
accelerator: 'Command+H'
@@ -124,31 +78,28 @@ export const roleList: Record<RoleId, Role> = {
minimize: {
label: 'Minimize',
accelerator: 'CommandOrControl+M',
windowMethod: (w) => {
windowMethod: w => {
if (w.minimizable) w.minimize();
}
},
paste: {
label: 'Paste',
accelerator: 'CommandOrControl+V',
webContentsMethod: (wc) => wc.paste(),
webContentsMethod: wc => wc.paste(),
registerAccelerator: false
},
pasteandmatchstyle: {
label: 'Paste and Match Style',
accelerator: isMac ? 'Cmd+Option+Shift+V' : 'Shift+CommandOrControl+V',
webContentsMethod: (wc) => wc.pasteAndMatchStyle(),
webContentsMethod: wc => wc.pasteAndMatchStyle(),
registerAccelerator: false
},
quit: {
get label() {
get label () {
switch (process.platform) {
case 'darwin':
return `Quit ${app.name}`;
case 'win32':
return 'Exit';
default:
return 'Quit';
case 'darwin': return `Quit ${app.name}`;
case 'win32': return 'Exit';
default: return 'Quit';
}
},
accelerator: isWindows ? undefined : 'CommandOrControl+Q',
@@ -157,7 +108,7 @@ export const roleList: Record<RoleId, Role> = {
redo: {
label: 'Redo',
accelerator: isWindows ? 'Control+Y' : 'Shift+CommandOrControl+Z',
webContentsMethod: (wc) => wc.redo()
webContentsMethod: wc => wc.redo()
},
reload: {
label: 'Reload',
@@ -180,7 +131,7 @@ export const roleList: Record<RoleId, Role> = {
selectall: {
label: 'Select All',
accelerator: 'CommandOrControl+A',
webContentsMethod: (wc) => wc.selectAll()
webContentsMethod: wc => wc.selectAll()
},
services: {
label: 'Services'
@@ -213,7 +164,7 @@ export const roleList: Record<RoleId, Role> = {
label: 'Toggle Developer Tools',
accelerator: isMac ? 'Alt+Command+I' : 'Ctrl+Shift+I',
nonNativeMacOSRole: true,
webContentsMethod: (wc) => {
webContentsMethod: wc => {
const bw = wc.getOwnerBrowserWindow();
if (bw) bw.webContents.toggleDevTools();
}
@@ -228,7 +179,7 @@ export const roleList: Record<RoleId, Role> = {
undo: {
label: 'Undo',
accelerator: 'CommandOrControl+Z',
webContentsMethod: (wc) => wc.undo()
webContentsMethod: wc => wc.undo()
},
unhide: {
label: 'Show All'
@@ -257,7 +208,7 @@ export const roleList: Record<RoleId, Role> = {
},
togglespellchecker: {
label: 'Check Spelling While Typing',
get checked() {
get checked () {
const wc = webContents.getFocusedWebContents();
const ses = wc ? wc.session : session.defaultSession;
return ses.spellCheckerEnabled;
@@ -270,7 +221,7 @@ export const roleList: Record<RoleId, Role> = {
},
// App submenu should be used for Mac only
appmenu: {
get label() {
get label () {
return app.name;
},
submenu: [
@@ -288,7 +239,9 @@ export const roleList: Record<RoleId, Role> = {
// File submenu
filemenu: {
label: 'File',
submenu: [isMac ? { role: 'close' } : { role: 'quit' }]
submenu: [
isMac ? { role: 'close' } : { role: 'quit' }
]
},
// Edit submenu
editmenu: {
@@ -301,27 +254,34 @@ export const roleList: Record<RoleId, Role> = {
{ role: 'copy' },
{ role: 'paste' },
...(isMac
? ([
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Substitutions',
submenu: [
{ role: 'showSubstitutions' },
{ type: 'separator' },
{ role: 'toggleSmartQuotes' },
{ role: 'toggleSmartDashes' },
{ role: 'toggleTextReplacement' }
]
},
{
label: 'Speech',
submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }]
}
] as MenuItemConstructorOptions[])
: ([{ role: 'delete' }, { type: 'separator' }, { role: 'selectAll' }] as MenuItemConstructorOptions[]))
? [
{ role: 'pasteAndMatchStyle' },
{ role: 'delete' },
{ role: 'selectAll' },
{ type: 'separator' },
{
label: 'Substitutions',
submenu: [
{ role: 'showSubstitutions' },
{ type: 'separator' },
{ role: 'toggleSmartQuotes' },
{ role: 'toggleSmartDashes' },
{ role: 'toggleTextReplacement' }
]
},
{
label: 'Speech',
submenu: [
{ role: 'startSpeaking' },
{ role: 'stopSpeaking' }
]
}
] as MenuItemConstructorOptions[]
: [
{ role: 'delete' },
{ type: 'separator' },
{ role: 'selectAll' }
] as MenuItemConstructorOptions[])
]
},
// View submenu
@@ -346,8 +306,13 @@ export const roleList: Record<RoleId, Role> = {
{ role: 'minimize' },
{ role: 'zoom' },
...(isMac
? ([{ type: 'separator' }, { role: 'front' }] as MenuItemConstructorOptions[])
: ([{ role: 'close' }] as MenuItemConstructorOptions[]))
? [
{ type: 'separator' },
{ role: 'front' }
] as MenuItemConstructorOptions[]
: [
{ role: 'close' }
] as MenuItemConstructorOptions[])
]
},
// Share submenu
@@ -369,34 +334,34 @@ const canExecuteRole = (role: keyof typeof roleList) => {
return roleList[role].nonNativeMacOSRole;
};
export function getDefaultType(role: RoleId) {
export function getDefaultType (role: RoleId) {
if (shouldOverrideCheckStatus(role)) return 'checkbox';
return 'normal';
}
export function getDefaultLabel(role: RoleId) {
export function getDefaultLabel (role: RoleId) {
return hasRole(role) ? roleList[role].label : '';
}
export function getCheckStatus(role: RoleId) {
export function getCheckStatus (role: RoleId) {
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');
}
export function getDefaultAccelerator(role: RoleId) {
export function getDefaultAccelerator (role: RoleId) {
if (hasRole(role)) return roleList[role].accelerator;
return undefined;
}
export function shouldRegisterAccelerator(role: RoleId) {
export function shouldRegisterAccelerator (role: RoleId) {
const hasRoleRegister = hasRole(role) && roleList[role].registerAccelerator !== undefined;
return hasRoleRegister ? roleList[role].registerAccelerator : true;
}
export function getDefaultSubmenu(role: RoleId) {
export function getDefaultSubmenu (role: RoleId) {
if (!hasRole(role)) return;
let { submenu } = roleList[role];
@@ -409,7 +374,7 @@ export function getDefaultSubmenu(role: RoleId) {
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;
const { appMethod, webContentsMethod, windowMethod } = roleList[role];

View File

@@ -56,7 +56,8 @@ const MenuItem = function (this: any, options: any) {
const click = options.click;
this.click = (event: KeyboardEvent, focusedWindow: BaseWindow, focusedWebContents: WebContents) => {
// Manually flip the checked flags when clicked.
if (!roles.shouldOverrideCheckStatus(this.role) && (this.type === 'checkbox' || this.type === 'radio')) {
if (!roles.shouldOverrideCheckStatus(this.role) &&
(this.type === 'checkbox' || this.type === 'radio')) {
this.checked = !this.checked;
}

View File

@@ -1,16 +1,13 @@
function splitArray<T>(arr: T[], predicate: (x: T) => boolean) {
const result = arr.reduce(
(multi, item) => {
const current = multi[multi.length - 1];
if (predicate(item)) {
if (current.length > 0) multi.push([]);
} else {
current.push(item);
}
return multi;
},
[[]] as T[][]
);
function splitArray<T> (arr: T[], predicate: (x: T) => boolean) {
const result = arr.reduce((multi, item) => {
const current = multi[multi.length - 1];
if (predicate(item)) {
if (current.length > 0) multi.push([]);
} else {
current.push(item);
}
return multi;
}, [[]] as T[][]);
if (result[result.length - 1].length === 0) {
return result.slice(0, result.length - 1);
@@ -18,7 +15,7 @@ function splitArray<T>(arr: T[], predicate: (x: T) => boolean) {
return result;
}
function joinArrays(arrays: any[][], joinIDs: any[]) {
function joinArrays (arrays: any[][], joinIDs: any[]) {
return arrays.reduce((joined, arr, i) => {
if (i > 0 && arr.length) {
if (joinIDs.length > 0) {
@@ -32,23 +29,26 @@ 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)) {
map.set(key, []);
}
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(
(candidateGroup) =>
candidateGroup !== ignoreGroup && candidateGroup.some((candidateItem) => candidateItem.id === id)
candidateGroup =>
candidateGroup !== ignoreGroup &&
candidateGroup.some(
candidateItem => candidateItem.id === id
)
);
}
// Sort nodes topologically using a depth-first approach. Encountered cycles
// 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 marked = new Set<T>();
@@ -71,7 +71,7 @@ function sortTopologically<T>(originalOrder: T[], edgesById: Map<T, T[]>) {
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++) {
const group = groups[i];
for (const item of group) {
@@ -90,7 +90,7 @@ function attemptToMergeAGroup<T>(groups: { before?: T[]; after?: T[]; id?: T }[]
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;
while (merged) {
merged = attemptToMergeAGroup(groups);
@@ -98,7 +98,7 @@ function mergeGroups<T>(groups: { before?: T[]; after?: T[]; id?: T }[][]) {
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 edges = new Map();
const idToIndex = new Map(group.map((item, i) => [item.id, i]));
@@ -123,14 +123,10 @@ function sortItemsInGroup<T>(group: { before?: T[]; after?: T[]; id?: T }[]) {
}
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];
for (const item of group) {
if (item.beforeGroupContaining) {
@@ -154,7 +150,7 @@ function findEdgesInGroup<T>(
}
}
function sortGroups<T>(groups: { id?: T }[][]) {
function sortGroups<T> (groups: {id?: T}[][]) {
const originalOrder = groups.map((item, i) => i);
const edges = new Map();
@@ -163,15 +159,13 @@ function sortGroups<T>(groups: { id?: T }[][]) {
}
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 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);

View File

@@ -92,7 +92,7 @@ Menu.prototype._executeCommand = function (event, id) {
Menu.prototype._menuWillShow = function () {
// Ensure radio groups have at least one menu item selected
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);
}
};
@@ -141,7 +141,7 @@ Menu.prototype.closePopup = function (window) {
Menu.prototype.getMenuItemById = function (id) {
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++) {
const { submenu } = items[i];
if (submenu) {
@@ -213,7 +213,7 @@ Menu.setApplicationMenu = function (menu: MenuType) {
bindings.setApplicationMenu(menu);
} else {
const windows = BaseWindow.getAllWindows();
windows.map((w) => w.setMenu(menu));
windows.map(w => w.setMenu(menu));
}
};
@@ -244,16 +244,14 @@ Menu.buildFromTemplate = function (template) {
/* Helper Functions */
// validate the template against having the wrong attribute
function areValidTemplateItems(template: (MenuItemConstructorOptions | MenuItem)[]) {
return template.every(
(item) =>
item != null &&
typeof item === 'object' &&
(Object.hasOwn(item, 'label') || Object.hasOwn(item, 'role') || item.type === 'separator')
);
function areValidTemplateItems (template: (MenuItemConstructorOptions | MenuItem)[]) {
return template.every(item =>
item != null &&
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);
for (const item of sorted) {
if (Array.isArray(item.submenu)) {
@@ -264,7 +262,7 @@ function sortTemplate(template: (MenuItemConstructorOptions | MenuItem)[]) {
}
// 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) {
for (let idx = pos - 1; idx >= 0; idx--) {
if (items[idx].type === 'radio') return (items[idx] as MenuItem).groupId;
@@ -280,7 +278,7 @@ function generateGroupId(items: (MenuItemConstructorOptions | MenuItem)[], pos:
return groupIdIndex;
}
function removeExtraSeparators(items: (MenuItemConstructorOptions | MenuItem)[]) {
function removeExtraSeparators (items: (MenuItemConstructorOptions | MenuItem)[]) {
// fold adjacent separators together
let ret = items.filter((e, idx, arr) => {
if (e.visible === false) return true;
@@ -296,7 +294,7 @@ function removeExtraSeparators(items: (MenuItemConstructorOptions | MenuItem)[])
return ret;
}
function insertItemByType(this: MenuType, item: MenuItem, pos: number) {
function insertItemByType (this: MenuType, item: MenuItem, pos: number) {
const types = {
normal: () => this.insertItem(pos, item.commandId, item.label),
header: () => this.insertItem(pos, item.commandId, item.label),

View File

@@ -7,7 +7,7 @@ const { createPair } = process._linkedBinding('electron_browser_message_port');
export default class MessageChannelMain extends EventEmitter implements Electron.MessageChannelMain {
port1: MessagePortMain;
port2: MessagePortMain;
constructor() {
constructor () {
super();
const { port1, port2 } = createPair();
this.port1 = new MessagePortMain(port1);

View File

@@ -4,11 +4,7 @@ import { ClientRequestConstructorOptions, ClientRequest, IncomingMessage, Sessio
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 rej: (e: E) => void;
const promise = new Promise<T>((resolve, reject) => {
@@ -19,12 +15,8 @@ function createDeferredPromise<T, E extends Error = Error>(): {
return { promise, resolve: res!, reject: rej! };
}
export function fetchWithSession(
input: RequestInfo,
init: (RequestInit & { bypassCustomProtocolHandlers?: boolean }) | undefined,
session: SessionT | undefined,
request: (options: ClientRequestConstructorOptions | string) => ClientRequest
) {
export function fetchWithSession (input: RequestInfo, init: (RequestInit & {bypassCustomProtocolHandlers?: boolean}) | undefined, session: SessionT | undefined,
request: (options: ClientRequestConstructorOptions | string) => ClientRequest) {
const p = createDeferredPromise<Response>();
let req: Request;
try {
@@ -84,18 +76,16 @@ export function fetchWithSession(
// 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 r = request(
allowAnyProtocol({
session,
method: req.method,
url: req.url,
origin,
credentials,
cache: req.cache,
referrerPolicy: req.referrerPolicy,
redirect: req.redirect
})
);
const r = request(allowAnyProtocol({
session,
method: req.method,
url: req.url,
origin,
credentials,
cache: req.cache,
referrerPolicy: req.referrerPolicy,
redirect: req.redirect
}));
(r as any)._urlLoaderOptions.bypassCustomProtocolHandlers = !!init?.bypassCustomProtocolHandlers;
@@ -115,10 +105,7 @@ export function fetchWithSession(
headers.set(k, Array.isArray(v) ? v.join(', ') : v);
}
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, {
headers,
status: resp.statusCode,
@@ -135,9 +122,7 @@ export function fetchWithSession(
// pipeTo expects a WritableStream<Uint8Array>. Node.js' Writable.toWeb returns WritableStream<any>,
// which causes a TS structural mismatch.
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;
}

View File

@@ -15,7 +15,7 @@ const stopLogging: typeof session.defaultSession.netLog.stopLogging = async () =
export default {
startLogging,
stopLogging,
get currentlyLogging(): boolean {
get currentlyLogging (): boolean {
if (!app.isReady()) return false;
return session.defaultSession.netLog.currentlyLogging;
}

View File

@@ -5,21 +5,18 @@ import type { ClientRequestConstructorOptions } from 'electron/main';
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()) {
throw new Error('net module can only be used after app is ready');
}
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);
}
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);
}

View File

@@ -1,7 +1,12 @@
import { EventEmitter } from 'events';
const { createPowerMonitor, getSystemIdleState, getSystemIdleTime, getCurrentThermalState, isOnBatteryPower } =
process._linkedBinding('electron_browser_power_monitor');
const {
createPowerMonitor,
getSystemIdleState,
getSystemIdleTime,
getCurrentThermalState,
isOnBatteryPower
} = process._linkedBinding('electron_browser_power_monitor');
// 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
@@ -10,7 +15,7 @@ const { createPowerMonitor, getSystemIdleState, getSystemIdleTime, getCurrentThe
let pm: any;
class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
constructor() {
constructor () {
super();
// Don't start the event source until both a) the app is ready and b)
// there's a listener registered for a powerMonitor event.
@@ -38,23 +43,23 @@ class PowerMonitor extends EventEmitter implements Electron.PowerMonitor {
});
}
getSystemIdleState(idleThreshold: number) {
getSystemIdleState (idleThreshold: number) {
return getSystemIdleState(idleThreshold);
}
getCurrentThermalState() {
getCurrentThermalState () {
return getCurrentThermalState();
}
getSystemIdleTime() {
getSystemIdleTime () {
return getSystemIdleTime();
}
isOnBatteryPower() {
isOnBatteryPower () {
return isOnBatteryPower();
}
get onBatteryPower() {
get onBatteryPower () {
return this.isOnBatteryPower();
}
}

View File

@@ -7,18 +7,17 @@ import { ReadableStream } from 'stream/web';
import type { ReadableStreamDefaultReader } from 'stream/web';
// 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_UNEXPECTED = -9;
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 */);
return new ReadableStream({
async pull(controller) {
async pull (controller) {
try {
const rv = await pipe.read(buf);
if (rv > 0) {
@@ -33,7 +32,7 @@ function makeStreamFromPipe(pipe: any): ReadableStream<Uint8Array> {
});
}
function makeStreamFromFileInfo({
function makeStreamFromFileInfo ({
filePath,
offset = 0,
length = -1
@@ -43,15 +42,13 @@ function makeStreamFromFileInfo({
length?: number;
}): ReadableStream<Uint8Array> {
// Node's Readable.toWeb produces a WHATWG ReadableStream whose chunks are Uint8Array.
return Readable.toWeb(
createReadStream(filePath, {
start: offset,
end: length >= 0 ? offset + length : undefined
})
) as ReadableStream<Uint8Array>;
return Readable.toWeb(createReadStream(filePath, {
start: offset,
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;
// Optimization: skip creating a stream if the request is just a single buffer.
if (uploadData.length === 1 && (uploadData[0] as any).type === 'rawData') {
@@ -63,7 +60,7 @@ function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): Reques
// Generic <Uint8Array> ensures reader.read() returns value?: Uint8Array consistent with enqueue.
let current: ReadableStreamDefaultReader<Uint8Array> | null = null;
return new ReadableStream<Uint8Array>({
async pull(controller) {
async pull (controller) {
if (current) {
const { done, value } = await current.read();
// (done => value === undefined) as per WHATWG spec
@@ -74,9 +71,7 @@ function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): Reques
controller.enqueue(value);
}
} else {
if (!chunks.length) {
return controller.close();
}
if (!chunks.length) { return controller.close(); }
const chunk = chunks.shift()!;
if (chunk.type === 'rawData') {
controller.enqueue(chunk.bytes as Uint8Array);
@@ -101,7 +96,7 @@ function convertToRequestBody(uploadData: ProtocolRequest['uploadData']): Reques
}) as RequestInit['body'];
}
function validateResponse(res: Response) {
function validateResponse (res: Response) {
if (!res || typeof res !== 'object') return false;
if (res.type === 'error') return true;
@@ -120,11 +115,7 @@ function validateResponse(res: Response) {
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 success = register.call(this, scheme, async (preq: ProtocolRequest, cb: any) => {
try {
@@ -164,9 +155,7 @@ Protocol.prototype.handle = function (
Protocol.prototype.unhandle = function (this: Electron.Protocol, scheme: string) {
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) {

View File

@@ -14,38 +14,35 @@ const createScreenIfNeeded = () => {
// exposes an instance created by createScreen. In order to avoid
// side-effecting and calling createScreen upon import of this module, instead
// we export a proxy which lazily calls createScreen on first access.
export default new Proxy(
{},
{
get: (target, property: keyof Electron.Screen) => {
createScreenIfNeeded();
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;
export default new Proxy({}, {
get: (target, property: keyof Electron.Screen) => {
createScreenIfNeeded();
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;
}
);
});

View File

@@ -3,7 +3,7 @@ import { IpcMainImpl } from '@electron/internal/browser/ipc-main-impl';
const { ServiceWorkerMain } = process._linkedBinding('electron_browser_service_worker_main');
Object.defineProperty(ServiceWorkerMain.prototype, 'ipc', {
get() {
get () {
const ipc = new IpcMainImpl();
Object.defineProperty(this, 'ipc', { value: ipc });
return ipc;

View File

@@ -15,7 +15,7 @@ let fakeVideoWindowId = -1;
const kMacOsNativePickerId = -4;
const systemPickerVideoSource = Object.create(null);
Object.defineProperty(systemPickerVideoSource, 'id', {
get() {
get () {
return `window:${kMacOsNativePickerId}:${fakeVideoWindowId--}`;
}
});
@@ -73,18 +73,13 @@ Session.prototype.setPreloads = function (preloads) {
.forEach((script) => {
this.unregisterPreloadScript(script.id);
});
preloads
.map(
(filePath) =>
({
type: 'frame',
filePath,
_deprecated: true
}) as Electron.PreloadScriptRegistration
)
.forEach((script) => {
this.registerPreloadScript(script);
});
preloads.map(filePath => ({
type: 'frame',
filePath,
_deprecated: true
}) as Electron.PreloadScriptRegistration).forEach(script => {
this.registerPreloadScript(script);
});
};
Session.prototype.getAllExtensions = deprecate.moveAPI(
@@ -119,7 +114,7 @@ Session.prototype.removeExtension = deprecate.moveAPI(
export default {
fromPartition,
fromPath,
get defaultSession() {
get defaultSession () {
return fromPartition('');
}
};

View File

@@ -5,16 +5,16 @@ import { EventEmitter } from 'events';
class ShareMenu extends EventEmitter implements Electron.ShareMenu {
private menu: Menu;
constructor(sharingItem: SharingItem) {
constructor (sharingItem: SharingItem) {
super();
this.menu = new (Menu as any)({ sharingItem });
}
popup(options?: PopupOptions) {
popup (options?: PopupOptions) {
this.menu.popup(options);
}
closePopup(browserWindow?: BrowserWindow) {
closePopup (browserWindow?: BrowserWindow) {
this.menu.closePopup(browserWindow);
}
}

View File

@@ -14,41 +14,36 @@ type SharedTextureImportedWrapper = {
texture: Electron.SharedTextureImported;
allReferencesReleased: AllReleasedCallback | undefined;
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) => {
const frameTreeNodeId = event.frameTreeNodeId ?? event.sender.mainFrame.frameTreeNodeId;
wrapperReleaseFromRenderer(textureId, frameTreeNodeId);
}
);
ipcMain.handle(IPC_MESSAGES.IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN, (event: Electron.IpcMainInvokeEvent, textureId: string) => {
const frameTreeNodeId = event.frameTreeNodeId ?? event.sender.mainFrame.frameTreeNodeId;
wrapperReleaseFromRenderer(textureId, frameTreeNodeId);
});
let checkManagedSharedTexturesInterval: NodeJS.Timeout | null = null;
function scheduleCheckManagedSharedTextures() {
function scheduleCheckManagedSharedTextures () {
if (checkManagedSharedTexturesInterval === null) {
checkManagedSharedTexturesInterval = setInterval(checkManagedSharedTextures, 1000);
}
}
function unscheduleCheckManagedSharedTextures() {
function unscheduleCheckManagedSharedTextures () {
if (checkManagedSharedTexturesInterval !== null) {
clearInterval(checkManagedSharedTexturesInterval);
checkManagedSharedTexturesInterval = null;
}
}
function checkManagedSharedTextures() {
function checkManagedSharedTextures () {
const texturesToRemoveTracking = new Set<string>();
for (const [, wrapper] of managedSharedTextures) {
for (const [frameTreeNodeId, entry] of wrapper.rendererFrameReferences) {
const frame = entry.reference;
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);
}
}
@@ -70,7 +65,7 @@ function checkManagedSharedTextures() {
}
}
function wrapperReleaseFromRenderer(id: string, frameTreeNodeId: number) {
function wrapperReleaseFromRenderer (id: string, frameTreeNodeId: number) {
const wrapper = managedSharedTextures.get(id);
if (!wrapper) {
throw new Error(`Shared texture with id ${id} not found`);
@@ -97,7 +92,7 @@ function wrapperReleaseFromRenderer(id: string, frameTreeNodeId: number) {
}
}
function wrapperReleaseFromMain(id: string) {
function wrapperReleaseFromMain (id: string) {
const wrapper = managedSharedTextures.get(id);
if (!wrapper) {
throw new Error(`Shared texture with id ${id} not found`);
@@ -113,18 +108,14 @@ 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 transfer = imported.subtle.startTransferSharedTexture();
let timeoutHandle: NodeJS.Timeout | null = null;
const timeoutPromise = new Promise<never>((resolve, reject) => {
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);
});
@@ -133,14 +124,13 @@ async function sendSharedTexture(options: Electron.SendSharedTextureOptions, ...
throw new Error('`frame` should be provided');
}
const invokePromise: Promise<Electron.SharedTextureSyncToken> =
ipcMainInternalUtils.invokeInWebFrameMain<Electron.SharedTextureSyncToken>(
targetFrame,
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER,
transfer,
imported.textureId,
...args
);
const invokePromise: Promise<Electron.SharedTextureSyncToken> = ipcMainInternalUtils.invokeInWebFrameMain<Electron.SharedTextureSyncToken>(
targetFrame,
IPC_MESSAGES.IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER,
transfer,
imported.textureId,
...args
);
try {
const syncToken = await Promise.race([invokePromise, timeoutPromise]);
@@ -169,7 +159,7 @@ async function sendSharedTexture(options: Electron.SendSharedTextureOptions, ...
scheduleCheckManagedSharedTextures();
}
function importSharedTexture(options: Electron.ImportSharedTextureOptions) {
function importSharedTexture (options: Electron.ImportSharedTextureOptions) {
const id = randomUUID();
const imported = sharedTextureNative.importSharedTexture(Object.assign(options.textureInfo, { id }));
const ret: Electron.SharedTextureImported = {

View File

@@ -10,10 +10,7 @@ if ('getEffectiveAppearance' 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;
Object.defineProperty(systemPreferences, 'accessibilityDisplayShouldReduceTransparency', {
get: () => {

View File

@@ -12,53 +12,41 @@ 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) => {
extendConstructHook(target, function (this: T) {
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config, (k, v) => {
(this as any)[hiddenProperties][k] = v;
});
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) => {
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, {
get: function () {
return this[hiddenProperties][propertyKey];
},
set: function () {
throw new Error(`Cannot override property ${name}`);
},
enumerable: true,
configurable: false
});
};
});
Object.defineProperty(target, propertyKey, {
get: function () {
return this[hiddenProperties][propertyKey];
},
set: function () {
throw new Error(`Cannot override property ${name}`);
},
enumerable: true,
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) => {
extendConstructHook(target, function (this: T) {
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config);
if (onMutate) onMutate(this as any, (this as any)[hiddenProperties][propertyKey]);
});
Object.defineProperty(target, propertyKey, {
get: function () {
return this[hiddenProperties][propertyKey];
},
set: function (value) {
if (onMutate) onMutate(this as any, value);
this[hiddenProperties][propertyKey] = value;
this.emit('change', this);
},
enumerable: true
});
};
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) => {
extendConstructHook(target, function (this: T) {
(this as any)[hiddenProperties][propertyKey] = def((this as any)._config);
if (onMutate) onMutate((this as any), (this as any)[hiddenProperties][propertyKey]);
});
Object.defineProperty(target, propertyKey, {
get: function () {
return this[hiddenProperties][propertyKey];
},
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 {
@ImmutableProperty(() => `${nextItemID++}`) id!: string;
@@ -69,17 +57,17 @@ abstract class TouchBarItem<ConfigType> extends EventEmitter {
private _parents: { id: string; type: string }[] = [];
private _config!: ConfigType;
constructor(config: ConfigType) {
constructor (config: ConfigType) {
super();
this._config = this._config || config || ({} as ConfigType);
this._config = this._config || config || {} as ConfigType;
(this as any)[hiddenProperties] = {};
const hook = (this as any)._hook;
if (hook) hook.call(this);
delete (this as any)._hook;
}
public _addParent(item: TouchBarItem<any>) {
const existing = this._parents.some((test) => test.id === item.id);
public _addParent (item: TouchBarItem<any>) {
const existing = this._parents.some(test => test.id === item.id);
if (!existing) {
this._parents.push({
id: item.id,
@@ -88,246 +76,211 @@ abstract class TouchBarItem<ConfigType> extends EventEmitter {
}
}
public _removeParent(item: TouchBarItem<any>) {
this._parents = this._parents.filter((test) => test.id !== item.id);
public _removeParent (item: TouchBarItem<any>) {
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')
type!: string;
type!: string;
@LiveProperty<TouchBarButton>((config) => config.label)
label!: string;
@LiveProperty<TouchBarButton>(config => config.label)
label!: string;
@LiveProperty<TouchBarButton>((config) => config.accessibilityLabel)
accessibilityLabel!: string;
@LiveProperty<TouchBarButton>(config => config.accessibilityLabel)
accessibilityLabel!: string;
@LiveProperty<TouchBarButton>((config) => config.backgroundColor)
backgroundColor!: string;
@LiveProperty<TouchBarButton>(config => config.backgroundColor)
backgroundColor!: string;
@LiveProperty<TouchBarButton>((config) => config.icon)
icon!: Electron.NativeImage;
@LiveProperty<TouchBarButton>(config => config.icon)
icon!: Electron.NativeImage;
@LiveProperty<TouchBarButton>((config) => config.iconPosition)
iconPosition!: Electron.TouchBarButton['iconPosition'];
@LiveProperty<TouchBarButton>(config => config.iconPosition)
iconPosition!: Electron.TouchBarButton['iconPosition'];
@LiveProperty<TouchBarButton>((config) => (typeof config.enabled !== 'boolean' ? true : config.enabled))
enabled!: boolean;
@LiveProperty<TouchBarButton>(config => typeof config.enabled !== 'boolean' ? true : config.enabled)
enabled!: boolean;
@ImmutableProperty<TouchBarButton>(({ click: onClick }) => (typeof onClick === 'function' ? () => onClick() : null))
onInteraction!: Function | null;
@ImmutableProperty<TouchBarButton>(({ click: onClick }) => typeof onClick === 'function' ? () => onClick() : null)
onInteraction!: Function | null;
}
class TouchBarColorPicker
extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions>
implements Electron.TouchBarColorPicker
{
class TouchBarColorPicker extends TouchBarItem<Electron.TouchBarColorPickerConstructorOptions> implements Electron.TouchBarColorPicker {
@ImmutableProperty(() => 'colorpicker')
type!: string;
type!: string;
@LiveProperty<TouchBarColorPicker>((config) => config.availableColors)
availableColors!: string[];
@LiveProperty<TouchBarColorPicker>(config => config.availableColors)
availableColors!: string[];
@LiveProperty<TouchBarColorPicker>((config) => config.selectedColor)
selectedColor!: string;
@LiveProperty<TouchBarColorPicker>(config => config.selectedColor)
selectedColor!: string;
@ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) =>
typeof onChange === 'function'
? (details: { color: string }) => {
setInternalProp('selectedColor', details.color);
onChange(details.color);
}
: null
)
onInteraction!: Function | null;
@ImmutableProperty<TouchBarColorPicker>(({ change: onChange }, setInternalProp) => typeof onChange === 'function'
? (details: { color: string }) => {
setInternalProp('selectedColor', details.color);
onChange(details.color);
}
: null)
onInteraction!: Function | null;
}
class TouchBarGroup extends TouchBarItem<Electron.TouchBarGroupConstructorOptions> implements Electron.TouchBarGroup {
@ImmutableProperty(() => 'group')
type!: string;
type!: string;
@LiveProperty<TouchBarGroup>(
(config) => (config.items instanceof TouchBar ? config.items : new TouchBar(config.items)),
(self, newChild: TouchBar) => {
if (self.child) {
for (const item of self.child.orderedItems) {
item._removeParent(self);
}
}
for (const item of newChild.orderedItems) {
item._addParent(self);
@LiveProperty<TouchBarGroup>(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => {
if (self.child) {
for (const item of self.child.orderedItems) {
item._removeParent(self);
}
}
)
child!: TouchBar;
for (const item of newChild.orderedItems) {
item._addParent(self);
}
})
child!: TouchBar;
onInteraction = null;
}
class TouchBarLabel extends TouchBarItem<Electron.TouchBarLabelConstructorOptions> implements Electron.TouchBarLabel {
@ImmutableProperty(() => 'label')
type!: string;
type!: string;
@LiveProperty<TouchBarLabel>((config) => config.label)
label!: string;
@LiveProperty<TouchBarLabel>(config => config.label)
label!: string;
@LiveProperty<TouchBarLabel>((config) => config.accessibilityLabel)
accessibilityLabel!: string;
@LiveProperty<TouchBarLabel>(config => config.accessibilityLabel)
accessibilityLabel!: string;
@LiveProperty<TouchBarLabel>((config) => config.textColor)
textColor!: string;
@LiveProperty<TouchBarLabel>(config => config.textColor)
textColor!: string;
onInteraction = null;
}
class TouchBarPopover
extends TouchBarItem<Electron.TouchBarPopoverConstructorOptions>
implements Electron.TouchBarPopover
{
class TouchBarPopover extends TouchBarItem<Electron.TouchBarPopoverConstructorOptions> implements Electron.TouchBarPopover {
@ImmutableProperty(() => 'popover')
type!: string;
type!: string;
@LiveProperty<TouchBarPopover>((config) => config.label)
label!: string;
@LiveProperty<TouchBarPopover>(config => config.label)
label!: string;
@LiveProperty<TouchBarPopover>((config) => config.icon)
icon!: Electron.NativeImage;
@LiveProperty<TouchBarPopover>(config => config.icon)
icon!: Electron.NativeImage;
@LiveProperty<TouchBarPopover>((config) => config.showCloseButton)
showCloseButton!: boolean;
@LiveProperty<TouchBarPopover>(config => config.showCloseButton)
showCloseButton!: boolean;
@LiveProperty<TouchBarPopover>(
(config) => (config.items instanceof TouchBar ? config.items : new TouchBar(config.items)),
(self, newChild: TouchBar) => {
if (self.child) {
for (const item of self.child.orderedItems) {
item._removeParent(self);
}
}
for (const item of newChild.orderedItems) {
item._addParent(self);
@LiveProperty<TouchBarPopover>(config => config.items instanceof TouchBar ? config.items : new TouchBar(config.items), (self, newChild: TouchBar) => {
if (self.child) {
for (const item of self.child.orderedItems) {
item._removeParent(self);
}
}
)
child!: TouchBar;
for (const item of newChild.orderedItems) {
item._addParent(self);
}
})
child!: TouchBar;
onInteraction = null;
}
class TouchBarSlider
extends TouchBarItem<Electron.TouchBarSliderConstructorOptions>
implements Electron.TouchBarSlider
{
class TouchBarSlider extends TouchBarItem<Electron.TouchBarSliderConstructorOptions> implements Electron.TouchBarSlider {
@ImmutableProperty(() => 'slider')
type!: string;
type!: string;
@LiveProperty<TouchBarSlider>((config) => config.label)
label!: string;
@LiveProperty<TouchBarSlider>(config => config.label)
label!: string;
@LiveProperty<TouchBarSlider>((config) => config.minValue)
minValue!: number;
@LiveProperty<TouchBarSlider>(config => config.minValue)
minValue!: number;
@LiveProperty<TouchBarSlider>((config) => config.maxValue)
maxValue!: number;
@LiveProperty<TouchBarSlider>(config => config.maxValue)
maxValue!: number;
@LiveProperty<TouchBarSlider>((config) => config.value)
value!: 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;
@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
{
class TouchBarSpacer extends TouchBarItem<Electron.TouchBarSpacerConstructorOptions> implements Electron.TouchBarSpacer {
@ImmutableProperty(() => 'spacer')
type!: string;
type!: string;
@ImmutableProperty<TouchBarSpacer>((config) => config.size)
size!: Electron.TouchBarSpacer['size'];
@ImmutableProperty<TouchBarSpacer>(config => config.size)
size!: Electron.TouchBarSpacer['size'];
onInteraction = null;
}
class TouchBarSegmentedControl
extends TouchBarItem<Electron.TouchBarSegmentedControlConstructorOptions>
implements Electron.TouchBarSegmentedControl
{
class TouchBarSegmentedControl extends TouchBarItem<Electron.TouchBarSegmentedControlConstructorOptions> implements Electron.TouchBarSegmentedControl {
@ImmutableProperty(() => 'segmented_control')
type!: string;
type!: string;
@LiveProperty<TouchBarSegmentedControl>((config) => config.segmentStyle)
segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle'];
@LiveProperty<TouchBarSegmentedControl>(config => config.segmentStyle)
segmentStyle!: Electron.TouchBarSegmentedControl['segmentStyle'];
@LiveProperty<TouchBarSegmentedControl>((config) => config.segments || [])
segments!: Electron.SegmentedControlSegment[];
@LiveProperty<TouchBarSegmentedControl>(config => config.segments || [])
segments!: Electron.SegmentedControlSegment[];
@LiveProperty<TouchBarSegmentedControl>((config) => config.selectedIndex)
selectedIndex!: number;
@LiveProperty<TouchBarSegmentedControl>(config => config.selectedIndex)
selectedIndex!: number;
@LiveProperty<TouchBarSegmentedControl>((config) => config.mode)
mode!: Electron.TouchBarSegmentedControl['mode'];
@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;
@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
{
class TouchBarScrubber extends TouchBarItem<Electron.TouchBarScrubberConstructorOptions> implements Electron.TouchBarScrubber {
@ImmutableProperty(() => 'scrubber')
type!: string;
type!: string;
@LiveProperty<TouchBarScrubber>((config) => config.items)
items!: Electron.ScrubberItem[];
@LiveProperty<TouchBarScrubber>(config => config.items)
items!: Electron.ScrubberItem[];
@LiveProperty<TouchBarScrubber>((config) => config.selectedStyle || null)
selectedStyle!: Electron.TouchBarScrubber['selectedStyle'];
@LiveProperty<TouchBarScrubber>(config => config.selectedStyle || null)
selectedStyle!: Electron.TouchBarScrubber['selectedStyle'];
@LiveProperty<TouchBarScrubber>((config) => config.overlayStyle || null)
overlayStyle!: Electron.TouchBarScrubber['overlayStyle'];
@LiveProperty<TouchBarScrubber>(config => config.overlayStyle || null)
overlayStyle!: Electron.TouchBarScrubber['overlayStyle'];
@LiveProperty<TouchBarScrubber>((config) => config.showArrowButtons || false)
showArrowButtons!: boolean;
@LiveProperty<TouchBarScrubber>(config => config.showArrowButtons || false)
showArrowButtons!: boolean;
@LiveProperty<TouchBarScrubber>((config) => config.mode || 'free')
mode!: Electron.TouchBarScrubber['mode'];
@LiveProperty<TouchBarScrubber>(config => config.mode || 'free')
mode!: Electron.TouchBarScrubber['mode'];
@LiveProperty<TouchBarScrubber>((config) => (typeof config.continuous === 'undefined' ? true : config.continuous))
continuous!: boolean;
@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);
}
@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;
}
: null)
onInteraction!: Function | null;
}
class TouchBarOtherItemsProxy extends TouchBarItem<null> implements Electron.TouchBarOtherItemsProxy {
@@ -339,7 +292,7 @@ const escapeItemSymbol = Symbol('escape item');
class TouchBar extends EventEmitter implements Electron.TouchBar {
// 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) {
window._touchBar._removeFromWindow(window);
}
@@ -359,7 +312,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
private items = new Map<string, TouchBarItem<any>>();
orderedItems: TouchBarItem<any>[] = [];
constructor(options: Electron.TouchBarConstructorOptions) {
constructor (options: Electron.TouchBarConstructorOptions) {
super();
if (options == null) {
@@ -407,7 +360,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
}
// 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);
registerItem(item);
}
@@ -419,7 +372,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
private [escapeItemSymbol]: TouchBarItem<unknown> | null = null;
set escapeItem(item: TouchBarItem<unknown> | null) {
set escapeItem (item: TouchBarItem<unknown> | null) {
if (item != null && !(item instanceof TouchBarItem)) {
throw new Error('Escape item must be an instance of TouchBarItem');
}
@@ -434,11 +387,11 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
this.emit('escape-item-change', item);
}
get escapeItem(): TouchBarItem<unknown> | null {
get escapeItem (): TouchBarItem<unknown> | null {
return this[escapeItemSymbol];
}
_addToWindow(window: Electron.BaseWindow) {
_addToWindow (window: Electron.BaseWindow) {
const { id } = window;
// Already added to window
@@ -494,7 +447,7 @@ class TouchBar extends EventEmitter implements Electron.TouchBar {
escapeItemListener(this.escapeItem);
}
_removeFromWindow(window: Electron.BaseWindow) {
_removeFromWindow (window: Electron.BaseWindow) {
const removeListeners = this.windowListeners.get(window.id);
if (removeListeners != null) removeListeners();
}

View File

@@ -10,7 +10,7 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
#handle: ElectronInternal.UtilityProcessWrapper | null;
#stdout: Duplex | null = null;
#stderr: Duplex | null = null;
constructor(modulePath: string, args?: string[], options?: Electron.ForkOptions) {
constructor (modulePath: string, args?: string[], options?: Electron.ForkOptions) {
super();
if (!modulePath) {
@@ -53,7 +53,7 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
}
if (typeof options.stdio === 'string') {
const stdio: Array<'pipe' | 'ignore' | 'inherit'> = [];
const stdio : Array<'pipe' | 'ignore' | 'inherit'> = [];
switch (options.stdio) {
case 'inherit':
case 'ignore':
@@ -119,27 +119,27 @@ class ForkUtilityProcess extends EventEmitter implements Electron.UtilityProcess
};
}
get pid() {
get pid () {
return this.#handle?.pid;
}
get stdout() {
get stdout () {
return this.#stdout;
}
get stderr() {
get stderr () {
return this.#stderr;
}
postMessage(message: any, transfer?: MessagePortMain[]) {
postMessage (message: any, transfer?: MessagePortMain[]) {
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);
}
kill(): boolean {
kill () : boolean {
if (this.#handle === null) {
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);
}

View File

@@ -1,8 +1,4 @@
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 * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-utils';
import { parseFeatures } from '@electron/internal/browser/parse-features-string';
@@ -114,7 +110,7 @@ const paperFormats: Record<string, ElectronInternal.PageSize> = {
// Practically, this means microns need to be > 352 microns.
// We therefore need to verify this or it will silently fail.
const isValidCustomPageSize = (width: number, height: number) => {
return [width, height].every((x) => x > 352);
return [width, height].every(x => x > 352);
};
// JavaScript implementations of WebContents.
@@ -134,10 +130,10 @@ WebContents.prototype._sendInternal = function (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') {
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]);
} else {
throw new Error('Missing required frame argument (must be number or [processId, frameId])');
@@ -152,12 +148,12 @@ WebContents.prototype.sendToFrame = function (frameId, channel, ...args) {
};
// Following methods are mapped to webFrame.
const webFrameMethods = ['insertCSS', 'insertText', 'removeInsertedCSS', 'setVisualZoomLevelLimits'] as (
| 'insertCSS'
| 'insertText'
| 'removeInsertedCSS'
| 'setVisualZoomLevelLimits'
)[];
const webFrameMethods = [
'insertCSS',
'insertText',
'removeInsertedCSS',
'setVisualZoomLevelLimits'
] as ('insertCSS' | 'insertText' | 'removeInsertedCSS' | 'setVisualZoomLevelLimits')[];
for (const method of webFrameMethods) {
WebContents.prototype[method] = function (...args: any[]): Promise<any> {
@@ -179,27 +175,14 @@ const waitTillCanExecuteJavaScript = async (webContents: Electron.WebContents) =
// WebContents has been loaded.
WebContents.prototype.executeJavaScript = async function (code, hasUserGesture) {
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) {
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
if (typeof value !== type) {
throw new TypeError(`${name} must be a ${type}`);
@@ -208,7 +191,7 @@ function checkType<T>(value: T, type: 'number' | 'boolean' | 'string' | 'object'
return value;
}
function parsePageSize(pageSize: string | ElectronInternal.PageSize) {
function parsePageSize (pageSize: string | ElectronInternal.PageSize) {
if (typeof pageSize === 'string') {
const format = paperFormats[pageSize.toLowerCase()];
if (!format) {
@@ -235,8 +218,8 @@ WebContents.prototype.printToPDF = async function (options) {
const pageSize = parsePageSize(options.pageSize ?? 'letter');
const { top, bottom, left, right } = margins;
const validHeight = [top, bottom].every((u) => u === undefined || u <= pageSize.paperHeight);
const validWidth = [left, right].every((u) => u === undefined || u <= pageSize.paperWidth);
const validHeight = [top, bottom].every(u => u === undefined || u <= pageSize.paperHeight);
const validWidth = [left, right].every(u => u === undefined || u <= pageSize.paperWidth);
if (!validHeight || !validWidth) {
throw new Error('margins must be less than or equal to pageSize');
@@ -349,21 +332,19 @@ WebContents.prototype.loadFile = function (filePath, options = {}) {
}
const { query, search, hash } = options;
return this.loadURL(
url.format({
protocol: 'file',
slashes: true,
pathname: path.resolve(app.getAppPath(), filePath),
query,
search,
hash
})
);
return this.loadURL(url.format({
protocol: 'file',
slashes: true,
pathname: path.resolve(app.getAppPath(), filePath),
query,
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) => {
const resolveAndCleanup = () => {
removeListeners();
@@ -371,9 +352,7 @@ function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
};
let error: LoadError | undefined;
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 });
removeListeners();
reject(err);
@@ -406,13 +385,7 @@ function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
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) {
error = { errorCode, errorDescription, url: validatedURL };
}
@@ -454,7 +427,7 @@ function _awaitNextLoad(this: Electron.WebContents, navigationUrl: string) {
this.on('did-stop-loading', stopLoadingListener);
this.on('destroyed', stopLoadingListener);
});
}
};
WebContents.prototype.loadURL = function (url, options) {
const p = _awaitNextLoad.call(this, url);
@@ -472,20 +445,11 @@ WebContents.prototype.saveVideoFrameAs = function (x: number, y: number) {
this.mainFrame.saveVideoFrameAs(x, y);
};
WebContents.prototype.setWindowOpenHandler = function (
handler: (details: Electron.HandlerDetails) => Electron.WindowOpenHandlerResponse
) {
WebContents.prototype.setWindowOpenHandler = function (handler: (details: Electron.HandlerDetails) => Electron.WindowOpenHandlerResponse) {
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 = {
browserWindowConstructorOptions: null,
outlivesOpener: false,
@@ -514,14 +478,13 @@ WebContents.prototype._callWindowOpenHandler = function (
return defaultResponse;
} else if (response.action === 'allow') {
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,
createWindow: typeof response.createWindow === 'function' ? response.createWindow : undefined
};
} else {
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;
}
};
@@ -539,19 +502,13 @@ WebContents.prototype.canGoBack = function () {
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 () {
canGoForwardDeprecated();
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) {
canGoToOffsetDeprecated();
return this._canGoToOffset(index);
@@ -587,17 +544,13 @@ WebContents.prototype.goToOffset = function (index: number) {
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.
WebContents.prototype._init = function () {
const prefs = this.getLastWebPreferences() || {};
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
// the underlying C++ WebContents is destroyed.
@@ -611,9 +564,7 @@ WebContents.prototype._init = function () {
const ipc = new IpcMainImpl();
Object.defineProperty(this, 'ipc', {
get() {
return ipc;
},
get () { return ipc; },
enumerable: true
});
@@ -634,15 +585,13 @@ WebContents.prototype._init = function () {
getEntryAtIndex: this._getNavigationEntryAtIndex.bind(this),
removeEntryAtIndex: this._removeNavigationEntryAtIndex.bind(this),
getAllEntries: this._getHistory.bind(this),
restore: ({ index, entries }: { index?: number; entries: NavigationEntry[] }) => {
restore: ({ index, entries }: { index?: number, entries: NavigationEntry[] }) => {
if (index === undefined) {
index = entries.length - 1;
}
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);
@@ -666,9 +615,7 @@ WebContents.prototype._init = function () {
// Log out a hint to help users better debug renderer crashes.
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.`);
}
});
@@ -678,9 +625,7 @@ WebContents.prototype._init = function () {
// All other types should ignore the "proceed" signal and unload
// regardless.
if (type === 'window' || type === 'offscreen' || type === 'browserView') {
if (!proceed) {
return event.preventDefault();
}
if (!proceed) { return event.preventDefault(); }
}
});
@@ -765,8 +710,8 @@ WebContents.prototype._init = function () {
const secureOverrideWebPreferences = windowOpenOverriddenOptions
? {
// Allow setting of backgroundColor as a webPreference even though
// it's technically a BrowserWindowConstructorOptions option because
// we need to access it in the renderer at init time.
// it's technically a BrowserWindowConstructorOptions option because
// we need to access it in the renderer at init time.
backgroundColor: windowOpenOverriddenOptions.backgroundColor,
transparent: windowOpenOverriddenOptions.transparent,
...windowOpenOverriddenOptions.webPreferences
@@ -787,54 +732,38 @@ WebContents.prototype._init = function () {
});
// 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
) => {
const overriddenOptions = windowOpenOverriddenOptions || undefined;
const outlivesOpener = windowOpenOutlivesOpenerOption;
const windowOpenFunction = createWindow;
this.on('-add-new-contents', (event, webContents, disposition, _userGesture, _left, _top, _width, _height, url, frameName, referrer, rawFeatures, postData) => {
const overriddenOptions = windowOpenOverriddenOptions || undefined;
const outlivesOpener = windowOpenOutlivesOpenerOption;
const windowOpenFunction = createWindow;
createWindow = undefined;
windowOpenOverriddenOptions = null;
// false is the default
windowOpenOutlivesOpenerOption = false;
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
});
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) => {
@@ -873,17 +802,14 @@ WebContents.prototype._init = function () {
originCounts.set(origin, (originCounts.get(origin) ?? 0) + 1);
// 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 abortController = new AbortController();
const options: MessageBoxOptions = {
message: info.messageText,
checkboxLabel: checkbox,
signal: abortController.signal,
...(info.dialogType === 'confirm'
...(info.dialogType === 'confirm')
? {
buttons: ['OK', 'Cancel'],
defaultId: 0,
@@ -893,11 +819,10 @@ WebContents.prototype._init = function () {
buttons: ['OK'],
defaultId: -1, // No default button
cancelId: 0
})
}
};
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 {
const result = await promise;
if (abortController.signal.aborted || this.isDestroyed()) return;
@@ -909,15 +834,13 @@ WebContents.prototype._init = function () {
});
this.on('-cancel-dialogs', () => {
for (const controller of openDialogs) {
controller.abort();
}
for (const controller of openDialogs) { controller.abort(); }
openDialogs.clear();
});
// TODO(samuelmaddock): remove deprecated 'console-message' arguments
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) {
consoleMessageDeprecated();
}
@@ -931,17 +854,7 @@ 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
@@ -977,23 +890,23 @@ WebContents.prototype._init = function () {
};
// Public APIs.
export function create(options = {}): Electron.WebContents {
export function create (options = {}): Electron.WebContents {
return new (WebContents as any)(options);
}
export function fromId(id: number) {
export function fromId (id: number) {
return binding.fromId(id);
}
export function fromFrame(frame: Electron.WebFrameMain) {
export function fromFrame (frame: Electron.WebFrameMain) {
return binding.fromFrame(frame);
}
export function fromDevToolsTargetId(targetId: string) {
export function fromDevToolsTargetId (targetId: string) {
return binding.fromDevToolsTargetId(targetId);
}
export function getFocusedWebContents() {
export function getFocusedWebContents () {
let focused = null;
for (const contents of binding.getAllWebContents()) {
if (!contents.isFocused()) continue;
@@ -1004,6 +917,6 @@ export function getFocusedWebContents() {
}
return focused;
}
export function getAllWebContents() {
export function getAllWebContents () {
return binding.getAllWebContents();
}

View File

@@ -4,7 +4,7 @@ import { MessagePortMain } from '@electron/internal/browser/message-port-main';
const { WebFrameMain, fromId, fromFrameToken } = process._linkedBinding('electron_browser_web_frame_main');
Object.defineProperty(WebFrameMain.prototype, 'ipc', {
get() {
get () {
const ipc = new IpcMainImpl();
Object.defineProperty(this, 'ipc', { value: ipc });
return ipc;
@@ -37,7 +37,7 @@ WebFrameMain.prototype._sendInternal = function (channel, ...args) {
WebFrameMain.prototype.postMessage = function (...args) {
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);
};

View File

@@ -1,5 +1,4 @@
import { shell } from 'electron/common';
import { app, Menu } from 'electron/main';
import { Menu } from 'electron/main';
const isMac = process.platform === 'darwin';
@@ -12,47 +11,13 @@ export const setApplicationMenuWasSet = () => {
export const setDefaultApplicationMenu = () => {
if (applicationMenuWasSet) return;
const helpMenu: Electron.MenuItemConstructorOptions = {
role: 'help',
submenu: app.isPackaged
? []
: [
{
label: 'Learn More',
click: async () => {
await shell.openExternal('https://electronjs.org');
}
},
{
label: 'Documentation',
click: async () => {
const version = process.versions.electron;
await shell.openExternal(`https://github.com/electron/electron/tree/v${version}/docs#readme`);
}
},
{
label: 'Community Discussions',
click: async () => {
await shell.openExternal('https://discord.gg/electronjs');
}
},
{
label: 'Search Issues',
click: async () => {
await shell.openExternal('https://github.com/electron/electron/issues');
}
}
]
};
const macAppMenu: Electron.MenuItemConstructorOptions = { role: 'appMenu' };
const template: Electron.MenuItemConstructorOptions[] = [
...(isMac ? [macAppMenu] : []),
{ role: 'fileMenu' },
{ role: 'editMenu' },
{ role: 'viewMenu' },
{ role: 'windowMenu' },
helpMenu
{ role: 'windowMenu' }
];
const menu = Menu.buildFromTemplate(template);

View File

@@ -8,30 +8,29 @@ import * as fs from 'fs';
const convertToMenuTemplate = function (items: ContextMenuItem[], handler: (id: number) => void) {
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: 'submenu',
label: item.label,
enabled: item.enabled,
submenu: convertToMenuTemplate(item.subItems, handler)
type: 'separator'
}
: item.type === 'separator'
: item.type === 'checkbox'
? {
type: 'separator'
type: 'checkbox',
label: item.label,
enabled: item.enabled,
checked: item.checked
}
: item.type === 'checkbox'
? {
type: 'checkbox',
label: item.label,
enabled: item.enabled,
checked: item.checked
}
: {
type: 'normal',
label: item.label,
enabled: item.enabled
};
: {
type: 'normal',
label: item.label,
enabled: item.enabled
};
if (item.id != null) {
transformed.click = () => handler(item.id);
@@ -68,21 +67,18 @@ const assertChromeDevTools = function (contents: Electron.WebContents, api: stri
}
};
ipcMainInternal.handle(
IPC_MESSAGES.INSPECTOR_CONTEXT_MENU,
function (event, items: ContextMenuItem[], isEditMenu: boolean) {
return new Promise<number | void>((resolve) => {
if (event.type !== 'frame') return;
assertChromeDevTools(event.sender, 'window.InspectorFrontendHost.showContextMenuAtPoint()');
ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, function (event, items: ContextMenuItem[], isEditMenu: boolean) {
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 menu = Menu.buildFromTemplate(template);
const window = event.sender.getOwnerBrowserWindow()!;
const template = isEditMenu ? getEditMenuItems() : convertToMenuTemplate(items, resolve);
const menu = Menu.buildFromTemplate(template);
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) {
if (event.type !== 'frame') return [];
@@ -97,20 +93,17 @@ ipcMainInternal.handle(IPC_MESSAGES.INSPECTOR_SELECT_FILE, async function (event
return [path, data];
});
ipcMainUtils.handleSync(
IPC_MESSAGES.INSPECTOR_CONFIRM,
async function (event, message: string = '', title: string = '') {
if (event.type !== 'frame') return;
assertChromeDevTools(event.sender, 'window.confirm()');
ipcMainUtils.handleSync(IPC_MESSAGES.INSPECTOR_CONFIRM, async function (event, message: string = '', title: string = '') {
if (event.type !== 'frame') return;
assertChromeDevTools(event.sender, 'window.confirm()');
const options = {
message: String(message),
title: String(title),
buttons: ['OK', 'Cancel'],
cancelId: 1
};
const window = event.sender.getOwnerBrowserWindow()!;
const { response } = await dialog.showMessageBox(window, options);
return response === 0;
}
);
const options = {
message: String(message),
title: String(title),
buttons: ['OK', 'Cancel'],
cancelId: 1
};
const window = event.sender.getOwnerBrowserWindow()!;
const { response } = await dialog.showMessageBox(window, options);
return response === 0;
});

View File

@@ -3,12 +3,7 @@ import * as ipcMainUtils from '@electron/internal/browser/ipc-main-internal-util
import { parseWebViewWebPreferences } from '@electron/internal/browser/parse-features-string';
import { webViewEvents } from '@electron/internal/browser/web-view-events';
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';
@@ -27,11 +22,13 @@ const supportedWebViewEvents = Object.keys(webViewEvents);
const guestInstances = new Map<number, GuestInstance>();
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
// this uses the same parsing rules as window.open uses for its features
const parsedWebPreferences =
typeof params.webpreferences === 'string' ? parseWebViewWebPreferences(params.webpreferences) : null;
typeof params.webpreferences === 'string'
? parseWebViewWebPreferences(params.webpreferences)
: null;
const webPreferences: Electron.WebPreferences = {
nodeIntegration: params.nodeintegration ?? false,
@@ -71,7 +68,7 @@ function makeWebPreferences(embedder: Electron.WebContents, params: Record<strin
return webPreferences;
}
function makeLoadURLOptions(params: Record<string, any>) {
function makeLoadURLOptions (params: Record<string, any>) {
const opts: Electron.LoadURLOptions = {};
if (params.httpreferrer) {
opts.httpReferrer = params.httpreferrer;
@@ -83,16 +80,11 @@ function makeLoadURLOptions(params: Record<string, any>) {
}
// 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 event = {
sender: embedder,
preventDefault() {
preventDefault () {
this.defaultPrevented = true;
},
defaultPrevented: false
@@ -274,18 +266,13 @@ const isWebViewTagEnabled = function (contents: Electron.WebContents) {
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[]) => {
if (event.type !== 'frame') return;
if (isWebViewTagEnabled(event.sender)) {
return handler(event as unknown as Event, ...args);
} 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');
}
};
@@ -295,19 +282,13 @@ const handleMessage = function (channel: string, handler: (event: Electron.IpcMa
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));
};
handleMessage(
IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST,
function (event, embedderFrameToken: string, elementInstanceId: number, params) {
return createGuest(event.sender, embedderFrameToken, elementInstanceId, params);
}
);
handleMessage(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) {
return detachGuest(event.sender, guestInstanceId);
@@ -320,61 +301,49 @@ 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[]) {
const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!asyncMethods.has(method)) {
throw new Error(`Invalid method: ${method}`);
}
return (guest as any)[method](...args);
handleMessage(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, function (event, guestInstanceId: number, method: string, args: any[]) {
const guest = getGuestForWebContents(guestInstanceId, event.sender);
if (!asyncMethods.has(method)) {
throw new Error(`Invalid method: ${method}`);
}
);
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}`);
}
);
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}`);
// 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)[property];
return (guest as any).navigationHistory[navigationMethod](...args);
}
);
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}`);
}
return (guest as any)[method](...args);
});
(guest as any)[property] = val;
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}`);
}
);
return (guest as any)[property];
});
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.
const getGuestForWebContents = function (guestInstanceId: number, contents: Electron.WebContents) {

View File

@@ -10,37 +10,27 @@ import { parseFeatures } from '@electron/internal/browser/parse-features-string'
import { BrowserWindow } from 'electron/main';
import type { BrowserWindowConstructorOptions, Referrer, WebContents, LoadURLOptions } from 'electron/main';
type PostData = LoadURLOptions['postData'];
type PostData = LoadURLOptions['postData']
export type WindowOpenArgs = {
url: string;
frameName: string;
features: string;
};
url: string,
frameName: string,
features: string,
}
/**
* `openGuestWindow` is called to create and setup event handling for the new
* window.
*/
export function openGuestWindow({
embedder,
guest,
referrer,
disposition,
postData,
overrideBrowserWindowOptions,
windowOpenArgs,
outlivesOpener,
createWindow
}: {
embedder: WebContents;
guest?: WebContents;
referrer: Referrer;
disposition: string;
postData?: PostData;
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions;
windowOpenArgs: WindowOpenArgs;
outlivesOpener: boolean;
createWindow?: Electron.CreateWindowFunction;
export function openGuestWindow ({ embedder, guest, referrer, disposition, postData, overrideBrowserWindowOptions, windowOpenArgs, outlivesOpener, createWindow }: {
embedder: WebContents,
guest?: WebContents,
referrer: Referrer,
disposition: string,
postData?: PostData,
overrideBrowserWindowOptions?: BrowserWindowConstructorOptions,
windowOpenArgs: WindowOpenArgs,
outlivesOpener: boolean,
createWindow?: Electron.CreateWindowFunction
}): void {
const { url, frameName, features } = windowOpenArgs;
const { options: parsedOptions } = parseFeatures(features);
@@ -60,9 +50,7 @@ export function openGuestWindow({
if (guest != null) {
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 });
@@ -91,14 +79,7 @@ export function openGuestWindow({
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 });
}
/**
@@ -107,14 +88,10 @@ export function openGuestWindow({
* too is the guest destroyed; this is Electron convention and isn't based in
* browser behavior.
*/
const handleWindowLifecycleEvents = function ({
embedder,
guest,
outlivesOpener
}: {
embedder: WebContents;
guest: WebContents;
outlivesOpener: boolean;
const handleWindowLifecycleEvents = function ({ embedder, guest, outlivesOpener }: {
embedder: WebContents,
guest: WebContents,
outlivesOpener: boolean
}) {
const closedByEmbedder = function () {
guest.removeListener('destroyed', closedByUser);
@@ -144,25 +121,21 @@ const securityWebPreferences: { [key: string]: boolean } = {
enableWebSQL: false
};
export function makeWebPreferences({
embedder,
secureOverrideWebPreferences = {},
insecureParsedWebPreferences: parsedWebPreferences = {}
}: {
embedder: WebContents;
insecureParsedWebPreferences?: ReturnType<typeof parseFeatures>['webPreferences'];
export function makeWebPreferences ({ embedder, secureOverrideWebPreferences = {}, insecureParsedWebPreferences: parsedWebPreferences = {} }: {
embedder: WebContents,
insecureParsedWebPreferences?: ReturnType<typeof parseFeatures>['webPreferences'],
// Note that override preferences are considered elevated, and should only be
// sourced from the main process, as they override security defaults. If you
// have unvetted prefs, use parsedWebPreferences.
secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'];
secureOverrideWebPreferences?: BrowserWindowConstructorOptions['webPreferences'],
}) {
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]) {
(map as any)[key] = parentWebPreferences[key as keyof Electron.WebPreferences];
}
return map;
}, {} as Electron.WebPreferences);
}, {} as Electron.WebPreferences));
return {
...parsedWebPreferences,
@@ -174,13 +147,11 @@ export function makeWebPreferences({
};
}
function formatPostDataHeaders(postData: PostData) {
function formatPostDataHeaders (postData: PostData) {
if (!postData) return;
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}`;
}

View File

@@ -29,11 +29,12 @@ process.on('uncaughtException', function (error) {
// We can't import { dialog } at the top of this file as this file is
// responsible for setting up the require hook for the "electron" module
// so we import it inside the handler down here
import('electron').then(({ dialog }) => {
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
const message = 'Uncaught Exception:\n' + stack;
dialog.showErrorBox('A JavaScript error occurred in the main process', message);
});
import('electron')
.then(({ dialog }) => {
const stack = error.stack ? error.stack : `${error.name}: ${error.message}`;
const message = 'Uncaught Exception:\n' + stack;
dialog.showErrorBox('A JavaScript error occurred in the main process', message);
});
});
// Emit 'exit' event on quit.
@@ -63,10 +64,7 @@ if (process.platform === 'win32') {
if (fs.existsSync(updateDotExe)) {
const packageDir = path.dirname(path.resolve(updateDotExe));
const packageName = path.basename(packageDir).replaceAll(/\s/g, '');
const exeName = path
.basename(process.execPath)
.replace(/\.exe$/i, '')
.replaceAll(/\s/g, '');
const exeName = path.basename(process.execPath).replace(/\.exe$/i, '').replaceAll(/\s/g, '');
app.setAppUserModelId(`com.squirrel.${packageName}.${exeName}`);
}
@@ -183,9 +181,7 @@ delete process.appCodeLoaded;
if (packagePath) {
// Finally load app's main.js and transfer control to C++.
if ((packageJson.type === 'module' && !mainStartupScript.endsWith('.cjs')) || mainStartupScript.endsWith('.mjs')) {
const { runEntryPointWithESMLoader } = __non_webpack_require__(
'internal/modules/run_main'
) as typeof import('@node/lib/internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
const main = (require('url') as typeof url).pathToFileURL(path.join(packagePath, mainStartupScript));
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
try {
@@ -203,6 +199,6 @@ if (packagePath) {
}
} else {
console.error('Failed to locate a valid package to load (app, app.asar or default_app.asar)');
console.error("This normally means you've damaged the Electron package somehow");
console.error('This normally means you\'ve damaged the Electron package somehow');
appCodeLoaded!();
}

View File

@@ -21,14 +21,10 @@ const addReturnValueToEvent = (event: Electron.IpcMainEvent | Electron.IpcMainSe
});
};
const getServiceWorkerFromEvent = (
event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent
): ServiceWorkerMain | undefined => {
const getServiceWorkerFromEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent): ServiceWorkerMain | undefined => {
return event.session.serviceWorkers._getWorkerFromVersionIDIfExists(event.versionId);
};
const addServiceWorkerPropertyToEvent = (
event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent
) => {
const addServiceWorkerPropertyToEvent = (event: Electron.IpcMainServiceWorkerEvent | Electron.IpcMainServiceWorkerInvokeEvent) => {
Object.defineProperty(event, 'serviceWorker', {
get: () => event.session.serviceWorkers.getWorkerFromVersionID(event.versionId)
});
@@ -45,9 +41,7 @@ const cachedIpcEmitters: (ElectronInternal.IpcMainInternal | undefined)[] = [
];
// Get list of relevant IPC emitters for dispatch.
const getIpcEmittersForFrameEvent = (
event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent
): (ElectronInternal.IpcMainInternal | undefined)[] => {
const getIpcEmittersForFrameEvent = (event: Electron.IpcMainEvent | Electron.IpcMainInvokeEvent): (ElectronInternal.IpcMainInternal | undefined)[] => {
// Lookup by FrameTreeNode ID to ensure IPCs received after a frame swap are
// always received. This occurs when a RenderFrame sends an IPC while it's
// unloading and its internal state is pending deletion.
@@ -61,121 +55,97 @@ const getIpcEmittersForFrameEvent = (
/**
* Listens for IPC dispatch events on `api`.
*/
export function addIpcDispatchListeners(api: NodeJS.EventEmitter) {
api.on(
'-ipc-message' as any,
function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
export function addIpcDispatchListeners (api: NodeJS.EventEmitter) {
api.on('-ipc-message' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
if (internal) {
ipcMainInternal.emit(channel, event, ...args);
} else if (event.type === 'frame') {
addReplyToEvent(event);
event.sender.emit('ipc-message', event, channel, ...args);
for (const ipcEmitter of getIpcEmittersForFrameEvent(event)) {
ipcEmitter?.emit(channel, event, ...args);
}
} else if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
if (internal) {
ipcMainInternal.emit(channel, event, ...args);
} else if (event.type === 'frame') {
addReplyToEvent(event);
event.sender.emit('ipc-message', event, channel, ...args);
for (const ipcEmitter of getIpcEmittersForFrameEvent(event)) {
ipcEmitter?.emit(channel, event, ...args);
}
} as any
);
} else if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
}
} as any);
api.on(
'-ipc-invoke' as any,
async function (
event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent,
channel: string,
args: any[]
) {
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
api.on('-ipc-invoke' as any, async function (event: Electron.IpcMainInvokeEvent | Electron.IpcMainServiceWorkerInvokeEvent, channel: string, args: any[]) {
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
const replyWithResult = (result: any) => event._replyChannel.sendReply({ result });
const replyWithError = (error: Error) => {
console.error(`Error occurred in handler for '${channel}':`, error);
event._replyChannel.sendReply({ error: error.toString() });
};
const replyWithResult = (result: any) => event._replyChannel.sendReply({ result });
const replyWithError = (error: Error) => {
console.error(`Error occurred in handler for '${channel}':`, error);
event._replyChannel.sendReply({ error: error.toString() });
};
const targets: (Electron.IpcMainServiceWorker | ElectronInternal.IpcMainInternal | undefined)[] = [];
const targets: (Electron.IpcMainServiceWorker | ElectronInternal.IpcMainInternal | undefined)[] = [];
if (internal) {
targets.push(ipcMainInternal);
} else if (event.type === 'frame') {
targets.push(...getIpcEmittersForFrameEvent(event));
} else if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
const workerIpc = getServiceWorkerFromEvent(event)?.ipc;
targets.push(workerIpc);
if (internal) {
targets.push(ipcMainInternal);
} else if (event.type === 'frame') {
targets.push(...getIpcEmittersForFrameEvent(event));
} else if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
const workerIpc = getServiceWorkerFromEvent(event)?.ipc;
targets.push(workerIpc);
}
const target = targets.find(target => (target as any)?._invokeHandlers.has(channel));
if (target) {
const handler = (target as any)._invokeHandlers.get(channel);
try {
replyWithResult(await Promise.resolve(handler(event, ...args)));
} catch (err) {
replyWithError(err as Error);
}
} else {
replyWithError(new Error(`No handler registered for '${channel}'`));
}
} as any);
const target = targets.find((target) => (target as any)?._invokeHandlers.has(channel));
if (target) {
const handler = (target as any)._invokeHandlers.get(channel);
try {
replyWithResult(await Promise.resolve(handler(event, ...args)));
} catch (err) {
replyWithError(err as Error);
}
} else {
replyWithError(new Error(`No handler registered for '${channel}'`));
api.on('-ipc-message-sync' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
addReturnValueToEvent(event);
if (internal) {
ipcMainInternal.emit(channel, event, ...args);
} else if (event.type === 'frame') {
addReplyToEvent(event);
const webContents = event.sender;
const ipcEmitters = getIpcEmittersForFrameEvent(event);
if (
webContents.listenerCount('ipc-message-sync') === 0 &&
ipcEmitters.every(emitter => !emitter || emitter.listenerCount(channel) === 0)
) {
console.warn(`WebContents #${webContents.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`);
}
} as any
);
api.on(
'-ipc-message-sync' as any,
function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, args: any[]) {
const internal = v8Util.getHiddenValue<boolean>(event, 'internal');
addReturnValueToEvent(event);
if (internal) {
ipcMainInternal.emit(channel, event, ...args);
} else if (event.type === 'frame') {
addReplyToEvent(event);
const webContents = event.sender;
const ipcEmitters = getIpcEmittersForFrameEvent(event);
if (
webContents.listenerCount('ipc-message-sync') === 0 &&
ipcEmitters.every((emitter) => !emitter || emitter.listenerCount(channel) === 0)
) {
console.warn(
`WebContents #${webContents.id} called ipcRenderer.sendSync() with '${channel}' channel without listeners.`
);
}
webContents.emit('ipc-message-sync', event, channel, ...args);
for (const ipcEmitter of ipcEmitters) {
ipcEmitter?.emit(channel, event, ...args);
}
} else if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
webContents.emit('ipc-message-sync', event, channel, ...args);
for (const ipcEmitter of ipcEmitters) {
ipcEmitter?.emit(channel, event, ...args);
}
} as any
);
} else if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, ...args);
}
} as any);
api.on('-ipc-message-host', function (event: Electron.IpcMainEvent, channel: string, args: any[]) {
event.sender.emit('-ipc-message-host', event, channel, args);
});
api.on(
'-ipc-ports' as any,
function (
event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent,
channel: string,
message: any,
ports: any[]
) {
event.ports = ports.map((p) => new MessagePortMain(p));
if (event.type === 'frame') {
const ipcEmitters = getIpcEmittersForFrameEvent(event);
for (const ipcEmitter of ipcEmitters) {
ipcEmitter?.emit(channel, event, message);
}
api.on('-ipc-ports' as any, function (event: Electron.IpcMainEvent | Electron.IpcMainServiceWorkerEvent, channel: string, message: any, ports: any[]) {
event.ports = ports.map(p => new MessagePortMain(p));
if (event.type === 'frame') {
const ipcEmitters = getIpcEmittersForFrameEvent(event);
for (const ipcEmitter of ipcEmitters) {
ipcEmitter?.emit(channel, event, message);
}
if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, message);
}
} as any
);
} if (event.type === 'service-worker') {
addServiceWorkerPropertyToEvent(event);
getServiceWorkerFromEvent(event)?.ipc.emit(channel, event, message);
}
} as any);
}

View File

@@ -5,7 +5,7 @@ import { EventEmitter } from 'events';
export class IpcMainImpl extends EventEmitter implements Electron.IpcMain {
private _invokeHandlers: Map<string, (e: IpcMainInvokeEvent, ...args: any[]) => void> = new Map();
constructor() {
constructor () {
super();
// Do not throw exception when channel name is "error".
@@ -29,7 +29,7 @@ export class IpcMainImpl extends EventEmitter implements Electron.IpcMain {
});
};
removeHandler(method: string) {
removeHandler (method: string) {
this._invokeHandlers.delete(method);
}
}

View File

@@ -1,8 +1,8 @@
import { ipcMainInternal } from '@electron/internal/browser/ipc-main-internal';
type IPCHandler = (event: ElectronInternal.IpcMainInternalEvent, ...args: any[]) => any;
type IPCHandler = (event: ElectronInternal.IpcMainInternalEvent, ...args: any[]) => any
export const handleSync = function <T extends IPCHandler>(channel: string, handler: T) {
export const handleSync = function <T extends IPCHandler> (channel: string, handler: T) {
ipcMainInternal.on(channel, async (event, ...args) => {
try {
event.returnValue = [null, await handler(event, ...args)];
@@ -14,11 +14,11 @@ export const handleSync = function <T extends IPCHandler>(channel: string, handl
let nextId = 0;
export function invokeInWebContents<T>(sender: Electron.WebContents, command: string, ...args: any[]) {
export function invokeInWebContents<T> (sender: Electron.WebContents, command: string, ...args: any[]) {
return new Promise<T>((resolve, reject) => {
const requestId = ++nextId;
const channel = `${command}_RESPONSE_${requestId}`;
ipcMainInternal.on(channel, function handler(event, error: Error, result: any) {
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
if (event.type !== 'frame' || event.sender !== sender) {
console.error(`Reply to ${command} sent by unexpected sender`);
return;
@@ -37,12 +37,12 @@ export function invokeInWebContents<T>(sender: Electron.WebContents, command: st
});
}
export function invokeInWebFrameMain<T>(sender: Electron.WebFrameMain, command: string, ...args: any[]) {
export function invokeInWebFrameMain<T> (sender: Electron.WebFrameMain, command: string, ...args: any[]) {
return new Promise<T>((resolve, reject) => {
const requestId = ++nextId;
const channel = `${command}_RESPONSE_${requestId}`;
const frameTreeNodeId = sender.frameTreeNodeId;
ipcMainInternal.on(channel, function handler(event, error: Error, result: any) {
ipcMainInternal.on(channel, function handler (event, error: Error, result: any) {
if (event.type !== 'frame' || event.frameTreeNodeId !== frameTreeNodeId) {
console.error(`Reply to ${command} sent by unexpected sender`);
return;

View File

@@ -2,28 +2,26 @@ import { EventEmitter } from 'events';
export class MessagePortMain extends EventEmitter implements Electron.MessagePortMain {
_internalPort: any;
constructor(internalPort: any) {
constructor (internalPort: any) {
super();
this._internalPort = internalPort;
this._internalPort.emit = (channel: string, event: { ports: any[] }) => {
if (channel === 'message') {
event = { ...event, ports: event.ports.map((p) => new MessagePortMain(p)) };
}
this._internalPort.emit = (channel: string, event: {ports: any[]}) => {
if (channel === 'message') { event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) }; }
this.emit(channel, event);
};
}
start() {
start () {
return this._internalPort.start();
}
close() {
close () {
return this._internalPort.close();
}
postMessage(...args: any[]) {
postMessage (...args: any[]) {
if (Array.isArray(args[1])) {
args[1] = args[1].map((o: any) => (o instanceof MessagePortMain ? o._internalPort : o));
args[1] = args[1].map((o: any) => o instanceof MessagePortMain ? o._internalPort : o);
}
return this._internalPort.postMessage(...args);
}

View File

@@ -6,15 +6,14 @@ import { BrowserWindowConstructorOptions } from 'electron/main';
type RequiredBrowserWindowConstructorOptions = Required<BrowserWindowConstructorOptions>;
type IntegerBrowserWindowOptionKeys = {
[K in keyof RequiredBrowserWindowConstructorOptions]: RequiredBrowserWindowConstructorOptions[K] extends number
? K
: never;
[K in keyof RequiredBrowserWindowConstructorOptions]:
RequiredBrowserWindowConstructorOptions[K] extends number ? K : never
}[keyof RequiredBrowserWindowConstructorOptions];
// This could be an array of keys, but an object allows us to add a compile-time
// check validating that we haven't added an integer property to
// BrowserWindowConstructorOptions that this module doesn't know about.
const keysOfTypeNumberCompileTimeCheck: { [K in IntegerBrowserWindowOptionKeys]: true } = {
const keysOfTypeNumberCompileTimeCheck: { [K in IntegerBrowserWindowOptionKeys] : true } = {
x: true,
y: true,
width: true,
@@ -32,13 +31,7 @@ const keysOfTypeNumberCompileTimeCheck: { [K in IntegerBrowserWindowOptionKeys]:
// to `innerWidth` / `innerHeight`. However, our implementation currently incorrectly maps
// `width` and `height` to `outerWidth` and `outerHeight`, or the size of the window
// with all border and related window chrome.
const keysOfTypeNumber = new Set([
'top',
'left',
'innerWidth',
'innerHeight',
...Object.keys(keysOfTypeNumberCompileTimeCheck)
]);
const keysOfTypeNumber = new Set(['top', 'left', 'innerWidth', 'innerHeight', ...Object.keys(keysOfTypeNumberCompileTimeCheck)]);
/**
* Note that we only allow "0" and "1" boolean conversion when the type is known
@@ -48,7 +41,7 @@ const keysOfTypeNumber = new Set([
* https://html.spec.whatwg.org/multipage/window-object.html#concept-window-open-features-parse-boolean
*/
type CoercedValue = string | number | boolean;
function coerce(key: string, value: string): CoercedValue {
function coerce (key: string, value: string): CoercedValue {
if (keysOfTypeNumber.has(key)) {
return parseInt(value, 10);
}
@@ -68,35 +61,27 @@ function coerce(key: string, value: string): CoercedValue {
}
}
export function parseCommaSeparatedKeyValue(source: string) {
export function parseCommaSeparatedKeyValue (source: string) {
const parsed = {} as { [key: string]: any };
for (const keyValuePair of source.split(',')) {
const [key, value] = keyValuePair.split('=').map((str) => str.trim());
if (key) {
parsed[key] = coerce(key, value);
}
const [key, value] = keyValuePair.split('=').map(str => str.trim());
if (key) { parsed[key] = coerce(key, value); }
}
return parsed;
}
export function parseWebViewWebPreferences(preferences: string) {
export function parseWebViewWebPreferences (preferences: string) {
return parseCommaSeparatedKeyValue(preferences);
}
const allowedWebPreferences = [
'zoomFactor',
'nodeIntegration',
'javascript',
'contextIsolation',
'webviewTag'
] as const;
const allowedWebPreferences = ['zoomFactor', 'nodeIntegration', 'javascript', 'contextIsolation', 'webviewTag'] as const;
type AllowedWebPreference = (typeof allowedWebPreferences)[number];
/**
* Parses a feature string that has the format used in window.open().
*/
export function parseFeatures(features: string) {
export function parseFeatures (features: string) {
const parsed = parseCommaSeparatedKeyValue(features);
const webPreferences: { [K in AllowedWebPreference]?: any } = {};

View File

@@ -54,19 +54,19 @@ const getPreloadScriptsFromEvent = (event: ElectronInternal.IpcMainInternalEvent
let preloadScripts = session.getPreloadScripts();
if (event.type === 'frame') {
preloadScripts = preloadScripts.filter((script) => script.type === 'frame');
preloadScripts = preloadScripts.filter(script => script.type === 'frame');
const webPrefPreload = event.sender._getPreloadScript();
if (webPrefPreload) preloadScripts.push(webPrefPreload);
} else if (event.type === 'service-worker') {
preloadScripts = preloadScripts.filter((script) => script.type === 'service-worker');
preloadScripts = preloadScripts.filter(script => script.type === 'service-worker');
} else {
throw new Error(`getPreloadScriptsFromEvent: event.type is invalid (${(event as any).type})`);
}
// TODO(samuelmaddock): Remove filter after Session.setPreloads is fully
// deprecated. The new API will prevent relative paths from being registered.
return preloadScripts.filter((script) => path.isAbsolute(script.filePath));
return preloadScripts.filter(script => path.isAbsolute(script.filePath));
};
const readPreloadScript = async function (script: Electron.PreloadScript): Promise<ElectronInternal.PreloadScript> {
@@ -103,7 +103,7 @@ ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_SANDBOX_LOAD, async function (event
ipcMainUtils.handleSync(IPC_MESSAGES.BROWSER_NONSANDBOX_LOAD, function (event) {
const preloadScripts = getPreloadScriptsFromEvent(event);
return { preloadPaths: preloadScripts.map((script) => script.filePath) };
return { preloadPaths: preloadScripts.map(script => script.filePath) };
});
ipcMainInternal.on(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, function (event, preloadPath: string, error: Error) {

View File

@@ -17,14 +17,7 @@ export const webViewEvents: Record<string, readonly string[]> = {
'did-start-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-redirect-navigation': ['url', 'isInPlace', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-navigate': ['url', 'httpResponseCode', 'httpStatusText'],
'did-frame-navigate': [
'url',
'httpResponseCode',
'httpStatusText',
'isMainFrame',
'frameProcessId',
'frameRoutingId'
],
'did-frame-navigate': ['url', 'httpResponseCode', 'httpStatusText', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'did-navigate-in-page': ['url', 'isMainFrame', 'frameProcessId', 'frameRoutingId'],
'-focus-change': ['focus'],
close: [],

View File

@@ -1,9 +1,16 @@
import type { ClientRequestConstructorOptions, UploadProgress } from 'electron/common';
import type {
ClientRequestConstructorOptions,
UploadProgress
} from 'electron/common';
import { Readable, Writable } from 'stream';
import * as url from 'url';
const { isValidHeaderName, isValidHeaderValue, createURLLoader } = process._linkedBinding('electron_common_net');
const {
isValidHeaderName,
isValidHeaderValue,
createURLLoader
} = process._linkedBinding('electron_common_net');
const kHttpProtocols = new Set(['http:', 'https:']);
@@ -36,20 +43,20 @@ class IncomingMessage extends Readable {
_responseHead: NodeJS.ResponseHead;
_resume: (() => void) | null = null;
constructor(responseHead: NodeJS.ResponseHead) {
constructor (responseHead: NodeJS.ResponseHead) {
super();
this._responseHead = responseHead;
}
get statusCode() {
get statusCode () {
return this._responseHead.statusCode;
}
get statusMessage() {
get statusMessage () {
return this._responseHead.statusMessage;
}
get headers() {
get headers () {
const filteredHeaders: Record<string, string | string[]> = {};
const { headers, rawHeaders } = this._responseHead;
for (const [name, values] of Object.entries(headers)) {
@@ -58,13 +65,11 @@ class IncomingMessage extends Readable {
const cookies = rawHeaders.filter(({ key }) => key.toLowerCase() === 'set-cookie').map(({ value }) => value);
// keep set-cookie as an array per Node.js rules
// see https://nodejs.org/api/http.html#http_message_headers
if (cookies.length) {
filteredHeaders['set-cookie'] = cookies;
}
if (cookies.length) { filteredHeaders['set-cookie'] = cookies; }
return filteredHeaders;
}
get rawHeaders() {
get rawHeaders () {
const rawHeadersArr: string[] = [];
const { rawHeaders } = this._responseHead;
for (const header of rawHeaders) {
@@ -73,34 +78,34 @@ class IncomingMessage extends Readable {
return rawHeadersArr;
}
get httpVersion() {
get httpVersion () {
return `${this.httpVersionMajor}.${this.httpVersionMinor}`;
}
get httpVersionMajor() {
get httpVersionMajor () {
return this._responseHead.httpVersion.major;
}
get httpVersionMinor() {
get httpVersionMinor () {
return this._responseHead.httpVersion.minor;
}
get rawTrailers() {
get rawTrailers () {
throw new Error('HTTP trailers are not supported');
}
get trailers() {
get trailers () {
throw new Error('HTTP trailers are not supported');
}
_storeInternalData(chunk: Buffer | null, resume: (() => void) | null) {
_storeInternalData (chunk: Buffer | null, resume: (() => void) | null) {
// save the network callback for use in _pushInternalData
this._resume = resume;
this._data.push(chunk);
this._pushInternalData();
}
_pushInternalData() {
_pushInternalData () {
while (this._shouldPush && this._data.length > 0) {
const chunk = this._data.shift();
this._shouldPush = this.push(chunk);
@@ -116,7 +121,7 @@ class IncomingMessage extends Readable {
}
}
_read() {
_read () {
this._shouldPush = true;
this._pushInternalData();
}
@@ -125,19 +130,17 @@ class IncomingMessage extends Readable {
/** Writable stream that buffers up everything written to it. */
class SlurpStream extends Writable {
_data: Buffer;
constructor() {
constructor () {
super();
this._data = Buffer.alloc(0);
}
_write(chunk: Buffer, encoding: string, callback: () => void) {
_write (chunk: Buffer, encoding: string, callback: () => void) {
this._data = Buffer.concat([this._data, chunk]);
callback();
}
data() {
return this._data;
}
data () { return this._data; }
}
class ChunkedBodyStream extends Writable {
@@ -146,12 +149,12 @@ class ChunkedBodyStream extends Writable {
_pendingCallback?: (error?: Error) => void;
_clientRequest: ClientRequest;
constructor(clientRequest: ClientRequest) {
constructor (clientRequest: ClientRequest) {
super();
this._clientRequest = clientRequest;
}
_write(chunk: Buffer, encoding: string, callback: () => void) {
_write (chunk: Buffer, encoding: string, callback: () => void) {
if (this._downstream) {
this._downstream.write(chunk).then(callback, callback);
} else {
@@ -165,12 +168,12 @@ class ChunkedBodyStream extends Writable {
}
}
_final(callback: () => void) {
_final (callback: () => void) {
this._downstream!.done();
callback();
}
startReading(pipe: NodeJS.DataPipe) {
startReading (pipe: NodeJS.DataPipe) {
if (this._downstream) {
throw new Error('two startReading calls???');
}
@@ -195,7 +198,7 @@ class ChunkedBodyStream extends Writable {
type RedirectPolicy = 'manual' | 'follow' | 'error';
const kAllowNonHttpProtocols = Symbol('kAllowNonHttpProtocols');
export function allowAnyProtocol(opts: ClientRequestConstructorOptions): ClientRequestConstructorOptions {
export function allowAnyProtocol (opts: ClientRequestConstructorOptions): ClientRequestConstructorOptions {
return {
...opts,
[kAllowNonHttpProtocols]: true
@@ -203,12 +206,12 @@ export function allowAnyProtocol(opts: ClientRequestConstructorOptions): ClientR
}
type ExtraURLLoaderOptions = {
redirectPolicy: RedirectPolicy;
headers: Record<string, { name: string; value: string | string[] }>;
allowNonHttpProtocols: boolean;
};
redirectPolicy: RedirectPolicy;
headers: Record<string, { name: string, value: string | string[] }>;
allowNonHttpProtocols: boolean;
}
function validateHeader(name: any, value: any): void {
function validateHeader (name: any, value: any): void {
if (typeof name !== 'string') {
throw new TypeError('`name` should be a string in setHeader(name, value)');
}
@@ -223,9 +226,7 @@ function validateHeader(name: any, value: any): void {
}
}
function parseOptions(
optionsIn: ClientRequestConstructorOptions | string
): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
function parseOptions (optionsIn: ClientRequestConstructorOptions | string): NodeJS.CreateURLLoaderOptions & ExtraURLLoaderOptions {
const options: any = typeof optionsIn === 'string' ? new URL(optionsIn) : { ...optionsIn };
let urlStr: string = options.url || options.href;
@@ -275,11 +276,7 @@ function parseOptions(
throw new TypeError('headers must be an object');
}
const urlLoaderOptions: NodeJS.CreateURLLoaderOptions & {
redirectPolicy: RedirectPolicy;
headers: Record<string, { name: string; value: string | string[] }>;
allowNonHttpProtocols: boolean;
} = {
const urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { redirectPolicy: RedirectPolicy, headers: Record<string, { name: string, value: string | string[] }>, allowNonHttpProtocols: boolean } = {
method: (options.method || 'GET').toUpperCase(),
url: urlStr,
redirectPolicy,
@@ -306,9 +303,7 @@ function parseOptions(
if (process.type !== 'utility') {
const { Session } = process._linkedBinding('electron_browser_session');
if (options.session) {
if (!(options.session instanceof Session)) {
throw new TypeError('`session` should be an instance of the Session class');
}
if (!(options.session instanceof Session)) { throw new TypeError('`session` should be an instance of the Session class'); }
urlLoaderOptions.session = options.session;
} else if (options.partition) {
if (typeof options.partition === 'string') {
@@ -327,16 +322,14 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
_aborted: boolean = false;
_chunkedEncoding: boolean | undefined;
_body: Writable | undefined;
_urlLoaderOptions: NodeJS.CreateURLLoaderOptions & {
headers: Record<string, { name: string; value: string | string[] }>;
};
_urlLoaderOptions: NodeJS.CreateURLLoaderOptions & { headers: Record<string, { name: string, value: string | string[] }> };
_redirectPolicy: RedirectPolicy;
_followRedirectCb?: () => void;
_uploadProgress?: { active: boolean; started: boolean; current: number; total: number };
_uploadProgress?: { active: boolean, started: boolean, current: number, total: number };
_urlLoader?: NodeJS.URLLoader;
_response?: IncomingMessage;
constructor(options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
constructor (options: ClientRequestConstructorOptions | string, callback?: (message: IncomingMessage) => void) {
super({ autoDestroy: true });
if (callback) {
@@ -348,18 +341,16 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
if (!urlLoaderOptions.allowNonHttpProtocols && !kHttpProtocols.has(urlObj.protocol)) {
throw new Error('ClientRequest only supports http: and https: protocols');
}
if (urlLoaderOptions.credentials === 'same-origin' && !urlLoaderOptions.origin) {
throw new Error('credentials: same-origin requires origin to be set');
}
if (urlLoaderOptions.credentials === 'same-origin' && !urlLoaderOptions.origin) { throw new Error('credentials: same-origin requires origin to be set'); }
this._urlLoaderOptions = urlLoaderOptions;
this._redirectPolicy = redirectPolicy;
}
get chunkedEncoding() {
get chunkedEncoding () {
return this._chunkedEncoding || false;
}
set chunkedEncoding(value: boolean) {
set chunkedEncoding (value: boolean) {
if (this._started) {
throw new Error('chunkedEncoding can only be set before the request is started');
}
@@ -375,9 +366,9 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
}
}
setHeader(name: string, value: string) {
setHeader (name: string, value: string) {
if (this._started || this._firstWrite) {
throw new Error("Can't set headers after they are sent");
throw new Error('Can\'t set headers after they are sent');
}
validateHeader(name, value);
@@ -385,30 +376,30 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
this._urlLoaderOptions.headers[key] = { name, value };
}
getHeader(name: string) {
getHeader (name: string) {
if (name == null) {
throw new Error('`name` is required for getHeader(name)');
}
const key = name.toLowerCase();
const header = this._urlLoaderOptions.headers[key];
return header && (header.value as any);
return header && header.value as any;
}
removeHeader(name: string) {
removeHeader (name: string) {
if (name == null) {
throw new Error('`name` is required for removeHeader(name)');
}
if (this._started || this._firstWrite) {
throw new Error("Can't remove headers after they are sent");
throw new Error('Can\'t remove headers after they are sent');
}
const key = name.toLowerCase();
delete this._urlLoaderOptions.headers[key];
}
_write(chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
_write (chunk: Buffer, encoding: BufferEncoding, callback: () => void) {
this._firstWrite = true;
if (!this._body) {
this._body = new SlurpStream();
@@ -421,7 +412,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
this._body.write(chunk, encoding, callback);
}
_final(callback: () => void) {
_final (callback: () => void) {
if (this._body) {
// TODO: is this the right way to forward to another stream?
this._body.end(callback);
@@ -432,9 +423,9 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
}
}
_startRequest() {
_startRequest () {
this._started = true;
const stringifyValues = (obj: Record<string, { name: string; value: string | string[] }>) => {
const stringifyValues = (obj: Record<string, { name: string, value: string | string[] }>) => {
const ret: Record<string, string> = {};
for (const { name, value } of Object.values(obj)) {
ret[name] = value.toString();
@@ -449,16 +440,14 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
const opts = { ...this._urlLoaderOptions, extraHeaders: stringifyValues(this._urlLoaderOptions.headers) };
this._urlLoader = createURLLoader(opts);
this._urlLoader.on('response-started', (event, finalUrl, responseHead) => {
const response = (this._response = new IncomingMessage(responseHead));
const response = this._response = new IncomingMessage(responseHead);
this.emit('response', response);
});
this._urlLoader.on('data', (event, data, resume) => {
this._response!._storeInternalData(Buffer.from(data), resume);
});
this._urlLoader.on('complete', () => {
if (this._response) {
this._response._storeInternalData(null, null);
}
if (this._response) { this._response._storeInternalData(null, null); }
});
this._urlLoader.on('error', (event, netErrorString) => {
const error = new Error(netErrorString);
@@ -477,12 +466,10 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
this._urlLoader.on('redirect', (event, redirectInfo, headers) => {
const { statusCode, newMethod, newUrl } = redirectInfo;
if (this._redirectPolicy === 'error') {
this._die(new Error("Attempted to redirect, but redirect policy was 'error'"));
this._die(new Error('Attempted to redirect, but redirect policy was \'error\''));
} else if (this._redirectPolicy === 'manual') {
let _followRedirect = false;
this._followRedirectCb = () => {
_followRedirect = true;
};
this._followRedirectCb = () => { _followRedirect = true; };
try {
this.emit('redirect', statusCode, newMethod, newUrl, headers);
} finally {
@@ -518,7 +505,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
});
}
followRedirect() {
followRedirect () {
if (this._followRedirectCb) {
this._followRedirectCb();
} else {
@@ -526,17 +513,15 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
}
}
abort() {
abort () {
if (!this._aborted) {
process.nextTick(() => {
this.emit('abort');
});
process.nextTick(() => { this.emit('abort'); });
}
this._aborted = true;
this._die();
}
_die(err?: Error) {
_die (err?: Error) {
// Node.js assumes that any stream which is ended is no longer capable of emitted events
// which is a faulty assumption for the case of an object that is acting like a stream
// (our urlRequest). If we don't emit here, this causes errors since we *do* expect
@@ -552,7 +537,7 @@ export class ClientRequest extends Writable implements Electron.ClientRequest {
}
}
getUploadProgress(): UploadProgress {
getUploadProgress (): UploadProgress {
return this._uploadProgress ? { ...this._uploadProgress } : { active: false, started: false, current: 0, total: 0 };
}
}

View File

@@ -5,7 +5,7 @@ const handleESModule = (loader: ElectronInternal.ModuleLoader) => () => {
};
// Attaches properties to |targetExports|.
export function defineProperties(targetExports: Object, moduleList: ElectronInternal.ModuleEntry[]) {
export function defineProperties (targetExports: Object, moduleList: ElectronInternal.ModuleEntry[]) {
const descriptors: PropertyDescriptorMap = {};
for (const module of moduleList) {
descriptors[module.name] = {

View File

@@ -2,15 +2,13 @@ type DeprecationHandler = (message: string) => void;
let deprecationHandler: DeprecationHandler | null = null;
export function warnOnce(oldName: string, newName?: string) {
return warnOnceMessage(
newName
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
: `'${oldName}' is deprecated and will be removed.`
);
export function warnOnce (oldName: string, newName?: string) {
return warnOnceMessage(newName
? `'${oldName}' is deprecated and will be removed. Please use '${newName}' instead.`
: `'${oldName}' is deprecated and will be removed.`);
}
export function warnOnceMessage(msg: string) {
export function warnOnceMessage (msg: string) {
let warned = false;
return () => {
if (!warned && !process.noDeprecation) {
@@ -20,21 +18,21 @@ export function warnOnceMessage(msg: string) {
};
}
export function setHandler(handler: DeprecationHandler | null): void {
export function setHandler (handler: DeprecationHandler | null): void {
deprecationHandler = handler;
}
export function getHandler(): DeprecationHandler | null {
export function getHandler (): DeprecationHandler | null {
return deprecationHandler;
}
export function warn(oldName: string, newName: string): void {
export function warn (oldName: string, newName: string): void {
if (!process.noDeprecation) {
log(`'${oldName}' is deprecated. Use '${newName}' instead.`);
}
}
export function log(message: string): void {
export function log (message: string): void {
if (typeof deprecationHandler === 'function') {
deprecationHandler(message);
} else if (process.throwDeprecation) {
@@ -47,10 +45,8 @@ export function log(message: string): void {
}
// remove a function with no replacement
export function removeFunction<T extends Function>(fn: T, removedName: string): T {
if (!fn) {
throw new Error(`'${removedName} function' is invalid or does not exist.`);
}
export function removeFunction<T extends Function> (fn: T, removedName: string): T {
if (!fn) { throw new Error(`'${removedName} function' is invalid or does not exist.`); }
// wrap the deprecated function to warn user
const warn = warnOnce(`${fn.name} function`);
@@ -61,7 +57,7 @@ export function removeFunction<T extends Function>(fn: T, removedName: string):
}
// change the name of a function
export function renameFunction<T extends Function>(fn: T, newName: string): T {
export function renameFunction<T extends Function> (fn: T, newName: string): T {
const warn = warnOnce(`${fn.name} function`, `${newName} function`);
return function (this: any) {
warn();
@@ -70,12 +66,7 @@ export function renameFunction<T extends Function>(fn: T, newName: string): T {
}
// change the name of an event
export function event(
emitter: NodeJS.EventEmitter,
oldName: string,
newName: string,
transformer: (...args: any[]) => any[] | undefined = (...args) => args
) {
export function event (emitter: NodeJS.EventEmitter, oldName: string, newName: string, transformer: (...args: any[]) => any[] | undefined = (...args) => args) {
const warn = newName.startsWith('-') /* internal event */
? warnOnce(`${oldName} event`)
: warnOnce(`${oldName} event`, `${newName} event`);
@@ -91,11 +82,7 @@ export function event(
}
// remove a property with no replacement
export function removeProperty<T extends Object, K extends keyof T & string>(
object: T,
removedName: K,
onlyForValues?: any[]
): T {
export function removeProperty<T extends Object, K extends (keyof T & string)>(object: T, removedName: K, onlyForValues?: any[]): T {
// if the property's already been removed, warn about it
// eslint-disable-next-line no-proto
const info = Object.getOwnPropertyDescriptor((object as any).__proto__, removedName);
@@ -116,7 +103,7 @@ export function removeProperty<T extends Object, K extends keyof T & string>(
warn();
return info.get!.call(object);
},
set: (newVal) => {
set: newVal => {
if (!onlyForValues || onlyForValues.includes(newVal)) {
warn();
}
@@ -126,16 +113,12 @@ export function removeProperty<T extends Object, K extends keyof T & string>(
}
// change the name of a property
export function renameProperty<T extends Object, K extends keyof T & string>(
object: T,
oldName: string,
newName: K
): T {
export function renameProperty<T extends Object, K extends (keyof T & string)>(object: T, oldName: string, newName: K): T {
const warn = warnOnce(oldName, newName);
// if the new property isn't there yet,
// inject it and warn about it
if (oldName in object && !(newName in object)) {
if ((oldName in object) && !(newName in object)) {
warn();
object[newName] = (object as any)[oldName];
}
@@ -147,14 +130,14 @@ export function renameProperty<T extends Object, K extends keyof T & string>(
warn();
return object[newName];
},
set: (value) => {
set: value => {
warn();
object[newName] = value;
}
});
}
export function moveAPI<T extends Function>(fn: T, oldUsage: string, newUsage: string): T {
export function moveAPI<T extends Function> (fn: T, oldUsage: string, newUsage: string): T {
const warn = warnOnce(oldUsage, newUsage);
return function (this: any) {
warn();

View File

@@ -3,7 +3,7 @@ import * as util from 'util';
import type * as stream from 'stream';
type AnyFn = (...args: any[]) => any;
type AnyFn = (...args: any[]) => any
// setImmediate and process.nextTick makes use of uv_check and uv_prepare to
// run the callbacks, however since we only run uv loop on requests, the
@@ -11,7 +11,7 @@ type AnyFn = (...args: any[]) => any;
// which would delay the callbacks for arbitrary long time. So we should
// initiatively activate the uv loop once setImmediate and process.nextTick is
// called.
const wrapWithActivateUvLoop = function <T extends AnyFn>(func: T): T {
const wrapWithActivateUvLoop = function <T extends AnyFn> (func: T): T {
return wrap(func, function (func) {
return function (this: any, ...args: any[]) {
process.activateUvLoop();
@@ -26,7 +26,7 @@ const wrapWithActivateUvLoop = function <T extends AnyFn>(func: T): T {
*
* Refs: https://github.com/Microsoft/TypeScript/issues/1863
*/
function wrap<T extends AnyFn>(func: T, wrapper: (fn: AnyFn) => T) {
function wrap <T extends AnyFn> (func: T, wrapper: (fn: AnyFn) => T) {
const wrapped = wrapper(func);
if ((func as any)[util.promisify.custom]) {
(wrapped as any)[util.promisify.custom] = wrapper((func as any)[util.promisify.custom]);
@@ -55,7 +55,8 @@ timers.setInterval = wrapWithActivateUvLoop(timers.setInterval);
// only in the process that runs node event loop alongside chromium
// event loop. We skip renderer with nodeIntegration here because node globals
// are deleted in these processes, see renderer/init.js for reference.
if (process.type === 'browser' || process.type === 'utility') {
if (process.type === 'browser' ||
process.type === 'utility') {
global.setTimeout = timers.setTimeout;
global.setInterval = timers.setInterval;
}
@@ -68,7 +69,7 @@ if (process.platform === 'win32') {
Object.defineProperty(process, 'stdin', {
configurable: false,
enumerable: true,
get() {
get () {
return stdin;
}
});
@@ -107,11 +108,7 @@ const originalResolveFilename = Module._resolveFilename;
// renderer process regardless of the names, they're superficial for TypeScript
// only.
const electronModuleNames = new Set([
'electron',
'electron/main',
'electron/renderer',
'electron/common',
'electron/utility'
'electron', 'electron/main', 'electron/renderer', 'electron/common', 'electron/utility'
]);
Module._resolveFilename = function (request, parent, isMain, options) {
if (electronModuleNames.has(request)) {

View File

@@ -27,5 +27,5 @@ export const enum IPC_MESSAGES {
INSPECTOR_SELECT_FILE = 'INSPECTOR_SELECT_FILE',
IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER = 'IMPORT_SHARED_TEXTURE_TRANSFER_MAIN_TO_RENDERER',
IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN = 'IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN'
IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN = 'IMPORT_SHARED_TEXTURE_RELEASE_RENDERER_TO_MAIN',
}

View File

@@ -14,18 +14,15 @@ class Timeout {
_id: ReturnType<typeof globalThis.setTimeout>;
_clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void;
constructor(
id: ReturnType<typeof globalThis.setTimeout>,
clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void
) {
constructor (id: ReturnType<typeof globalThis.setTimeout>, clearFn: (id: ReturnType<typeof globalThis.setTimeout>) => void) {
this._id = id;
this._clearFn = clearFn;
}
unref() {}
ref() {}
unref () {}
ref () {}
close() {
close () {
this._clearFn.call(globalThis, this._id);
}
}
@@ -67,7 +64,7 @@ export const active = function (item: EnrollableItem) {
const msecs = item._idleTimeout;
if (msecs !== undefined && msecs >= 0) {
item._idleTimeoutId = setTimeout(function onTimeout() {
item._idleTimeoutId = setTimeout(function onTimeout () {
if (item._onTimeout) item._onTimeout();
}, msecs);
}
@@ -83,23 +80,23 @@ const clearImmediateFallback = function (id: number) {
delete immediateIds[id];
};
export const setImmediate =
typeof globalThis.setImmediate === 'function'
? globalThis.setImmediate
: function (fn: (...args: unknown[]) => void, ...rest: unknown[]) {
const id = nextImmediateId++;
export const setImmediate = typeof globalThis.setImmediate === 'function'
? globalThis.setImmediate
: function (fn: (...args: unknown[]) => void, ...rest: unknown[]) {
const id = nextImmediateId++;
immediateIds[id] = true;
immediateIds[id] = true;
Promise.resolve().then(function onMicrotask() {
if (immediateIds[id]) {
fn(...rest);
clearImmediateFallback(id);
}
});
Promise.resolve().then(function onMicrotask () {
if (immediateIds[id]) {
fn(...rest);
clearImmediateFallback(id);
}
});
return id;
};
return id;
};
export const clearImmediate =
typeof globalThis.clearImmediate === 'function' ? globalThis.clearImmediate : clearImmediateFallback;
export const clearImmediate = typeof globalThis.clearImmediate === 'function'
? globalThis.clearImmediate
: clearImmediateFallback;

View File

@@ -58,7 +58,13 @@ export const syncMethods = new Set([
...navigationHistorySyncMethods
]);
export const properties = new Set(['audioMuted', 'userAgent', 'zoomLevel', 'zoomFactor', 'frameRate']);
export const properties = new Set([
'audioMuted',
'userAgent',
'zoomLevel',
'zoomFactor',
'frameRate'
]);
export const asyncMethods = new Set([
'capturePage',

View File

@@ -11,4 +11,8 @@ const _global = typeof globalThis !== 'undefined' ? globalThis.global : (self ||
const process = _global.process;
const Buffer = _global.Buffer;
export { _global, process, Buffer };
export {
_global,
process,
Buffer
};

View File

@@ -5,7 +5,6 @@ declare const isolatedApi: WebViewImplHooks;
if (isolatedApi.guestViewInternal) {
// Must setup the WebView element in main world.
const { setupWebView } =
require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
setupWebView(isolatedApi);
}

View File

@@ -12,7 +12,9 @@ const Module = require('module') as NodeJS.ModuleInternal;
const Promise: PromiseConstructor = global.Promise;
const envNoAsar = process.env.ELECTRON_NO_ASAR && process.type !== 'browser' && process.type !== 'renderer';
const envNoAsar = process.env.ELECTRON_NO_ASAR &&
process.type !== 'browser' &&
process.type !== 'renderer';
const isAsarDisabled = () => process.noAsar || envNoAsar;
const internalBinding = process.internalBinding!;
@@ -46,15 +48,20 @@ process._getOrCreateArchive = getOrCreateArchive;
const asarRe = /\.asar/i;
const { getValidatedPath, getOptions, getDirent } = __non_webpack_require__(
'internal/fs/utils'
) as typeof import('@node/lib/internal/fs/utils');
const {
getValidatedPath,
getOptions,
getDirent
} = __non_webpack_require__('internal/fs/utils') as typeof import('@node/lib/internal/fs/utils');
const { assignFunctionName } = __non_webpack_require__('internal/util') as typeof import('@node/lib/internal/util');
const {
assignFunctionName
} = __non_webpack_require__('internal/util') as typeof import('@node/lib/internal/util');
const { validateBoolean, validateFunction } = __non_webpack_require__(
'internal/validators'
) as typeof import('@node/lib/internal/validators');
const {
validateBoolean,
validateFunction
} = __non_webpack_require__('internal/validators') as typeof import('@node/lib/internal/validators');
// In the renderer node internals use the node global URL but we do not set that to be
// the global URL instance. We need to do instanceof checks against the internal URL impl
@@ -87,7 +94,7 @@ const gid = process.getgid?.() ?? 0;
const fakeTime = new Date();
function getDirents(p: string, { 0: names, 1: types }: any[][]): Dirent[] {
function getDirents (p: string, { 0: names, 1: types }: any[][]): Dirent[] {
for (let i = 0; i < names.length; i++) {
let type = types[i];
const info = splitPath(path.join(p, names[i]));
@@ -107,7 +114,7 @@ function getDirents(p: string, { 0: names, 1: types }: any[][]): Dirent[] {
enum AsarFileType {
kFile = (constants as any).UV_DIRENT_FILE,
kDirectory = (constants as any).UV_DIRENT_DIR,
kLink = (constants as any).UV_DIRENT_LINK
kLink = (constants as any).UV_DIRENT_LINK,
}
const fileTypeToMode = new Map<AsarFileType, number>([
@@ -119,8 +126,7 @@ const fileTypeToMode = new Map<AsarFileType, number>([
const asarStatsToFsStats = function (stats: NodeJS.AsarFileStat) {
const { Stats } = require('fs');
const mode =
constants.S_IROTH | constants.S_IRGRP | constants.S_IRUSR | constants.S_IWUSR | fileTypeToMode.get(stats.type)!;
const mode = constants.S_IROTH | constants.S_IRGRP | constants.S_IRUSR | constants.S_IWUSR | fileTypeToMode.get(stats.type)!;
return new Stats(
1, // dev
@@ -147,9 +153,9 @@ const enum AsarError {
INVALID_ARCHIVE = 'INVALID_ARCHIVE'
}
type AsarErrorObject = Error & { code?: string; errno?: number };
type AsarErrorObject = Error & { code?: string, errno?: number };
const createError = (errorType: AsarError, { asarPath, filePath }: { asarPath?: string; filePath?: string } = {}) => {
const createError = (errorType: AsarError, { asarPath, filePath }: { asarPath?: string, filePath?: string } = {}) => {
let error: AsarErrorObject;
switch (errorType) {
case AsarError.NOT_FOUND:
@@ -176,12 +182,7 @@ const createError = (errorType: AsarError, { asarPath, filePath }: { asarPath?:
return error;
};
const overrideAPISync = function (
module: Record<string, any>,
name: string,
pathArgumentIndex?: number | null,
fromAsync: boolean = false
) {
const overrideAPISync = function (module: Record<string, any>, name: string, pathArgumentIndex?: number | null, fromAsync: boolean = false) {
if (pathArgumentIndex == null) pathArgumentIndex = 0;
const old = module[name];
const func = function (this: any, ...args: any[]) {
@@ -250,7 +251,7 @@ const overrideAPI = function (module: Record<string, any>, name: string, pathArg
};
let crypto: typeof Crypto;
function validateBufferIntegrity(buffer: Buffer, integrity: NodeJS.AsarFileInfo['integrity']) {
function validateBufferIntegrity (buffer: Buffer, integrity: NodeJS.AsarFileInfo['integrity']) {
if (!integrity) return;
// Delay load crypto to improve app boot performance
@@ -316,7 +317,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
if (!archive) {
if (shouldThrowStatError(options)) {
throw createError(AsarError.INVALID_ARCHIVE, { asarPath });
}
};
return null;
}
@@ -324,7 +325,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
if (!stats) {
if (shouldThrowStatError(options)) {
throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
}
};
return null;
}
@@ -452,7 +453,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
fs.promises.realpath = util.promisify(fs.realpath.native);
const { exists: nativeExists } = fs;
fs.exists = function exists(pathArgument: string, callback: any) {
fs.exists = function exists (pathArgument: string, callback: any) {
let pathInfo: ReturnType<typeof splitPath>;
try {
pathInfo = splitPath(pathArgument);
@@ -470,11 +471,11 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
return;
}
const pathExists = archive.stat(filePath) !== false;
const pathExists = (archive.stat(filePath) !== false);
nextTick(callback, [pathExists]);
};
fs.exists[util.promisify.custom] = function exists(pathArgument: string) {
fs.exists[util.promisify.custom] = function exists (pathArgument: string) {
const pathInfo = splitPath(pathArgument);
if (!pathInfo.isAsar) return nativeExists[util.promisify.custom](pathArgument);
const { asarPath, filePath } = pathInfo;
@@ -595,7 +596,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
}
};
function fsReadFileAsar(pathArgument: string, options: any, callback: any) {
function fsReadFileAsar (pathArgument: string, options: any, callback: any) {
const pathInfo = splitPath(pathArgument);
if (pathInfo.isAsar) {
const { asarPath, filePath } = pathInfo;
@@ -673,7 +674,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
return p(pathArgument, options);
};
function readFileFromArchiveSync(
function readFileFromArchiveSync (
pathInfo: { asarPath: string; filePath: string },
options: any
): ReturnType<typeof readFileSync> {
@@ -685,7 +686,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
const info = archive.getFileInfo(filePath);
if (!info) throw createError(AsarError.NOT_FOUND, { asarPath, filePath });
if (info.size === 0) return options ? '' : Buffer.alloc(0);
if (info.size === 0) return (options) ? '' : Buffer.alloc(0);
if (info.unpacked) {
const realPath = archive.copyFileOut(filePath);
return fs.readFileSync(realPath, options);
@@ -709,7 +710,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
logASARAccess(asarPath, filePath, info.offset);
fs.readSync(fd, buffer, 0, info.size, info.offset);
validateBufferIntegrity(buffer, info.integrity);
return encoding ? buffer.toString(encoding) : buffer;
return (encoding) ? buffer.toString(encoding) : buffer;
}
const { readFileSync } = fs;
@@ -720,16 +721,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
return readFileFromArchiveSync(pathInfo, options);
};
type ReaddirOptions =
| { encoding: BufferEncoding | null; withFileTypes?: false; recursive?: false }
| undefined
| null;
type ReaddirOptions = { encoding: BufferEncoding | null; withFileTypes?: false, recursive?: false } | undefined | null;
type ReaddirCallback = (err: NodeJS.ErrnoException | null, files?: string[]) => void;
const processReaddirResult = (args: any) =>
args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args);
const processReaddirResult = (args: any) => (args.context.withFileTypes ? handleDirents(args) : handleFilePaths(args));
function handleDirents({ result, currentPath, context }: { result: any[]; currentPath: string; context: any }) {
function handleDirents ({ result, currentPath, context }: { result: any[], currentPath: string, context: any }) {
const length = result[0].length;
for (let i = 0; i < length; i++) {
const resultPath = path.join(currentPath, result[0][i]);
@@ -754,7 +751,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
}
}
function handleFilePaths({ result, currentPath, context }: { result: string[]; currentPath: string; context: any }) {
function handleFilePaths ({ result, currentPath, context }: { result: string[], currentPath: string, context: any }) {
for (let i = 0; i < result.length; i++) {
const resultPath = path.join(currentPath, result[i]);
const relativeResultPath = path.relative(context.basePath, resultPath);
@@ -767,7 +764,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
}
}
function readdirRecursive(basePath: string, options: ReaddirOptions, callback: ReaddirCallback) {
function readdirRecursive (basePath: string, options: ReaddirOptions, callback: ReaddirCallback) {
const context = {
withFileTypes: Boolean(options!.withFileTypes),
encoding: options!.encoding,
@@ -778,7 +775,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
let i = 0;
function read(pathArg: string) {
function read (pathArg: string) {
const req = new binding.FSReqCallback();
req.oncomplete = (err: any, result: string) => {
if (err) {
@@ -827,8 +824,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
// native call to readdir withFileTypes i.e. an array of arrays.
if (context.withFileTypes) {
readdirResult = [
[...readdirResult],
readdirResult.map((p: string) => {
[...readdirResult], readdirResult.map((p: string) => {
return internalBinding('fs').internalModuleStat(path.join(pathArg, p));
})
];
@@ -846,7 +842,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
callback(null, context.readdirResults);
}
} else {
binding.readdir(pathArg, context.encoding, context.withFileTypes, req);
binding.readdir(
pathArg,
context.encoding,
context.withFileTypes,
req
);
}
}
@@ -1022,11 +1023,11 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
const stats = archive.stat(filePath);
if (!stats) return -34;
return stats.type === AsarFileType.kDirectory ? 1 : 0;
return (stats.type === AsarFileType.kDirectory) ? 1 : 0;
};
const { kUsePromises } = binding;
async function readdirRecursivePromises(originalPath: string, options: ReaddirOptions) {
async function readdirRecursivePromises (originalPath: string, options: ReaddirOptions) {
const result: any[] = [];
const pathInfo = splitPath(originalPath);
@@ -1045,8 +1046,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
initialItem = files;
if (withFileTypes) {
initialItem = [
[...initialItem],
initialItem.map((p: string) => {
[...initialItem], initialItem.map((p: string) => {
return internalBinding('fs').internalModuleStat(path.join(originalPath, p));
})
];
@@ -1079,13 +1079,17 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
if (!files) continue;
readdirResult = [
[...files],
files.map((p: string) => {
[...files], files.map((p: string) => {
return internalBinding('fs').internalModuleStat(path.join(direntPath, p));
})
];
} else {
readdirResult = await binding.readdir(direntPath, options!.encoding, true, kUsePromises);
readdirResult = await binding.readdir(
direntPath,
options!.encoding,
true,
kUsePromises
);
}
queue.push([direntPath, readdirResult]);
}
@@ -1110,7 +1114,12 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
if (!files) return result;
item = files;
} else {
item = await binding.readdir(path.toNamespacedPath(direntPath), options!.encoding, false, kUsePromises);
item = await binding.readdir(
path.toNamespacedPath(direntPath),
options!.encoding,
false,
kUsePromises
);
}
queue.push([direntPath, item]);
}
@@ -1121,7 +1130,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
return result;
}
function readdirSyncRecursive(basePath: string, options: ReaddirOptions) {
function readdirSyncRecursive (basePath: string, options: ReaddirOptions) {
const context = {
withFileTypes: Boolean(options!.withFileTypes),
encoding: options!.encoding,
@@ -1130,7 +1139,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
pathsQueue: [basePath]
};
function read(pathArg: string) {
function read (pathArg: string) {
let readdirResult;
const pathInfo = splitPath(pathArg);
@@ -1145,14 +1154,17 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
// native call to readdir withFileTypes i.e. an array of arrays.
if (context.withFileTypes) {
readdirResult = [
[...readdirResult],
readdirResult.map((p: string) => {
[...readdirResult], readdirResult.map((p: string) => {
return internalBinding('fs').internalModuleStat(path.join(pathArg, p));
})
];
}
} else {
readdirResult = binding.readdir(path.toNamespacedPath(pathArg), context.encoding, context.withFileTypes);
readdirResult = binding.readdir(
path.toNamespacedPath(pathArg),
context.encoding,
context.withFileTypes
);
}
if (readdirResult === undefined) {
@@ -1203,7 +1215,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
};
}
function invokeWithNoAsar(func: Function) {
function invokeWithNoAsar (func: Function) {
return function (this: any) {
const processNoAsarOriginalValue = process.noAsar;
process.noAsar = true;
@@ -1235,10 +1247,7 @@ export const wrapFsWithAsar = (fs: Record<string, any>) => {
// command as a single path to an archive.
const { exec, execSync } = childProcess;
childProcess.exec = invokeWithNoAsar(exec);
childProcess.exec[util.promisify.custom] = assignFunctionName(
'exec',
invokeWithNoAsar(exec[util.promisify.custom])
);
childProcess.exec[util.promisify.custom] = assignFunctionName('exec', invokeWithNoAsar(exec[util.promisify.custom]));
childProcess.execSync = invokeWithNoAsar(execSync);
overrideAPI(childProcess, 'execFile');

View File

@@ -25,11 +25,8 @@ cp.fork = (modulePath, args?, options?: cp.ForkOptions) => {
args = [];
}
// Fallback to original fork to report arg type errors.
if (
typeof modulePath !== 'string' ||
!Array.isArray(args) ||
(typeof options !== 'object' && typeof options !== 'undefined')
) {
if (typeof modulePath !== 'string' || !Array.isArray(args) ||
(typeof options !== 'object' && typeof options !== 'undefined')) {
return originalFork(modulePath, args, options);
}
// When forking a child script, we setup a special environment to make

View File

@@ -1,23 +1,22 @@
import '@electron/internal/sandboxed_renderer/pre-init';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
import type * as ipcRendererUtilsModule from '@electron/internal/renderer/ipc-renderer-internal-utils';
import {
createPreloadProcessObject,
executeSandboxedPreloadScripts
} from '@electron/internal/sandboxed_renderer/preload';
import { createPreloadProcessObject, executeSandboxedPreloadScripts } from '@electron/internal/sandboxed_renderer/preload';
import * as events from 'events';
declare const binding: {
get: (name: string) => any;
process: NodeJS.Process;
createPreloadScript: (src: string) => Function;
createPreloadScript: (src: string) => Function
};
const ipcRendererUtils =
require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
const { preloadScripts, process: processProps } = ipcRendererUtils.invokeSync<{
const {
preloadScripts,
process: processProps
} = ipcRendererUtils.invokeSync<{
preloadScripts: ElectronInternal.PreloadScript[];
process: NodeJS.Process;
}>(IPC_MESSAGES.BROWSER_SANDBOX_LOAD);
@@ -45,18 +44,15 @@ Object.assign(process, processProps);
require('@electron/internal/renderer/ipc-native-setup');
executeSandboxedPreloadScripts(
{
loadedModules,
loadableModules,
process: preloadProcess,
createPreloadScript: binding.createPreloadScript,
exposeGlobals: {
Buffer,
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
// `__webpack_require__.g,` which causes script error
global: globalThis
}
},
preloadScripts
);
executeSandboxedPreloadScripts({
loadedModules,
loadableModules,
process: preloadProcess,
createPreloadScript: binding.createPreloadScript,
exposeGlobals: {
Buffer,
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
// `__webpack_require__.g,` which causes script error
global: globalThis
}
}, preloadScripts);

View File

@@ -1,15 +1,15 @@
const binding = process._linkedBinding('electron_renderer_crash_reporter');
export default {
addExtraParameter(key: string, value: string) {
addExtraParameter (key: string, value: string) {
binding.addExtraParameter(key, value);
},
removeExtraParameter(key: string) {
removeExtraParameter (key: string) {
binding.removeExtraParameter(key);
},
getParameters() {
getParameters () {
return binding.getParameters();
}
};

View File

@@ -6,19 +6,19 @@ const ipc = getIPCRenderer();
const internal = false;
class IpcRenderer extends EventEmitter implements Electron.IpcRenderer {
send(channel: string, ...args: any[]) {
send (channel: string, ...args: any[]) {
return ipc.send(internal, channel, args);
}
sendSync(channel: string, ...args: any[]) {
sendSync (channel: string, ...args: any[]) {
return ipc.sendSync(internal, channel, args);
}
sendToHost(channel: string, ...args: any[]) {
sendToHost (channel: string, ...args: any[]) {
return ipc.sendToHost(channel, args);
}
async invoke(channel: string, ...args: any[]) {
async invoke (channel: string, ...args: any[]) {
const { error, result } = await ipc.invoke(internal, channel, args);
if (error) {
throw new Error(`Error invoking remote method '${channel}': ${error}`);
@@ -26,7 +26,7 @@ class IpcRenderer extends EventEmitter implements Electron.IpcRenderer {
return result;
}
postMessage(channel: string, message: any, transferables: any) {
postMessage (channel: string, message: any, transferables: any) {
return ipc.postMessage(channel, message, transferables);
}
}

View File

@@ -14,12 +14,17 @@ Object.defineProperty(WebFramePrototype, 'routingId', {
configurable: true,
get: function (this: Electron.WebFrame) {
routingIdDeprecated();
return ipcRendererUtils.invokeSync<number>(IPC_MESSAGES.BROWSER_GET_FRAME_ROUTING_ID_SYNC, this.frameToken);
return ipcRendererUtils.invokeSync<number>(
IPC_MESSAGES.BROWSER_GET_FRAME_ROUTING_ID_SYNC,
this.frameToken
);
}
});
const findFrameByRoutingIdDeprecated = deprecate.warnOnce('webFrame.findFrameByRoutingId', 'webFrame.findFrameByToken');
WebFramePrototype.findFrameByRoutingId = function (routingId: number): Electron.WebFrame | null {
WebFramePrototype.findFrameByRoutingId = function (
routingId: number
): Electron.WebFrame | null {
findFrameByRoutingIdDeprecated();
const frameToken = ipcRendererUtils.invokeSync<string | undefined>(
IPC_MESSAGES.BROWSER_GET_FRAME_TOKEN_SYNC,

View File

@@ -43,7 +43,6 @@ webFrameInit();
// Warn about security issues
if (process.isMainFrame) {
const { securityWarnings } =
require('@electron/internal/renderer/security-warnings') as typeof securityWarningsModule;
const { securityWarnings } = require('@electron/internal/renderer/security-warnings') as typeof securityWarningsModule;
securityWarnings(nodeIntegration);
}

View File

@@ -12,9 +12,7 @@ const Module = require('module') as NodeJS.ModuleInternal;
const originalModuleLoad = Module._load;
Module._load = function (request: string) {
if (request === 'vm') {
console.warn(
"The vm module of Node.js is unsupported in Electron's renderer process due to incompatibilities with the Blink rendering engine. Crashes are likely and avoiding the module is highly recommended. This module may be removed in a future release."
);
console.warn('The vm module of Node.js is unsupported in Electron\'s renderer process due to incompatibilities with the Blink rendering engine. Crashes are likely and avoiding the module is highly recommended. This module may be removed in a future release.');
}
return originalModuleLoad.apply(this, arguments as any);
};
@@ -35,9 +33,9 @@ Module._load = function (request: string) {
// variables to this wrapper please ensure to update that plugin as well.
Module.wrapper = [
'(function (exports, require, module, __filename, __dirname, process, global, Buffer) { ' +
// By running the code in a new closure, it would be possible for the module
// code to override "process" and "Buffer" with local variables.
'return function (exports, require, module, __filename, __dirname) { ',
// By running the code in a new closure, it would be possible for the module
// code to override "process" and "Buffer" with local variables.
'return function (exports, require, module, __filename, __dirname) { ',
'\n}.call(this, exports, require, module, __filename, __dirname); });'
];
@@ -48,10 +46,8 @@ process.argv.splice(1, 1);
// Import common settings.
require('@electron/internal/common/init');
const { ipcRendererInternal } =
require('@electron/internal/renderer/ipc-renderer-internal') as typeof ipcRendererInternalModule;
const ipcRendererUtils =
require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
const { ipcRendererInternal } = require('@electron/internal/renderer/ipc-renderer-internal') as typeof ipcRendererInternalModule;
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
process.getProcessMemoryInfo = () => {
return ipcRendererInternal.invoke<Electron.ProcessMemoryInfo>(IPC_MESSAGES.BROWSER_GET_PROCESS_MEMORY_INFO);
@@ -69,9 +65,7 @@ require('@electron/internal/renderer/common-init');
if (nodeIntegration) {
// Export node bindings to global.
const { makeRequireFunction } = __non_webpack_require__(
'internal/modules/helpers'
) as typeof import('@node/lib/internal/modules/helpers');
const { makeRequireFunction } = __non_webpack_require__('internal/modules/helpers') as typeof import('@node/lib/internal/modules/helpers');
global.module = new Module('electron/js2c/renderer_init');
global.require = makeRequireFunction(global.module) as NodeRequire;
@@ -142,8 +136,8 @@ const { appCodeLoaded } = process;
delete process.appCodeLoaded;
const { preloadPaths } = ipcRendererUtils.invokeSync<{ preloadPaths: string[] }>(IPC_MESSAGES.BROWSER_NONSANDBOX_LOAD);
const cjsPreloads = preloadPaths.filter((p) => path.extname(p) !== '.mjs');
const esmPreloads = preloadPaths.filter((p) => path.extname(p) === '.mjs');
const cjsPreloads = preloadPaths.filter(p => path.extname(p) !== '.mjs');
const esmPreloads = preloadPaths.filter(p => path.extname(p) === '.mjs');
if (cjsPreloads.length) {
// Load the preload scripts.
for (const preloadScript of cjsPreloads) {
@@ -158,21 +152,17 @@ if (cjsPreloads.length) {
}
}
if (esmPreloads.length) {
const { runEntryPointWithESMLoader } = __non_webpack_require__(
'internal/modules/run_main'
) as typeof import('@node/lib/internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
runEntryPointWithESMLoader(async (cascadedLoader: any) => {
// Load the preload scripts.
for (const preloadScript of esmPreloads) {
await cascadedLoader
.import(pathToFileURL(preloadScript).toString(), undefined, Object.create(null))
.catch((err: Error) => {
console.error(`Unable to load preload script: ${preloadScript}`);
console.error(err);
await cascadedLoader.import(pathToFileURL(preloadScript).toString(), undefined, Object.create(null)).catch((err: Error) => {
console.error(`Unable to load preload script: ${preloadScript}`);
console.error(err);
ipcRendererInternal.send(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, preloadScript, err);
});
ipcRendererInternal.send(IPC_MESSAGES.BROWSER_PRELOAD_ERROR, preloadScript, err);
});
}
}).finally(() => appCodeLoaded!());
} else {

View File

@@ -8,14 +8,13 @@ import { webFrame } from 'electron/renderer';
const { contextIsolationEnabled } = internalContextBridge;
/* Corrects for some Inspector adaptations needed in Electron.
* 1) Use menu API to show context menu.
*/
* 1) Use menu API to show context menu.
*/
window.onload = function () {
if (contextIsolationEnabled) {
internalContextBridge.tryOverrideGlobalValueFromIsolatedWorld(
['InspectorFrontendHost', 'showContextMenuAtPoint'],
createMenu
);
internalContextBridge.tryOverrideGlobalValueFromIsolatedWorld([
'InspectorFrontendHost', 'showContextMenuAtPoint'
], createMenu);
} else {
window.InspectorFrontendHost!.showContextMenuAtPoint = createMenu;
}
@@ -27,19 +26,16 @@ window.confirm = function (message?: string, title?: string) {
};
const useEditMenuItems = function (x: number, y: number, items: ContextMenuItem[]) {
return (
items.length === 0 &&
document.elementsFromPoint(x, y).some((element) => {
return (
element.nodeName === 'INPUT' || element.nodeName === 'TEXTAREA' || (element as HTMLElement).isContentEditable
);
})
);
return items.length === 0 && document.elementsFromPoint(x, y).some(element => {
return element.nodeName === 'INPUT' ||
element.nodeName === 'TEXTAREA' ||
(element as HTMLElement).isContentEditable;
});
};
const createMenu = function (x: number, y: number, items: ContextMenuItem[]) {
const isEditMenu = useEditMenuItems(x, y, items);
ipcRendererInternal.invoke<number>(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, items, isEditMenu).then((id) => {
ipcRendererInternal.invoke<number>(IPC_MESSAGES.INSPECTOR_CONTEXT_MENU, items, isEditMenu).then(id => {
if (typeof id === 'number') {
webFrame.executeJavaScript(`window.DevToolsAPI.contextMenuItemSelected(${JSON.stringify(id)})`);
}

View File

@@ -7,7 +7,7 @@ const v8Util = process._linkedBinding('electron_common_v8_util');
// ElectronApiServiceImpl will look for the "ipcNative" hidden object when
// invoking the 'onMessage' callback.
v8Util.setHiddenValue(globalThis, 'ipcNative', {
onMessage(internal: boolean, channel: string, ports: MessagePort[], args: any[]) {
onMessage (internal: boolean, channel: string, ports: MessagePort[], args: any[]) {
const sender = internal ? ipcRendererInternal : ipcRenderer;
sender.emit(channel, { sender, ports }, ...args);
}

View File

@@ -3,7 +3,7 @@ let ipc: NodeJS.IpcRendererImpl | undefined;
/**
* Get IPCRenderer implementation for the current process.
*/
export function getIPCRenderer() {
export function getIPCRenderer () {
if (ipc) return ipc;
const ipcBinding = process._linkedBinding('electron_renderer_ipc');
switch (process.type) {
@@ -14,4 +14,4 @@ export function getIPCRenderer() {
default:
throw new Error(`Cannot create IPCRenderer for '${process.type}' process`);
}
}
};

View File

@@ -1,8 +1,8 @@
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal';
type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any;
type IPCHandler = (event: Electron.IpcRendererEvent, ...args: any[]) => any
export const handle = function <T extends IPCHandler>(channel: string, handler: T) {
export const handle = function <T extends IPCHandler> (channel: string, handler: T) {
ipcRendererInternal.on(channel, async (event, requestId, ...args) => {
const replyChannel = `${channel}_RESPONSE_${requestId}`;
try {
@@ -13,7 +13,7 @@ export const handle = function <T extends IPCHandler>(channel: string, handler:
});
};
export function invokeSync<T>(command: string, ...args: any[]): T {
export function invokeSync<T> (command: string, ...args: any[]): T {
const [error, result] = ipcRendererInternal.sendSync(command, ...args);
if (error) {

View File

@@ -6,21 +6,21 @@ const ipc = getIPCRenderer();
const internal = true;
class IpcRendererInternal extends EventEmitter implements ElectronInternal.IpcRendererInternal {
send(channel: string, ...args: any[]) {
send (channel: string, ...args: any[]) {
return ipc.send(internal, channel, args);
}
sendSync(channel: string, ...args: any[]) {
sendSync (channel: string, ...args: any[]) {
return ipc.sendSync(internal, channel, args);
}
async invoke<T>(channel: string, ...args: any[]) {
async invoke<T> (channel: string, ...args: any[]) {
const { error, result } = await ipc.invoke<T>(internal, channel, args);
if (error) {
throw new Error(`Error invoking remote method '${channel}': ${error}`);
}
return result;
}
};
}
export const ipcRendererInternal = new IpcRendererInternal();

View File

@@ -22,7 +22,8 @@ const shouldLogSecurityWarnings = function (): boolean {
switch (platform) {
case 'darwin':
shouldLog = execPath.endsWith('MacOS/Electron') || execPath.includes('Electron.app/Contents/Frameworks/');
shouldLog = execPath.endsWith('MacOS/Electron') ||
execPath.includes('Electron.app/Contents/Frameworks/');
break;
case 'freebsd':
case 'linux':
@@ -35,11 +36,13 @@ const shouldLogSecurityWarnings = function (): boolean {
shouldLog = false;
}
if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) || (window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
if ((env && env.ELECTRON_DISABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_DISABLE_SECURITY_WARNINGS)) {
shouldLog = false;
}
if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) || (window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
if ((env && env.ELECTRON_ENABLE_SECURITY_WARNINGS) ||
(window && window.ELECTRON_ENABLE_SECURITY_WARNINGS)) {
shouldLog = true;
}
@@ -94,8 +97,10 @@ const warnAboutInsecureResources = function () {
return;
}
const isLocal = (url: URL): boolean => ['localhost', '127.0.0.1', '[::1]', ''].includes(url.hostname);
const isInsecure = (url: URL): boolean => ['http:', 'ftp:'].includes(url.protocol) && !isLocal(url);
const isLocal = (url: URL): boolean =>
['localhost', '127.0.0.1', '[::1]', ''].includes(url.hostname);
const isInsecure = (url: URL): boolean =>
['http:', 'ftp:'].includes(url.protocol) && !isLocal(url);
const resources = window.performance
.getEntriesByType('resource')
@@ -112,7 +117,8 @@ const warnAboutInsecureResources = function () {
Consider loading the following resources over HTTPS or FTPS. \n${resources}
\n${moreInformation}`;
console.warn('%cElectron Security Warning (Insecure Resources)', 'font-weight: bold;', warning);
console.warn('%cElectron Security Warning (Insecure Resources)',
'font-weight: bold;', warning);
};
/**
@@ -129,11 +135,8 @@ const warnAboutNodeWithRemoteContent = function (nodeIntegration: boolean) {
and attempted to load remote content from '${window.location}'. This
exposes users of this app to severe security risks.\n${moreInformation}`;
console.warn(
'%cElectron Security Warning (Node.js Integration with Remote Content)',
'font-weight: bold;',
warning
);
console.warn('%cElectron Security Warning (Node.js Integration with Remote Content)',
'font-weight: bold;', warning);
}
};
@@ -154,7 +157,8 @@ const warnAboutDisabledWebSecurity = function (webPreferences?: Electron.WebPref
const warning = `This renderer process has "webSecurity" disabled. This
exposes users of this app to severe security risks.\n${moreInformation}`;
console.warn('%cElectron Security Warning (Disabled webSecurity)', 'font-weight: bold;', warning);
console.warn('%cElectron Security Warning (Disabled webSecurity)',
'font-weight: bold;', warning);
};
/**
@@ -170,7 +174,8 @@ const warnAboutInsecureCSP = function () {
Policy set or a policy with "unsafe-eval" enabled. This exposes users of
this app to unnecessary security risks.\n${moreInformation}`;
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)', 'font-weight: bold;', warning);
console.warn('%cElectron Security Warning (Insecure Content-Security-Policy)',
'font-weight: bold;', warning);
};
/**
@@ -185,7 +190,8 @@ const warnAboutInsecureContentAllowed = function (webPreferences?: Electron.WebP
enabled. This exposes users of this app to severe security risks.\n
${moreInformation}`;
console.warn('%cElectron Security Warning (allowRunningInsecureContent)', 'font-weight: bold;', warning);
console.warn('%cElectron Security Warning (allowRunningInsecureContent)',
'font-weight: bold;', warning);
};
/**
@@ -194,7 +200,7 @@ const warnAboutInsecureContentAllowed = function (webPreferences?: Electron.WebP
* Logs a warning message about experimental features.
*/
const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPreferences) {
if (!webPreferences || !webPreferences.experimentalFeatures) {
if (!webPreferences || (!webPreferences.experimentalFeatures)) {
return;
}
@@ -202,7 +208,8 @@ const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPre
This exposes users of this app to some security risk. If you do not need
this feature, you should disable it.\n${moreInformation}`;
console.warn('%cElectron Security Warning (experimentalFeatures)', 'font-weight: bold;', warning);
console.warn('%cElectron Security Warning (experimentalFeatures)',
'font-weight: bold;', warning);
};
/**
@@ -211,11 +218,9 @@ const warnAboutExperimentalFeatures = function (webPreferences?: Electron.WebPre
* Logs a warning message about enableBlinkFeatures
*/
const warnAboutEnableBlinkFeatures = function (webPreferences?: Electron.WebPreferences) {
if (
!webPreferences ||
if (!webPreferences ||
!Object.hasOwn(webPreferences, 'enableBlinkFeatures') ||
(webPreferences.enableBlinkFeatures != null && webPreferences.enableBlinkFeatures.length === 0)
) {
(webPreferences.enableBlinkFeatures != null && webPreferences.enableBlinkFeatures.length === 0)) {
return;
}
@@ -223,7 +228,8 @@ const warnAboutEnableBlinkFeatures = function (webPreferences?: Electron.WebPref
enabled. This exposes users of this app to some security risk. If you do not
need this feature, you should disable it.\n${moreInformation}`;
console.warn('%cElectron Security Warning (enableBlinkFeatures)', 'font-weight: bold;', warning);
console.warn('%cElectron Security Warning (enableBlinkFeatures)',
'font-weight: bold;', warning);
};
/**
@@ -244,7 +250,8 @@ const warnAboutAllowedPopups = function () {
BrowserWindows. If you do not need this feature, you should disable it.\n
${moreInformation}`;
console.warn('%cElectron Security Warning (allowpopups)', 'font-weight: bold;', warning);
console.warn('%cElectron Security Warning (allowpopups)',
'font-weight: bold;', warning);
}
};
@@ -254,7 +261,9 @@ const warnAboutAllowedPopups = function () {
// #13 Disable or limit creation of new windows
// #14 Do not use `openExternal` with untrusted content
const logSecurityWarnings = function (webPreferences: Electron.WebPreferences | undefined, nodeIntegration: boolean) {
const logSecurityWarnings = function (
webPreferences: Electron.WebPreferences | undefined, nodeIntegration: boolean
) {
warnAboutNodeWithRemoteContent(nodeIntegration);
warnAboutDisabledWebSecurity(webPreferences);
warnAboutInsecureResources();
@@ -273,7 +282,7 @@ const getWebPreferences = async function () {
}
};
export function securityWarnings(nodeIntegration: boolean) {
export function securityWarnings (nodeIntegration: boolean) {
const loadHandler = async function () {
if (shouldLogSecurityWarnings()) {
const webPreferences = await getWebPreferences();

View File

@@ -5,18 +5,18 @@ import { webFrame, WebFrame } from 'electron/renderer';
// All keys of WebFrame that extend Function
type WebFrameMethod = {
[K in keyof WebFrame]: WebFrame[K] extends Function ? K : never;
};
[K in keyof WebFrame]:
WebFrame[K] extends Function ? K : never
}
export const webFrameInit = () => {
// Call webFrame method
ipcRendererUtils.handle(
IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD,
(event, method: keyof WebFrameMethod, ...args: any[]) => {
// The TypeScript compiler cannot handle the sheer number of
// call signatures here and simply gives up. Incorrect invocations
// will be caught by "keyof WebFrameMethod" though.
return (webFrame[method] as any)(...args);
}
);
ipcRendererUtils.handle(IPC_MESSAGES.RENDERER_WEB_FRAME_METHOD, (
event, method: keyof WebFrameMethod, ...args: any[]
) => {
// The TypeScript compiler cannot handle the sheer number of
// call signatures here and simply gives up. Incorrect invocations
// will be caught by "keyof WebFrameMethod" though.
return (webFrame[method] as any)(...args);
});
};

View File

@@ -5,61 +5,48 @@ import * as ipcRendererUtils from '@electron/internal/renderer/ipc-renderer-inte
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
export interface GuestViewDelegate {
dispatchEvent(eventName: string, props: Record<string, any>): void;
dispatchEvent (eventName: string, props: Record<string, any>): void;
}
export function registerEvents(viewInstanceId: number, delegate: GuestViewDelegate) {
ipcRendererInternal.on(
`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`,
function (event, eventName, props) {
delegate.dispatchEvent(eventName, props);
}
);
export function registerEvents (viewInstanceId: number, delegate: GuestViewDelegate) {
ipcRendererInternal.on(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`, function (event, eventName, props) {
delegate.dispatchEvent(eventName, props);
});
}
export function deregisterEvents(viewInstanceId: number) {
export function deregisterEvents (viewInstanceId: number) {
ipcRendererInternal.removeAllListeners(`${IPC_MESSAGES.GUEST_VIEW_INTERNAL_DISPATCH_EVENT}-${viewInstanceId}`);
}
export function createGuest(
iframe: HTMLIFrameElement,
elementInstanceId: number,
params: Record<string, any>
): Promise<number> {
export function createGuest (iframe: HTMLIFrameElement, elementInstanceId: number, params: Record<string, any>): Promise<number> {
if (!(iframe instanceof HTMLIFrameElement)) {
throw new TypeError('Invalid embedder frame');
}
const embedderFrame = webFrame._findFrameByWindow(iframe.contentWindow!);
if (!embedderFrame) {
// this error should not happen.
if (!embedderFrame) { // this error should not happen.
throw new Error('Invalid embedder frame');
}
return ipcRendererInternal.invoke(
IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST,
embedderFrame.frameToken,
elementInstanceId,
params
);
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CREATE_AND_ATTACH_GUEST, embedderFrame.frameToken, elementInstanceId, params);
}
export function detachGuest(guestInstanceId: number) {
export function detachGuest (guestInstanceId: number) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_DETACH_GUEST, guestInstanceId);
}
export function invoke(guestInstanceId: number, method: string, args: any[]) {
export function invoke (guestInstanceId: number, method: string, args: any[]) {
return ipcRendererInternal.invoke(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, guestInstanceId, method, args);
}
export function invokeSync(guestInstanceId: number, method: string, args: any[]) {
export function invokeSync (guestInstanceId: number, method: string, args: any[]) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_CALL, guestInstanceId, method, args);
}
export function propertyGet(guestInstanceId: number, name: string) {
export function propertyGet (guestInstanceId: number, name: string) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_GET, guestInstanceId, name);
}
export function propertySet(guestInstanceId: number, name: string, value: any) {
export function propertySet (guestInstanceId: number, name: string, value: any) {
return ipcRendererUtils.invokeSync(IPC_MESSAGES.GUEST_VIEW_MANAGER_PROPERTY_SET, guestInstanceId, name, value);
}

View File

@@ -6,7 +6,7 @@ const resolveURL = function (url?: string | null) {
};
interface MutationHandler {
handleMutation(_oldValue: any, _newValue: any): any;
handleMutation (_oldValue: any, _newValue: any): any;
}
// Attribute objects.
@@ -15,10 +15,7 @@ export class WebViewAttribute implements MutationHandler {
public value: any;
public ignoreMutation = false;
constructor(
public name: string,
public webViewImpl: WebViewImpl
) {
constructor (public name: string, public webViewImpl: WebViewImpl) {
this.name = name;
this.value = (webViewImpl.webviewNode as Record<string, any>)[name] || '';
this.webViewImpl = webViewImpl;
@@ -26,24 +23,24 @@ export class WebViewAttribute implements MutationHandler {
}
// Retrieves and returns the attribute's value.
public getValue() {
public getValue () {
return this.webViewImpl.webviewNode.getAttribute(this.name) || this.value;
}
// Sets the attribute's value.
public setValue(value: any) {
public setValue (value: any) {
this.webViewImpl.webviewNode.setAttribute(this.name, value || '');
}
// Changes the attribute's value without triggering its mutation handler.
public setValueIgnoreMutation(value: any) {
public setValueIgnoreMutation (value: any) {
this.ignoreMutation = true;
this.setValue(value);
this.ignoreMutation = false;
}
// Defines this attribute as a property on the webview node.
public defineProperty() {
public defineProperty () {
return Object.defineProperty(this.webViewImpl.webviewNode, this.name, {
get: () => {
return this.getValue();
@@ -61,11 +58,11 @@ export class WebViewAttribute implements MutationHandler {
// An attribute that is treated as a Boolean.
class BooleanAttribute extends WebViewAttribute {
getValue() {
getValue () {
return this.webViewImpl.webviewNode.hasAttribute(this.name);
}
setValue(value: boolean) {
setValue (value: boolean) {
if (value) {
this.webViewImpl.webviewNode.setAttribute(this.name, '');
} else {
@@ -78,7 +75,7 @@ class BooleanAttribute extends WebViewAttribute {
export class PartitionAttribute extends WebViewAttribute {
public validPartitionId = true;
constructor(public webViewImpl: WebViewImpl) {
constructor (public webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.PARTITION, webViewImpl);
}
@@ -102,12 +99,12 @@ export class PartitionAttribute extends WebViewAttribute {
export class SrcAttribute extends WebViewAttribute {
public observer!: MutationObserver;
constructor(public webViewImpl: WebViewImpl) {
constructor (public webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.SRC, webViewImpl);
this.setupMutationObserver();
}
public getValue() {
public getValue () {
if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
return resolveURL(this.webViewImpl.webviewNode.getAttribute(this.name));
} else {
@@ -115,7 +112,7 @@ export class SrcAttribute extends WebViewAttribute {
}
}
public setValueIgnoreMutation(value: any) {
public setValueIgnoreMutation (value: any) {
super.setValueIgnoreMutation(value);
// takeRecords() is needed to clear queued up src mutations. Without it, it
@@ -143,7 +140,7 @@ export class SrcAttribute extends WebViewAttribute {
// attribute without any changes to its value. This is useful in the case
// where the webview guest has crashed and navigating to the same address
// spawns off a new process.
public setupMutationObserver() {
public setupMutationObserver () {
this.observer = new MutationObserver((mutations) => {
for (const mutation of mutations) {
const { oldValue } = mutation;
@@ -164,12 +161,8 @@ export class SrcAttribute extends WebViewAttribute {
this.observer.observe(this.webViewImpl.webviewNode, params);
}
public parse() {
if (
!this.webViewImpl.elementAttached ||
!(this.webViewImpl.attributes.get(WEB_VIEW_ATTRIBUTES.PARTITION) as PartitionAttribute).validPartitionId ||
!this.getValue()
) {
public parse () {
if (!this.webViewImpl.elementAttached || !(this.webViewImpl.attributes.get(WEB_VIEW_ATTRIBUTES.PARTITION) as PartitionAttribute).validPartitionId || !this.getValue()) {
return;
}
if (this.webViewImpl.guestInstanceId == null) {
@@ -193,33 +186,34 @@ export class SrcAttribute extends WebViewAttribute {
opts.userAgent = useragent;
}
(this.webViewImpl.webviewNode as Electron.WebviewTag).loadURL(this.getValue(), opts).catch((err) => {
console.error('Unexpected error while loading URL', err);
});
(this.webViewImpl.webviewNode as Electron.WebviewTag).loadURL(this.getValue(), opts)
.catch(err => {
console.error('Unexpected error while loading URL', err);
});
}
}
// Attribute specifies HTTP referrer.
class HttpReferrerAttribute extends WebViewAttribute {
constructor(webViewImpl: WebViewImpl) {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.HTTPREFERRER, webViewImpl);
}
}
// Attribute specifies user agent
class UserAgentAttribute extends WebViewAttribute {
constructor(webViewImpl: WebViewImpl) {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.USERAGENT, webViewImpl);
}
}
// Attribute that set preload script.
class PreloadAttribute extends WebViewAttribute {
constructor(webViewImpl: WebViewImpl) {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.PRELOAD, webViewImpl);
}
public getValue() {
public getValue () {
if (!this.webViewImpl.webviewNode.hasAttribute(this.name)) {
return this.value;
}
@@ -238,37 +232,34 @@ class PreloadAttribute extends WebViewAttribute {
// Attribute that specifies the blink features to be enabled.
class BlinkFeaturesAttribute extends WebViewAttribute {
constructor(webViewImpl: WebViewImpl) {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.BLINKFEATURES, webViewImpl);
}
}
// Attribute that specifies the blink features to be disabled.
class DisableBlinkFeaturesAttribute extends WebViewAttribute {
constructor(webViewImpl: WebViewImpl) {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.DISABLEBLINKFEATURES, webViewImpl);
}
}
// Attribute that specifies the web preferences to be enabled.
class WebPreferencesAttribute extends WebViewAttribute {
constructor(webViewImpl: WebViewImpl) {
constructor (webViewImpl: WebViewImpl) {
super(WEB_VIEW_ATTRIBUTES.WEBPREFERENCES, webViewImpl);
}
}
// Sets up all of the webview attributes.
export function setupWebViewAttributes(self: WebViewImpl) {
export function setupWebViewAttributes (self: WebViewImpl) {
return new Map<string, WebViewAttribute>([
[WEB_VIEW_ATTRIBUTES.PARTITION, new PartitionAttribute(self)],
[WEB_VIEW_ATTRIBUTES.SRC, new SrcAttribute(self)],
[WEB_VIEW_ATTRIBUTES.HTTPREFERRER, new HttpReferrerAttribute(self)],
[WEB_VIEW_ATTRIBUTES.USERAGENT, new UserAgentAttribute(self)],
[WEB_VIEW_ATTRIBUTES.NODEINTEGRATION, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATION, self)],
[
WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES,
new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, self)
],
[WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.NODEINTEGRATIONINSUBFRAMES, self)],
[WEB_VIEW_ATTRIBUTES.PLUGINS, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.PLUGINS, self)],
[WEB_VIEW_ATTRIBUTES.DISABLEWEBSECURITY, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.DISABLEWEBSECURITY, self)],
[WEB_VIEW_ATTRIBUTES.ALLOWPOPUPS, new BooleanAttribute(WEB_VIEW_ATTRIBUTES.ALLOWPOPUPS, self)],

View File

@@ -12,7 +12,7 @@ export const enum WEB_VIEW_ATTRIBUTES {
USERAGENT = 'useragent',
BLINKFEATURES = 'blinkfeatures',
DISABLEBLINKFEATURES = 'disableblinkfeatures',
WEBPREFERENCES = 'webpreferences'
WEBPREFERENCES = 'webpreferences',
}
export const enum WEB_VIEW_ERROR_MESSAGES {

View File

@@ -17,7 +17,7 @@ const internals = new WeakMap<HTMLElement, WebViewImpl>();
// Return a WebViewElement class that is defined in this context.
const defineWebViewElement = (hooks: WebViewImplHooks) => {
return class WebViewElement extends HTMLElement {
static get observedAttributes() {
static get observedAttributes () {
return [
WEB_VIEW_ATTRIBUTES.PARTITION,
WEB_VIEW_ATTRIBUTES.SRC,
@@ -35,12 +35,12 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => {
];
}
constructor() {
constructor () {
super();
internals.set(this, new WebViewImpl(this, hooks));
}
getWebContentsId() {
getWebContentsId () {
const internal = internals.get(this);
if (!internal || !internal.guestInstanceId) {
throw new Error(WEB_VIEW_ERROR_MESSAGES.NOT_ATTACHED);
@@ -48,7 +48,7 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => {
return internal.guestInstanceId;
}
connectedCallback() {
connectedCallback () {
const internal = internals.get(this);
if (!internal) {
return;
@@ -62,14 +62,14 @@ const defineWebViewElement = (hooks: WebViewImplHooks) => {
}
}
attributeChangedCallback(name: string, oldValue: any, newValue: any) {
attributeChangedCallback (name: string, oldValue: any, newValue: any) {
const internal = internals.get(this);
if (internal) {
internal.handleWebviewAttributeMutation(name, oldValue, newValue);
}
}
disconnectedCallback() {
disconnectedCallback () {
const internal = internals.get(this);
if (!internal) {
return;

View File

@@ -32,10 +32,7 @@ export class WebViewImpl {
public attributes: Map<string, WebViewAttribute>;
constructor(
public webviewNode: HTMLElement,
private hooks: WebViewImplHooks
) {
constructor (public webviewNode: HTMLElement, private hooks: WebViewImplHooks) {
// Create internal iframe element.
this.internalElement = this.createInternalElement();
const shadowRoot = this.webviewNode.attachShadow({ mode: 'open' });
@@ -55,7 +52,7 @@ export class WebViewImpl {
});
}
createInternalElement() {
createInternalElement () {
const iframeElement = document.createElement('iframe');
iframeElement.style.flex = '1 1 auto';
iframeElement.style.width = '100%';
@@ -66,7 +63,7 @@ export class WebViewImpl {
}
// Resets some state upon reattaching <webview> element to the DOM.
reset() {
reset () {
// If guestInstanceId is defined then the <webview> has navigated and has
// already picked up a partition ID. Thus, we need to reset the initialization
// state. However, it may be the case that beforeFirstNavigation is false BUT
@@ -96,7 +93,7 @@ export class WebViewImpl {
// a BrowserPlugin property will update the corresponding BrowserPlugin
// attribute, if necessary. See BrowserPlugin::UpdateDOMAttribute for more
// details.
handleWebviewAttributeMutation(attributeName: string, oldValue: any, newValue: any) {
handleWebviewAttributeMutation (attributeName: string, oldValue: any, newValue: any) {
if (!this.attributes.has(attributeName) || this.attributes.get(attributeName)!.ignoreMutation) {
return;
}
@@ -105,16 +102,15 @@ export class WebViewImpl {
this.attributes.get(attributeName)!.handleMutation(oldValue, newValue);
}
createGuest() {
createGuest () {
this.internalInstanceId = getNextId();
this.hooks.guestViewInternal
.createGuest(this.internalElement, this.internalInstanceId, this.buildParams())
.then((guestInstanceId) => {
this.hooks.guestViewInternal.createGuest(this.internalElement, this.internalInstanceId, this.buildParams())
.then(guestInstanceId => {
this.attachGuestInstance(guestInstanceId);
});
}
dispatchEvent(eventName: string, props: Record<string, any> = {}) {
dispatchEvent (eventName: string, props: Record<string, any> = {}) {
const event = new Event(eventName);
Object.assign(event, props);
this.webviewNode.dispatchEvent(event);
@@ -128,7 +124,7 @@ export class WebViewImpl {
// Adds an 'on<event>' property on the webview, which can be used to set/unset
// an event handler.
setupEventProperty(eventName: string) {
setupEventProperty (eventName: string) {
const propertyName = `on${eventName.toLowerCase()}`;
return Object.defineProperty(this.webviewNode, propertyName, {
get: () => {
@@ -148,10 +144,10 @@ export class WebViewImpl {
}
// Updates state upon loadcommit.
onLoadCommit(props: Record<string, any>) {
onLoadCommit (props: Record<string, any>) {
const oldValue = this.webviewNode.getAttribute(WEB_VIEW_ATTRIBUTES.SRC);
const newValue = props.url;
if (props.isMainFrame && oldValue !== newValue) {
if (props.isMainFrame && (oldValue !== newValue)) {
// Touching the src attribute triggers a navigation. To avoid
// triggering a page reload on every guest-initiated navigation,
// we do not handle this mutation.
@@ -160,7 +156,7 @@ export class WebViewImpl {
}
// Emits focus/blur events.
onFocusChange() {
onFocusChange () {
const hasFocus = this.webviewNode.ownerDocument.activeElement === this.webviewNode;
if (hasFocus !== this.hasFocus) {
this.hasFocus = hasFocus;
@@ -168,11 +164,11 @@ export class WebViewImpl {
}
}
onAttach(storagePartitionId: number) {
onAttach (storagePartitionId: number) {
return this.attributes.get(WEB_VIEW_ATTRIBUTES.PARTITION)!.setValue(storagePartitionId);
}
buildParams() {
buildParams () {
const params: Record<string, any> = {
instanceId: this.viewInstanceId
};
@@ -184,7 +180,7 @@ export class WebViewImpl {
return params;
}
attachGuestInstance(guestInstanceId: number) {
attachGuestInstance (guestInstanceId: number) {
if (guestInstanceId === -1) {
this.dispatchEvent('destroyed');
return;
@@ -209,19 +205,13 @@ export const setupMethods = (WebViewElement: typeof ElectronInternal.WebViewElem
// Forward proto.foo* method calls to WebViewImpl.foo*.
for (const method of syncMethods) {
(WebViewElement.prototype as Record<string, any>)[method] = function (
this: ElectronInternal.WebViewElement,
...args: Array<any>
) {
(WebViewElement.prototype as Record<string, any>)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return hooks.guestViewInternal.invokeSync(this.getWebContentsId(), method, args);
};
}
for (const method of asyncMethods) {
(WebViewElement.prototype as Record<string, any>)[method] = function (
this: ElectronInternal.WebViewElement,
...args: Array<any>
) {
(WebViewElement.prototype as Record<string, any>)[method] = function (this: ElectronInternal.WebViewElement, ...args: Array<any>) {
return hooks.guestViewInternal.invoke(this.getWebContentsId(), method, args);
};
}

View File

@@ -6,7 +6,7 @@ import type * as webViewElementModule from '@electron/internal/renderer/web-view
const v8Util = process._linkedBinding('electron_common_v8_util');
const { mainFrame: webFrame } = process._linkedBinding('electron_renderer_web_frame');
function handleFocusBlur() {
function handleFocusBlur () {
// Note that while Chromium content APIs have observer for focus/blur, they
// unfortunately do not work for webview.
@@ -19,20 +19,18 @@ function handleFocusBlur() {
});
}
export function webViewInit(webviewTag: boolean, isWebView: boolean) {
export function webViewInit (webviewTag: boolean, isWebView: boolean) {
// Don't allow recursive `<webview>`.
if (webviewTag && !isWebView) {
const guestViewInternal =
require('@electron/internal/renderer/web-view/guest-view-internal') as typeof guestViewInternalModule;
const guestViewInternal = require('@electron/internal/renderer/web-view/guest-view-internal') as typeof guestViewInternalModule;
if (process.contextIsolated) {
v8Util.setHiddenValue(window, 'guestViewInternal', guestViewInternal);
} else {
const { setupWebView } =
require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
const { setupWebView } = require('@electron/internal/renderer/web-view/web-view-element') as typeof webViewElementModule;
setupWebView({
guestViewInternal,
allowGuestViewElementDefinition: webFrame.allowGuestViewElementDefinition,
setIsWebView: (iframe) => v8Util.setHiddenValue(iframe, 'isWebView', true)
setIsWebView: iframe => v8Util.setHiddenValue(iframe, 'isWebView', true)
});
}
}

View File

@@ -30,34 +30,24 @@ export const windowSetup = (isWebView: boolean, isHiddenPage: boolean) => {
let cachedVisibilityState = isHiddenPage ? 'hidden' : 'visible';
// Subscribe to visibilityState changes.
ipcRendererInternal.on(
IPC_MESSAGES.GUEST_INSTANCE_VISIBILITY_CHANGE,
function (_event, visibilityState: DocumentVisibilityState) {
if (cachedVisibilityState !== visibilityState) {
cachedVisibilityState = visibilityState;
document.dispatchEvent(new Event('visibilitychange'));
}
ipcRendererInternal.on(IPC_MESSAGES.GUEST_INSTANCE_VISIBILITY_CHANGE, function (_event, visibilityState: DocumentVisibilityState) {
if (cachedVisibilityState !== visibilityState) {
cachedVisibilityState = visibilityState;
document.dispatchEvent(new Event('visibilitychange'));
}
);
});
// Make document.hidden and document.visibilityState return the correct value.
const getDocumentHidden = () => cachedVisibilityState !== 'visible';
Object.defineProperty(document, 'hidden', {
get: getDocumentHidden
});
if (contextIsolationEnabled) {
internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'hidden'], getDocumentHidden);
}
if (contextIsolationEnabled) internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'hidden'], getDocumentHidden);
const getDocumentVisibilityState = () => cachedVisibilityState;
Object.defineProperty(document, 'visibilityState', {
get: getDocumentVisibilityState
});
if (contextIsolationEnabled) {
internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(
['document', 'visibilityState'],
getDocumentVisibilityState
);
}
if (contextIsolationEnabled) internalContextBridge.overrideGlobalPropertyFromIsolatedWorld(['document', 'visibilityState'], getDocumentVisibilityState);
}
};

View File

@@ -1,23 +1,22 @@
import '@electron/internal/sandboxed_renderer/pre-init';
import { IPC_MESSAGES } from '@electron/internal/common/ipc-messages';
import type * as ipcRendererUtilsModule from '@electron/internal/renderer/ipc-renderer-internal-utils';
import {
createPreloadProcessObject,
executeSandboxedPreloadScripts
} from '@electron/internal/sandboxed_renderer/preload';
import { createPreloadProcessObject, executeSandboxedPreloadScripts } from '@electron/internal/sandboxed_renderer/preload';
import * as events from 'events';
import { setImmediate, clearImmediate } from 'timers';
declare const binding: {
process: NodeJS.Process;
createPreloadScript: (src: string) => Function;
createPreloadScript: (src: string) => Function
};
const ipcRendererUtils =
require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
const ipcRendererUtils = require('@electron/internal/renderer/ipc-renderer-internal-utils') as typeof ipcRendererUtilsModule;
const { preloadScripts, process: processProps } = ipcRendererUtils.invokeSync<{
const {
preloadScripts,
process: processProps
} = ipcRendererUtils.invokeSync<{
preloadScripts: ElectronInternal.PreloadScript[];
process: NodeJS.Process;
}>(IPC_MESSAGES.BROWSER_SANDBOX_LOAD);
@@ -56,20 +55,17 @@ Object.assign(process, processProps);
// Common renderer initialization
require('@electron/internal/renderer/common-init');
executeSandboxedPreloadScripts(
{
loadedModules,
loadableModules,
process: preloadProcess,
createPreloadScript: binding.createPreloadScript,
exposeGlobals: {
Buffer,
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
// `__webpack_require__.g,` which causes script error
global: globalThis,
setImmediate,
clearImmediate
}
},
preloadScripts
);
executeSandboxedPreloadScripts({
loadedModules,
loadableModules,
process: preloadProcess,
createPreloadScript: binding.createPreloadScript,
exposeGlobals: {
Buffer,
// FIXME(samuelmaddock): workaround webpack bug replacing this with just
// `__webpack_require__.g,` which causes script error
global: globalThis,
setImmediate,
clearImmediate
}
}, preloadScripts);

View File

@@ -10,13 +10,13 @@ interface PreloadContext {
/** Process object to pass into preloads. */
process: NodeJS.Process;
createPreloadScript: (src: string) => Function;
createPreloadScript: (src: string) => Function
/** Globals to be exposed to preload context. */
exposeGlobals: any;
}
export function createPreloadProcessObject(): NodeJS.Process {
export function createPreloadProcessObject (): NodeJS.Process {
const preloadProcess: NodeJS.Process = new EventEmitter() as any;
preloadProcess.getProcessMemoryInfo = () => {
@@ -24,10 +24,10 @@ export function createPreloadProcessObject(): NodeJS.Process {
};
Object.defineProperty(preloadProcess, 'noDeprecation', {
get() {
get () {
return process.noDeprecation;
},
set(value) {
set (value) {
process.noDeprecation = value;
}
});
@@ -44,7 +44,7 @@ export function createPreloadProcessObject(): NodeJS.Process {
}
// This is the `require` function that will be visible to the preload script
function preloadRequire(context: PreloadContext, module: string) {
function preloadRequire (context: PreloadContext, module: string) {
if (context.loadedModules.has(module)) {
return context.loadedModules.get(module);
}
@@ -63,7 +63,7 @@ function preloadRequire(context: PreloadContext, module: string) {
// - `process`: The `preloadProcess` object
// - `Buffer`: Shim of `Buffer` implementation
// - `global`: The window object, which is aliased to `global` by webpack.
function runPreloadScript(context: PreloadContext, preloadSrc: string) {
function runPreloadScript (context: PreloadContext, preloadSrc: string) {
const globalVariables = [];
const fnParameters = [];
for (const [key, value] of Object.entries(context.exposeGlobals)) {
@@ -84,10 +84,7 @@ function runPreloadScript(context: PreloadContext, preloadSrc: string) {
/**
* Execute preload scripts within a sandboxed process.
*/
export function executeSandboxedPreloadScripts(
context: PreloadContext,
preloadScripts: ElectronInternal.PreloadScript[]
) {
export function executeSandboxedPreloadScripts (context: PreloadContext, preloadScripts: ElectronInternal.PreloadScript[]) {
for (const { filePath, contents, error } of preloadScripts) {
try {
if (contents) {

View File

@@ -5,14 +5,11 @@ import type { ClientRequestConstructorOptions, IncomingMessage } from 'electron/
const { isOnline, resolveHost } = 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) {
return new ClientRequest(options, callback);
}
export function fetch(input: RequestInfo, init?: RequestInit): Promise<Response> {
export function fetch (input: RequestInfo, init?: RequestInit): Promise<Response> {
return fetchWithSession(input, init, undefined, request);
}

View File

@@ -36,9 +36,7 @@ parentPort.on('removeListener', (name: string) => {
});
// Finally load entry script.
const { runEntryPointWithESMLoader } = __non_webpack_require__(
'internal/modules/run_main'
) as typeof import('@node/lib/internal/modules/run_main');
const { runEntryPointWithESMLoader } = __non_webpack_require__('internal/modules/run_main') as typeof import('@node/lib/internal/modules/run_main');
const mainEntry = pathToFileURL(entryScript);
runEntryPointWithESMLoader(async (cascadedLoader: any) => {

View File

@@ -6,27 +6,27 @@ const { createParentPort } = process._linkedBinding('electron_utility_parent_por
export class ParentPort extends EventEmitter implements Electron.ParentPort {
#port: ParentPort;
constructor() {
constructor () {
super();
this.#port = createParentPort();
this.#port.emit = (channel: string | symbol, event: { ports: any[] }) => {
if (channel === 'message') {
event = { ...event, ports: event.ports.map((p) => new MessagePortMain(p)) };
event = { ...event, ports: event.ports.map(p => new MessagePortMain(p)) };
}
this.emit(channel, event);
return false;
};
}
start(): void {
start () : void {
this.#port.start();
}
pause(): void {
pause () : void {
this.#port.pause();
}
postMessage(message: any): void {
postMessage (message: any) : void {
this.#port.postMessage(message);
}
}

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