refactor: allocate api::App on cpp heap (#48118)
This commit is contained in:
parent
d4b7d9e9cf
commit
dd54e84a58
7 changed files with 133 additions and 26 deletions
|
@ -1,11 +1,14 @@
|
||||||
import { Menu } from 'electron/main';
|
import { Menu } from 'electron/main';
|
||||||
|
|
||||||
|
import { EventEmitter } from 'events';
|
||||||
import * as fs from 'fs';
|
import * as fs from 'fs';
|
||||||
|
|
||||||
const bindings = process._linkedBinding('electron_browser_app');
|
const bindings = process._linkedBinding('electron_browser_app');
|
||||||
const commandLine = process._linkedBinding('electron_common_command_line');
|
const commandLine = process._linkedBinding('electron_common_command_line');
|
||||||
const { app } = bindings;
|
const { app } = bindings;
|
||||||
|
|
||||||
|
Object.setPrototypeOf(app, EventEmitter.prototype);
|
||||||
|
|
||||||
// Only one app object permitted.
|
// Only one app object permitted.
|
||||||
export default app;
|
export default app;
|
||||||
|
|
||||||
|
|
|
@ -138,3 +138,4 @@ fix_add_macos_memory_query_fallback_to_avoid_crash.patch
|
||||||
fix_resolve_dynamic_background_material_update_issue_on_windows_11.patch
|
fix_resolve_dynamic_background_material_update_issue_on_windows_11.patch
|
||||||
chore_restore_some_deprecated_wrapper_utility_in_gin.patch
|
chore_restore_some_deprecated_wrapper_utility_in_gin.patch
|
||||||
feat_add_support_for_embedder_snapshot_validation.patch
|
feat_add_support_for_embedder_snapshot_validation.patch
|
||||||
|
chore_add_electron_objects_to_wrappablepointertag.patch
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||||
|
From: deepak1556 <hop2deep@gmail.com>
|
||||||
|
Date: Wed, 20 Aug 2025 04:03:11 +0900
|
||||||
|
Subject: chore: add electron objects to WrappablePointerTag
|
||||||
|
|
||||||
|
Extends gin::WrappablePointerTag with tags needed for
|
||||||
|
electron objects that extend gin::Wrappable and gets
|
||||||
|
allocated on the cpp heap
|
||||||
|
|
||||||
|
diff --git a/gin/public/wrappable_pointer_tags.h b/gin/public/wrappable_pointer_tags.h
|
||||||
|
index a507d1d837ab3ec2b2d3ae7978d9d410ab2ec2d1..a547ecacad732f73512abbe5da81483208bb9e81 100644
|
||||||
|
--- a/gin/public/wrappable_pointer_tags.h
|
||||||
|
+++ b/gin/public/wrappable_pointer_tags.h
|
||||||
|
@@ -72,7 +72,8 @@ enum WrappablePointerTag : uint16_t {
|
||||||
|
kTextInputControllerBindings, // content::TextInputControllerBindings
|
||||||
|
kWebAXObjectProxy, // content::WebAXObjectProxy
|
||||||
|
kWrappedExceptionHandler, // extensions::WrappedExceptionHandler
|
||||||
|
- kLastPointerTag = kWrappedExceptionHandler,
|
||||||
|
+ kElectronApp, // electron::api::App
|
||||||
|
+ kLastPointerTag = kElectronApp,
|
||||||
|
};
|
||||||
|
|
||||||
|
static_assert(kLastPointerTag <
|
|
@ -75,7 +75,6 @@
|
||||||
#include "shell/common/gin_converters/value_converter.h"
|
#include "shell/common/gin_converters/value_converter.h"
|
||||||
#include "shell/common/gin_helper/dictionary.h"
|
#include "shell/common/gin_helper/dictionary.h"
|
||||||
#include "shell/common/gin_helper/error_thrower.h"
|
#include "shell/common/gin_helper/error_thrower.h"
|
||||||
#include "shell/common/gin_helper/handle.h"
|
|
||||||
#include "shell/common/gin_helper/object_template_builder.h"
|
#include "shell/common/gin_helper/object_template_builder.h"
|
||||||
#include "shell/common/language_util.h"
|
#include "shell/common/language_util.h"
|
||||||
#include "shell/common/node_includes.h"
|
#include "shell/common/node_includes.h"
|
||||||
|
@ -83,6 +82,8 @@
|
||||||
#include "shell/common/thread_restrictions.h"
|
#include "shell/common/thread_restrictions.h"
|
||||||
#include "shell/common/v8_util.h"
|
#include "shell/common/v8_util.h"
|
||||||
#include "ui/gfx/image/image.h"
|
#include "ui/gfx/image/image.h"
|
||||||
|
#include "v8/include/cppgc/allocation.h"
|
||||||
|
#include "v8/include/v8-traced-handle.h"
|
||||||
|
|
||||||
#if BUILDFLAG(IS_WIN)
|
#if BUILDFLAG(IS_WIN)
|
||||||
#include "base/strings/utf_string_conversions.h"
|
#include "base/strings/utf_string_conversions.h"
|
||||||
|
@ -365,7 +366,11 @@ struct Converter<net::SecureDnsMode> {
|
||||||
|
|
||||||
namespace electron::api {
|
namespace electron::api {
|
||||||
|
|
||||||
gin::DeprecatedWrapperInfo App::kWrapperInfo = {gin::kEmbedderNativeGin};
|
gin::WrapperInfo App::kWrapperInfo = {{gin::kEmbedderNativeGin},
|
||||||
|
gin::kElectronApp};
|
||||||
|
|
||||||
|
// static
|
||||||
|
cppgc::Persistent<App> App::instance_;
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
@ -561,7 +566,6 @@ App::App() {
|
||||||
App::~App() {
|
App::~App() {
|
||||||
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
static_cast<ElectronBrowserClient*>(ElectronBrowserClient::Get())
|
||||||
->set_delegate(nullptr);
|
->set_delegate(nullptr);
|
||||||
Browser::Get()->RemoveObserver(this);
|
|
||||||
content::GpuDataManager::GetInstance()->RemoveObserver(this);
|
content::GpuDataManager::GetInstance()->RemoveObserver(this);
|
||||||
content::BrowserChildProcessObserver::Remove(this);
|
content::BrowserChildProcessObserver::Remove(this);
|
||||||
}
|
}
|
||||||
|
@ -1582,7 +1586,7 @@ void DockSetMenu(electron::api::Menu* menu) {
|
||||||
}
|
}
|
||||||
|
|
||||||
v8::Local<v8::Value> App::GetDockAPI(v8::Isolate* isolate) {
|
v8::Local<v8::Value> App::GetDockAPI(v8::Isolate* isolate) {
|
||||||
if (dock_.IsEmpty()) {
|
if (dock_.IsEmptyThreadSafe()) {
|
||||||
// Initialize the Dock API, the methods are bound to "dock" which exists
|
// Initialize the Dock API, the methods are bound to "dock" which exists
|
||||||
// for the lifetime of "app"
|
// for the lifetime of "app"
|
||||||
auto browser = base::Unretained(Browser::Get());
|
auto browser = base::Unretained(Browser::Get());
|
||||||
|
@ -1610,7 +1614,7 @@ v8::Local<v8::Value> App::GetDockAPI(v8::Isolate* isolate) {
|
||||||
|
|
||||||
dock_.Reset(isolate, dock_obj.GetHandle());
|
dock_.Reset(isolate, dock_obj.GetHandle());
|
||||||
}
|
}
|
||||||
return v8::Local<v8::Value>::New(isolate, dock_);
|
return dock_.Get(isolate);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -1697,18 +1701,33 @@ void ConfigureHostResolver(v8::Isolate* isolate,
|
||||||
|
|
||||||
// static
|
// static
|
||||||
App* App::Get() {
|
App* App::Get() {
|
||||||
static base::NoDestructor<App> app;
|
CHECK_NE(instance_, nullptr);
|
||||||
return app.get();
|
return instance_.Get();
|
||||||
}
|
}
|
||||||
|
|
||||||
// static
|
// static
|
||||||
gin_helper::Handle<App> App::Create(v8::Isolate* isolate) {
|
App* App::Create(v8::Isolate* isolate) {
|
||||||
return gin_helper::CreateHandle(isolate, Get());
|
if (!instance_) {
|
||||||
|
instance_ = cppgc::MakeGarbageCollected<App>(
|
||||||
|
isolate->GetCppHeap()->GetAllocationHandle());
|
||||||
|
}
|
||||||
|
return instance_.Get();
|
||||||
|
}
|
||||||
|
|
||||||
|
const gin::WrapperInfo* App::wrapper_info() const {
|
||||||
|
return &kWrapperInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
void App::Trace(cppgc::Visitor* visitor) const {
|
||||||
|
gin::Wrappable<App>::Trace(visitor);
|
||||||
|
#if BUILDFLAG(IS_MAC)
|
||||||
|
visitor->Trace(dock_);
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
|
gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
|
||||||
auto browser = base::Unretained(Browser::Get());
|
auto browser = base::Unretained(Browser::Get());
|
||||||
return gin_helper::EventEmitterMixin<App>::GetObjectTemplateBuilder(isolate)
|
return gin::Wrappable<App>::GetObjectTemplateBuilder(isolate)
|
||||||
.SetMethod("quit", base::BindRepeating(&Browser::Quit, browser))
|
.SetMethod("quit", base::BindRepeating(&Browser::Quit, browser))
|
||||||
.SetMethod("exit", base::BindRepeating(&Browser::Exit, browser))
|
.SetMethod("exit", base::BindRepeating(&Browser::Exit, browser))
|
||||||
.SetMethod("focus", base::BindRepeating(&Browser::Focus, browser))
|
.SetMethod("focus", base::BindRepeating(&Browser::Focus, browser))
|
||||||
|
@ -1850,8 +1869,8 @@ gin::ObjectTemplateBuilder App::GetObjectTemplateBuilder(v8::Isolate* isolate) {
|
||||||
.SetMethod("resolveProxy", &App::ResolveProxy);
|
.SetMethod("resolveProxy", &App::ResolveProxy);
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* App::GetTypeName() {
|
const char* App::GetHumanReadableName() const {
|
||||||
return "App";
|
return "Electron / App";
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace electron::api
|
} // namespace electron::api
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
#include "content/public/browser/scoped_accessibility_mode.h"
|
#include "content/public/browser/scoped_accessibility_mode.h"
|
||||||
#include "crypto/crypto_buildflags.h"
|
#include "crypto/crypto_buildflags.h"
|
||||||
#include "electron/mas.h"
|
#include "electron/mas.h"
|
||||||
|
#include "gin/wrappable.h"
|
||||||
#include "net/base/completion_once_callback.h"
|
#include "net/base/completion_once_callback.h"
|
||||||
#include "net/base/completion_repeating_callback.h"
|
#include "net/base/completion_repeating_callback.h"
|
||||||
#include "net/base/features.h"
|
#include "net/base/features.h"
|
||||||
|
@ -27,7 +28,7 @@
|
||||||
#include "shell/browser/browser_observer.h"
|
#include "shell/browser/browser_observer.h"
|
||||||
#include "shell/browser/electron_browser_client.h"
|
#include "shell/browser/electron_browser_client.h"
|
||||||
#include "shell/browser/event_emitter_mixin.h"
|
#include "shell/browser/event_emitter_mixin.h"
|
||||||
#include "shell/common/gin_helper/wrappable.h"
|
#include "v8/include/cppgc/persistent.h"
|
||||||
|
|
||||||
#if BUILDFLAG(USE_NSS_CERTS)
|
#if BUILDFLAG(USE_NSS_CERTS)
|
||||||
#include "shell/browser/certificate_manager_model.h"
|
#include "shell/browser/certificate_manager_model.h"
|
||||||
|
@ -40,10 +41,13 @@ class FilePath;
|
||||||
namespace gin_helper {
|
namespace gin_helper {
|
||||||
class Dictionary;
|
class Dictionary;
|
||||||
class ErrorThrower;
|
class ErrorThrower;
|
||||||
template <typename T>
|
|
||||||
class Handle;
|
|
||||||
} // namespace gin_helper
|
} // namespace gin_helper
|
||||||
|
|
||||||
|
namespace v8 {
|
||||||
|
template <typename T>
|
||||||
|
class TracedReference;
|
||||||
|
}
|
||||||
|
|
||||||
namespace electron {
|
namespace electron {
|
||||||
|
|
||||||
struct ProcessMetric;
|
struct ProcessMetric;
|
||||||
|
@ -54,21 +58,23 @@ enum class JumpListResult : int;
|
||||||
|
|
||||||
namespace api {
|
namespace api {
|
||||||
|
|
||||||
class App final : public ElectronBrowserClient::Delegate,
|
class App final : public gin::Wrappable<App>,
|
||||||
public gin_helper::DeprecatedWrappable<App>,
|
public ElectronBrowserClient::Delegate,
|
||||||
public gin_helper::EventEmitterMixin<App>,
|
public gin_helper::EventEmitterMixin<App>,
|
||||||
private BrowserObserver,
|
private BrowserObserver,
|
||||||
private content::GpuDataManagerObserver,
|
private content::GpuDataManagerObserver,
|
||||||
private content::BrowserChildProcessObserver {
|
private content::BrowserChildProcessObserver {
|
||||||
public:
|
public:
|
||||||
static gin_helper::Handle<App> Create(v8::Isolate* isolate);
|
static App* Create(v8::Isolate* isolate);
|
||||||
static App* Get();
|
static App* Get();
|
||||||
|
|
||||||
// gin_helper::Wrappable
|
// gin::Wrappable
|
||||||
static gin::DeprecatedWrapperInfo kWrapperInfo;
|
static gin::WrapperInfo kWrapperInfo;
|
||||||
|
void Trace(cppgc::Visitor*) const override;
|
||||||
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
gin::ObjectTemplateBuilder GetObjectTemplateBuilder(
|
||||||
v8::Isolate* isolate) override;
|
v8::Isolate* isolate) override;
|
||||||
const char* GetTypeName() override;
|
const gin::WrapperInfo* wrapper_info() const override;
|
||||||
|
const char* GetHumanReadableName() const override;
|
||||||
|
|
||||||
#if BUILDFLAG(USE_NSS_CERTS)
|
#if BUILDFLAG(USE_NSS_CERTS)
|
||||||
void OnCertificateManagerModelCreated(
|
void OnCertificateManagerModelCreated(
|
||||||
|
@ -84,14 +90,13 @@ class App final : public ElectronBrowserClient::Delegate,
|
||||||
static bool IsPackaged();
|
static bool IsPackaged();
|
||||||
|
|
||||||
App();
|
App();
|
||||||
|
~App() override;
|
||||||
|
|
||||||
// disable copy
|
// disable copy
|
||||||
App(const App&) = delete;
|
App(const App&) = delete;
|
||||||
App& operator=(const App&) = delete;
|
App& operator=(const App&) = delete;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
~App() override;
|
|
||||||
|
|
||||||
// BrowserObserver:
|
// BrowserObserver:
|
||||||
void OnBeforeQuit(bool* prevent_default) override;
|
void OnBeforeQuit(bool* prevent_default) override;
|
||||||
void OnWillQuit(bool* prevent_default) override;
|
void OnWillQuit(bool* prevent_default) override;
|
||||||
|
@ -236,7 +241,7 @@ class App final : public ElectronBrowserClient::Delegate,
|
||||||
bool MoveToApplicationsFolder(gin_helper::ErrorThrower, gin::Arguments* args);
|
bool MoveToApplicationsFolder(gin_helper::ErrorThrower, gin::Arguments* args);
|
||||||
bool IsInApplicationsFolder();
|
bool IsInApplicationsFolder();
|
||||||
v8::Local<v8::Value> GetDockAPI(v8::Isolate* isolate);
|
v8::Local<v8::Value> GetDockAPI(v8::Isolate* isolate);
|
||||||
v8::Global<v8::Value> dock_;
|
v8::TracedReference<v8::Value> dock_;
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
|
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
|
||||||
|
@ -277,6 +282,8 @@ class App final : public ElectronBrowserClient::Delegate,
|
||||||
bool watch_singleton_socket_on_ready_ = false;
|
bool watch_singleton_socket_on_ready_ = false;
|
||||||
|
|
||||||
std::unique_ptr<content::ScopedAccessibilityMode> scoped_accessibility_mode_;
|
std::unique_ptr<content::ScopedAccessibilityMode> scoped_accessibility_mode_;
|
||||||
|
|
||||||
|
static cppgc::Persistent<App> instance_;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace api
|
} // namespace api
|
||||||
|
|
|
@ -377,9 +377,18 @@ bool IsAllowedOption(const std::string_view option) {
|
||||||
"--no-experimental-global-navigator",
|
"--no-experimental-global-navigator",
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// This should be aligned with what's possible to set via the process object.
|
||||||
|
static constexpr auto unpacked_options =
|
||||||
|
base::MakeFixedFlatSet<std::string_view>({
|
||||||
|
"--expose-internals",
|
||||||
|
});
|
||||||
|
|
||||||
if (debug_options.contains(option))
|
if (debug_options.contains(option))
|
||||||
return electron::fuses::IsNodeCliInspectEnabled();
|
return electron::fuses::IsNodeCliInspectEnabled();
|
||||||
|
|
||||||
|
if (unpacked_options.contains(option))
|
||||||
|
return !electron::api::App::IsPackaged();
|
||||||
|
|
||||||
return options.contains(option);
|
return options.contains(option);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -601,6 +610,7 @@ void NodeBindings::Initialize(v8::Isolate* const isolate,
|
||||||
node::per_process::cli_options->disable_wasm_trap_handler = true;
|
node::per_process::cli_options->disable_wasm_trap_handler = true;
|
||||||
|
|
||||||
uint64_t process_flags =
|
uint64_t process_flags =
|
||||||
|
node::ProcessInitializationFlags::kNoInitializeCppgc |
|
||||||
node::ProcessInitializationFlags::kNoInitializeV8 |
|
node::ProcessInitializationFlags::kNoInitializeV8 |
|
||||||
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform;
|
node::ProcessInitializationFlags::kNoInitializeNodeV8Platform;
|
||||||
|
|
||||||
|
@ -610,8 +620,7 @@ void NodeBindings::Initialize(v8::Isolate* const isolate,
|
||||||
process_flags |= node::ProcessInitializationFlags::kEnableStdioInheritance;
|
process_flags |= node::ProcessInitializationFlags::kEnableStdioInheritance;
|
||||||
|
|
||||||
if (browser_env_ == BrowserEnvironment::kRenderer)
|
if (browser_env_ == BrowserEnvironment::kRenderer)
|
||||||
process_flags |= node::ProcessInitializationFlags::kNoInitializeCppgc |
|
process_flags |= node::ProcessInitializationFlags::kNoDefaultSignalHandling;
|
||||||
node::ProcessInitializationFlags::kNoDefaultSignalHandling;
|
|
||||||
|
|
||||||
if (!fuses::IsNodeOptionsEnabled())
|
if (!fuses::IsNodeOptionsEnabled())
|
||||||
process_flags |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv;
|
process_flags |= node::ProcessInitializationFlags::kDisableNodeOptionsEnv;
|
||||||
|
|
45
spec/cpp-heap-spec.ts
Normal file
45
spec/cpp-heap-spec.ts
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import { expect } from 'chai';
|
||||||
|
|
||||||
|
import * as path from 'node:path';
|
||||||
|
|
||||||
|
import { startRemoteControlApp } from './lib/spec-helpers';
|
||||||
|
|
||||||
|
describe('cpp heap', () => {
|
||||||
|
describe('app module', () => {
|
||||||
|
it('should not allocate on every require', async () => {
|
||||||
|
const { remotely } = await startRemoteControlApp();
|
||||||
|
const [usedBefore, usedAfter] = await remotely(async () => {
|
||||||
|
const { getCppHeapStatistics } = require('node:v8');
|
||||||
|
const heapStatsBefore = getCppHeapStatistics('brief');
|
||||||
|
{
|
||||||
|
const { app } = require('electron');
|
||||||
|
console.log(app.name);
|
||||||
|
}
|
||||||
|
{
|
||||||
|
const { app } = require('electron');
|
||||||
|
console.log(app.dock);
|
||||||
|
}
|
||||||
|
const heapStatsAfter = getCppHeapStatistics('brief');
|
||||||
|
return [heapStatsBefore.used_size_bytes, heapStatsAfter.used_size_bytes];
|
||||||
|
});
|
||||||
|
expect(usedBefore).to.be.equal(usedAfter);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should record as node in heap snapshot', async () => {
|
||||||
|
const { remotely } = await startRemoteControlApp(['--expose-internals']);
|
||||||
|
const [nodeCount, hasPersistentParent] = await remotely(async (heap: string) => {
|
||||||
|
const { recordState } = require(heap);
|
||||||
|
const state = recordState();
|
||||||
|
const rootNodes = state.snapshot.filter(
|
||||||
|
(node: any) => node.name === 'Electron / App' && node.type !== 'string');
|
||||||
|
const hasParent = rootNodes.some((node: any) => node.incomingEdges.some(
|
||||||
|
(edge: any) => {
|
||||||
|
return edge.type === 'element' && edge.from.name === 'C++ Persistent roots';
|
||||||
|
}));
|
||||||
|
return [rootNodes.length, hasParent];
|
||||||
|
}, path.join(__dirname, '../../third_party/electron_node/test/common/heap'));
|
||||||
|
expect(nodeCount).to.equal(1);
|
||||||
|
expect(hasPersistentParent).to.be.true();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
Loading…
Add table
Add a link
Reference in a new issue