From 0e66d9f77460ba2bf9eb94609d1ca9032307c57d Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:40:40 -0500 Subject: [PATCH] test: add cppgc backed menu leak regression test (#50882) * spec: add menu leak regression test Co-authored-by: deepak1556 * spec: reduce menu count to remove CI flakiness Co-authored-by: deepak1556 --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: deepak1556 --- spec/cpp-heap-spec.ts | 46 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/spec/cpp-heap-spec.ts b/spec/cpp-heap-spec.ts index bc36fd8283..74395d7a2b 100644 --- a/spec/cpp-heap-spec.ts +++ b/spec/cpp-heap-spec.ts @@ -77,6 +77,52 @@ describe('cpp heap', () => { }); }); + describe('menu module', () => { + // Regression test for https://github.com/electron/electron/issues/50791 + it('should not leak when rebuilding application menu', async () => { + const { remotely } = await startRemoteControlApp(['--js-flags=--expose-gc']); + const result = await remotely(async () => { + const { Menu } = require('electron'); + const v8Util = (process as any)._linkedBinding('electron_common_v8_util'); + const { getCppHeapStatistics } = require('node:v8'); + + function buildLargeMenu () { + return Menu.buildFromTemplate( + Array.from({ length: 10 }, (_, i) => ({ + label: `Menu ${i}`, + submenu: Array.from({ length: 20 }, (_, j) => ({ + label: `Item ${i}-${j}`, + click: () => {} + })) + })) + ); + } + + async function rebuildAndMeasure (n: number) { + for (let i = 0; i < n; i++) { + Menu.setApplicationMenu(buildLargeMenu()); + } + for (let i = 0; i < 10; i++) { + await new Promise(resolve => setTimeout(resolve, 0)); + v8Util.requestGarbageCollectionForTesting(); + } + return getCppHeapStatistics('brief').used_size_bytes; + } + + await rebuildAndMeasure(50); + const after1 = await rebuildAndMeasure(50); + const after2 = await rebuildAndMeasure(50); + return { after1, after2 }; + }); + + const growth = result.after2 - result.after1; + expect(growth).to.be.at.most( + result.after1 * 0.1, + `C++ heap grew by ${growth} bytes between two identical rounds of 100 menu rebuilds — likely a leak` + ); + }); + }); + describe('internal event', () => { it('should record as node in heap snapshot', async () => { const { remotely } = await startRemoteControlApp(['--expose-internals']);