refactor: allocate api::Session on cpp heap (#48141)

This commit is contained in:
Robo 2025-08-25 18:52:06 +09:00 committed by GitHub
commit 3ccb1bc0a8
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
32 changed files with 632 additions and 293 deletions

View file

@ -8,16 +8,17 @@ 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
index a507d1d837ab3ec2b2d3ae7978d9d410ab2ec2d1..5a69c5c7c5ed0e834e09ff3a2d0f0126ba4ccf99 100644
--- a/gin/public/wrappable_pointer_tags.h
+++ b/gin/public/wrappable_pointer_tags.h
@@ -72,7 +72,8 @@ enum WrappablePointerTag : uint16_t {
@@ -72,7 +72,9 @@ enum WrappablePointerTag : uint16_t {
kTextInputControllerBindings, // content::TextInputControllerBindings
kWebAXObjectProxy, // content::WebAXObjectProxy
kWrappedExceptionHandler, // extensions::WrappedExceptionHandler
- kLastPointerTag = kWrappedExceptionHandler,
+ kElectronApp, // electron::api::App
+ kLastPointerTag = kElectronApp,
+ kElectronSession, // electron::api::Session
+ kLastPointerTag = kElectronSession,
};
static_assert(kLastPointerTag <

View file

@ -8,6 +8,30 @@ Restores part of https://chromium-review.googlesource.com/c/chromium/src/+/67991
Patch can be removed once cppgc migration is complete
https://github.com/electron/electron/issues/47922
diff --git a/gin/function_template.h b/gin/function_template.h
index 84ab9585240a49048774811718f7ebd6f988e485..f062163cdd81def12fae7e507d18a9133dd0804d 100644
--- a/gin/function_template.h
+++ b/gin/function_template.h
@@ -77,6 +77,7 @@ class GIN_EXPORT CallbackHolderBase {
CallbackHolderBase* holder);
~DisposeObserver() override;
void OnBeforeDispose(v8::Isolate* isolate) override;
+ void OnBeforeMicrotasksRunnerDispose(v8::Isolate* isolate) override {}
void OnDisposed() override;
private:
diff --git a/gin/isolate_holder.cc b/gin/isolate_holder.cc
index bb1639d73070a99984b72eb61afd001dec5b08ff..b036f324309c46c53b74124da3ea830d39a973e3 100644
--- a/gin/isolate_holder.cc
+++ b/gin/isolate_holder.cc
@@ -224,6 +224,7 @@ void IsolateHolder::WillCreateMicrotasksRunner() {
void IsolateHolder::WillDestroyMicrotasksRunner() {
DCHECK(g_initialized_microtasks_runner);
+ isolate_data_->NotifyBeforeMicrotasksRunnerDispose();
g_destroyed_microtasks_runner = true;
}
diff --git a/gin/object_template_builder.cc b/gin/object_template_builder.cc
index 5a31687bbd0fca61db3a7c41ed73d938340d6446..b84f5fd336bc4b61b2cd0b2fc92382b00e928701 100644
--- a/gin/object_template_builder.cc
@ -22,10 +46,10 @@ index 5a31687bbd0fca61db3a7c41ed73d938340d6446..b84f5fd336bc4b61b2cd0b2fc92382b0
ObjectTemplateBuilder::ObjectTemplateBuilder(v8::Isolate* isolate,
diff --git a/gin/per_isolate_data.cc b/gin/per_isolate_data.cc
index 884990426f13a6abca22a60dd8cc0685f8435b23..64ac0a64a05105532f3cda898aeac68c21338e60 100644
index 884990426f13a6abca22a60dd8cc0685f8435b23..d1014af4b63da244820ff865a8e824ddf68433a9 100644
--- a/gin/per_isolate_data.cc
+++ b/gin/per_isolate_data.cc
@@ -68,12 +68,32 @@ PerIsolateData* PerIsolateData::From(Isolate* isolate) {
@@ -68,12 +68,37 @@ PerIsolateData* PerIsolateData::From(Isolate* isolate) {
return static_cast<PerIsolateData*>(isolate->GetData(kEmbedderNativeGin));
}
@ -40,8 +64,13 @@ index 884990426f13a6abca22a60dd8cc0685f8435b23..64ac0a64a05105532f3cda898aeac68c
object_templates_[info] = Eternal<ObjectTemplate>(isolate_, templ);
}
+void PerIsolateData::SetFunctionTemplate(DeprecatedWrapperInfo* info,
+ Local<FunctionTemplate> templ) {
+void PerIsolateData::DeprecatedSetFunctionTemplate(
+ DeprecatedWrapperInfo* info, Local<FunctionTemplate> templ) {
+ deprecated_function_templates_[info] = Eternal<FunctionTemplate>(isolate_, templ);
+}
+
+void PerIsolateData::SetFunctionTemplate(
+ const WrapperInfo* info, Local<FunctionTemplate> templ) {
+ function_templates_[info] = Eternal<FunctionTemplate>(isolate_, templ);
+}
+
@ -58,12 +87,22 @@ index 884990426f13a6abca22a60dd8cc0685f8435b23..64ac0a64a05105532f3cda898aeac68c
v8::Local<v8::ObjectTemplate> PerIsolateData::GetObjectTemplate(
const WrapperInfo* info) {
ObjectTemplateMap::iterator it = object_templates_.find(info);
@@ -83,6 +103,15 @@ v8::Local<v8::ObjectTemplate> PerIsolateData::GetObjectTemplate(
@@ -83,6 +108,25 @@ v8::Local<v8::ObjectTemplate> PerIsolateData::GetObjectTemplate(
return it->second.Get(isolate_);
}
+v8::Local<v8::FunctionTemplate> PerIsolateData::GetFunctionTemplate(
+v8::Local<v8::FunctionTemplate> PerIsolateData::DeprecatedGetFunctionTemplate(
+ DeprecatedWrapperInfo* info) {
+ DeprecatedFunctionTemplateMap::iterator it =
+ deprecated_function_templates_.find(info);
+ if (it == deprecated_function_templates_.end()) {
+ return v8::Local<v8::FunctionTemplate>();
+ }
+ return it->second.Get(isolate_);
+}
+
+v8::Local<v8::FunctionTemplate> PerIsolateData::GetFunctionTemplate(
+ const WrapperInfo* info) {
+ FunctionTemplateMap::iterator it = function_templates_.find(info);
+ if (it == function_templates_.end()) {
+ return v8::Local<v8::FunctionTemplate>();
@ -74,11 +113,35 @@ index 884990426f13a6abca22a60dd8cc0685f8435b23..64ac0a64a05105532f3cda898aeac68c
void PerIsolateData::AddDisposeObserver(DisposeObserver* observer) {
dispose_observers_.AddObserver(observer);
}
@@ -97,6 +141,12 @@ void PerIsolateData::NotifyBeforeDispose() {
}
}
+void PerIsolateData::NotifyBeforeMicrotasksRunnerDispose() {
+ for (auto& observer : dispose_observers_) {
+ observer.OnBeforeMicrotasksRunnerDispose(isolate_.get());
+ }
+}
+
void PerIsolateData::NotifyDisposed() {
for (auto& observer : dispose_observers_) {
observer.OnDisposed();
diff --git a/gin/per_isolate_data.h b/gin/per_isolate_data.h
index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bcd530541d 100644
index bce889749415da341e6e6e4082ac06bbeb4bb80a..2f8abc344c77713fb10d83e51ba486c84ab93474 100644
--- a/gin/per_isolate_data.h
+++ b/gin/per_isolate_data.h
@@ -51,11 +51,24 @@ class GIN_EXPORT PerIsolateData {
@@ -34,6 +34,10 @@ class GIN_EXPORT PerIsolateData {
// be entered before the observer is notified, but there will not be a
// handle scope by default.
virtual void OnBeforeDispose(v8::Isolate* isolate) = 0;
+
+ // Called just before the microtasks runner is about to be disposed.
+ virtual void OnBeforeMicrotasksRunnerDispose(v8::Isolate* isolate) = 0;
+
// Called just after the isolate has been disposed.
virtual void OnDisposed() = 0;
};
@@ -51,14 +55,36 @@ class GIN_EXPORT PerIsolateData {
static PerIsolateData* From(v8::Isolate* isolate);
@ -89,7 +152,12 @@ index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bc
void SetObjectTemplate(const WrapperInfo* info,
v8::Local<v8::ObjectTemplate> object_template);
+ void SetFunctionTemplate(DeprecatedWrapperInfo* info,
+ void DeprecatedSetFunctionTemplate(
+ DeprecatedWrapperInfo* info,
+ v8::Local<v8::FunctionTemplate> function_template);
+
+ void SetFunctionTemplate(
+ const WrapperInfo* info,
+ v8::Local<v8::FunctionTemplate> function_template);
+
+ v8::Local<v8::ObjectTemplate> DeprecatedGetObjectTemplate(
@ -97,13 +165,20 @@ index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bc
+
v8::Local<v8::ObjectTemplate> GetObjectTemplate(const WrapperInfo* info);
+ v8::Local<v8::FunctionTemplate> GetFunctionTemplate(
+ v8::Local<v8::FunctionTemplate> DeprecatedGetFunctionTemplate(
+ DeprecatedWrapperInfo* info);
+
+ v8::Local<v8::FunctionTemplate> GetFunctionTemplate(
+ const WrapperInfo* info);
+
void AddDisposeObserver(DisposeObserver* observer);
void RemoveDisposeObserver(DisposeObserver* observer);
void NotifyBeforeDispose();
@@ -74,14 +87,20 @@ class GIN_EXPORT PerIsolateData {
+ void NotifyBeforeMicrotasksRunnerDispose();
void NotifyDisposed();
void EnableIdleTasks(std::unique_ptr<V8IdleTaskRunner> idle_task_runner);
@@ -74,14 +100,23 @@ class GIN_EXPORT PerIsolateData {
}
private:
@ -112,6 +187,8 @@ index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bc
typedef std::map<const WrapperInfo*, v8::Eternal<v8::ObjectTemplate>>
ObjectTemplateMap;
+ typedef std::map<DeprecatedWrapperInfo*, v8::Eternal<v8::FunctionTemplate>>
+ DeprecatedFunctionTemplateMap;
+ typedef std::map<const WrapperInfo*, v8::Eternal<v8::FunctionTemplate>>
+ FunctionTemplateMap;
// PerIsolateData doesn't actually own |isolate_|. Instead, the isolate is
@ -120,6 +197,7 @@ index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bc
raw_ptr<v8::ArrayBuffer::Allocator, DanglingUntriaged> allocator_;
+ DeprecatedObjectTemplateMap deprecated_object_templates_;
ObjectTemplateMap object_templates_;
+ DeprecatedFunctionTemplateMap deprecated_function_templates_;
+ FunctionTemplateMap function_templates_;
base::ObserverList<DisposeObserver> dispose_observers_;
std::shared_ptr<V8ForegroundTaskRunnerBase> task_runner_;

View file

@ -99,6 +99,7 @@
#include "third_party/blink/public/mojom/mediastream/media_stream.mojom.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/origin.h"
#include "v8/include/v8-traced-handle.h"
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "shell/browser/api/electron_api_extensions.h"
@ -544,23 +545,27 @@ class DictionaryObserver final : public SpellcheckCustomDictionary::Observer {
#endif // BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
struct UserDataLink : base::SupportsUserData::Data {
explicit UserDataLink(base::WeakPtr<Session> session_in)
: session{std::move(session_in)} {}
explicit UserDataLink(
cppgc::WeakPersistent<gin::WeakCell<Session>> session_in)
: session{session_in} {}
base::WeakPtr<Session> session;
cppgc::WeakPersistent<gin::WeakCell<Session>> session;
};
const void* kElectronApiSessionKey = &kElectronApiSessionKey;
} // namespace
gin::DeprecatedWrapperInfo Session::kWrapperInfo = {gin::kEmbedderNativeGin};
gin::WrapperInfo Session::kWrapperInfo = {{gin::kEmbedderNativeGin},
gin::kElectronSession};
Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context)
: isolate_(isolate),
network_emulation_token_(base::UnguessableToken::Create()),
browser_context_{
raw_ref<ElectronBrowserContext>::from_ptr(browser_context)} {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
data->AddDisposeObserver(this);
// Observe DownloadManager to get download notifications.
browser_context->GetDownloadManager()->AddObserver(this);
@ -572,7 +577,10 @@ Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context)
browser_context->SetUserData(
kElectronApiSessionKey,
std::make_unique<UserDataLink>(weak_factory_.GetWeakPtr()));
std::make_unique<UserDataLink>(
cppgc::WeakPersistent<gin::WeakCell<Session>>(
weak_factory_.GetWeakCell(
isolate->GetCppHeap()->GetAllocationHandle()))));
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
if (auto* service =
@ -583,6 +591,11 @@ Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context)
}
Session::~Session() {
Dispose();
}
void Session::Dispose() {
if (keep_alive_) {
browser_context()->GetDownloadManager()->RemoveObserver(this);
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
@ -591,6 +604,7 @@ Session::~Session() {
service->SetHunspellObserver(nullptr);
}
#endif
}
}
void Session::OnDownloadCreated(content::DownloadManager* manager,
@ -1307,7 +1321,7 @@ v8::Local<v8::Promise> Session::GetSharedDictionaryUsageInfo() {
}
v8::Local<v8::Value> Session::Cookies(v8::Isolate* isolate) {
if (cookies_.IsEmpty()) {
if (cookies_.IsEmptyThreadSafe()) {
auto handle = Cookies::Create(isolate, browser_context());
cookies_.Reset(isolate, handle.ToV8());
}
@ -1316,7 +1330,7 @@ v8::Local<v8::Value> Session::Cookies(v8::Isolate* isolate) {
v8::Local<v8::Value> Session::Extensions(v8::Isolate* isolate) {
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
if (extensions_.IsEmpty()) {
if (extensions_.IsEmptyThreadSafe()) {
v8::Local<v8::Value> handle;
handle = Extensions::Create(isolate, browser_context()).ToV8();
extensions_.Reset(isolate, handle);
@ -1330,7 +1344,7 @@ v8::Local<v8::Value> Session::Protocol(v8::Isolate* isolate) {
}
v8::Local<v8::Value> Session::ServiceWorkerContext(v8::Isolate* isolate) {
if (service_worker_context_.IsEmpty()) {
if (service_worker_context_.IsEmptyThreadSafe()) {
v8::Local<v8::Value> handle;
handle = ServiceWorkerContext::Create(isolate, browser_context()).ToV8();
service_worker_context_.Reset(isolate, handle);
@ -1339,7 +1353,7 @@ v8::Local<v8::Value> Session::ServiceWorkerContext(v8::Isolate* isolate) {
}
v8::Local<v8::Value> Session::WebRequest(v8::Isolate* isolate) {
if (web_request_.IsEmpty()) {
if (web_request_.IsEmptyThreadSafe()) {
auto handle = WebRequest::Create(isolate, browser_context());
web_request_.Reset(isolate, handle.ToV8());
}
@ -1347,7 +1361,7 @@ v8::Local<v8::Value> Session::WebRequest(v8::Isolate* isolate) {
}
v8::Local<v8::Value> Session::NetLog(v8::Isolate* isolate) {
if (net_log_.IsEmpty()) {
if (net_log_.IsEmptyThreadSafe()) {
auto handle = NetLog::Create(isolate, browser_context());
net_log_.Reset(isolate, handle.ToV8());
}
@ -1656,40 +1670,47 @@ bool Session::IsSpellCheckerEnabled() const {
#endif // BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
// static
Session* Session::FromBrowserContext(content::BrowserContext* context) {
gin::WeakCell<Session>* Session::FromBrowserContext(
content::BrowserContext* context) {
auto* data =
static_cast<UserDataLink*>(context->GetUserData(kElectronApiSessionKey));
return data ? data->session.get() : nullptr;
if (data && data->session)
return data->session.Get();
return nullptr;
}
// static
gin_helper::Handle<Session> Session::CreateFrom(
v8::Isolate* isolate,
Session* Session::CreateFrom(v8::Isolate* isolate,
ElectronBrowserContext* browser_context) {
Session* existing = FromBrowserContext(browser_context);
if (existing)
return gin_helper::CreateHandle(isolate, existing);
auto handle =
gin_helper::CreateHandle(isolate, new Session(isolate, browser_context));
// The Sessions should never be garbage collected, since the common pattern is
// to use partition strings, instead of using the Session object directly.
handle->Pin(isolate);
v8::TryCatch try_catch(isolate);
gin_helper::CallMethod(isolate, handle.get(), "_init");
if (try_catch.HasCaught()) {
node::errors::TriggerUncaughtException(isolate, try_catch);
gin::WeakCell<Session>* existing = FromBrowserContext(browser_context);
if (existing && existing->Get()) {
return existing->Get();
}
App::Get()->EmitWithoutEvent("session-created", handle);
auto* session = cppgc::MakeGarbageCollected<Session>(
isolate->GetCppHeap()->GetAllocationHandle(), isolate, browser_context);
return handle;
v8::TryCatch try_catch(isolate);
gin_helper::CallMethod(isolate, session, "_init");
if (try_catch.HasCaught()) {
node::errors::TriggerUncaughtException(isolate, try_catch);
return nullptr;
}
{
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> wrapper;
if (!session->GetWrapper(isolate).ToLocal(&wrapper)) {
return nullptr;
}
App::Get()->EmitWithoutEvent("session-created", wrapper);
}
return session;
}
// static
gin_helper::Handle<Session> Session::FromPartition(v8::Isolate* isolate,
Session* Session::FromPartition(v8::Isolate* isolate,
const std::string& partition,
base::Value::Dict options) {
ElectronBrowserContext* browser_context;
@ -1708,34 +1729,30 @@ gin_helper::Handle<Session> Session::FromPartition(v8::Isolate* isolate,
}
// static
std::optional<gin_helper::Handle<Session>> Session::FromPath(
v8::Isolate* isolate,
Session* Session::FromPath(gin::Arguments* args,
const base::FilePath& path,
base::Value::Dict options) {
ElectronBrowserContext* browser_context;
if (path.empty()) {
gin_helper::Promise<v8::Local<v8::Value>> promise(isolate);
promise.RejectWithErrorMessage("An empty path was specified");
return std::nullopt;
args->ThrowTypeError("An empty path was specified");
return nullptr;
}
if (!path.IsAbsolute()) {
gin_helper::Promise<v8::Local<v8::Value>> promise(isolate);
promise.RejectWithErrorMessage("An absolute path was not provided");
return std::nullopt;
args->ThrowTypeError("An absolute path was not provided");
return nullptr;
}
browser_context =
ElectronBrowserContext::FromPath(std::move(path), std::move(options));
return CreateFrom(isolate, browser_context);
return CreateFrom(args->isolate(), browser_context);
}
// static
gin_helper::Handle<Session> Session::New() {
void Session::New() {
gin_helper::ErrorThrower(JavascriptEnvironment::GetIsolate())
.ThrowError("Session objects cannot be created with 'new'");
return {};
}
void Session::FillObjectTemplate(v8::Isolate* isolate,
@ -1821,12 +1838,31 @@ void Session::FillObjectTemplate(v8::Isolate* isolate,
.Build();
}
const char* Session::GetTypeName() {
return GetClassName();
void Session::Trace(cppgc::Visitor* visitor) const {
gin::Wrappable<Session>::Trace(visitor);
visitor->Trace(cookies_);
visitor->Trace(extensions_);
visitor->Trace(protocol_);
visitor->Trace(net_log_);
visitor->Trace(service_worker_context_);
visitor->Trace(web_request_);
visitor->Trace(weak_factory_);
}
void Session::WillBeDestroyed() {
ClearWeak();
const gin::WrapperInfo* Session::wrapper_info() const {
return &kWrapperInfo;
}
const char* Session::GetHumanReadableName() const {
return "Electron / Session";
}
void Session::OnBeforeMicrotasksRunnerDispose(v8::Isolate* isolate) {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
data->RemoveDisposeObserver(this);
Dispose();
weak_factory_.Invalidate();
keep_alive_.Clear();
}
} // namespace electron::api
@ -1843,8 +1879,19 @@ v8::Local<v8::Value> FromPartition(const std::string& partition,
}
base::Value::Dict options;
args->GetNext(&options);
return Session::FromPartition(args->isolate(), partition, std::move(options))
.ToV8();
Session* session =
Session::FromPartition(args->isolate(), partition, std::move(options));
if (session) {
v8::HandleScope handle_scope(args->isolate());
v8::Local<v8::Object> wrapper;
if (!session->GetWrapper(args->isolate()).ToLocal(&wrapper)) {
return v8::Null(args->isolate());
}
return wrapper;
} else {
return v8::Null(args->isolate());
}
}
v8::Local<v8::Value> FromPath(const base::FilePath& path,
@ -1855,13 +1902,18 @@ v8::Local<v8::Value> FromPath(const base::FilePath& path,
}
base::Value::Dict options;
args->GetNext(&options);
std::optional<gin_helper::Handle<Session>> session_handle =
Session::FromPath(args->isolate(), path, std::move(options));
Session* session = Session::FromPath(args, path, std::move(options));
if (session_handle)
return session_handle.value().ToV8();
else
if (session) {
v8::HandleScope handle_scope(args->isolate());
v8::Local<v8::Object> wrapper;
if (!session->GetWrapper(args->isolate()).ToLocal(&wrapper)) {
return v8::Null(args->isolate());
}
return wrapper;
} else {
return v8::Null(args->isolate());
}
}
void Initialize(v8::Local<v8::Object> exports,
@ -1870,7 +1922,8 @@ void Initialize(v8::Local<v8::Object> exports,
void* priv) {
v8::Isolate* const isolate = electron::JavascriptEnvironment::GetIsolate();
gin_helper::Dictionary dict(isolate, exports);
dict.Set("Session", Session::GetConstructor(isolate, context));
dict.Set("Session",
Session::GetConstructor(isolate, context, &Session::kWrapperInfo));
dict.SetMethod("fromPartition", &FromPartition);
dict.SetMethod("fromPath", &FromPath);
}

View file

@ -15,15 +15,15 @@
#include "base/values.h"
#include "content/public/browser/download_manager.h"
#include "electron/buildflags/buildflags.h"
#include "gin/weak_cell.h"
#include "gin/wrappable.h"
#include "services/network/public/mojom/host_resolver.mojom-forward.h"
#include "services/network/public/mojom/ssl_config.mojom-forward.h"
#include "shell/browser/api/ipc_dispatcher.h"
#include "shell/browser/event_emitter_mixin.h"
#include "shell/browser/net/resolve_proxy_helper.h"
#include "shell/common/gin_helper/cleaned_up_at_exit.h"
#include "shell/common/gin_helper/constructible.h"
#include "shell/common/gin_helper/pinnable.h"
#include "shell/common/gin_helper/wrappable.h"
#include "shell/common/gin_helper/self_keep_alive.h"
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
#include "chrome/browser/spellchecker/spellcheck_hunspell_dictionary.h" // nogncheck
@ -39,11 +39,6 @@ namespace gin {
class Arguments;
} // namespace gin
namespace gin_helper {
template <typename T>
class Handle;
} // namespace gin_helper
namespace gin_helper {
class Dictionary;
class ErrorThrower;
@ -53,6 +48,11 @@ namespace net {
class ProxyConfig;
}
namespace v8 {
template <typename T>
class TracedReference;
}
namespace electron {
class ElectronBrowserContext;
@ -60,11 +60,10 @@ struct PreloadScript;
namespace api {
class Session final : public gin_helper::DeprecatedWrappable<Session>,
public gin_helper::Pinnable<Session>,
class Session final : public gin::Wrappable<Session>,
public gin_helper::Constructible<Session>,
public gin_helper::EventEmitterMixin<Session>,
public gin_helper::CleanedUpAtExit,
public gin::PerIsolateData::DisposeObserver,
public IpcDispatcher<Session>,
#if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER)
private SpellcheckHunspellDictionary::Observer,
@ -72,39 +71,46 @@ class Session final : public gin_helper::DeprecatedWrappable<Session>,
private content::DownloadManager::Observer {
public:
// Gets or creates Session from the |browser_context|.
static gin_helper::Handle<Session> CreateFrom(
v8::Isolate* isolate,
static Session* CreateFrom(v8::Isolate* isolate,
ElectronBrowserContext* browser_context);
static gin_helper::Handle<Session> New(); // Dummy, do not use!
static void New(); // Dummy, do not use!
static Session* FromBrowserContext(content::BrowserContext* context);
static gin::WeakCell<Session>* FromBrowserContext(
content::BrowserContext* context);
// Gets the Session of |partition|.
static gin_helper::Handle<Session> FromPartition(
v8::Isolate* isolate,
static Session* FromPartition(v8::Isolate* isolate,
const std::string& partition,
base::Value::Dict options = {});
// Gets the Session based on |path|.
static std::optional<gin_helper::Handle<Session>> FromPath(
v8::Isolate* isolate,
static Session* FromPath(gin::Arguments* args,
const base::FilePath& path,
base::Value::Dict options = {});
static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
static const char* GetClassName() { return "Session"; }
Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context);
~Session() override;
ElectronBrowserContext* browser_context() const {
return &browser_context_.get();
}
// gin_helper::Wrappable
static gin::DeprecatedWrapperInfo kWrapperInfo;
static void FillObjectTemplate(v8::Isolate*, v8::Local<v8::ObjectTemplate>);
static const char* GetClassName() { return "Session"; }
const char* GetTypeName() override;
// gin::Wrappable
static gin::WrapperInfo kWrapperInfo;
void Trace(cppgc::Visitor*) const override;
const gin::WrapperInfo* wrapper_info() const override;
const char* GetHumanReadableName() const override;
// gin_helper::CleanedUpAtExit
void WillBeDestroyed() override;
// gin::PerIsolateData::DisposeObserver
void OnBeforeDispose(v8::Isolate* isolate) override {}
void OnBeforeMicrotasksRunnerDispose(v8::Isolate* isolate) override;
void OnDisposed() override {}
// Methods.
void Dispose();
v8::Local<v8::Promise> ResolveHost(
std::string host,
std::optional<network::mojom::ResolveHostParametersPtr> params);
@ -180,9 +186,6 @@ class Session final : public gin_helper::DeprecatedWrappable<Session>,
Session& operator=(const Session&) = delete;
protected:
Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context);
~Session() override;
// content::DownloadManager::Observer:
void OnDownloadCreated(content::DownloadManager* manager,
download::DownloadItem* item) override;
@ -202,12 +205,12 @@ class Session final : public gin_helper::DeprecatedWrappable<Session>,
v8::Local<v8::Value> val);
// Cached gin_helper::Wrappable objects.
v8::Global<v8::Value> cookies_;
v8::Global<v8::Value> extensions_;
v8::Global<v8::Value> protocol_;
v8::Global<v8::Value> net_log_;
v8::Global<v8::Value> service_worker_context_;
v8::Global<v8::Value> web_request_;
v8::TracedReference<v8::Value> cookies_;
v8::TracedReference<v8::Value> extensions_;
v8::TracedReference<v8::Value> protocol_;
v8::TracedReference<v8::Value> net_log_;
v8::TracedReference<v8::Value> service_worker_context_;
v8::TracedReference<v8::Value> web_request_;
raw_ptr<v8::Isolate> isolate_;
@ -216,7 +219,9 @@ class Session final : public gin_helper::DeprecatedWrappable<Session>,
const raw_ref<ElectronBrowserContext> browser_context_;
base::WeakPtrFactory<Session> weak_factory_{this};
gin::WeakCellFactory<Session> weak_factory_{this};
gin_helper::SelfKeepAlive<Session> keep_alive_{this};
};
} // namespace api

View file

@ -87,7 +87,6 @@
#include "services/service_manager/public/cpp/interface_provider.h"
#include "shell/browser/api/electron_api_browser_window.h"
#include "shell/browser/api/electron_api_debugger.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/api/electron_api_web_frame_main.h"
#include "shell/browser/api/frame_subscriber.h"
#include "shell/browser/api/message_port.h"
@ -755,8 +754,7 @@ WebContents::WebContents(v8::Isolate* isolate,
script_executor_ = std::make_unique<extensions::ScriptExecutor>(web_contents);
#endif
auto session = Session::CreateFrom(isolate, GetBrowserContext());
session_.Reset(isolate, session.ToV8());
session_ = Session::CreateFrom(isolate, GetBrowserContext());
SetUserAgent(GetBrowserContext()->GetUserAgent());
@ -778,9 +776,9 @@ WebContents::WebContents(v8::Isolate* isolate,
{
DCHECK(type != Type::kRemote)
<< "Can't take ownership of a remote WebContents";
auto session = Session::CreateFrom(isolate, GetBrowserContext());
session_.Reset(isolate, session.ToV8());
InitWithSessionAndOptions(isolate, std::move(web_contents), session,
session_ = Session::CreateFrom(isolate, GetBrowserContext());
InitWithSessionAndOptions(isolate, std::move(web_contents),
session_->browser_context(),
gin::Dictionary::CreateEmpty(isolate));
}
@ -829,15 +827,15 @@ WebContents::WebContents(v8::Isolate* isolate,
// Obtain the session.
std::string partition;
gin_helper::Handle<api::Session> session;
if (options.Get("session", &session) && !session.IsEmpty()) {
api::Session* session = nullptr;
if (options.Get("session", &session) && session) {
} else if (options.Get("partition", &partition)) {
session = Session::FromPartition(isolate, partition);
} else {
// Use the default session if not specified.
session = Session::FromPartition(isolate, "");
}
session_.Reset(isolate, session.ToV8());
session_ = session;
std::unique_ptr<content::WebContents> web_contents;
if (is_guest()) {
@ -886,7 +884,8 @@ WebContents::WebContents(v8::Isolate* isolate,
web_contents = content::WebContents::Create(params);
}
InitWithSessionAndOptions(isolate, std::move(web_contents), session, options);
InitWithSessionAndOptions(isolate, std::move(web_contents),
session->browser_context(), options);
}
void WebContents::InitZoomController(content::WebContents* web_contents,
@ -907,10 +906,10 @@ void WebContents::InitZoomController(content::WebContents* web_contents,
void WebContents::InitWithSessionAndOptions(
v8::Isolate* isolate,
std::unique_ptr<content::WebContents> owned_web_contents,
gin_helper::Handle<api::Session> session,
ElectronBrowserContext* browser_context,
const gin_helper::Dictionary& options) {
Observe(owned_web_contents.get());
InitWithWebContents(std::move(owned_web_contents), session->browser_context(),
InitWithWebContents(std::move(owned_web_contents), browser_context,
is_guest());
inspectable_web_contents_->GetView()->SetDelegate(this);
@ -3754,7 +3753,12 @@ v8::Local<v8::Value> WebContents::GetOwnerBrowserWindow(
}
v8::Local<v8::Value> WebContents::Session(v8::Isolate* isolate) {
return v8::Local<v8::Value>::New(isolate, session_);
v8::HandleScope handle_scope(isolate);
v8::Local<v8::Object> wrapper;
if (!session_->GetWrapper(isolate).ToLocal(&wrapper)) {
return v8::Null(isolate);
}
return v8::Local<v8::Value>::New(isolate, wrapper);
}
content::WebContents* WebContents::HostWebContents() const {

View file

@ -31,6 +31,7 @@
#include "content/public/common/stop_find_action.h"
#include "electron/buildflags/buildflags.h"
#include "printing/buildflags/buildflags.h"
#include "shell/browser/api/electron_api_session.h"
#include "shell/browser/api/save_page_handler.h"
#include "shell/browser/background_throttling_source.h"
#include "shell/browser/event_emitter_mixin.h"
@ -47,6 +48,7 @@
#include "shell/common/gin_helper/wrappable.h"
#include "shell/common/web_contents_utility.mojom.h"
#include "ui/base/models/image_model.h"
#include "v8/include/cppgc/persistent.h"
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
#include "extensions/common/mojom/view_type.mojom-forward.h"
@ -479,7 +481,7 @@ class WebContents final : public ExclusiveAccessContext,
void InitWithSessionAndOptions(
v8::Isolate* isolate,
std::unique_ptr<content::WebContents> web_contents,
gin_helper::Handle<class Session> session,
ElectronBrowserContext* browser_context,
const gin_helper::Dictionary& options);
#if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS)
@ -768,7 +770,7 @@ class WebContents final : public ExclusiveAccessContext,
// Update the html fullscreen flag in both browser and renderer.
void UpdateHtmlApiFullscreen(bool fullscreen);
v8::Global<v8::Value> session_;
cppgc::Persistent<api::Session> session_;
v8::Global<v8::Value> devtools_web_contents_;
v8::Global<v8::Value> debugger_;

View file

@ -44,68 +44,80 @@ void ElectronApiIPCHandlerImpl::OnConnectionError() {
void ElectronApiIPCHandlerImpl::Message(bool internal,
const std::string& channel,
blink::CloneableMessage arguments) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, internal);
auto event = MakeIPCEvent(isolate, session->Get(), internal);
if (event.IsEmpty())
return;
session->Message(event, channel, std::move(arguments));
session->Get()->Message(event, channel, std::move(arguments));
}
}
void ElectronApiIPCHandlerImpl::Invoke(bool internal,
const std::string& channel,
blink::CloneableMessage arguments,
InvokeCallback callback) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, internal, std::move(callback));
auto event =
MakeIPCEvent(isolate, session->Get(), internal, std::move(callback));
if (event.IsEmpty())
return;
session->Invoke(event, channel, std::move(arguments));
session->Get()->Invoke(event, channel, std::move(arguments));
}
}
void ElectronApiIPCHandlerImpl::ReceivePostMessage(
const std::string& channel,
blink::TransferableMessage message) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, false);
auto event = MakeIPCEvent(isolate, session->Get(), false);
if (event.IsEmpty())
return;
session->ReceivePostMessage(event, channel, std::move(message));
session->Get()->ReceivePostMessage(event, channel, std::move(message));
}
}
void ElectronApiIPCHandlerImpl::MessageSync(bool internal,
const std::string& channel,
blink::CloneableMessage arguments,
MessageSyncCallback callback) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, internal, std::move(callback));
auto event =
MakeIPCEvent(isolate, session->Get(), internal, std::move(callback));
if (event.IsEmpty())
return;
session->MessageSync(event, channel, std::move(arguments));
session->Get()->MessageSync(event, channel, std::move(arguments));
}
}
void ElectronApiIPCHandlerImpl::MessageHost(const std::string& channel,
blink::CloneableMessage arguments) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, false);
auto event = MakeIPCEvent(isolate, session->Get(), false);
if (event.IsEmpty())
return;
session->MessageHost(event, channel, std::move(arguments));
session->Get()->MessageHost(event, channel, std::move(arguments));
}
}
content::RenderFrameHost* ElectronApiIPCHandlerImpl::GetRenderFrameHost() {
return content::RenderFrameHost::FromID(render_frame_host_id_);
}
api::Session* ElectronApiIPCHandlerImpl::GetSession() {
gin::WeakCell<api::Session>* ElectronApiIPCHandlerImpl::GetSession() {
auto* rfh = GetRenderFrameHost();
return rfh ? api::Session::FromBrowserContext(rfh->GetBrowserContext())
: nullptr;

View file

@ -18,6 +18,11 @@ namespace content {
class RenderFrameHost;
}
namespace gin {
template <typename T>
class WeakCell;
} // namespace gin
namespace electron {
class ElectronApiIPCHandlerImpl : public mojom::ElectronApiIPC,
private content::WebContentsObserver {
@ -65,7 +70,7 @@ class ElectronApiIPCHandlerImpl : public mojom::ElectronApiIPC,
void OnConnectionError();
content::RenderFrameHost* GetRenderFrameHost();
api::Session* GetSession();
gin::WeakCell<api::Session>* GetSession();
gin_helper::Handle<gin_helper::internal::Event> MakeIPCEvent(
v8::Isolate* isolate,

View file

@ -71,51 +71,61 @@ void ElectronApiSWIPCHandlerImpl::RemoteDisconnected() {
void ElectronApiSWIPCHandlerImpl::Message(bool internal,
const std::string& channel,
blink::CloneableMessage arguments) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, internal);
auto event = MakeIPCEvent(isolate, session->Get(), internal);
if (event.IsEmpty())
return;
session->Message(event, channel, std::move(arguments));
session->Get()->Message(event, channel, std::move(arguments));
}
}
void ElectronApiSWIPCHandlerImpl::Invoke(bool internal,
const std::string& channel,
blink::CloneableMessage arguments,
InvokeCallback callback) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, internal, std::move(callback));
auto event =
MakeIPCEvent(isolate, session->Get(), internal, std::move(callback));
if (event.IsEmpty())
return;
session->Invoke(event, channel, std::move(arguments));
session->Get()->Invoke(event, channel, std::move(arguments));
}
}
void ElectronApiSWIPCHandlerImpl::ReceivePostMessage(
const std::string& channel,
blink::TransferableMessage message) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, false);
auto event = MakeIPCEvent(isolate, session->Get(), false);
if (event.IsEmpty())
return;
session->ReceivePostMessage(event, channel, std::move(message));
session->Get()->ReceivePostMessage(event, channel, std::move(message));
}
}
void ElectronApiSWIPCHandlerImpl::MessageSync(bool internal,
const std::string& channel,
blink::CloneableMessage arguments,
MessageSyncCallback callback) {
auto* session = GetSession();
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope(isolate);
auto event = MakeIPCEvent(isolate, session, internal, std::move(callback));
auto event =
MakeIPCEvent(isolate, session->Get(), internal, std::move(callback));
if (event.IsEmpty())
return;
session->MessageSync(event, channel, std::move(arguments));
session->Get()->MessageSync(event, channel, std::move(arguments));
}
}
void ElectronApiSWIPCHandlerImpl::MessageHost(
@ -130,7 +140,7 @@ ElectronBrowserContext* ElectronApiSWIPCHandlerImpl::GetBrowserContext() {
return browser_context;
}
api::Session* ElectronApiSWIPCHandlerImpl::GetSession() {
gin::WeakCell<api::Session>* ElectronApiSWIPCHandlerImpl::GetSession() {
return api::Session::FromBrowserContext(GetBrowserContext());
}

View file

@ -19,6 +19,11 @@ namespace content {
class RenderProcessHost;
}
namespace gin {
template <typename T>
class WeakCell;
} // namespace gin
namespace electron {
class ElectronBrowserContext;
@ -68,7 +73,7 @@ class ElectronApiSWIPCHandlerImpl : public mojom::ElectronApiIPC,
private:
ElectronBrowserContext* GetBrowserContext();
api::Session* GetSession();
gin::WeakCell<api::Session>* GetSession();
gin_helper::Handle<gin_helper::internal::Event> MakeIPCEvent(
v8::Isolate* isolate,

View file

@ -56,13 +56,13 @@ class EventEmitterMixin {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
auto* wrapper_info = &(static_cast<T*>(this)->kWrapperInfo);
v8::Local<v8::FunctionTemplate> constructor =
data->GetFunctionTemplate(wrapper_info);
data->DeprecatedGetFunctionTemplate(wrapper_info);
if (constructor.IsEmpty()) {
constructor = v8::FunctionTemplate::New(isolate);
constructor->SetClassName(
gin::StringToV8(isolate, static_cast<T*>(this)->GetTypeName()));
constructor->Inherit(internal::GetEventEmitterTemplate(isolate));
data->SetFunctionTemplate(wrapper_info, constructor);
data->DeprecatedSetFunctionTemplate(wrapper_info, constructor);
}
return gin::ObjectTemplateBuilder(isolate,
static_cast<T*>(this)->GetTypeName(),

View file

@ -732,8 +732,9 @@ void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
}
if (should_block) {
auto* session =
gin::WeakCell<api::Session>* session =
electron::api::Session::FromBrowserContext(browser_context());
if (session && session->Get()) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
v8::Local<v8::Object> details =
@ -742,11 +743,12 @@ void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist(
.Set("isDirectory", handle_type == HandleType::kDirectory)
.Set("path", path_info.path)
.Build();
session->Emit(
session->Get()->Emit(
"file-system-access-restricted", details,
base::BindRepeating(
&FileSystemAccessPermissionContext::OnRestrictedPathResult,
weak_factory_.GetWeakPtr(), path_info.path));
}
return;
}

View file

@ -293,14 +293,15 @@ void HidChooserContext::RevokeDevicePermission(
} else {
RevokeEphemeralDevicePermission(origin, device);
}
api::Session* session = api::Session::FromBrowserContext(browser_context_);
if (session) {
gin::WeakCell<api::Session>* session =
api::Session::FromBrowserContext(browser_context_);
if (session && session->Get()) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
auto details = gin_helper::Dictionary::CreateEmpty(isolate);
details.Set("device", device.Clone());
details.Set("origin", origin.Serialize());
session->Emit("hid-device-revoked", details);
session->Get()->Emit("hid-device-revoked", details);
}
}

View file

@ -124,7 +124,7 @@ const std::string& HidChooserController::PhysicalDeviceIdFromDeviceInfo(
: device.physical_device_id;
}
api::Session* HidChooserController::GetSession() {
gin::WeakCell<api::Session>* HidChooserController::GetSession() {
if (!web_contents()) {
return nullptr;
}
@ -137,8 +137,8 @@ void HidChooserController::OnDeviceAdded(
return;
if (AddDeviceInfo(device)) {
api::Session* session = GetSession();
if (session) {
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
@ -146,7 +146,7 @@ void HidChooserController::OnDeviceAdded(
.Set("device", device.Clone())
.Set("frame", rfh)
.Build();
session->Emit("hid-device-added", details);
session->Get()->Emit("hid-device-added", details);
}
}
}
@ -156,8 +156,8 @@ void HidChooserController::OnDeviceRemoved(
if (!base::Contains(items_, PhysicalDeviceIdFromDeviceInfo(device)))
return;
api::Session* session = GetSession();
if (session) {
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
@ -165,7 +165,7 @@ void HidChooserController::OnDeviceRemoved(
.Set("device", device.Clone())
.Set("frame", rfh)
.Build();
session->Emit("hid-device-removed", details);
session->Get()->Emit("hid-device-removed", details);
}
RemoveDeviceInfo(device);
}
@ -239,8 +239,8 @@ void HidChooserController::OnGotDevices(
observation_.Observe(chooser_context_.get());
bool prevent_default = false;
api::Session* session = GetSession();
if (session) {
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
@ -248,8 +248,8 @@ void HidChooserController::OnGotDevices(
.Set("deviceList", devicesToDisplay)
.Set("frame", rfh)
.Build();
prevent_default =
session->Emit("select-hid-device", details,
prevent_default = session->Get()->Emit(
"select-hid-device", details,
base::BindRepeating(&HidChooserController::OnDeviceChosen,
weak_factory_.GetWeakPtr()));
}

View file

@ -28,7 +28,9 @@ class WebContents;
namespace gin {
class Arguments;
}
template <typename T>
class WeakCell;
} // namespace gin
namespace electron {
namespace api {
@ -78,7 +80,7 @@ class HidChooserController
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
private:
api::Session* GetSession();
gin::WeakCell<api::Session>* GetSession();
void OnGotDevices(std::vector<device::mojom::HidDeviceInfoPtr> devices);
bool DisplayDevice(const device::mojom::HidDeviceInfo& device) const;
bool FilterMatchesAny(const device::mojom::HidDeviceInfo& device) const;

View file

@ -31,9 +31,10 @@ void NetworkHintsHandlerImpl::Preconnect(const url::SchemeHostPort& url,
if (!browser_context_) {
return;
}
auto* session = electron::api::Session::FromBrowserContext(browser_context_);
if (session) {
session->Emit("preconnect", url.GetURL(), allow_credentials);
gin::WeakCell<electron::api::Session>* session =
electron::api::Session::FromBrowserContext(browser_context_);
if (session && session->Get()) {
session->Get()->Emit("preconnect", url.GetURL(), allow_credentials);
}
}

View file

@ -149,17 +149,16 @@ void SerialChooserContext::RevokePortPermissionWebInitiated(
auto* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host);
api::Session* session =
gin::WeakCell<api::Session>* session =
api::Session::FromBrowserContext(web_contents->GetBrowserContext());
if (session) {
if (session && session->Get()) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
auto details = gin_helper::Dictionary::CreateEmpty(isolate);
details.Set("port", it->second);
details.SetGetter("frame", render_frame_host);
details.Set("origin", origin.Serialize());
session->Emit("serial-port-revoked", details);
session->Get()->Emit("serial-port-revoked", details);
}
}

View file

@ -143,7 +143,7 @@ SerialChooserController::~SerialChooserController() {
RunCallback(/*port=*/nullptr);
}
api::Session* SerialChooserController::GetSession() {
gin::WeakCell<api::Session>* SerialChooserController::GetSession() {
if (!web_contents_) {
return nullptr;
}
@ -180,9 +180,10 @@ void SerialChooserController::OnPortAdded(
ports_.push_back(port.Clone());
api::Session* session = GetSession();
if (session) {
session->Emit("serial-port-added", port.Clone(), web_contents_.get());
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("serial-port-added", port.Clone(),
web_contents_.get());
}
}
@ -191,8 +192,11 @@ void SerialChooserController::OnPortRemoved(
const auto it = std::ranges::find(ports_, port.token,
&device::mojom::SerialPortInfo::token);
if (it != ports_.end()) {
if (api::Session* session = GetSession())
session->Emit("serial-port-removed", port.Clone(), web_contents_.get());
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("serial-port-removed", port.Clone(),
web_contents_.get());
}
ports_.erase(it);
}
}
@ -236,8 +240,9 @@ void SerialChooserController::OnGetDevices(
}
bool prevent_default = false;
if (api::Session* session = GetSession()) {
prevent_default = session->Emit(
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
prevent_default = session->Get()->Emit(
"select-serial-port", ports_, web_contents_.get(),
base::BindRepeating(&SerialChooserController::OnDeviceChosen,
weak_factory_.GetWeakPtr()));

View file

@ -24,6 +24,11 @@ class RenderFrameHost;
class WebContents;
} // namespace content
namespace gin {
template <typename T>
class WeakCell;
} // namespace gin
namespace electron {
namespace api {
@ -64,7 +69,7 @@ class SerialChooserController final
bool powered) override;
private:
api::Session* GetSession();
gin::WeakCell<api::Session>* GetSession();
void GetDevices();
void OnGetDevices(std::vector<device::mojom::SerialPortInfoPtr> ports);
bool DisplayDevice(const device::mojom::SerialPortInfo& port) const;

View file

@ -276,14 +276,15 @@ void UsbChooserContext::RevokeObjectPermissionInternal(
}
}
api::Session* session = api::Session::FromBrowserContext(browser_context_);
if (session) {
gin::WeakCell<api::Session>* session =
api::Session::FromBrowserContext(browser_context_);
if (session && session->Get()) {
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope scope(isolate);
auto details = gin_helper::Dictionary::CreateEmpty(isolate);
details.Set("device", object);
details.Set("origin", origin.Serialize());
session->Emit("usb-device-revoked", details);
session->Get()->Emit("usb-device-revoked", details);
}
}

View file

@ -58,7 +58,7 @@ UsbChooserController::~UsbChooserController() {
RunCallback(/*device_info=*/nullptr);
}
api::Session* UsbChooserController::GetSession() {
gin::WeakCell<api::Session>* UsbChooserController::GetSession() {
if (!web_contents()) {
return nullptr;
}
@ -68,18 +68,20 @@ api::Session* UsbChooserController::GetSession() {
void UsbChooserController::OnDeviceAdded(
const device::mojom::UsbDeviceInfo& device_info) {
if (DisplayDevice(device_info)) {
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-added", device_info.Clone(), web_contents());
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("usb-device-added", device_info.Clone(),
web_contents());
}
}
}
void UsbChooserController::OnDeviceRemoved(
const device::mojom::UsbDeviceInfo& device_info) {
api::Session* session = GetSession();
if (session) {
session->Emit("usb-device-removed", device_info.Clone(), web_contents());
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
session->Get()->Emit("usb-device-removed", device_info.Clone(),
web_contents());
}
}
@ -114,8 +116,8 @@ void UsbChooserController::GotUsbDeviceList(
observation_.Observe(chooser_context_.get());
bool prevent_default = false;
api::Session* session = GetSession();
if (session) {
gin::WeakCell<api::Session>* session = GetSession();
if (session && session->Get()) {
auto* rfh = content::RenderFrameHost::FromID(render_frame_host_id_);
v8::Isolate* isolate = JavascriptEnvironment::GetIsolate();
v8::HandleScope handle_scope{isolate};
@ -130,8 +132,8 @@ void UsbChooserController::GotUsbDeviceList(
.Set("frame", rfh)
.Build();
prevent_default =
session->Emit("select-usb-device", details,
prevent_default = session->Get()->Emit(
"select-usb-device", details,
base::BindRepeating(&UsbChooserController::OnDeviceChosen,
weak_factory_.GetWeakPtr()));
}

View file

@ -22,7 +22,9 @@ class WebContents;
namespace gin {
class Arguments;
}
template <typename T>
class WeakCell;
} // namespace gin
namespace electron {
class ElectronUsbDelegate;
@ -57,7 +59,7 @@ class UsbChooserController final : private UsbChooserContext::DeviceObserver,
void RenderFrameDeleted(content::RenderFrameHost* render_frame_host) override;
private:
api::Session* GetSession();
gin::WeakCell<api::Session>* GetSession();
void GotUsbDeviceList(std::vector<device::mojom::UsbDeviceInfoPtr> devices);
bool DisplayDevice(const device::mojom::UsbDeviceInfo& device) const;
void RunCallback(device::mojom::UsbDeviceInfoPtr device_info);

View file

@ -577,11 +577,11 @@ gin::ObjectTemplateBuilder NativeImage::GetObjectTemplateBuilder(
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
auto* wrapper_info = &kWrapperInfo;
v8::Local<v8::FunctionTemplate> constructor =
data->GetFunctionTemplate(wrapper_info);
data->DeprecatedGetFunctionTemplate(wrapper_info);
if (constructor.IsEmpty()) {
constructor = v8::FunctionTemplate::New(isolate);
constructor->SetClassName(gin::StringToV8(isolate, GetTypeName()));
data->SetFunctionTemplate(wrapper_info, constructor);
data->DeprecatedSetFunctionTemplate(wrapper_info, constructor);
}
return gin::ObjectTemplateBuilder(isolate, GetTypeName(),
constructor->InstanceTemplate())

View file

@ -698,13 +698,14 @@ gin_helper::Handle<SimpleURLLoaderWrapper> SimpleURLLoaderWrapper::Create(
ElectronBrowserContext* browser_context = nullptr;
if (electron::IsBrowserProcess()) {
std::string partition;
gin_helper::Handle<Session> session;
Session* session = nullptr;
if (!opts.Get("session", &session)) {
if (opts.Get("partition", &partition))
session = Session::FromPartition(args->isolate(), partition);
else // default session
session = Session::FromPartition(args->isolate(), "");
}
if (session)
browser_context = session->browser_context();
}

View file

@ -45,7 +45,7 @@ class Constructible {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
auto* wrapper_info = &T::kWrapperInfo;
v8::Local<v8::FunctionTemplate> constructor =
data->GetFunctionTemplate(wrapper_info);
data->DeprecatedGetFunctionTemplate(wrapper_info);
if (constructor.IsEmpty()) {
constructor = gin::CreateConstructorFunctionTemplate(
isolate, base::BindRepeating(&T::New));
@ -59,6 +59,30 @@ class Constructible {
T::FillObjectTemplate(isolate, constructor->PrototypeTemplate());
data->DeprecatedSetObjectTemplate(wrapper_info,
constructor->InstanceTemplate());
data->DeprecatedSetFunctionTemplate(wrapper_info, constructor);
}
return constructor->GetFunction(context).ToLocalChecked();
}
static v8::Local<v8::Function> GetConstructor(
v8::Isolate* const isolate,
v8::Local<v8::Context> context,
gin::WrapperInfo* wrapper_info) {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
v8::Local<v8::FunctionTemplate> constructor =
data->GetFunctionTemplate(wrapper_info);
if (constructor.IsEmpty()) {
constructor = gin::CreateConstructorFunctionTemplate(
isolate, base::BindRepeating(&T::New));
if (std::is_base_of<EventEmitterMixin<T>, T>::value) {
constructor->Inherit(
gin_helper::internal::GetEventEmitterTemplate(isolate));
}
constructor->InstanceTemplate()->SetInternalFieldCount(
gin::kNumberOfInternalFields);
constructor->SetClassName(gin::StringToV8(isolate, T::GetClassName()));
T::FillObjectTemplate(isolate, constructor->PrototypeTemplate());
data->SetObjectTemplate(wrapper_info, constructor->InstanceTemplate());
data->SetFunctionTemplate(wrapper_info, constructor);
}
return constructor->GetFunction(context).ToLocalChecked();

View file

@ -10,6 +10,7 @@
#include "base/containers/span.h"
#include "gin/converter.h"
#include "gin/wrappable.h"
#include "shell/common/gin_converters/std_converter.h" // for ConvertToV8(iso, &&)
#include "shell/common/gin_helper/wrappable.h"
@ -76,6 +77,28 @@ v8::Local<v8::Value> CallMethod(gin_helper::DeprecatedWrappable<T>* object,
return CallMethod(isolate, object, method_name, std::forward<Args>(args)...);
}
template <typename T, typename... Args>
v8::Local<v8::Value> CallMethod(v8::Isolate* isolate,
gin::Wrappable<T>* object,
const char* method_name,
Args&&... args) {
v8::EscapableHandleScope scope(isolate);
v8::Local<v8::Object> v8_object;
if (object->GetWrapper(isolate).ToLocal(&v8_object))
return scope.Escape(CustomEmit(isolate, v8_object, method_name,
std::forward<Args>(args)...));
else
return {};
}
template <typename T, typename... Args>
v8::Local<v8::Value> CallMethod(gin::Wrappable<T>* object,
const char* method_name,
Args&&... args) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
return CallMethod(isolate, object, method_name, std::forward<Args>(args)...);
}
} // namespace gin_helper
#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_EMITTER_CALLER_H_

View file

@ -17,7 +17,7 @@ gin::DeprecatedWrapperInfo kWrapperInfo = {gin::kEmbedderNativeGin};
v8::Local<v8::FunctionTemplate> GetEventEmitterTemplate(v8::Isolate* isolate) {
gin::PerIsolateData* data = gin::PerIsolateData::From(isolate);
v8::Local<v8::FunctionTemplate> tmpl =
data->GetFunctionTemplate(&kWrapperInfo);
data->DeprecatedGetFunctionTemplate(&kWrapperInfo);
if (tmpl.IsEmpty()) {
tmpl = v8::FunctionTemplate::New(isolate);
@ -35,7 +35,7 @@ v8::Local<v8::FunctionTemplate> GetEventEmitterTemplate(v8::Isolate* isolate) {
->SetPrototypeV2(context, eventemitter_prototype)
.ToChecked());
data->SetFunctionTemplate(&kWrapperInfo, tmpl);
data->DeprecatedSetFunctionTemplate(&kWrapperInfo, tmpl);
}
return tmpl;

View file

@ -81,6 +81,7 @@ class CallbackHolderBase {
// gin::PerIsolateData::DisposeObserver
void OnBeforeDispose(v8::Isolate* isolate) override;
void OnBeforeMicrotasksRunnerDispose(v8::Isolate* isolate) override {}
void OnDisposed() override;
private:

View file

@ -0,0 +1,36 @@
// Copyright (c) 2025 Microsoft, GmbH.
// Use of this source code is governed by the MIT license that can be
// found in the LICENSE file.
#ifndef ELECTRON_SHELL_COMMON_GIN_HELPER_SELF_KEEP_ALIVE_H_
#define ELECTRON_SHELL_COMMON_GIN_HELPER_SELF_KEEP_ALIVE_H_
#include "gin/weak_cell.h"
namespace gin_helper {
// Based on third_party/blink/renderer/platform/heap/self_keep_alive.h
template <typename Self>
class SelfKeepAlive final {
GIN_DISALLOW_NEW();
public:
explicit SelfKeepAlive(Self* self) : keep_alive_(self) {}
SelfKeepAlive& operator=(Self* self) {
DCHECK(!keep_alive_ || keep_alive_.Get() == self);
keep_alive_ = self;
return *this;
}
void Clear() { keep_alive_.Clear(); }
explicit operator bool() const { return keep_alive_; }
private:
cppgc::Persistent<Self> keep_alive_;
};
} // namespace gin_helper
#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_SELF_KEEP_ALIVE_H_

View file

@ -37,19 +37,19 @@ class Wrappable : public WrappableBase {
isolate, base::BindRepeating(&internal::InvokeNew<Sig>, constructor));
templ->InstanceTemplate()->SetInternalFieldCount(1);
T::BuildPrototype(isolate, templ);
gin::PerIsolateData::From(isolate)->SetFunctionTemplate(&kWrapperInfo,
templ);
gin::PerIsolateData::From(isolate)->DeprecatedSetFunctionTemplate(
&kWrapperInfo, templ);
}
static v8::Local<v8::FunctionTemplate> GetConstructor(v8::Isolate* isolate) {
// Fill the object template.
auto* data = gin::PerIsolateData::From(isolate);
auto templ = data->GetFunctionTemplate(&kWrapperInfo);
auto templ = data->DeprecatedGetFunctionTemplate(&kWrapperInfo);
if (templ.IsEmpty()) {
templ = v8::FunctionTemplate::New(isolate);
templ->InstanceTemplate()->SetInternalFieldCount(1);
T::BuildPrototype(isolate, templ);
data->SetFunctionTemplate(&kWrapperInfo, templ);
data->DeprecatedSetFunctionTemplate(&kWrapperInfo, templ);
}
return templ;
}

View file

@ -27,19 +27,53 @@ describe('cpp heap', () => {
it('should record as node in heap snapshot', async () => {
const { remotely } = await startRemoteControlApp(['--expose-internals']);
const [nodeCount, hasPersistentParent] = await remotely(async (heap: string) => {
const result = await remotely(async (heap: string, snapshotHelper: string) => {
const { recordState } = require(heap);
const { containsRetainingPath } = require(snapshotHelper);
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();
return containsRetainingPath(state.snapshot, ['C++ Persistent roots', 'Electron / App']);
}, path.join(__dirname, '../../third_party/electron_node/test/common/heap'),
path.join(__dirname, 'lib', 'heapsnapshot-helpers.js'));
expect(result).to.equal(true);
});
});
describe('session module', () => {
it('should record as node in heap snapshot', async () => {
const { remotely } = await startRemoteControlApp(['--expose-internals']);
const result = await remotely(async (heap: string, snapshotHelper: string) => {
const { session, BrowserWindow } = require('electron');
const { once } = require('node:events');
const assert = require('node:assert');
const { recordState } = require(heap);
const { containsRetainingPath } = require(snapshotHelper);
const session1 = session.defaultSession;
console.log(session1.getStoragePath());
const session2 = session.fromPartition('cppheap1');
const session3 = session.fromPartition('cppheap1');
const session4 = session.fromPartition('cppheap2');
console.log(session2.cookies);
assert.strictEqual(session2, session3);
assert.notStrictEqual(session2, session4);
const w = new BrowserWindow({
show: false,
webPreferences: {
session: session.fromPartition('cppheap1')
}
});
await w.loadURL('about:blank');
const state = recordState();
const isClosed = once(w, 'closed');
w.destroy();
await isClosed;
const numSessions = containsRetainingPath(state.snapshot, ['C++ Persistent roots', 'Electron / Session'], {
occurrences: 4
});
const canTraceJSReferences = containsRetainingPath(state.snapshot, ['C++ Persistent roots', 'Electron / Session', 'Cookies']);
return numSessions && canTraceJSReferences;
}, path.join(__dirname, '../../third_party/electron_node/test/common/heap'),
path.join(__dirname, 'lib', 'heapsnapshot-helpers.js'));
expect(result).to.equal(true);
});
});
});

View file

@ -0,0 +1,25 @@
export function containsRetainingPath (snapshot, retainingPath, options) {
let root = snapshot.filter(
(node) => node.name === retainingPath[0] && node.type !== 'string');
for (let i = 1; i < retainingPath.length; i++) {
const needle = retainingPath[i];
const newRoot = [];
for (const node of root) {
for (let j = 0; j < node.outgoingEdges.length; j++) {
const child = node.outgoingEdges[j].to;
if (child.type === 'string') continue;
if (child.name === needle) {
newRoot.push(child);
}
}
}
if (!newRoot.length) {
console.log(`No retaining path found for ${needle}`);
return false;
}
root = newRoot;
}
return options?.occurrances
? root.length === options.occurrances
: true;
}