Files
Samuel Attard a74f72884d fix: cppgc object lifetime follow-ups (#53738)
* 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.
2026-09-09 23:19:04 +00:00

134 lines
5.3 KiB
C++

// Copyright (c) 2019 GitHub, Inc.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_COMMON_NODE_UTIL_H_
#define ELECTRON_SHELL_COMMON_NODE_UTIL_H_
#include <string>
#include <string_view>
#include <vector>
#include "base/containers/span.h"
#include "v8-microtask-queue.h"
#include "v8/include/cppgc/macros.h"
#include "v8/include/v8-forward.h"
namespace node {
class Environment;
class IsolateData;
namespace EnvironmentFlags {
enum Flags : uint64_t;
}
} // namespace node
namespace electron::util {
// Emit a warning via node's process.emitWarning()
void EmitWarning(v8::Isolate* isolate,
std::string_view warning_msg,
std::string_view warning_type);
// Emit a warning via node's process.emitWarning() on the currently entered
// isolate, or log it if there is no isolate / Node.js environment.
void EmitWarning(std::string_view warning_msg, std::string_view warning_type);
// Emit a deprecation warning via node's process.emitWarning()
void EmitDeprecationWarning(v8::Isolate* isolate,
std::string_view warning_msg,
std::string_view deprecation_code = "");
// Emit a deprecation warning via node's process.emitWarning() on the current
// isolate, or log it if there is no isolate / Node.js environment.
void EmitDeprecationWarning(std::string_view warning_msg,
std::string_view deprecation_code = "");
// Compiles one of the electron/js2c/* bundles (with the build-time code cache)
// into a function taking `parameters`; logs and returns empty on failure.
v8::MaybeLocal<v8::Function> CompileBundle(
v8::Local<v8::Context> context,
const char* id,
v8::LocalVector<v8::String>* parameters);
// Run a script with JS source bundled inside the binary as if it's wrapped
// in a function called with a null receiver and arguments specified in C++.
// The returned value is empty if an exception is encountered; the exception
// is caught and logged. JS code run with this method can assume that their
// top-level declarations won't affect the global scope.
v8::MaybeLocal<v8::Value> CompileAndCall(
v8::Isolate* isolate,
v8::Local<v8::Context> context,
const char* id,
v8::LocalVector<v8::String>* parameters,
v8::LocalVector<v8::Value>* arguments);
// Feeds the build-time js2c code cache to the Environment's BuiltinLoader
// so the *_init bundles are consumed. Call once, after CreateEnvironment and
// before LoadEnvironment.
void FeedEnvironmentCodeCache(node::Environment* env);
// Install this process's build-time code cache (see node_natives_code_cache.h)
// as the default every node BuiltinLoader constructed from now on starts with,
// so builtins compiled before Electron can reach an Environment's loader --
// the per-context scripts node::NewContext runs and the whole
// internal/bootstrap/* sequence inside node::CreateEnvironment -- consume it
// too. Idempotent; call before the first node::NewContext / CreateEnvironment
// in a process that hosts a Node.js environment.
void InstallProcessCodeCache();
// Wrapper for node::CreateEnvironment that logs failure
node::Environment* CreateEnvironment(v8::Isolate* isolate,
node::IsolateData* isolate_data,
v8::Local<v8::Context> context,
const std::vector<std::string>& args,
const std::vector<std::string>& exec_args,
node::EnvironmentFlags::Flags env_flags,
std::string_view process_type = "");
v8::Local<v8::Object> CreateAbortController(v8::Isolate* isolate);
// A scope that temporarily changes the microtask policy to explicit. Use this
// anywhere that can trigger Node.js or uv_run().
//
// Node.js expects `kExplicit` microtasks policy and will run microtasks
// checkpoints after every call into JavaScript. Since we use a different
// policy in the renderer, this scope temporarily changes the policy to
// `kExplicit` while the scope is active, then restores the original policy
// when it's destroyed.
class ExplicitMicrotasksScope {
CPPGC_STACK_ALLOCATED();
public:
explicit ExplicitMicrotasksScope(v8::MicrotaskQueue* queue);
~ExplicitMicrotasksScope();
ExplicitMicrotasksScope(const ExplicitMicrotasksScope&) = delete;
ExplicitMicrotasksScope& operator=(const ExplicitMicrotasksScope&) = delete;
private:
v8::MicrotaskQueue* microtask_queue_;
v8::MicrotasksPolicy original_policy_;
};
} // namespace electron::util
namespace electron::Buffer {
// Convenience function to view a Node buffer's data as a base::span().
// Analogous to base::as_byte_span()
[[nodiscard]] base::span<uint8_t> as_byte_span(
v8::Local<v8::Value> node_buffer);
// span-friendly version of node::Buffer::Copy()
[[nodiscard]] v8::MaybeLocal<v8::Object> Copy(v8::Isolate* isolate,
base::span<const char> data);
// span-friendly version of node::Buffer::Copy()
[[nodiscard]] v8::MaybeLocal<v8::Object> Copy(v8::Isolate* isolate,
base::span<const uint8_t> data);
} // namespace electron::Buffer
#endif // ELECTRON_SHELL_COMMON_NODE_UTIL_H_