From 3ccb1bc0a8b13bf11c538c7ef0fea63379936891 Mon Sep 17 00:00:00 2001 From: Robo Date: Mon, 25 Aug 2025 18:52:06 +0900 Subject: [PATCH] refactor: allocate api::Session on cpp heap (#48141) --- ...ctron_objects_to_wrappablepointertag.patch | 7 +- ...me_deprecated_wrapper_utility_in_gin.patch | 102 ++++++++-- shell/browser/api/electron_api_session.cc | 183 +++++++++++------- shell/browser/api/electron_api_session.h | 87 +++++---- .../browser/api/electron_api_web_contents.cc | 30 +-- shell/browser/api/electron_api_web_contents.h | 6 +- .../browser/electron_api_ipc_handler_impl.cc | 84 ++++---- shell/browser/electron_api_ipc_handler_impl.h | 7 +- .../electron_api_sw_ipc_handler_impl.cc | 68 ++++--- .../electron_api_sw_ipc_handler_impl.h | 7 +- shell/browser/event_emitter_mixin.h | 4 +- .../file_system_access_permission_context.cc | 30 +-- shell/browser/hid/hid_chooser_context.cc | 7 +- shell/browser/hid/hid_chooser_controller.cc | 26 +-- shell/browser/hid/hid_chooser_controller.h | 6 +- shell/browser/network_hints_handler_impl.cc | 7 +- .../browser/serial/serial_chooser_context.cc | 7 +- .../serial/serial_chooser_controller.cc | 21 +- .../serial/serial_chooser_controller.h | 7 +- shell/browser/usb/usb_chooser_context.cc | 7 +- shell/browser/usb/usb_chooser_controller.cc | 28 +-- shell/browser/usb/usb_chooser_controller.h | 6 +- shell/common/api/electron_api_native_image.cc | 4 +- shell/common/api/electron_api_url_loader.cc | 5 +- shell/common/gin_helper/constructible.h | 26 ++- .../common/gin_helper/event_emitter_caller.h | 23 +++ .../gin_helper/event_emitter_template.cc | 4 +- shell/common/gin_helper/function_template.h | 1 + shell/common/gin_helper/self_keep_alive.h | 36 ++++ shell/common/gin_helper/wrappable.h | 8 +- spec/cpp-heap-spec.ts | 56 ++++-- spec/lib/heapsnapshot-helpers.js | 25 +++ 32 files changed, 632 insertions(+), 293 deletions(-) create mode 100644 shell/common/gin_helper/self_keep_alive.h create mode 100644 spec/lib/heapsnapshot-helpers.js diff --git a/patches/chromium/chore_add_electron_objects_to_wrappablepointertag.patch b/patches/chromium/chore_add_electron_objects_to_wrappablepointertag.patch index 57551dbaba..525b2eb3c6 100644 --- a/patches/chromium/chore_add_electron_objects_to_wrappablepointertag.patch +++ b/patches/chromium/chore_add_electron_objects_to_wrappablepointertag.patch @@ -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 < diff --git a/patches/chromium/chore_restore_some_deprecated_wrapper_utility_in_gin.patch b/patches/chromium/chore_restore_some_deprecated_wrapper_utility_in_gin.patch index 551f3b0524..96d734a82b 100644 --- a/patches/chromium/chore_restore_some_deprecated_wrapper_utility_in_gin.patch +++ b/patches/chromium/chore_restore_some_deprecated_wrapper_utility_in_gin.patch @@ -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(isolate->GetData(kEmbedderNativeGin)); } @@ -40,8 +64,13 @@ index 884990426f13a6abca22a60dd8cc0685f8435b23..64ac0a64a05105532f3cda898aeac68c object_templates_[info] = Eternal(isolate_, templ); } -+void PerIsolateData::SetFunctionTemplate(DeprecatedWrapperInfo* info, -+ Local templ) { ++void PerIsolateData::DeprecatedSetFunctionTemplate( ++ DeprecatedWrapperInfo* info, Local templ) { ++ deprecated_function_templates_[info] = Eternal(isolate_, templ); ++} ++ ++void PerIsolateData::SetFunctionTemplate( ++ const WrapperInfo* info, Local templ) { + function_templates_[info] = Eternal(isolate_, templ); +} + @@ -58,12 +87,22 @@ index 884990426f13a6abca22a60dd8cc0685f8435b23..64ac0a64a05105532f3cda898aeac68c v8::Local PerIsolateData::GetObjectTemplate( const WrapperInfo* info) { ObjectTemplateMap::iterator it = object_templates_.find(info); -@@ -83,6 +103,15 @@ v8::Local PerIsolateData::GetObjectTemplate( +@@ -83,6 +108,25 @@ v8::Local PerIsolateData::GetObjectTemplate( return it->second.Get(isolate_); } -+v8::Local PerIsolateData::GetFunctionTemplate( ++v8::Local PerIsolateData::DeprecatedGetFunctionTemplate( + DeprecatedWrapperInfo* info) { ++ DeprecatedFunctionTemplateMap::iterator it = ++ deprecated_function_templates_.find(info); ++ if (it == deprecated_function_templates_.end()) { ++ return v8::Local(); ++ } ++ return it->second.Get(isolate_); ++} ++ ++v8::Local PerIsolateData::GetFunctionTemplate( ++ const WrapperInfo* info) { + FunctionTemplateMap::iterator it = function_templates_.find(info); + if (it == function_templates_.end()) { + return v8::Local(); @@ -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,21 +152,33 @@ index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bc void SetObjectTemplate(const WrapperInfo* info, v8::Local object_template); -+ void SetFunctionTemplate(DeprecatedWrapperInfo* info, -+ v8::Local function_template); ++ void DeprecatedSetFunctionTemplate( ++ DeprecatedWrapperInfo* info, ++ v8::Local function_template); ++ ++ void SetFunctionTemplate( ++ const WrapperInfo* info, ++ v8::Local function_template); + + v8::Local DeprecatedGetObjectTemplate( + DeprecatedWrapperInfo* info); + v8::Local GetObjectTemplate(const WrapperInfo* info); -+ v8::Local GetFunctionTemplate( ++ v8::Local DeprecatedGetFunctionTemplate( + DeprecatedWrapperInfo* info); ++ ++ v8::Local 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 idle_task_runner); +@@ -74,14 +100,23 @@ class GIN_EXPORT PerIsolateData { } private: @@ -112,6 +187,8 @@ index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bc typedef std::map> ObjectTemplateMap; + typedef std::map> ++ DeprecatedFunctionTemplateMap; ++ typedef std::map> + FunctionTemplateMap; // PerIsolateData doesn't actually own |isolate_|. Instead, the isolate is @@ -120,6 +197,7 @@ index bce889749415da341e6e6e4082ac06bbeb4bb80a..d748c1cf8cef7da90686686f1b8072bc raw_ptr allocator_; + DeprecatedObjectTemplateMap deprecated_object_templates_; ObjectTemplateMap object_templates_; ++ DeprecatedFunctionTemplateMap deprecated_function_templates_; + FunctionTemplateMap function_templates_; base::ObserverList dispose_observers_; std::shared_ptr task_runner_; diff --git a/shell/browser/api/electron_api_session.cc b/shell/browser/api/electron_api_session.cc index 90c4a67847..2bb6795d66 100644 --- a/shell/browser/api/electron_api_session.cc +++ b/shell/browser/api/electron_api_session.cc @@ -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_in) - : session{std::move(session_in)} {} + explicit UserDataLink( + cppgc::WeakPersistent> session_in) + : session{session_in} {} - base::WeakPtr session; + cppgc::WeakPersistent> 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::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(weak_factory_.GetWeakPtr())); + std::make_unique( + cppgc::WeakPersistent>( + weak_factory_.GetWeakCell( + isolate->GetCppHeap()->GetAllocationHandle())))); #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) if (auto* service = @@ -583,14 +591,20 @@ Session::Session(v8::Isolate* isolate, ElectronBrowserContext* browser_context) } Session::~Session() { - browser_context()->GetDownloadManager()->RemoveObserver(this); + Dispose(); +} + +void Session::Dispose() { + if (keep_alive_) { + browser_context()->GetDownloadManager()->RemoveObserver(this); #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) - if (auto* service = - SpellcheckServiceFactory::GetForContext(browser_context())) { - service->SetHunspellObserver(nullptr); - } + if (auto* service = + SpellcheckServiceFactory::GetForContext(browser_context())) { + service->SetHunspellObserver(nullptr); + } #endif + } } void Session::OnDownloadCreated(content::DownloadManager* manager, @@ -1307,7 +1321,7 @@ v8::Local Session::GetSharedDictionaryUsageInfo() { } v8::Local 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 Session::Cookies(v8::Isolate* isolate) { v8::Local Session::Extensions(v8::Isolate* isolate) { #if BUILDFLAG(ENABLE_ELECTRON_EXTENSIONS) - if (extensions_.IsEmpty()) { + if (extensions_.IsEmptyThreadSafe()) { v8::Local handle; handle = Extensions::Create(isolate, browser_context()).ToV8(); extensions_.Reset(isolate, handle); @@ -1330,7 +1344,7 @@ v8::Local Session::Protocol(v8::Isolate* isolate) { } v8::Local Session::ServiceWorkerContext(v8::Isolate* isolate) { - if (service_worker_context_.IsEmpty()) { + if (service_worker_context_.IsEmptyThreadSafe()) { v8::Local handle; handle = ServiceWorkerContext::Create(isolate, browser_context()).ToV8(); service_worker_context_.Reset(isolate, handle); @@ -1339,7 +1353,7 @@ v8::Local Session::ServiceWorkerContext(v8::Isolate* isolate) { } v8::Local 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 Session::WebRequest(v8::Isolate* isolate) { } v8::Local 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,42 +1670,49 @@ bool Session::IsSpellCheckerEnabled() const { #endif // BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) // static -Session* Session::FromBrowserContext(content::BrowserContext* context) { +gin::WeakCell* Session::FromBrowserContext( + content::BrowserContext* context) { auto* data = static_cast(context->GetUserData(kElectronApiSessionKey)); - return data ? data->session.get() : nullptr; + if (data && data->session) + return data->session.Get(); + return nullptr; } // static -gin_helper::Handle 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); +Session* Session::CreateFrom(v8::Isolate* isolate, + ElectronBrowserContext* browser_context) { + gin::WeakCell* existing = FromBrowserContext(browser_context); + if (existing && existing->Get()) { + return existing->Get(); } - App::Get()->EmitWithoutEvent("session-created", handle); + auto* session = cppgc::MakeGarbageCollected( + 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 wrapper; + if (!session->GetWrapper(isolate).ToLocal(&wrapper)) { + return nullptr; + } + App::Get()->EmitWithoutEvent("session-created", wrapper); + } + + return session; } // static -gin_helper::Handle Session::FromPartition(v8::Isolate* isolate, - const std::string& partition, - base::Value::Dict options) { +Session* Session::FromPartition(v8::Isolate* isolate, + const std::string& partition, + base::Value::Dict options) { ElectronBrowserContext* browser_context; if (partition.empty()) { browser_context = @@ -1708,34 +1729,30 @@ gin_helper::Handle Session::FromPartition(v8::Isolate* isolate, } // static -std::optional> Session::FromPath( - v8::Isolate* isolate, - const base::FilePath& path, - base::Value::Dict options) { +Session* Session::FromPath(gin::Arguments* args, + const base::FilePath& path, + base::Value::Dict options) { ElectronBrowserContext* browser_context; if (path.empty()) { - gin_helper::Promise> 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> 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::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::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 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 wrapper; + if (!session->GetWrapper(args->isolate()).ToLocal(&wrapper)) { + return v8::Null(args->isolate()); + } + return wrapper; + } else { + return v8::Null(args->isolate()); + } } v8::Local FromPath(const base::FilePath& path, @@ -1855,13 +1902,18 @@ v8::Local FromPath(const base::FilePath& path, } base::Value::Dict options; args->GetNext(&options); - std::optional> 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 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 exports, @@ -1870,7 +1922,8 @@ void Initialize(v8::Local 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); } diff --git a/shell/browser/api/electron_api_session.h b/shell/browser/api/electron_api_session.h index 12d71ac634..822603fa14 100644 --- a/shell/browser/api/electron_api_session.h +++ b/shell/browser/api/electron_api_session.h @@ -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 -class Handle; -} // namespace gin_helper - namespace gin_helper { class Dictionary; class ErrorThrower; @@ -53,6 +48,11 @@ namespace net { class ProxyConfig; } +namespace v8 { +template +class TracedReference; +} + namespace electron { class ElectronBrowserContext; @@ -60,11 +60,10 @@ struct PreloadScript; namespace api { -class Session final : public gin_helper::DeprecatedWrappable, - public gin_helper::Pinnable, +class Session final : public gin::Wrappable, public gin_helper::Constructible, public gin_helper::EventEmitterMixin, - public gin_helper::CleanedUpAtExit, + public gin::PerIsolateData::DisposeObserver, public IpcDispatcher, #if BUILDFLAG(ENABLE_BUILTIN_SPELLCHECKER) private SpellcheckHunspellDictionary::Observer, @@ -72,39 +71,46 @@ class Session final : public gin_helper::DeprecatedWrappable, private content::DownloadManager::Observer { public: // Gets or creates Session from the |browser_context|. - static gin_helper::Handle CreateFrom( - v8::Isolate* isolate, - ElectronBrowserContext* browser_context); - static gin_helper::Handle New(); // Dummy, do not use! + static Session* CreateFrom(v8::Isolate* isolate, + ElectronBrowserContext* browser_context); + static void New(); // Dummy, do not use! - static Session* FromBrowserContext(content::BrowserContext* context); + static gin::WeakCell* FromBrowserContext( + content::BrowserContext* context); // Gets the Session of |partition|. - static gin_helper::Handle FromPartition( - v8::Isolate* isolate, - const std::string& partition, - base::Value::Dict options = {}); + static Session* FromPartition(v8::Isolate* isolate, + const std::string& partition, + base::Value::Dict options = {}); // Gets the Session based on |path|. - static std::optional> FromPath( - v8::Isolate* isolate, - const base::FilePath& path, - base::Value::Dict options = {}); + static Session* FromPath(gin::Arguments* args, + const base::FilePath& path, + base::Value::Dict options = {}); + + static void FillObjectTemplate(v8::Isolate*, v8::Local); + 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); - 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 ResolveHost( std::string host, std::optional params); @@ -180,9 +186,6 @@ class Session final : public gin_helper::DeprecatedWrappable, 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, v8::Local val); // Cached gin_helper::Wrappable objects. - v8::Global cookies_; - v8::Global extensions_; - v8::Global protocol_; - v8::Global net_log_; - v8::Global service_worker_context_; - v8::Global web_request_; + v8::TracedReference cookies_; + v8::TracedReference extensions_; + v8::TracedReference protocol_; + v8::TracedReference net_log_; + v8::TracedReference service_worker_context_; + v8::TracedReference web_request_; raw_ptr isolate_; @@ -216,7 +219,9 @@ class Session final : public gin_helper::DeprecatedWrappable, const raw_ref browser_context_; - base::WeakPtrFactory weak_factory_{this}; + gin::WeakCellFactory weak_factory_{this}; + + gin_helper::SelfKeepAlive keep_alive_{this}; }; } // namespace api diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index 4679ab95cf..e187828ff1 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/shell/browser/api/electron_api_web_contents.cc @@ -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(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 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 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 owned_web_contents, - gin_helper::Handle 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 WebContents::GetOwnerBrowserWindow( } v8::Local WebContents::Session(v8::Isolate* isolate) { - return v8::Local::New(isolate, session_); + v8::HandleScope handle_scope(isolate); + v8::Local wrapper; + if (!session_->GetWrapper(isolate).ToLocal(&wrapper)) { + return v8::Null(isolate); + } + return v8::Local::New(isolate, wrapper); } content::WebContents* WebContents::HostWebContents() const { diff --git a/shell/browser/api/electron_api_web_contents.h b/shell/browser/api/electron_api_web_contents.h index e0daa212ee..9deb95e341 100644 --- a/shell/browser/api/electron_api_web_contents.h +++ b/shell/browser/api/electron_api_web_contents.h @@ -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 web_contents, - gin_helper::Handle 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 session_; + cppgc::Persistent session_; v8::Global devtools_web_contents_; v8::Global debugger_; diff --git a/shell/browser/electron_api_ipc_handler_impl.cc b/shell/browser/electron_api_ipc_handler_impl.cc index 8a69482c12..0080717485 100644 --- a/shell/browser/electron_api_ipc_handler_impl.cc +++ b/shell/browser/electron_api_ipc_handler_impl.cc @@ -44,68 +44,80 @@ void ElectronApiIPCHandlerImpl::OnConnectionError() { void ElectronApiIPCHandlerImpl::Message(bool internal, const std::string& channel, blink::CloneableMessage arguments) { - auto* session = GetSession(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, internal); - if (event.IsEmpty()) - return; - session->Message(event, channel, std::move(arguments)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session->Get(), internal); + if (event.IsEmpty()) + return; + 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(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); - if (event.IsEmpty()) - return; - session->Invoke(event, channel, std::move(arguments)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = + MakeIPCEvent(isolate, session->Get(), internal, std::move(callback)); + if (event.IsEmpty()) + return; + session->Get()->Invoke(event, channel, std::move(arguments)); + } } void ElectronApiIPCHandlerImpl::ReceivePostMessage( const std::string& channel, blink::TransferableMessage message) { - auto* session = GetSession(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, false); - if (event.IsEmpty()) - return; - session->ReceivePostMessage(event, channel, std::move(message)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session->Get(), false); + if (event.IsEmpty()) + return; + 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(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); - if (event.IsEmpty()) - return; - session->MessageSync(event, channel, std::move(arguments)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = + MakeIPCEvent(isolate, session->Get(), internal, std::move(callback)); + if (event.IsEmpty()) + return; + session->Get()->MessageSync(event, channel, std::move(arguments)); + } } void ElectronApiIPCHandlerImpl::MessageHost(const std::string& channel, blink::CloneableMessage arguments) { - auto* session = GetSession(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, false); - if (event.IsEmpty()) - return; - session->MessageHost(event, channel, std::move(arguments)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session->Get(), false); + if (event.IsEmpty()) + return; + 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* ElectronApiIPCHandlerImpl::GetSession() { auto* rfh = GetRenderFrameHost(); return rfh ? api::Session::FromBrowserContext(rfh->GetBrowserContext()) : nullptr; diff --git a/shell/browser/electron_api_ipc_handler_impl.h b/shell/browser/electron_api_ipc_handler_impl.h index a3e730f1ef..ff73a6ea1f 100644 --- a/shell/browser/electron_api_ipc_handler_impl.h +++ b/shell/browser/electron_api_ipc_handler_impl.h @@ -18,6 +18,11 @@ namespace content { class RenderFrameHost; } +namespace gin { +template +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* GetSession(); gin_helper::Handle MakeIPCEvent( v8::Isolate* isolate, diff --git a/shell/browser/electron_api_sw_ipc_handler_impl.cc b/shell/browser/electron_api_sw_ipc_handler_impl.cc index 98ca9bab18..d8938f5863 100644 --- a/shell/browser/electron_api_sw_ipc_handler_impl.cc +++ b/shell/browser/electron_api_sw_ipc_handler_impl.cc @@ -71,51 +71,61 @@ void ElectronApiSWIPCHandlerImpl::RemoteDisconnected() { void ElectronApiSWIPCHandlerImpl::Message(bool internal, const std::string& channel, blink::CloneableMessage arguments) { - auto* session = GetSession(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, internal); - if (event.IsEmpty()) - return; - session->Message(event, channel, std::move(arguments)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session->Get(), internal); + if (event.IsEmpty()) + return; + 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(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); - if (event.IsEmpty()) - return; - session->Invoke(event, channel, std::move(arguments)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = + MakeIPCEvent(isolate, session->Get(), internal, std::move(callback)); + if (event.IsEmpty()) + return; + session->Get()->Invoke(event, channel, std::move(arguments)); + } } void ElectronApiSWIPCHandlerImpl::ReceivePostMessage( const std::string& channel, blink::TransferableMessage message) { - auto* session = GetSession(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, false); - if (event.IsEmpty()) - return; - session->ReceivePostMessage(event, channel, std::move(message)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = MakeIPCEvent(isolate, session->Get(), false); + if (event.IsEmpty()) + return; + 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(); - v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); - v8::HandleScope handle_scope(isolate); - auto event = MakeIPCEvent(isolate, session, internal, std::move(callback)); - if (event.IsEmpty()) - return; - session->MessageSync(event, channel, std::move(arguments)); + gin::WeakCell* session = GetSession(); + if (session && session->Get()) { + v8::Isolate* isolate = electron::JavascriptEnvironment::GetIsolate(); + v8::HandleScope handle_scope(isolate); + auto event = + MakeIPCEvent(isolate, session->Get(), internal, std::move(callback)); + if (event.IsEmpty()) + return; + 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* ElectronApiSWIPCHandlerImpl::GetSession() { return api::Session::FromBrowserContext(GetBrowserContext()); } diff --git a/shell/browser/electron_api_sw_ipc_handler_impl.h b/shell/browser/electron_api_sw_ipc_handler_impl.h index 47bff37a0d..aceae5ccbb 100644 --- a/shell/browser/electron_api_sw_ipc_handler_impl.h +++ b/shell/browser/electron_api_sw_ipc_handler_impl.h @@ -19,6 +19,11 @@ namespace content { class RenderProcessHost; } +namespace gin { +template +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* GetSession(); gin_helper::Handle MakeIPCEvent( v8::Isolate* isolate, diff --git a/shell/browser/event_emitter_mixin.h b/shell/browser/event_emitter_mixin.h index 03c0c63d7f..38005202b3 100644 --- a/shell/browser/event_emitter_mixin.h +++ b/shell/browser/event_emitter_mixin.h @@ -56,13 +56,13 @@ class EventEmitterMixin { gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); auto* wrapper_info = &(static_cast(this)->kWrapperInfo); v8::Local constructor = - data->GetFunctionTemplate(wrapper_info); + data->DeprecatedGetFunctionTemplate(wrapper_info); if (constructor.IsEmpty()) { constructor = v8::FunctionTemplate::New(isolate); constructor->SetClassName( gin::StringToV8(isolate, static_cast(this)->GetTypeName())); constructor->Inherit(internal::GetEventEmitterTemplate(isolate)); - data->SetFunctionTemplate(wrapper_info, constructor); + data->DeprecatedSetFunctionTemplate(wrapper_info, constructor); } return gin::ObjectTemplateBuilder(isolate, static_cast(this)->GetTypeName(), diff --git a/shell/browser/file_system_access/file_system_access_permission_context.cc b/shell/browser/file_system_access/file_system_access_permission_context.cc index 08f1cf937a..34eb74a7f6 100644 --- a/shell/browser/file_system_access/file_system_access_permission_context.cc +++ b/shell/browser/file_system_access/file_system_access_permission_context.cc @@ -732,21 +732,23 @@ void FileSystemAccessPermissionContext::DidCheckPathAgainstBlocklist( } if (should_block) { - auto* session = + gin::WeakCell* session = electron::api::Session::FromBrowserContext(browser_context()); - v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); - v8::HandleScope scope(isolate); - v8::Local details = - gin::DataObjectBuilder(isolate) - .Set("origin", origin.GetURL().spec()) - .Set("isDirectory", handle_type == HandleType::kDirectory) - .Set("path", path_info.path) - .Build(); - session->Emit( - "file-system-access-restricted", details, - base::BindRepeating( - &FileSystemAccessPermissionContext::OnRestrictedPathResult, - weak_factory_.GetWeakPtr(), path_info.path)); + if (session && session->Get()) { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + v8::HandleScope scope(isolate); + v8::Local details = + gin::DataObjectBuilder(isolate) + .Set("origin", origin.GetURL().spec()) + .Set("isDirectory", handle_type == HandleType::kDirectory) + .Set("path", path_info.path) + .Build(); + session->Get()->Emit( + "file-system-access-restricted", details, + base::BindRepeating( + &FileSystemAccessPermissionContext::OnRestrictedPathResult, + weak_factory_.GetWeakPtr(), path_info.path)); + } return; } diff --git a/shell/browser/hid/hid_chooser_context.cc b/shell/browser/hid/hid_chooser_context.cc index fa6ea210c0..1c53a86c2e 100644 --- a/shell/browser/hid/hid_chooser_context.cc +++ b/shell/browser/hid/hid_chooser_context.cc @@ -293,14 +293,15 @@ void HidChooserContext::RevokeDevicePermission( } else { RevokeEphemeralDevicePermission(origin, device); } - api::Session* session = api::Session::FromBrowserContext(browser_context_); - if (session) { + gin::WeakCell* 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); } } diff --git a/shell/browser/hid/hid_chooser_controller.cc b/shell/browser/hid/hid_chooser_controller.cc index 6cf1b0a910..3ca6f805fe 100644 --- a/shell/browser/hid/hid_chooser_controller.cc +++ b/shell/browser/hid/hid_chooser_controller.cc @@ -124,7 +124,7 @@ const std::string& HidChooserController::PhysicalDeviceIdFromDeviceInfo( : device.physical_device_id; } -api::Session* HidChooserController::GetSession() { +gin::WeakCell* 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* 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* 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* 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,10 +248,10 @@ void HidChooserController::OnGotDevices( .Set("deviceList", devicesToDisplay) .Set("frame", rfh) .Build(); - prevent_default = - session->Emit("select-hid-device", details, - base::BindRepeating(&HidChooserController::OnDeviceChosen, - weak_factory_.GetWeakPtr())); + prevent_default = session->Get()->Emit( + "select-hid-device", details, + base::BindRepeating(&HidChooserController::OnDeviceChosen, + weak_factory_.GetWeakPtr())); } if (!prevent_default) { RunCallback({}); diff --git a/shell/browser/hid/hid_chooser_controller.h b/shell/browser/hid/hid_chooser_controller.h index 87ac13ca58..cf1b83223e 100644 --- a/shell/browser/hid/hid_chooser_controller.h +++ b/shell/browser/hid/hid_chooser_controller.h @@ -28,7 +28,9 @@ class WebContents; namespace gin { class Arguments; -} +template +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* GetSession(); void OnGotDevices(std::vector devices); bool DisplayDevice(const device::mojom::HidDeviceInfo& device) const; bool FilterMatchesAny(const device::mojom::HidDeviceInfo& device) const; diff --git a/shell/browser/network_hints_handler_impl.cc b/shell/browser/network_hints_handler_impl.cc index d03c0affe2..fc52aa1eda 100644 --- a/shell/browser/network_hints_handler_impl.cc +++ b/shell/browser/network_hints_handler_impl.cc @@ -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* session = + electron::api::Session::FromBrowserContext(browser_context_); + if (session && session->Get()) { + session->Get()->Emit("preconnect", url.GetURL(), allow_credentials); } } diff --git a/shell/browser/serial/serial_chooser_context.cc b/shell/browser/serial/serial_chooser_context.cc index 22b09744a8..d238ac59a8 100644 --- a/shell/browser/serial/serial_chooser_context.cc +++ b/shell/browser/serial/serial_chooser_context.cc @@ -149,17 +149,16 @@ void SerialChooserContext::RevokePortPermissionWebInitiated( auto* web_contents = content::WebContents::FromRenderFrameHost(render_frame_host); - api::Session* session = + gin::WeakCell* 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); } } diff --git a/shell/browser/serial/serial_chooser_controller.cc b/shell/browser/serial/serial_chooser_controller.cc index 66a98fde22..e58604eb31 100644 --- a/shell/browser/serial/serial_chooser_controller.cc +++ b/shell/browser/serial/serial_chooser_controller.cc @@ -143,7 +143,7 @@ SerialChooserController::~SerialChooserController() { RunCallback(/*port=*/nullptr); } -api::Session* SerialChooserController::GetSession() { +gin::WeakCell* 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* 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* 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* 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())); diff --git a/shell/browser/serial/serial_chooser_controller.h b/shell/browser/serial/serial_chooser_controller.h index b030be80c8..d36381f51b 100644 --- a/shell/browser/serial/serial_chooser_controller.h +++ b/shell/browser/serial/serial_chooser_controller.h @@ -24,6 +24,11 @@ class RenderFrameHost; class WebContents; } // namespace content +namespace gin { +template +class WeakCell; +} // namespace gin + namespace electron { namespace api { @@ -64,7 +69,7 @@ class SerialChooserController final bool powered) override; private: - api::Session* GetSession(); + gin::WeakCell* GetSession(); void GetDevices(); void OnGetDevices(std::vector ports); bool DisplayDevice(const device::mojom::SerialPortInfo& port) const; diff --git a/shell/browser/usb/usb_chooser_context.cc b/shell/browser/usb/usb_chooser_context.cc index 1e4b76d803..a1c8de932c 100644 --- a/shell/browser/usb/usb_chooser_context.cc +++ b/shell/browser/usb/usb_chooser_context.cc @@ -276,14 +276,15 @@ void UsbChooserContext::RevokeObjectPermissionInternal( } } - api::Session* session = api::Session::FromBrowserContext(browser_context_); - if (session) { + gin::WeakCell* 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); } } diff --git a/shell/browser/usb/usb_chooser_controller.cc b/shell/browser/usb/usb_chooser_controller.cc index d786846a0e..0d738f2763 100644 --- a/shell/browser/usb/usb_chooser_controller.cc +++ b/shell/browser/usb/usb_chooser_controller.cc @@ -58,7 +58,7 @@ UsbChooserController::~UsbChooserController() { RunCallback(/*device_info=*/nullptr); } -api::Session* UsbChooserController::GetSession() { +gin::WeakCell* 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* 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* 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* 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,10 +132,10 @@ void UsbChooserController::GotUsbDeviceList( .Set("frame", rfh) .Build(); - prevent_default = - session->Emit("select-usb-device", details, - base::BindRepeating(&UsbChooserController::OnDeviceChosen, - weak_factory_.GetWeakPtr())); + prevent_default = session->Get()->Emit( + "select-usb-device", details, + base::BindRepeating(&UsbChooserController::OnDeviceChosen, + weak_factory_.GetWeakPtr())); } if (!prevent_default) { RunCallback(/*device_info=*/nullptr); diff --git a/shell/browser/usb/usb_chooser_controller.h b/shell/browser/usb/usb_chooser_controller.h index e2eaa60354..55deaa279f 100644 --- a/shell/browser/usb/usb_chooser_controller.h +++ b/shell/browser/usb/usb_chooser_controller.h @@ -22,7 +22,9 @@ class WebContents; namespace gin { class Arguments; -} +template +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* GetSession(); void GotUsbDeviceList(std::vector devices); bool DisplayDevice(const device::mojom::UsbDeviceInfo& device) const; void RunCallback(device::mojom::UsbDeviceInfoPtr device_info); diff --git a/shell/common/api/electron_api_native_image.cc b/shell/common/api/electron_api_native_image.cc index 5827ce1043..ced163c16a 100644 --- a/shell/common/api/electron_api_native_image.cc +++ b/shell/common/api/electron_api_native_image.cc @@ -577,11 +577,11 @@ gin::ObjectTemplateBuilder NativeImage::GetObjectTemplateBuilder( gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); auto* wrapper_info = &kWrapperInfo; v8::Local 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()) diff --git a/shell/common/api/electron_api_url_loader.cc b/shell/common/api/electron_api_url_loader.cc index 946afe3a2c..81084d9f1b 100644 --- a/shell/common/api/electron_api_url_loader.cc +++ b/shell/common/api/electron_api_url_loader.cc @@ -698,14 +698,15 @@ gin_helper::Handle SimpleURLLoaderWrapper::Create( ElectronBrowserContext* browser_context = nullptr; if (electron::IsBrowserProcess()) { std::string partition; - gin_helper::Handle 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(), ""); } - browser_context = session->browser_context(); + if (session) + browser_context = session->browser_context(); } auto ret = gin_helper::CreateHandle( diff --git a/shell/common/gin_helper/constructible.h b/shell/common/gin_helper/constructible.h index d854c66bef..dc07046269 100644 --- a/shell/common/gin_helper/constructible.h +++ b/shell/common/gin_helper/constructible.h @@ -45,7 +45,7 @@ class Constructible { gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); auto* wrapper_info = &T::kWrapperInfo; v8::Local 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 GetConstructor( + v8::Isolate* const isolate, + v8::Local context, + gin::WrapperInfo* wrapper_info) { + gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); + v8::Local constructor = + data->GetFunctionTemplate(wrapper_info); + if (constructor.IsEmpty()) { + constructor = gin::CreateConstructorFunctionTemplate( + isolate, base::BindRepeating(&T::New)); + if (std::is_base_of, 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(); diff --git a/shell/common/gin_helper/event_emitter_caller.h b/shell/common/gin_helper/event_emitter_caller.h index fe98a153ed..25c935d660 100644 --- a/shell/common/gin_helper/event_emitter_caller.h +++ b/shell/common/gin_helper/event_emitter_caller.h @@ -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 CallMethod(gin_helper::DeprecatedWrappable* object, return CallMethod(isolate, object, method_name, std::forward(args)...); } +template +v8::Local CallMethod(v8::Isolate* isolate, + gin::Wrappable* object, + const char* method_name, + Args&&... args) { + v8::EscapableHandleScope scope(isolate); + v8::Local v8_object; + if (object->GetWrapper(isolate).ToLocal(&v8_object)) + return scope.Escape(CustomEmit(isolate, v8_object, method_name, + std::forward(args)...)); + else + return {}; +} + +template +v8::Local CallMethod(gin::Wrappable* object, + const char* method_name, + Args&&... args) { + v8::Isolate* isolate = v8::Isolate::GetCurrent(); + return CallMethod(isolate, object, method_name, std::forward(args)...); +} + } // namespace gin_helper #endif // ELECTRON_SHELL_COMMON_GIN_HELPER_EVENT_EMITTER_CALLER_H_ diff --git a/shell/common/gin_helper/event_emitter_template.cc b/shell/common/gin_helper/event_emitter_template.cc index 44bd4a962c..a9fb4dce26 100644 --- a/shell/common/gin_helper/event_emitter_template.cc +++ b/shell/common/gin_helper/event_emitter_template.cc @@ -17,7 +17,7 @@ gin::DeprecatedWrapperInfo kWrapperInfo = {gin::kEmbedderNativeGin}; v8::Local GetEventEmitterTemplate(v8::Isolate* isolate) { gin::PerIsolateData* data = gin::PerIsolateData::From(isolate); v8::Local tmpl = - data->GetFunctionTemplate(&kWrapperInfo); + data->DeprecatedGetFunctionTemplate(&kWrapperInfo); if (tmpl.IsEmpty()) { tmpl = v8::FunctionTemplate::New(isolate); @@ -35,7 +35,7 @@ v8::Local GetEventEmitterTemplate(v8::Isolate* isolate) { ->SetPrototypeV2(context, eventemitter_prototype) .ToChecked()); - data->SetFunctionTemplate(&kWrapperInfo, tmpl); + data->DeprecatedSetFunctionTemplate(&kWrapperInfo, tmpl); } return tmpl; diff --git a/shell/common/gin_helper/function_template.h b/shell/common/gin_helper/function_template.h index 2732237963..43bace908b 100644 --- a/shell/common/gin_helper/function_template.h +++ b/shell/common/gin_helper/function_template.h @@ -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: diff --git a/shell/common/gin_helper/self_keep_alive.h b/shell/common/gin_helper/self_keep_alive.h new file mode 100644 index 0000000000..0e0c622f03 --- /dev/null +++ b/shell/common/gin_helper/self_keep_alive.h @@ -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 +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 keep_alive_; +}; + +} // namespace gin_helper + +#endif // ELECTRON_SHELL_COMMON_GIN_HELPER_SELF_KEEP_ALIVE_H_ diff --git a/shell/common/gin_helper/wrappable.h b/shell/common/gin_helper/wrappable.h index 8f8c6507b7..30197ca8ce 100644 --- a/shell/common/gin_helper/wrappable.h +++ b/shell/common/gin_helper/wrappable.h @@ -37,19 +37,19 @@ class Wrappable : public WrappableBase { isolate, base::BindRepeating(&internal::InvokeNew, 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 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; } diff --git a/spec/cpp-heap-spec.ts b/spec/cpp-heap-spec.ts index 55713688a2..1458819241 100644 --- a/spec/cpp-heap-spec.ts +++ b/spec/cpp-heap-spec.ts @@ -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); }); }); }); diff --git a/spec/lib/heapsnapshot-helpers.js b/spec/lib/heapsnapshot-helpers.js new file mode 100644 index 0000000000..025f8973f0 --- /dev/null +++ b/spec/lib/heapsnapshot-helpers.js @@ -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; +}