diff --git a/shell/browser/api/electron_api_power_monitor.h b/shell/browser/api/electron_api_power_monitor.h index 9fe720752f..6e0994d733 100644 --- a/shell/browser/api/electron_api_power_monitor.h +++ b/shell/browser/api/electron_api_power_monitor.h @@ -8,7 +8,6 @@ #include "base/power_monitor/power_observer.h" #include "gin/wrappable.h" #include "shell/browser/event_emitter_mixin.h" -#include "shell/common/gin_helper/self_keep_alive.h" #if BUILDFLAG(IS_LINUX) #include "shell/browser/lib/power_observer_linux.h" @@ -89,13 +88,14 @@ class PowerMonitor final : public gin::Wrappable, // The window used for processing events. HWND window_; + + // Handle returned by RegisterSuspendResumeNotification. + HPOWERNOTIFY power_notify_handle_ = nullptr; #endif #if BUILDFLAG(IS_LINUX) PowerObserverLinux power_observer_linux_{this}; #endif - - gin_helper::SelfKeepAlive keep_alive_{this}; }; } // namespace electron::api diff --git a/shell/browser/api/electron_api_power_monitor_win.cc b/shell/browser/api/electron_api_power_monitor_win.cc index f52b43bb23..9c223dcaf7 100644 --- a/shell/browser/api/electron_api_power_monitor_win.cc +++ b/shell/browser/api/electron_api_power_monitor_win.cc @@ -45,14 +45,19 @@ void PowerMonitor::InitPlatformSpecificMonitors() { // For Windows 8 and later, a new "connected standby" mode has been added and // we must explicitly register for its notifications. - RegisterSuspendResumeNotification(static_cast(window_), - DEVICE_NOTIFY_WINDOW_HANDLE); + power_notify_handle_ = RegisterSuspendResumeNotification( + static_cast(window_), DEVICE_NOTIFY_WINDOW_HANDLE); + PLOG_IF(ERROR, !power_notify_handle_) + << "RegisterSuspendResumeNotification failed"; } void PowerMonitor::DestroyPlatformSpecificMonitors() { if (window_) { WTSUnRegisterSessionNotification(window_); - UnregisterSuspendResumeNotification(static_cast(window_)); + if (power_notify_handle_) { + UnregisterSuspendResumeNotification(power_notify_handle_); + power_notify_handle_ = nullptr; + } gfx::SetWindowUserData(window_, nullptr); DestroyWindow(window_); window_ = nullptr; @@ -90,7 +95,8 @@ LRESULT CALLBACK PowerMonitor::WndProc(HWND hwnd, } if (should_treat_as_current_session) { if (wparam == WTS_SESSION_LOCK) { - // SelfKeepAlive prevents GC of this object, so Unretained is safe. + // JS module reference prevents GC of this object, so Unretained is + // safe. content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce([](PowerMonitor* pm) { pm->Emit("lock-screen"); }, diff --git a/spec/cpp-heap-spec.ts b/spec/cpp-heap-spec.ts index 15c03c4076..50f2b10bd2 100644 --- a/spec/cpp-heap-spec.ts +++ b/spec/cpp-heap-spec.ts @@ -176,4 +176,44 @@ describe('cpp heap', () => { expect(result).to.equal(true); }); }); + + describe('powerMonitor module', () => { + it('should retain native PowerMonitor via JS module reference', async () => { + const rc = await startRemoteControlApp(['--expose-internals', '--js-flags=--expose-gc']); + const result = await rc.remotely(async (heap: string) => { + const { powerMonitor, app } = require('electron'); + const { recordState } = require(heap); + const v8Util = (process as any)._linkedBinding('electron_common_v8_util'); + + // Register a listener to trigger native PowerMonitor creation. + const listener = () => {}; + powerMonitor.on('suspend', listener); + + // Add and remove several listeners to exercise the path, + // then GC to ensure no duplicate native objects are created. + for (let i = 0; i < 5; i++) { + const tmp = () => {}; + powerMonitor.on('resume', tmp); + powerMonitor.removeListener('resume', tmp); + } + + v8Util.requestGarbageCollectionForTesting(); + + const state = recordState(); + const nodes = state.snapshot.filter( + (node: any) => node.name === 'Electron / PowerMonitor' + ); + const found = nodes.length > 0; + const noDuplicates = nodes.length === 1; + + setTimeout(() => app.quit()); + return { found, noDuplicates }; + }, path.join(__dirname, '../../third_party/electron_node/test/common/heap')); + + const [code] = await once(rc.process, 'exit'); + expect(code).to.equal(0); + expect(result.found).to.equal(true, 'PowerMonitor should be in snapshot (held by JS module)'); + expect(result.noDuplicates).to.equal(true, 'should have exactly one PowerMonitor instance'); + }); + }); });