* chore: patch webrtc to keep the ScreenCast persist mode with the restore token
Carries https://webrtc-review.googlesource.com/c/src/+/505860 until it
rolls. RestoreTokenManager stores the portal persist mode next to each
restore token and BaseCapturerPipeWire::Start() requests that mode when
restoring, so a persistent token stays persistent. Existing callers
default to transient and behave as before.
* feat: allow restoring a desktopCapturer source across launches on Wayland
On Wayland desktopCapturer.getSources() shows the xdg-desktop-portal
picker on every call, so a screen-capture app has to ask the user again
on every launch even though ScreenCast v4 can persist the grant.
Add `persistMode` ('transient' | 'persistent') and `restoreToken`
options to getSources(), a `restoreToken` property on the returned
sources, and desktopCapturer.getRestoreToken(sourceId) to read the
token again after it rotates when a stream starts. With a stored
persistent token the picker is skipped; a revoked or stale token falls
back to the picker. Electron does not store tokens; the docs show how
an app can. Other platforms ignore the options and return an empty
restoreToken.
* docs: say when persistMode is needed with a token and add API history
* chore: update the carried webrtc patch to the latest upstream revision
The persist mode enum moved to modules/portal/screencast_persist_mode.h
upstream, so use it from there instead of including screencast_portal.h.
* docs: say how a restored window is matched and handle a cancelled picker
GNOME and KDE save a window as its app id and title and restore the open
window of that app with the closest title, so a token does not always bring
back the window the user picked. The sample now denies the request when the
user closes the picker.
* docs: format the restore token sample
* fix: forward restore tokens from xdg-desktop-portal 1.22.1
Only tokens that parse as a UUID were passed to the portal. Since 1.22.1 it
issues 22 character base64 tokens, and accepts those and UUIDs, so every
token from a new portal was dropped and the picker came back. Forward what
the portal itself accepts.
* chore: drop the webrtc patch that has rolled in
https://webrtc-review.googlesource.com/c/src/+/505860 is part of the WebRTC
that Chromium 157.0.8085.0 pins.
* fix: only look up and seed restore tokens for picker sessions
Also covers an empty source list and a second request in the docs sample, and adds a round-trip spec.
* fix: pass restore tokens through unchecked and require persistMode with them
The portal's token grammar changed once already (1.22.1) and copying it here
silently dropped valid tokens, so the token now goes to the portal as given: a
value it did not issue makes getSources() reject, which the docs and the sample
handle. restoreToken without persistMode restored a persistent grant as
transient and ended it, so that combination is now an invalid option.
fix: create windows opened from links natively in the opener's session
A window opened by middle- or modifier-clicking a link was assembled in JS
with `new BrowserWindow(options)` from a `-new-window` event, so it only got
whatever the options dict carried: it landed in the default session with
default webPreferences instead of inheriting from its opener the way a
window.open() popup does.
OpenURLFromTab now builds the child the same way a renderer-created popup is
built: `-will-add-new-contents` runs the app's window-open handler, the
content::WebContents is created in the opener's browser context with the
resulting preferences and starting sandbox flags, and it is handed to JS
through `-add-new-contents` with the navigation started as the initiating
document. The `-new-window` event, the guest-less branch of openGuestWindow
and the `openerSandboxFlags` webPreference are removed, and AddNewContents
reports a rejected window through `was_blocked` so neither caller navigates
it. `createWindow` now always receives `options.webContents`.
* fix(net): support non-ASCII headers and multiple cookies in net.fetch
* fix: address PR review feedback on protocol handler header collapse and request aborts
* feat: add protocol.registerSource()
A protocol.handle() handler runs on the main thread, so requests to an
app's own scheme wait whenever the main process is busy there, most
visibly at startup when the first window's HTML, scripts and styles are
served by that handler. For the common case of serving bundled files
there is nothing for JavaScript to decide, so let the app declare it:
protocol.registerSource('app', {
routes: [
{ match: { host: 'bundle', path: '/assets/' },
source: { type: 'directory', root: assetsDir } },
{ match: { host: 'bundle' },
source: { type: 'directory', root: distDir } }
]
})
The routes are compiled into an immutable ProtocolSource held by the
session's ProtocolRegistry. The URLLoaderFactory the registry hands out
for the scheme (to renderers for subresources, to navigations, and to
net.fetch through ProtocolRegistry::CreateRegisteredFactory) is bound on
the IO thread; it picks the most specific route (a host match over a
wildcard, then the longest path prefix), resolves the rest of the path
inside that route's root, refusing anything that would leave it, and
hands the file to AsarURLLoader, which reads it on a blocking sequence
the way file: URLs are read, asar archives included, with Content-Type
from the extension and any headers the route adds. AsarURLLoader reads
plain files itself for this (read_plain_files) rather than delegating to
Chromium's file loader, whose Last-Modified formatting costs more than
the read for small files. A scheme has either a handler or a source;
unhandle() removes either and isProtocolHandled() reports both.
Linux x64, page with 60 stylesheets, median of 3 warm loads:
protocol.handle on the main thread 22 ms idle / 120 ms with the main
process busy for 100 ms after navigation; registerSource 13 ms / 14 ms.
Renderer fetch() round trip 0.33 -> 0.20 ms; browser-process CPU per
request 234 -> 200 us with no main-thread time at all.
* test: protocol.registerSource
* fix: address review of protocol.registerSource
- HEAD requests get the response head with Content-Type/Content-Length
and an empty body, without opening or reading the file.
- A satisfiable Range is answered 206 with Content-Range and
Content-Length; an unsatisfiable one 416 with `bytes */size`.
- Route roots are canonicalized at registration and each requested file
is resolved (symlinks included) before it is opened; a file that is not
under the root is not served.
- Plain-file sizes no longer pass through Archive::FileInfo's 32-bit
size, so files over 4 GiB are served whole.
- registerSource rejects the full set of built-in schemes
(ProtocolRegistry::IsBuiltinScheme), not just the legacy list.
- Reduce a few variable scopes, drop Route's out-of-line special members.
* fix: address second review of protocol.registerSource
* chore: oxfmt
* docs: mark protocol.registerSource() and getSource() as experimental
* fix: tighten registerSource() route validation and root containment
* docs: note that loader-computed headers override route headers
The ScreenCaptureKit workaround from #43013 recorded each window's
resizable state under an array key and looked it up by number, so the
restore step never ran. Key the map by window id and cover BaseWindow.
* fix: stop registering powerMonitor for Windows suspend notifications it ignores
#47162 removed the WM_POWERBROADCAST handling from powerMonitor's message
window (suspend/resume come from base::PowerMonitor), but left the
RegisterSuspendResumeNotification() call behind. That registration no
longer fed anything and its unregistration is the ARM64 shutdown crash
that #50045, #50878 and #51198 have been chasing.
Drop the private message window entirely and take session lock/unlock
from ui::SessionChangeObserver, which registers WTS notifications on the
UI thread's singleton window and already filters to the current session.
* fix: complete ui::SessionChangeObserver where PowerMonitor is destroyed and emit directly
~PowerMonitor() is defined in electron_api_power_monitor.cc, which only saw
the forward declaration, so the unique_ptr member did not compile on
Windows. The session change callback already runs on the UI thread, so emit
there instead of posting an Unretained task that nothing can cancel.
* perf: hand back the same V8 string from app.getName(), getVersion() and getAppPath()
These built a new V8 string from the same value on every call. Keep the
string and return it again until the value changes (setName, setVersion,
setAppPath).
* fix: accept partial bounds in BaseWindow.setBounds() natively
BrowserWindow merged a partial rectangle over getBounds() in a per-instance
JavaScript wrapper, which cost a second native call and two objects, and
BaseWindow had no wrapper at all, so win.setBounds({ width }) threw there
despite being documented. Do the merge in BaseWindow::SetBounds.
* perf: post to the main frame without crossing the boundary twice
webContents.postMessage() went through the mainFrame accessor and then
WebFrameMain.postMessage(). Bind it natively, like send() since #54209.
* refactor: move CachedString to shell/common/gin_helper
* refactor: define BaseWindow's property accessors on the native prototype
* refactor: define WebContents' property accessors and id natively
* refactor: implement app's property accessors, commandLine, dock menu getter and Linux memory metrics natively
* refactor: route certificate events to the WebContents and the client certificate password handler natively
* refactor: define systemPreferences.effectiveAppearance and its deprecated getter natively
* refactor: attach Notification's static methods to the constructor natively
* refactor: parse inAppPurchase.purchaseProduct's options and provide the non-macOS stub natively
* refactor: implement the MessageChannelMain constructor natively
* refactor: parse crashReporter.start's options and find the last crash report natively
* refactor: implement send() for WebContents, WebFrameMain and ServiceWorkerMain and ServiceWorkerMain.startTask natively
fix: deliver utilityProcess output that was still in the pipe when it exited
When the child exited, utilityProcess removed every listener from the
stdout/stderr streams it hands out, so output the child wrote just before
exiting (still in the pipe, or read but not yet emitted) never reached
'data' listeners. Leave the streams alone and let them end once the pipe
has been drained; only end a stream that never got connected. child.stdout
and child.stderr still read as null after 'exit', as documented.
The stdout/stderr property specs and the electron/lol import spec read
whatever had arrived by 'exit' and so failed whenever the last chunk came
late; they now wait for the expected output.
* build: remove unused and duplicated sources from chromium_src
- global_accelerator_listener{,_linux}.cc were compiled here and again by
the //ui/base/accelerators/global_accelerator_listener dep; keep the dep
(as a public_dep, shell/ includes its header) and drop the copies.
- native_window_tracker_aura duplicates //ui/native_window_tracker, which
is already linked; //ui/views/native_window_tracker.h no longer exists.
- chrome's platform_util{,_aura,_mac} were added for the colour chooser
in 2018 and nothing includes chrome/browser/platform_util.h any more.
- browser_theme_pack, custom_theme_supplier and window_frame_util.cc only
reference each other; theme_properties and window_frame_util.h stay.
- back_to_tab_label_button, skip_ad_label_button, resize_handle_button
and overlay_window_live_caption_button are never constructed on desktop
or sit entirely inside our #if 0 hunks.
- scoped_picture_in_picture_occlusion_observation, predictors_features
(+ its proto dep), exclusive_access_bubble_type.cc and the spellcheck
language policy handlers have no callers in anything Electron builds.
- delegated_source_list_capturer is only used by thumbnail_capturer_mac,
printing_service.cc only by pdf_to_emf_converter (Windows), and the
hang_crash_dump.cc Linux stub is never called.
- web_request_resource_type.{cc,h} moved upstream in June; the
enable_electron_extensions=false branch could not gn gen with it listed.
* refactor: remove unregistered browserAction/pageAction extension classes
browser_action.json is not part of the function registration target, so
the twelve BrowserAction*Function and seven PageAction*Function classes
were never instantiated, and the ExtensionActionAPI keyed service was
never built because nothing referenced its factory. Drop them together
with a few other extension leftovers that have no callers:
ElectronExtensionSystem::OnExtensionRegisteredWithRequestContexts,
ElectronExtensionsBrowserClient::SetAPIClientForTest and
ElectronExtensionsRendererClient::AllowPopup (its upstream virtual is
gone), and use extensions::util::AllowFileAccess instead of two local
copies of it.
* refactor: remove unreferenced C++ from shell/
None of these have a caller on any platform:
- OffScreenRenderWidgetHostView::AddGuestHostView/RemoveGuestHostView/
RemoveViewProxy and guest_host_views_ (never populated, so every loop
over it was dead too)
- FileSelectHelper::IsAcceptTypeValid/GetSanitizedFileName/
SetFileSelectListenerForTesting
- FileSystemAccessPermissionContext::OriginHasReadAccess/WriteAccess
- ElectronBrowserClient::GetSiteInstanceFromAffinity
- ElectronPermissionManager::HasPermissionRequestHandler
- api::BaseWindow::SetGTKDarkThemeEnabled and the NativeWindow virtual
behind it (NativeWindowViews keeps its own private helper)
- crash_reporter::IsCrashReporterEnabled/GetGlobalCrashKeys
- InspectableWebContents::GetDelegate
- NativeWindowObserver::WillCreatePopupWindow/WillNavigate
- WebRequest::RequestFilter's three-argument constructor
- declarations with no definition: ServiceWorkerContext::
StopWorkersForScope, WebContents::OnElectronBrowserConnectionError/
OnGetZoomLevel, NetworkContextService::CreateNetworkContextParams,
WindowsToastNotification::ShowInternal, ElectronApiServiceImpl::
ProcessPendingMessages
- NativeWindow::is_closed(), WebContentsZoomController::
persist_zoom_mode(), BrowserProcessImpl::CreateGlobalFeaturesForTesting()
- the Linux stubs for asar::Archive::HeaderIntegrity/RelativePath and the
non-mac Clipboard::ReadFindText stub (their callers are already
platform-guarded, so guard the declarations instead)
- the 'render-view-deleted' event, which existed for the removed remote
module's object registry
* refactor: remove unused gin_helper and converter code
- gin_helper::ErrorThrower::ThrowReferenceError/ThrowSyntaxError, the
zero-argument PromiseBase::Reject() and PromiseBase::RejectPromise had
no callers.
- CreateDataPropertyCallback and Dispatcher::DispatchToCallbackForProperty
were never instantiated.
- Converter<blink::mojom::Referrer> (Electron uses content::Referrer) and
Converter<blink::mojom::MenuItem::Type>::ToV8 had no users; drop the
full popup_menu.mojom.h include from content_converter.h and only pull
the ReferrerPolicy enum into blink_converter.
* refactor: remove native bindings and methods that JS never calls
- The electron_common_environment binding has had no caller since the
forwarded WebContents events moved native (#53975).
- Nothing requests the electron_browser_printing binding;
webContents.getPrintersAsync() calls GetPrinterListAsync() directly.
- features.isExtensionsEnabled(), the top-level executeInWorld/
exposeAPIInWorld exports on the context_bridge binding,
webFrameMain._fromIdIfExists/_fromFtnIdIfExists,
WebContents._getPreloadScript, webFrame._isEvalAllowed and
Menu._getUserAcceleratorAt are not used by lib/ or spec/.
- Protocol::IsProtocolHandled was installed on the prototype template and
then unconditionally replaced by the JS implementation in
lib/browser/api/protocol.ts, so the C++ copy could never run.
* chore: remove dead code and stale typings from lib/
- WebFrameMain.prototype._sendInternal, WebViewImpl.setupEventProperty/
on/onAttach, the 'setLayoutZoomLevelLimits' webview method (removed in
Electron 9) and WEB_VIEW_ATTRIBUTES.NAME had no callers.
- The Electron 20 sandbox-default deprecation log could never fire:
getLastWebPreferences() never contains 'preload' and always contains
'sandbox'.
- lib/browser/api/crash-reporter.ts only ships in the browser bundle, so
its process.type checks were constant.
- WebSQL was removed in Electron 31; drop enableWebSQL from the inherited
guest preferences and the docs, and the no-op 'webSQL' clearData type.
- Drop typings for things that no longer exist (ProcessType, ReplyChannel/
_replyChannel, the WebContents '-ipc-*' and '-window-visibility-change'
overloads, ElectronInternal.LoadURLOptions, Chrome.Tabs, NodeJS.Global
augmentations, Process.log/mainModule/_serviceStartupScript, the
document-start/end overloads, NetBinding.Net/net, App: Function,
Window.ELECTRON_*_SECURITY_WARNINGS, _awaitNextLoad) and the .oxlintrc
globs for lib/preload_realm and lib/sandboxed_renderer.
* chore: remove orphaned fixtures, scripts and CI leftovers
- spec/fixtures/release-notes/ (its spec left with the release scripts in
#52100), devtools-extensions/bad-manifest/, assets/osr.png,
pages/storage/web_sql.html, pages/history.html, module/hello-child.js,
esm/local.mjs and api/local-ai-handler/no-language-model.js are not
referenced by any spec.
- script/verify-chromedriver.py and script/run-if-exists.js lost their
last callers with the AppVeyor removal; script/lib/npm.py,
script/lib/native_tests.py, build/args/native_tests.gn, the pyyaml DEPS
entry and shell/browser/ui/{accelerator_util_unittests,run_all_unittests}.cc
are leftovers of the native test target removed in #40190.
- script/lib/git.py is_repo_root/get_repo_root and script/lib/utils.js
getCurrentBranch/handleGitCall have no callers.
- pipeline-electron-build-and-test-and-nan.yml is not referenced by any
workflow since #49072.
- Three spec/disabled-tests.json titles, one node-disabled-tests entry, a
wayland allowlist entry, a sysroots.json arch and two CODEOWNERS paths
named things that no longer exist; four docs/images files are unlinked.
webContents.send() reached the frame through the mainFrame accessor, a
full call into C++ that looks up or builds a WebFrameMain wrapper, and
then made a second call to send. Resolve the primary main frame in C++
so a send crosses once. The channel check and error handling move to the
webContents wrapper unchanged: a destroyed contents or missing frame
still throws a TypeError, a serialization failure is still logged.
Saves roughly 250 ns per send on a testing build; the rest of a send is
serialization and mojo.
TypeScript 7.0 is the native compiler and ships no JavaScript API. The
earlier changes in this series already moved everything that transpiled
on the fly (script/ and spec/) onto Node's own type stripping, so what
is left here is the compiler swap itself:
* typescript ^5.8.3 -> ^7.0.2.
* build/bundle/typecheck.mjs spawns tsc instead of driving the compiler
API (the rootDir/private-field diagnostics it used to filter out are
handled by tsconfig.electron.json setting rootDir to the parent
checkout instead).
* tsconfig.json drops the removed baseUrl option and anchors the paths
mapping explicitly; tsconfig.default_app.json moves off the removed
"node" module resolution; `declare module NodeJS` becomes a namespace.
* A few lib/, spec and docs snippets wrap Buffers for the stricter
Buffer/ArrayBuffer typings that Blob now expects.
Co-authored-by: Claude <noreply@anthropic.com>
* test: mechanically rewrite spec imports and __dirname for native ESM
This is the automated half of loading spec/ as native ES modules and is
not expected to pass on its own; the hand-written half follows in the
next commit. A script (included in the pull request description) made
three changes to spec/*.ts and spec/lib/*.ts, then oxfmt was re-run:
* relative import specifiers gain their real file extension
* __dirname / __filename become import.meta.dirname / .filename
* files that still call require() from module code get a
createRequire(import.meta.url) prelude
Code inside functions that are stringified and evaluated in another
process (remotely(), itremote(), template-interpolated functions and
similar) is left as it was.
* test: load specs as native ES modules with Node's type stripping
spec/package.json declares "type": "module", so the spec runner and every
spec file now load through Node's own ESM loader and built-in TypeScript
type stripping; the require.extensions transpile hook is gone. A new
spec/fixtures/package.json keeps fixtures in a CommonJS scope.
The hand-written changes on top of the mechanical rewrite:
* spec/index.js becomes an ES module and import()s its test dependencies
after the app is ready, then uses mocha.loadFilesAsync().
* Functions that are stringified and sent to another process now arrive
as written rather than as CommonJS transpiler output, so the
remote-control and utility-process fixtures define the plain names
those bodies use (chai, expect, once, setTimeout, ...) instead of the
old `electron_1` style shims, and a few bodies require() what they use.
* The two specs that exercise lib/ sources require() them, since lib/ is
not in a "type": "module" scope.
* spec-helpers resolves BrowserWindow lazily so it stays loadable from a
utility process.
* tsconfig.spec.json allows .ts extension imports; spec/tsconfig.json
points editors at it.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Removing a <webview> from the DOM tears down its internal iframe first,
which destroys the guest and drops it from guestInstances. The element's
disconnectedCallback then sends GUEST_VIEW_MANAGER_DETACH_GUEST for that
id. Since #53684 the handler validates the id with getGuestForFrame()
before detaching, so the already-gone guest made the sync IPC throw back
into disconnectedCallback. The uncaught error also skipped the element's
reset, so a removed <webview> that was re-inserted never got a new guest.
Treat a detach for an unknown guest as a no-op again (as detachGuest()
always did) and keep the sender-frame check for live guests.
Fixes#53989.
* build: bundle lib/ with rolldown instead of webpack
Replace the webpack + ts-loader pipeline that builds Electron's internal
JavaScript bundles with a small rolldown driver (build/bundle/bundle.mjs).
- build/bundle/targets.mjs describes the seven bundles (browser, renderer,
worker, webview, isolated_renderer, node, utility) and the per-target
behaviour that used to live in one webpack config each.
- BUILDFLAG(...) folding, the `electron` aliases, the Promise/process/
Buffer/global providers and the profiling/try-catch wrappers are kept
with the same semantics. Node built-ins imported from lazily evaluated
modules stay lazy via a one line CommonJS shim so that load order
matches the old bundles.
- Type checking moves out of the bundling step into a single
electron_lib_typecheck action (build/bundle/typecheck.mjs) that every
bundle depends on, instead of running seven times.
- gen-filenames.ts asks bundle.mjs for the module graph directly and
emits a lib_typecheck_sources list for the new action.
- A few lib/ modules are adjusted for an ESM-first bundler: net.ts uses
a plain module.exports object, web-contents.ts keeps its session
side-effect import explicitly, and defineProperties caches the loaded
module so each electron.* getter keeps returning the same object.
Drops webpack, webpack-cli and ts-loader from devDependencies.
* chore: wrap comment for clang-format
* build: keep bundle declarations out of the wrapper's parameter scope
node_mksnapshot compiles the js2c bundles with Node's built-in module
parameters (exports, require, module, process, internalBinding,
primordials), and a hoisted top-level `const { internalBinding } = ...`
from lib/node redeclared one of them. Wrap the bundle body in an IIFE, as
the webpack output was.
---------
Co-authored-by: Claude <noreply@anthropic.com>
* refactor: give View, BaseWindow, MessagePortMain and ParentPort their prototypes natively
View/WebContentsView/ImageView and BaseWindow/BrowserWindow were plain
constructor templates whose prototype chains (EventEmitter, and the
subclass -> base links) were stitched together afterwards in lib/ with
Object.setPrototypeOf, which is why browser/init.ts had to load
web-contents-view.ts before anything could create a window. MessagePort
and the utility process's ParentPort were wrapped in JS EventEmitter
classes (MessagePortMain, ParentPort) that re-emitted every event and
wrapped/unwrapped ports on the way in and out, in the browser, in
ipc_dispatch and in the utility bundle.
The constructor templates now Inherit() from the EventEmitter template or
their base class's template, app gets its EventEmitter prototype when the
binding creates it, and MessagePort / ParentPort are Constructible
EventEmitters themselves (class names MessagePortMain / ParentPort, not
constructible from JavaScript). lib/browser/api/view.ts,
web-contents-view.ts, views/image-view.ts, message-port-main.ts and
lib/utility/parent-port.ts are deleted along with the port wrapping in
message-channel.ts, utility-process.ts, web-frame-main.ts, ipc-dispatch
and utility/init.ts.
* fix: let process.parentPort.postMessage() be called with no message again
---------
Co-authored-by: Claude <noreply@anthropic.com>
* fix: don't re-copy the whole request body for every chunk written to net.request
ClientRequest buffered a non-chunked body by Buffer.concat-ing each written
chunk onto everything written so far, so a body arriving in many chunks (a
fetch() ReadableStream body, a Blob or FormData) cost O(n^2) copying before
the request even started: a 32 MiB stream upload spent ~4.7 s in the main
process. Collect the chunks and concatenate once when the body is complete.
Each chunk is copied as it is written, because the write callback lets the
caller reuse or mutate its buffer before the body is concatenated.
* test: pass an explicit encoding so the write callback type-checks
The net.request write() typing generated from the docs takes
(chunk, encoding?, callback?), so passing the callback as the second
argument fails tsc and stops the whole spec shard from starting.
---------
Co-authored-by: Claude <noreply@anthropic.com>
Sandboxed renderers and service worker preload realms ran a webpack bundle
(sandbox_bundle / preload_realm_bundle) that built the preload scripts'
`process` object and `require`, assembled the `electron` module from the
JavaScript API wrappers, registered the <webview> element and then compiled
and ran each preload script. With the API modules now provided by their
bindings, the rest moves to C++ (shell/renderer/preload_environment.cc):
- `process` is a native EventEmitter carrying the same properties as
before, plus getProcessMemoryInfo(); 'loaded', 'exit', 'document-start'
and 'document-end' are emitted on it directly.
- `require('electron')` returns a module object whose properties load their
binding on first access.
- Preload scripts are compiled with CreatePreloadScript() and called with
(require, process, exports, module, global); failures are logged and
reported as 'preload-error' as before.
The only JavaScript Electron still evaluates in a sandboxed renderer is the
<webview> element implementation, now its own `webview_bundle`, and only in
renderers that enable `webviewTag`. The `process` npm polyfill is no longer
needed.
* refactor: create ipcRenderer natively
`ipcRenderer` and the internal `ipcRendererInternal` were JavaScript
classes extending EventEmitter that forwarded to an `electron_renderer_ipc`
binding object, and a start-up module registered them as the targets for
incoming messages. The binding now builds both objects itself when it is
loaded into a context: instances of the shared native EventEmitter class
with `send`/`sendSync`/`invoke`/`postMessage`/`sendToHost` implemented in
C++ on an intermediate prototype (so, as before, they are not own
properties and work unbound), and it registers them for incoming messages.
The TypeScript modules re-export the binding's objects.
gin_helper::GetNodeEventEmitterConstructor() caches one EventEmitter class
per context so that these objects and the class exposed to scripts share a
prototype.
* refactor: create contextBridge natively
The `contextBridge` module was a JavaScript wrapper that checked
`process.contextIsolated` and forwarded to the
`electron_renderer_context_bridge` binding. The binding now exports the
module object directly, doing the context isolation check in C++ (the
frame's web preference, or always allowed in a service worker preload
realm), and in debug builds attaches the `internalContextBridge` helpers
the tests use.
* refactor: make webFrame an EventEmitter natively
The `webFrame` module wrapped the `electron_renderer_web_frame` binding in
JavaScript to make `WebFrame` an EventEmitter, to start emitting
'isolated-world-created' when something first listens for it, and to
implement the deprecated `routingId` / `findFrameByRoutingId()` with a
synchronous internal IPC to the browser.
The binding now chains `WebFrame.prototype` to the context's native
EventEmitter, hooks `on()` and friends for the lazy 'isolated-world-created'
registration, and implements the two deprecated members over a sync call on
ElectronWebContentsUtility, so the browser-side IPC handlers go away. The
renderer `crashReporter` module likewise re-exports its binding.
* refactor: receive transferred shared textures in the renderer natively
`sharedTexture.sendSharedTexture()` reached the target frame through an
internal IPC round trip answered by the renderer-side `sharedTexture`
module, which imported the texture, replied with its sync token and called
the receiver registered with `setSharedTextureReceiver()`.
That now happens over mojom::ElectronFrame::ReceiveSharedTexture(),
implemented by ElectronApiServiceImpl: it imports the texture with the
`electron_common_shared_texture` binding in the frame's Electron context,
replies with the frame creation sync token (or the error), and invokes the
registered receiver with the same `{ importedSharedTexture }` object as
before. The receiver is registered natively by the binding, so the
renderer module is a plain re-export and the last `invokeInWebFrameMain`
helper is removed.
* fix: address review comments on the native renderer API modules
- Import a transferred shared texture by calling
shared_texture::FinishTransferSharedTexture() directly rather than going
through the binding's exports object.
- WebFrame's listener hooks carry the EventEmitter.prototype method they
defer to as callback data instead of looking it up on every call.
* refactor: set up window overrides and <webview> focus/visibility natively
The renderer bundle patched `window.prompt` (to throw), `window.close` in
Node.js renderers (to close the BrowserWindow over an internal IPC), and in
<webview> guests replaced `document.visibilityState` with a copy of the
embedder window's state and reported window focus/blur to the browser so
the <webview> element could emit focus events.
- `prompt` and `close` are now installed from C++ when the context is
created; `close` calls a new ElectronWebContentsUtility.CloseWindow().
- Guest visibility needs no shim: content propagates the embedder's
visibility to inner WebContents, so the BrowserWindow-side forwarding and
the renderer override are removed. The previously disabled <webview>
visibility spec is rewritten to cover hide/show.
- Guest focus/blur is reported by a native DOM event listener through
ElectronWebContentsUtility.NotifyGuestFocusChange(), which emits the same
`-focus-change` event the guest view manager already forwards.
* fix: skip guest focus reports from a window already detached from its frame
* refactor: emit the renderer security warnings from C++
The "Electron Security Warning" console messages were produced by a module
in the renderer bundle that fetched the web preferences over an internal
IPC on every page load. A small RenderFrameObserver now performs the same
checks natively after the main frame loads (web preferences, command line,
CSP eval check, `[allowpopups]` elements and insecure resource timing
entries) and logs through the frame's console, so neither the sandboxed
nor the Node.js renderer bundle carries the module any more.
* test: ignore security warnings when waiting for a <webview> guest's first console message
The warnings are now logged as soon as the guest finishes loading, which can
be before a fixture logs the message a test is waiting for.
* refactor: run webContents.executeJavaScript and friends over mojo
`webContents.executeJavaScript`, `executeJavaScriptInIsolatedWorld`,
`insertCSS`, `removeInsertedCSS`, `insertText` and
`setVisualZoomLevelLimits` used to send an internal IPC message that a
handler in the renderer's Electron bundle answered by calling `webFrame`.
They are now methods on mojom::ElectronRenderer implemented by
ElectronApiServiceImpl, so they no longer depend on Electron's JavaScript
running in the renderer. Script results still come back through Electron's
V8 serializer and a returned promise is still awaited.
* fix: address review comments on executeJavaScript over mojo
- Let Blink await a returned promise: extend the RequestExecuteScript patch
so the raw-value callback also receives the rejection reason of an
awaited result, use PromiseResultOption::kAwait, and reply from a plain
function instead of the self-owned helper that chained on the promise.
- Bind the browser-side replies with base::SplitOnceCallback instead of a
shared_ptr'd promise, and enter the promise's context before
deserializing the result (WebFrameMain::BindPromiseToReply).
* refactor: implement EventEmitter natively for sandboxed preload bundles
Adds a C++ implementation of the `EventEmitter` class
(shell/common/gin_helper/node_event_emitter.cc, exposed through the
`electron_common_events` binding) and resolves `events` to it in the
bundles that run without a Node.js environment, so `ipcRenderer`,
`webFrame` and the sandboxed preload `process` object no longer pull the
`events` npm package into every sandboxed renderer.
The class provides the same instance API the `events` package did and
nothing more: on/once/off/emit/addListener/removeListener/prependListener/
prependOnceListener/removeAllListeners/listeners/rawListeners/
listenerCount/eventNames/setMaxListeners/getMaxListeners, the
'newListener' and 'removeListener' events, the max-listener console
warning and `EventEmitter.defaultMaxListeners`. As in lib/events.js the
listener state lives in `_events` / `_eventsCount` / `_maxListeners` on
the instance, so the methods are generic over their receiver and classes
can extend it. A spec runs the same scenarios against Node's EventEmitter
and this one and requires identical results.
* perf: dispatch IPC messages to listeners without going through JS
The native EventEmitter keeps `_events`, `_eventsCount` and
`_maxListeners` in internal fields on real instances (still surfaced as
the usual own data properties), caches its property keys, builds once()
wrappers by binding one shared native function instead of minting a new
function per call, and calls straight into its own C++ bodies for the
internal `this.emit()` / `this.on()` / `this.removeListener()` calls when
those methods are not overridden.
It also exports EmitEvent(), and ipc_native::EmitIPCEvent() now builds the
event object and emits on ipcRenderer / ipcRendererInternal from C++
instead of calling a JS `onMessage` trampoline that called `emit()`. In a
renderer with Node.js integration the emitters are Node's and EmitEvent()
calls their `emit` method.
* perf: call multiple listeners for one event from a JS loop
Calling N listeners through N v8::Function::Call() entries from C++ costs
about four times per listener what a JIT-compiled loop does. When an
event has more than one listener, hand the listener array to a one-line
`for (...) Reflect.apply(listener, self, args)` function compiled once per
context instead. The single-listener case is still one direct call from
C++. Also pins the listener-throws semantics (later listeners skipped,
emit rethrows) against Node's in the spec.
* fix: handle object event names and large removeAllListeners in the native EventEmitter
Deleting an event whose name is an arbitrary object asked v8::Object::Delete
to convert it to a property key, which needs to run script and is not
allowed there; convert it first. Emitting 'removeListener' for every
listener of an event kept all the temporary handles of the loop alive until
it finished; give each iteration its own HandleScope.
* fix: address review comments on the native EventEmitter
- removeListener() searches the listener array in place from the back
instead of copying it first, so removing the newest listener (what once()
wrappers and removeAllListeners() do) no longer costs a full copy each time.
- The 'removeListener' event reports the originally registered function when
a once() wrapper is removed from a multi-listener event, as events@3 did.
- A once() wrapper is only marked fired after its removeListener() call
succeeded, matching events@3's order.
* refactor: remove Node.js shims from sandboxed preload scripts
BREAKING CHANGE. Sandboxed preload scripts and service worker preload
scripts no longer get the `events`, `timers` and `url` module shims from
`require`, and are no longer run with `Buffer`, `setImmediate` and
`clearImmediate` in scope. `require` only loads `electron`; `process` and
`global` are still provided.
These were npm browser polyfills compiled into the bundle that every
sandboxed renderer evaluates, and made up about two thirds of it. With them
gone the preload wrapper's parameter list is fixed again, so the code cache
no longer needs to be keyed on the wrapper shape.
* docs: tabulate the removed preload shims and their replacements
With context isolation the preload realm's global is private to preloads, so
Buffer is defined there as a property that loads the polyfill on first use
instead of evaluating it for every frame before any preload runs. Without
context isolation it stays a preload-scoped parameter as before.
The persisted preload code cache is now also keyed on that wrapper shape,
because V8's cached-data check does not notice a changed parameter list.
Also drops the `Buffer` hidden value set on the sandboxed global; its native
consumer was removed in #34794.
With the wrapper shape now depending on where the bundle runs, the renderer
decides it from the actual script context (main world vs isolated world)
rather than the contextIsolation preference, and names the parameter list in
the compiled source so a persisted code cache produced for one shape is
rejected rather than run against the other.
* perf: find and apply the app's package.json natively before the main-process JS runs
lib/browser/init.ts located the app by pushing each of resources/app.asar,
app and default_app.asar through Module._load('<dir>/package.json') inside
a try/catch (a full CommonJS resolve, an fs.existsSync-style archive probe
and a thrown MODULE_NOT_FOUND per miss), then set the name, version, Linux
desktop name (desktop-name.ts, NFKD + two regex passes) and v8Flags one
app.* call at a time, stat'ed for Squirrel's Update.exe on Windows, and
registered a will-finish-launching listener that built the default menu
from default-menu.ts. base-window.ts gave every new window the application
menu from a JS _init hook.
ElectronBrowserMainParts now does the lookup with one asar-aware read per
candidate and applies the manifest to Browser directly (app_package.cc),
leaving init.ts only {path, main, esm} to load the entry script from. The
default File/Edit/View/Window menu is installed by Menu before
will-finish-launching is emitted unless the app already called
Menu.setApplicationMenu, and BaseWindow picks up the application menu when
it is wrapped. desktop-name.ts and default-menu.ts are deleted; the
desktop-name spec runs against the native implementation.
* fix: keep the app menu ahead of browser-window-created, tolerate non-UTF-8 package.json, register Menu templates before the default menu is built
* fix: apply package.json v8Flags just before the entry script so the startup bundles keep their code cache
* refactor: validate print() and printToPDF() options natively
webContents.print()'s pageSize handling, webContents/webFrameMain
printToPDF()'s option validation and per-frame-tree job queue
(lib/browser/print-to-pdf.ts) and getPrintersAsync() were JavaScript
wrappers around _print/_printToPDF/getPrinterListAsync. They are now the
native methods themselves: print_to_pdf.cc validates the options and
queues jobs per frame tree, WebContents::Print turns pageSize into the
media size, and the errors keep their messages and their Error /
TypeError / RangeError classes.
To keep those messages, option reading goes through two new gin_helper
pieces: ConversionError carries why a conversion failed, the property
path it failed at and the JavaScript error class to raise, and
OptionsReader reads an options object property by property through gin's
converters (or a converter that accepts the ConversionError itself),
recording the first failure as "<path> must be <type>".
* fix: address review findings in native print option handling
- print(): validate pageSize before the callback, reject a function as the
options object, and only take the no-options fast path when no pageSize
was given
- printToPDF(): reject instead of throwing for a destroyed WebContents or a
disposed WebFrameMain, when reading the options throws, and for BigInt
margins; getPrintersAsync() works on a destroyed WebContents again
- drop non-finite numbers as the base::Value conversion used to, keep
out-of-int-range media sizes as doubles
- start PDF jobs from a posted task and refuse frames that are not live
* fix: match print() and printToPDF() error classes and ordering in the remaining edge cases
- print(): pageSize null throws the TypeError it used to, a function is an
unsupported pageSize rather than a custom one
- printToPDF(): a WebContents destroyed while its job is queued rejects with
a TypeError as before; parameter-range errors are reported before the
frame-is-not-live failure
* fix: stop reading options after the first failure so later getters do not run
* fix: reproduce the margins <= coercion exactly and let a throwing pageSize getter throw out of print()
* fix: read a custom pageSize's height before its width, as the JS did
* fix: read all four margins before comparing them, as the destructuring did
* refactor: settle the loadURL() promise natively
webContents.loadURL()/loadFile() and navigationHistory.restore() built
their promise in lib/ by attaching six listeners (did-finish-load,
did-fail-load, did-start-navigation, did-navigate-in-page,
did-stop-loading, destroyed) per call and running a small state machine
over them. LoadURLPromises is that state machine in C++, fed by
WebContents at the points it emits those events (after the emit, and
only for promises that existed before it, as the appended listeners
were), so loadURL is the native method and _awaitNextLoad/_loadURL go.
The promise resolves and rejects ({errno, code, url}, marked handled)
exactly as before, and reactions still run before the emit returns to
content/ so a load started from a rejection during renderer teardown is
still deferred.
* fix: settle loadURL() promises before the event's listeners run
Notify LoadURLPromises right before each emit instead of after it: promise
reactions are queued first and run when the emit drains microtasks, i.e.
straight after the listeners as when a JS listener settled them, so
listeners added after loadURL() and async continuations of any listener
no longer change the outcome. Also mark the promise silent for debuggers
(as the old no-op catch handler did) and truncate the URL in the message
by UTF-16 code units.
* refactor: emit WebContents' forwarded events natively
WebContents' JS _init registered listeners on itself whose only job was to
re-emit with data C++ already had: '-console-message' -> 'console-message'
(plus the deprecated positional arguments), '-unresponsive' ->
'unresponsive' (when not ignored/hidden/uninitialised), 'login' and
'render-process-gone' -> app (and a debugging hint when logging is on),
'ready-to-show' -> the owner BrowserWindow, '-before-unload-fired' ->
preventDefault for non-interactive types, 'devtools-reload-page' ->
reload(), 'select-bluetooth-device' -> cancel when nobody listens, and
app 'web-contents-created' at the end of _init. Each is now done where the
event originates, in the order the JS listener (always the first one) gave:
app before the WebContents' own listeners for login/render-process-gone,
the window after the WebContents for ready-to-show, reload() before
'devtools-reload-page'. The 'console-message' deprecation warning is now
given when a listener with the old signature is added rather than on its
first message.
* fix: take the weak pointer before app listeners can destroy the WebContents
* fix: run paired emits under one callback scope so ticks and microtasks drain after both
* fix: read the login event's defaultPrevented after listeners' microtasks have run
* fix: notify the window that owned the WebContents before its ready-to-show listeners ran
* fix: hand app and webContents 'login' listeners the same authInfo object
* fix: count select-bluetooth-device listeners under the emit's callback scope
Chromium removed the synchronous OSCrypt backend in
https://chromium-review.googlesource.com/c/chromium/src/+/7765593. Remove
safeStorage.isEncryptionAvailable(), encryptString() and decryptString(),
which were deprecated in Electron 45 and were the last users of it, move
pref registration, the Windows key load and the Linux backend name onto
os_crypt_async, and drop the revert of that CL together with the
os_crypt/sync visibility patch.
Closes https://github.com/electron/electron/issues/51301
Every message from a renderer or service worker was emitted as a private
'-ipc-*' event on the Session and fanned out by a JS listener that read a
hidden internal flag through a native call, allocated reply()/returnValue
closures per event, looked the sending WebFrameMain or ServiceWorkerMain up
through another native call, and re-spread the argument array into each of
up to four emit() calls. The event itself was a generic Event that had
seven properties and an accessor defined on it one at a time per message.
api::ipc_dispatch now delivers IPC directly: ipcMainInternal for internal
channels, otherwise the WebContents ('ipc-message' / 'ipc-message-sync'),
the sending frame's WebFrameMain.ipc, WebContents.ipc and ipcMain for
frames, or the worker's ServiceWorkerMain.ipc for service workers, in the
same order as before and inside a single callback scope so microtasks still
run once afterwards. The event is an api::IpcMainEvent /
IpcMainServiceWorkerEvent whose properties are native data properties of
the instance template; event.reply, event.returnValue, event.serviceWorker,
event.ports and the invoke reply are provided natively, and an invoke whose
handler returned a plain value or a settled Promise replies from a microtask
without allocating promise reactions. lib/browser/ipc-dispatch.ts is
reduced to registering ipcMain, ipcMainInternal and MessagePortMain, and the
IpcDispatcher mixin on Session is removed. The '-ipc-*' Session events are
still emitted when something listens for them (IPC inspectors such as
Devtron).
* perf: make fs operations on paths inside an asar archive cheaper than real files
Loading modules from app.asar was measurably slower than loading the same
files from disk because every stat/realpath/readdir on an archive path paid
for a native path split that built a V8 dictionary, a fake Stats array built
through the slow iterable path, and a real realpath() of the archive file.
- splitPath: find the archive prefix in JS and memoise the native verdict per
prefix, with a last-prefix fast path; skip path.normalize() for already
normalized absolute POSIX paths.
- internalModuleStat: consult the module stat cache before splitting the path.
- Fill a shared scratch Float64Array/BigInt64Array for Stats instead of
allocating one from a JS array per call.
- Memoise the archive file's own realpath per archive; honour the `encoding`
option for asar realpath results (previously threw for 'buffer').
- readdir(withFileTypes)/opendir: one native readdirWithTypes() call instead
of a stat per child.
- Native: allocation-free header walk over string_view, a per-archive
path->node memo, zero-copy ASCII path arguments, and DictionaryTemplate
results for stat()/getFileInfo().
- Wrap child_process eagerly instead of through a Module._load hook (init.ts
already requires it; the hook taxed every require() and missed ESM
imports of node:child_process).
- Read ELECTRON_LOG_ASAR_READS once.
* perf: locate the archive component of a path natively in one call
Replace the JS-side prefix memo and normalization regexes in the asar fs
wrapper with a lean native asar.splitPath(): it reads the path without
copying when it is ASCII, walks the components once, memoises "is this
*.asar prefix an archive file" by string, and reports when the path has
"."/".."/empty components so the caller can path.normalize() and ask again
(user-supplied paths only; the module loader's are already normalized).
The JS splitPath() is reduced to the type coercion, the /\.asar/i pre-check
that keeps unrelated paths out of native code, the call, and two slices.
Spec coverage for the binding is updated to the new integer contract and a
wrapper-level test for un-normalized archive paths is added.
* test: cover native asar path splitting and archive lookups; fix edge cases
Add an "internals" suite to asar-spec that builds its own archives (nested
directories, link chains and cycles, non-ASCII names, an upper-case .ASAR,
an archive inside a real directory named *.asar, a bogus *.asar file) and
exercises the electron_common_asar binding directly: splitPath() prefix
detection and its normalization contract, Archive.stat/getFileInfo/readdir/
readdirWithTypes/realpath, the lookup memo past its size limit, concurrent
use from worker threads, and the fs wrapper on Buffer/URL/relative/
un-normalized paths.
Fixes found while writing them and by review:
- splitPath() returned a UTF-8 byte offset; report UTF-16 code units so an
archive under a non-ASCII directory splits correctly.
- Decide "needs normalization" lexically before probing the disk, and ignore
any number of trailing separators.
- internalModuleStat goes through the normalizing splitPath() again so
un-normalized NODE_PATH / require.resolve({ paths }) entries keep working.
- Keep separate archive realpath memos for realpath() and realpath.native().
- Skip empty components in the archive header walk.
- Read ELECTRON_LOG_ASAR_READS per call again so it can be set from main.js.
* fix: keep un-openable and not-yet-existing *.asar paths behaving as before
Follow-ups from review of the asar fast path:
- Native splitPath() now rejects paths containing control characters, as the
base::FilePath converter used by Archive::New() does, so an .asar file under
such a directory stays an ordinary file instead of an "Invalid package".
- IsDirectoryCached()/IsArchivePrefix() only memoise answers for paths that
exist. A *.asar path probed before anything is created there (the win32
mkdir wrapper does this) is re-evaluated later instead of being pinned as
an archive for the life of the process. This predates the branch for
IsDirectoryCached().
- getFileInfo() fills the integrity slot with undefined when absent so the
DictionaryTemplate result keeps using its cached map.
- Spec: create the *.asar-named fixture directory through original-fs, derive
the realpath.native expectation with the native flavour (8.3 temp paths on
Windows runners), and cover the cases above.
* perf: read ELECTRON_LOG_ASAR_READS once per process
process.env goes through Node's env interceptor (getenv under a lock) and the
flag was checked on every read of a packed file. Sample it at startup instead
and document that it must be set in the launching environment.
Sandboxed frames already receive their preload list from the browser over the
ElectronFrameStartup channel ahead of CommitNavigation. Frames with Node.js
integration still blocked on a synchronous BROWSER_NONSANDBOX_LOAD round trip
into browser-process JS from renderer init to learn the same thing.
The browser now pushes RendererStartupData to those frames too, with only the
preload paths filled in (no file contents, environment or code cache, since
Node loads the files itself): at frame creation, so a script context created
on the initial empty document still sees it, and again ahead of each
committed navigation. ElectronRendererClient hands the paths to renderer
init and the sync IPC and its handler are removed.
The rest of lib/browser/api/menu.ts moves into api::Menu: popup() and
closePopup() (window resolution included), getMenuItemById(), and
Menu.setApplicationMenu()/getApplicationMenu(), which on Windows and Linux
now sets each window's menu without a round trip through the BaseWindow
wrappers. menu.ts becomes a re-export of the binding and default-menu.ts
asks api::Menu whether an application menu was set.
MenuItem was a JavaScript object that the native menu model called back
into for every property of every item each time a menu was shown, had its
accelerators registered or (on macOS) was checked for a key equivalent,
and menu.insert() and click dispatch bounced between lib/browser/api and
api::Menu several times per item.
MenuItem is now a gin/cppgc object in shell/browser/api whose documented
properties are native data properties; api::Menu holds its items natively
and answers the menu model from them, and insert/append, menu.items,
buildFromTemplate and click dispatch are native. The role submenus are
built from native items. Showing a menu no longer enters JS; the only JS
that runs is the app's own click handlers. lib/browser/api/menu-item.ts
becomes a re-export of the binding; menu.ts keeps popup(),
getMenuItemById() and set/getApplicationMenu().
The before/after/beforeGroupContaining/afterGroupContaining ordering and
the separator clean-up move from lib/browser/api/menu-utils.ts to
shell/browser/api/electron_api_menu_template.cc. Ids are still compared
with ===, and Proxy-wrapped and sparse template arrays are still accepted.
The built-in roles (default label and accelerator, registerAccelerator,
the computed checked state for toggleSpellChecker, the default submenus
and what each role does when activated) move from
lib/browser/api/menu-item-roles.ts to
shell/browser/api/electron_api_menu_roles.{h,cc}; roles now act directly
on api::WebContents / NativeWindow / Browser / prefs. menu-item.ts reads
the table through the menu binding.
- gin_helper::ConvertedValue<T>: a property store for cppgc wrappables
that keeps the JS value a script assigned (for identity on read) together
with its native conversion, done once at assignment.
- gin_helper::Constructible: an optional FillInstanceTemplate() hook for
own (instance) properties.
- gin_helper::TrackableObject::GetAllNative(): the tracked instances
without going through their wrappers.
- BaseWindow::GetFocusedWindow() (now also backing
BaseWindow.getFocusedWindow()), FromValue(), IsLive(), SetMenuNatively().
- WebContents::GetFocusedWebContents() and session().
protocol.ts (loaded by browser init) and utility init imported stream/web at
the top level, pulling all of Node's internal/webstreams into every launch and
every utilityProcess.fork(); require it where it is used instead. The default
desktop name is only computed on Linux, the only platform that uses it.
* fix: don't start an app instance when a helper is executed without --type
On macOS the helper executables link the same framework as the main
binary, so executing one without a --type argument runs it as a browser
process from inside the helper bundle. That is what child_process.fork()
does when the runAsNode fuse is disabled: the fork wrapper points execPath
at the helper and sets ELECTRON_RUN_AS_NODE, the fuse ignores the
variable, and a second app instance starts whose GPU, network-service and
renderer children then fail to launch because their helper paths are
derived from the helper's own name.
Make helper executables exit with EX_USAGE when no --type is present,
matching Chrome's "Helper application requires --type" check, and make
child_process.fork() throw when the runAsNode fuse is disabled instead of
launching a process that cannot work.
* fix: satisfy -Wunsafe-buffer-usage in the helper argv scan
* chore: format
* fix: detach Debugger from its DevToolsAgentHost when it is collected
The agent host keeps a raw client pointer. If the Debugger was still
attached when cppgc finalized it, a later ForceDetachAllSessions() at
quit called AgentHostClosed() on freed memory.
* fix: prune DataPipeHolder registry entries on collection
The id -> WeakPersistent map was never erased after the cppgc migration,
so it grew by one entry per blob upload element for the life of the
process.
* fix: keep the invoke callback when a handler result cannot be serialized
ReplyChannel moved the mojo callback out before serializing, so a
non-cloneable ipcMain.handle() result dropped it unrun and the renderer's
ipcRenderer.invoke() never settled. Serialize first and leave the callback
in place for the error reply.
* fix: do not CHECK when emitting a warning from a renderer
The isolate-less EmitWarning/EmitDeprecationWarning overloads used
JavascriptEnvironment::GetIsolate(), which only exists in the browser and
utility processes, so nativeImage.createFromNamedImage(name, [h,s,l]) and
long crashReporter.addExtraParameter keys crashed the renderer. Use the
current isolate and fall back to logging.
* fix: settle shell.openPath() on Linux
XDGUtil only ran the callback when waiting for exit, so the promise from
shell.openPath() never settled on Linux. Report launch failure and
success on the no-wait path too.
* fix: keep ServiceWorkerMain registry entries owned by the live instance
A destroyed instance waiting for GC re-ran Destroy() from its destructor
and erased the version-id entry that a newer instance had registered, and
a version that became redundant while already stopped never reached
Destroy() so it stayed pinned. Only erase our own entry and destroy
immediately when the redundant version is not running.
* fix: release UtilityProcess wrappers whose child never launches
Constructor failure paths and a child launch failure never reached
HandleTermination(), so the wrapper stayed self-pinned and no 'exit' was
emitted. Treat both as termination and tolerate a null connector.
* fix: unregister portal shortcuts on Linux and ignore stale activations
On the xdg GlobalShortcuts portal path unregister()/unregisterAll() never
told the portal, so the binding survived and the next key press reached
ExecuteCommand() with an empty map and hit NOTREACHED().
* fix: do not orphan a platform notification created by a re-entrant show()
Close() can emit 'close' synchronously (macOS), and a listener calling
show() from there created a platform notification that the outer Show()
then overwrote, leaving it with a delegate pointer that outlived the
Notification.
* fix: look up the RenderFrame after reading webFrame arguments
executeJavaScriptInIsolatedWorld and setSpellCheckProvider cached the
RenderFrame and then read JS-supplied properties whose getters can detach
the frame, leaving a dangling pointer.
* fix: adopt a newly committed RenderFrameHost in WebFrameMain::From()
content makes the new host current and can run embedder callbacks
(focus/blur from CommitPending) before RenderFrameHostChanged re-keys the
existing WebFrameMain, so touching webContents.mainFrame in that window
constructed a second instance for the same FrameTreeNode and CHECKed.
Also dispose WebFrameMains of a WebContents that api::WebContents stops
observing without owning (webview guests, background pages), and replace
a stale FrameTreeNode entry instead of CHECKing.
* fix: keep gin_helper::Promise on the UI thread in systemPreferences and the MSIX updater
promptTouchID, askForMediaAccess and the MSIX autoUpdater moved or
destroyed the Promise (a cppgc::Persistent) on LocalAuthentication,
AVFoundation, IO and WinRT threads. Bind it into a UI-sequence callback
so only that callback crosses threads, and fix the MSIX put_Completed
failure branch reading callback data that a temporary had already freed.
* fix: release imported shared textures on their owning sequence
VideoFrames created from a shared texture co-own it and can be destroyed
on any thread, which ran the JS release callback and GPU teardown there.
Delete on the owning sequence and post the frame release callback back.
* fix: keep Menu alive while native code still references its model
Root the menu passed to tray.popUpContextMenu() until it is replaced,
remove the tray icon when the Tray constructor throws, and clean up a
views MenuRunner that refused to run. On macOS root the installed
application menu from the holder that owns its controller; on GTK drop
the status icon MenuGtk when the menu is removed; on X11 clear the
in-window menu bar when no global menu bar was in use; and record an
inserted item before setters that can throw.
* fix: fail a chunked net.request upload that the network stack asks to replay
A 307/308 redirect or retry calls StartReading again, which re-entered the
JS body function, threw, and left the request pinned. A JS stream cannot
be replayed, so abort the upload instead.
* fix: re-check MessagePortMain entanglement after serializing the message
Serialization runs JS that can transfer or close the port being posted
from, which left connector_ null for the rest of PostMessage().
* fix: settle the netLog.stopLogging() promise if the exporter pipe disconnects
The Stop() reply owns the promise and is dropped unrun on disconnect, so
the promise never settled. Wrap it so a dropped reply rejects.
* fix: reject foreign receivers in Destroyable destroy()
The unsignatured destroy() read internal field 0 of any receiver as a
WrappableBase and deleted it, including DeprecatedWrappable and Node
objects whose field 0 is something else.
* fix: tear down utility-process AI receivers before the isolate
UtilityAIManager receivers were self-owned and could outlive NodeService's
JavascriptEnvironment; their destructors call into V8. Own them on
NodeService and clear them first.
* fix: check the sending frame for internal guest, window and reply IPCs
* guest-view IPCs (attach/detach/call/property get/set) are honoured only
from the frame that created the <webview>, not from any frame in the
embedder WebContents
* window.close() via the internal IPC is honoured only from the top-level
document, and the renderer override is installed for the main frame only
* replies to webContents.executeJavaScript()/insertCSS() and friends are
accepted only from the main frame they were sent to
* webContents.sendToFrame() only addresses frames that belong to that
WebContents
* the <webview> `ipc-message` event carries `frameOrigin` / `frameUrl` of the
frame in the guest that sent it
* process.getProcessMemoryInfo() reports the calling frame's own process
* docs: validateSender example checks senderFrame.origin
* docs: add API history PR reference
* refactor: drop frameOrigin/frameUrl from the webview ipc-message event
* a link opened into a new window (modifier-click, middle-click, target form
post) is a popup like window.open(): `<webview>` without `allowpopups`
(disablePopups) now blocks it in OpenURLFromTab as CanCreateWindow already
does for window.open()
* the window the app creates for such a link starts its navigation from the
original OpenURLParams (initiator origin/frame, renderer-initiated, user
gesture, post body) instead of a fresh browser-initiated loadURL, so
Sec-Fetch-Site / SameSite, `did-start-navigation` `initiator` and external
protocol attribution describe the clicking document
* docs: allowpopups covers links opened into new windows
* specs for both
* feat: identify the requesting frame and initiator on network, auth and download events
* webRequest listener details gain `initiator`, the origin that issued the
request (kept from the original request across redirects, as in Chrome's
webRequest API)
* protocol.handle() requests and ProtocolRequest gain `initiator`
* DownloadItem gains getInitiatorOrigin(); session 'will-download' gets a
trailing `frame`
* session 'preconnect' gets a trailing `frame`
* specs for each
* docs: add API history PR reference
Emit the bundles under gen/internal/electron/js2c so js2c gives them
internal/electron/js2c/* ids: they leave module.builtinModules, cannot be
required or fetched with process.getBuiltinModule() from app code (which
crashed under ELECTRON_RUN_AS_NODE), and parallel/test-process-get-builtin is
re-enabled.
* refactor: run the init bundles from Node's embedder start callback
Pass a StartExecutionCallback to node::LoadEnvironment() that compiles and
runs electron/js2c/<type>_init, instead of inserting the bundle id as argv[1]
for run_main_module and patching Node not to resolve it. Drops
chore_allow_the_node_entrypoint_to_be_a_builtin_module.patch and the
run_main.js hunks of fix_do_not_resolve_electron_entrypoints.patch; the init
bundles no longer splice argv, and --inspect-brk still pauses on the app's
entry rather than the bundle.
* fix: let exceptions from the init bundle reach uncaughtException
Module.runMain ran the bundle with no v8::TryCatch, so an app main that throws
synchronously was reported through V8's message listener to
process.on('uncaughtException'); CompileAndCall's TryCatch swallowed it and
the app kept running (spec/api-autoupdater-darwin-spec.ts unsigned-app cases).
Split CompileBundle out of CompileAndCall and call the entry bundle bare.
* perf: only refresh Node's cached options when --inspect-brk was masked
The unconditional refreshOptions() in lib/common/init.ts cost ~0.2 ms of every
process start; do it from the start callback only when break_first_line was
actually set.
* refactor: hand crashpad env to run-as-node children from lib/node/init
Wrap internal/child_process's ChildProcess.prototype.spawn and spawnSync in
lib/node/init.ts to add CRASHDUMP_SIGNAL_FD / CRASHPAD_HANDLER_PID for
ELECTRON_RUN_AS_NODE children of process.execPath on Linux, replacing the
patch to Node's normalizeSpawnArguments().
* chore: oxfmt
* fix: leave invalid spawn options to Node's validation
parallel/test-child-process-constructor calls ChildProcess.prototype.spawn with
non-object options and non-array envPairs and expects Node's own errors.
* refactor: look up internalBinding and linked bindings without patching Node
asar-fs-wrapper takes internalBinding from require('internal/bootstrap/realm');
sandboxed preloads resolve linked bindings through NodeBindings::GetLinkedBinding(),
backed by accessors the NODE_LINKED_BINDING_CONTEXT_AWARE macro now emits. Drops
the two Node patches that exposed process.internalBinding and
node::binding::get_linked_module().
* chore: clang-format
* fix: only run the node_init preload in the principal realm
Node runs the embedder preload for ShadowRealms too. The bundle used to bail
there by accident (process.internalBinding was undefined); now that
internalBinding resolves it got as far as require('child_process') and hit the
PipeWrap template CHECK. Skip non-principal realms explicitly, which also lets
parallel/test-shadow-realm-custom-loaders run again.
* chore: clang-format