From 93925042dd2ebe565032cbcdd902cb293df364e0 Mon Sep 17 00:00:00 2001 From: skomerko <168652295+skomerko@users.noreply.github.com> Date: Sat, 8 Feb 2025 10:51:21 +0100 Subject: [PATCH] WebUI: Fix memory leak in context menus This PR fixes a memory leak in context menus. Previously, for some reason, each menu retained references to its target elements without utilizing them further. Since the targets property was accessible/reachable from the root (window object), these references persisted even after the elements were removed from the DOM, preventing them from being garbage collected. It's easily reproducible - just add a decent amount of torrents, switch between categories multiple times, then capture heap/detached elements snapshot in the Memory tab (Chrome dev tools). The number of detached elements will continue to increase after each category switch and they won't be cleaned up. [More context](https://github.com/qbittorrent/qBittorrent/pull/22220/files#r1941137796) PR #22234. --- src/webui/www/private/scripts/contextmenu.js | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index 542a8d057c95..7831461bf5fe 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -67,9 +67,6 @@ window.qBittorrent.ContextMenu ??= (() => { // option diffs menu this.menu = $(this.options.menu); - this.targets = (this.options.targets.length > 0) - ? Array.from(document.querySelectorAll(this.options.targets)) - : []; // fx this.fx = new Fx.Tween(this.menu, { @@ -169,15 +166,18 @@ window.qBittorrent.ContextMenu ??= (() => { } addTarget(t) { + if (t.hasEventListeners) + return; + // prevent long press from selecting this text t.style.userSelect = "none"; - - this.targets[this.targets.length] = t; + t.hasEventListeners = true; this.setupEventListeners(t); } searchAndAddTargets() { - document.querySelectorAll(this.options.targets).forEach((target) => { this.addTarget(target); }); + if (this.options.targets.length > 0) + document.querySelectorAll(this.options.targets).forEach((target) => { this.addTarget(target); }); } triggerMenu(e, el) { @@ -199,8 +199,7 @@ window.qBittorrent.ContextMenu ??= (() => { // get things started startListener() { /* all elements */ - for (const el of this.targets) - this.setupEventListeners(el); + this.searchAndAddTargets(); /* menu items */ this.menu.addEventListener("click", (e) => { @@ -222,6 +221,7 @@ window.qBittorrent.ContextMenu ??= (() => { // hide on body click $(document.body).addEventListener("click", () => { this.hide(); + this.options.element = null; }); }