From 1179fc3de3d4707edb87328f54f86e5c8579f1e4 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 9 Aug 2024 15:52:46 +0800 Subject: [PATCH 1/4] WebUI: prevent passing wrong parameter The `event` object will be passed as the first parameter to the event handler. So wrap the event handler with a closure to prevent `event` leaking to other functions. --- src/webui/www/private/scripts/client.js | 14 +++++++------- src/webui/www/private/scripts/contextmenu.js | 4 ++-- src/webui/www/private/scripts/search.js | 5 +++-- src/webui/www/private/views/searchplugins.html | 10 ++++++---- 4 files changed, 18 insertions(+), 15 deletions(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index dfdad4cb6..de6135e9c 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -1059,8 +1059,8 @@ window.addEventListener("DOMContentLoaded", () => { }).send(); }); - $("DlInfos").addEventListener("click", globalDownloadLimitFN); - $("UpInfos").addEventListener("click", globalUploadLimitFN); + $("DlInfos").addEventListener("click", () => { globalDownloadLimitFN(); }); + $("UpInfos").addEventListener("click", () => { globalUploadLimitFN(); }); $("showTopToolbarLink").addEventListener("click", (e) => { showTopToolbar = !showTopToolbar; @@ -1206,7 +1206,7 @@ window.addEventListener("DOMContentLoaded", () => { $("mainWindowTabs").addClass("invisible"); }; - $("StatisticsLink").addEventListener("click", StatisticsLinkFN); + $("StatisticsLink").addEventListener("click", () => { StatisticsLinkFN(); }); // main window tabs @@ -1567,10 +1567,10 @@ window.addEventListener("DOMContentLoaded", () => { document.getElementById("torrentsFilterToolbar").addEventListener("change", (e) => { torrentsTable.updateTable(); }); - $("transfersTabLink").addEventListener("click", showTransfersTab); - $("searchTabLink").addEventListener("click", showSearchTab); - $("rssTabLink").addEventListener("click", showRssTab); - $("logTabLink").addEventListener("click", showLogTab); + $("transfersTabLink").addEventListener("click", () => { showTransfersTab(); }); + $("searchTabLink").addEventListener("click", () => { showSearchTab(); }); + $("rssTabLink").addEventListener("click", () => { showRssTab(); }); + $("logTabLink").addEventListener("click", () => { showLogTab(); }); updateTabDisplay(); const registerDragAndDrop = () => { diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index 979766ba6..ad1b92d02 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -440,7 +440,7 @@ window.qBittorrent.ContextMenu ??= (() => { const createMenuItem = (text, imgURL, clickFn) => { const anchor = document.createElement("a"); anchor.textContent = text; - anchor.addEventListener("click", clickFn); + anchor.addEventListener("click", () => { clickFn(); }); const img = document.createElement("img"); img.src = imgURL; @@ -495,7 +495,7 @@ window.qBittorrent.ContextMenu ??= (() => { const createMenuItem = (text, imgURL, clickFn) => { const anchor = document.createElement("a"); anchor.textContent = text; - anchor.addEventListener("click", clickFn); + anchor.addEventListener("click", () => { clickFn(); }); const img = document.createElement("img"); img.src = imgURL; diff --git a/src/webui/www/private/scripts/search.js b/src/webui/www/private/scripts/search.js index 7db6064b0..5d44efd79 100644 --- a/src/webui/www/private/scripts/search.js +++ b/src/webui/www/private/scripts/search.js @@ -753,14 +753,15 @@ window.qBittorrent.Search ??= (() => { }; const setupSearchTableEvents = function(enable) { + const clickHandler = (e) => { downloadSearchTorrent(); }; if (enable) { $$(".searchTableRow").each((target) => { - target.addEventListener("dblclick", downloadSearchTorrent, false); + target.addEventListener("dblclick", clickHandler); }); } else { $$(".searchTableRow").each((target) => { - target.removeEventListener("dblclick", downloadSearchTorrent, false); + target.removeEventListener("dblclick", clickHandler); }); } }; diff --git a/src/webui/www/private/views/searchplugins.html b/src/webui/www/private/views/searchplugins.html index b4d0672b7..1aee0bf7e 100644 --- a/src/webui/www/private/views/searchplugins.html +++ b/src/webui/www/private/views/searchplugins.html @@ -182,16 +182,18 @@ }; const setupSearchPluginTableEvents = function(enable) { + const clickHandler = (e) => { enablePlugin(); }; + const menuHandler = (e) => { updateSearchPluginsTableContextMenuOffset(); }; if (enable) { $$(".searchPluginsTableRow").each((target) => { - target.addEventListener("dblclick", enablePlugin, false); - target.addEventListener("contextmenu", updateSearchPluginsTableContextMenuOffset, true); + target.addEventListener("dblclick", clickHandler); + target.addEventListener("contextmenu", menuHandler, true); }); } else { $$(".searchPluginsTableRow").each((target) => { - target.removeEventListener("dblclick", enablePlugin, false); - target.removeEventListener("contextmenu", updateSearchPluginsTableContextMenuOffset, true); + target.removeEventListener("dblclick", clickHandler); + target.removeEventListener("contextmenu", menuHandler, true); }); } }; From 0c580c3174a7468fb9c92676a28d4f33cdb044f5 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 9 Aug 2024 17:21:01 +0800 Subject: [PATCH 2/4] WebUI: remove redundant events The base class already handle them. Also optimize the base implementation a bit. --- src/webui/www/private/scripts/dynamicTable.js | 17 ++--------------- 1 file changed, 2 insertions(+), 15 deletions(-) diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index b8af2dacb..5c177bb1c 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -95,8 +95,9 @@ window.qBittorrent.DynamicTable ??= (() => { const tableDiv = $(this.dynamicTableDivId); const tableFixedHeaderDiv = $(this.dynamicTableFixedHeaderDivId); + const tableElement = tableFixedHeaderDiv.querySelector("table"); tableDiv.addEventListener("scroll", () => { - tableFixedHeaderDiv.getElements("table")[0].style.left = `${-tableDiv.scrollLeft}px`; + tableElement.style.left = `${-tableDiv.scrollLeft}px`; }); // if the table exists within a panel @@ -2764,13 +2765,6 @@ window.qBittorrent.DynamicTable ??= (() => { this.hiddenTableHeader.appendChild(new Element("th")); this.fixedTableHeader.appendChild(new Element("th")); - }, - setupCommonEvents: function() { - const scrollFn = function() { - $(this.dynamicTableFixedHeaderDivId).getElements("table")[0].style.left = -$(this.dynamicTableDivId).scrollLeft + "px"; - }.bind(this); - - $(this.dynamicTableDivId).addEventListener("scroll", scrollFn); } }); @@ -2859,13 +2853,6 @@ window.qBittorrent.DynamicTable ??= (() => { this.hiddenTableHeader.appendChild(new Element("th")); this.fixedTableHeader.appendChild(new Element("th")); - }, - setupCommonEvents: function() { - const scrollFn = function() { - $(this.dynamicTableFixedHeaderDivId).getElements("table")[0].style.left = -$(this.dynamicTableDivId).scrollLeft + "px"; - }.bind(this); - - $(this.dynamicTableDivId).addEventListener("scroll", scrollFn); } }); From 29379232aa77234a1f0ac816c8d3bc32e3755b8f Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 9 Aug 2024 17:48:03 +0800 Subject: [PATCH 3/4] WebUI: implement debounce behavior for resize events --- src/webui/www/private/scripts/client.js | 22 +++++++++++-------- src/webui/www/private/scripts/dynamicTable.js | 11 +++------- src/webui/www/private/scripts/misc.js | 13 +++++++++++ src/webui/www/private/scripts/mocha-init.js | 20 ++++++++--------- src/webui/www/private/scripts/search.js | 4 ++-- src/webui/www/private/views/rss.html | 4 ++-- 6 files changed, 43 insertions(+), 31 deletions(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index de6135e9c..90ec1ff96 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -165,11 +165,11 @@ window.addEventListener("DOMContentLoaded", () => { LocalPreferences.set("properties_height_rel", properties_height_rel); }; - window.addEventListener("resize", () => { + window.addEventListener("resize", window.qBittorrent.Misc.createDebounceHandler(500, (e) => { // only save sizes if the columns are visible if (!$("mainColumn").hasClass("invisible")) - saveColumnSizes.delay(200); // Resizing might takes some time. - }); + saveColumnSizes(); + })); /* MochaUI.Desktop = new MochaUI.Desktop(); MochaUI.Desktop.desktop.style.background = "#fff"; @@ -181,7 +181,9 @@ window.addEventListener("DOMContentLoaded", () => { new MochaUI.Column({ id: "filtersColumn", placement: "left", - onResize: saveColumnSizes, + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { + saveColumnSizes(); + }), width: filt_w, resizeLimit: [1, 300] }); @@ -1449,7 +1451,9 @@ window.addEventListener("DOMContentLoaded", () => { updateMainData(); }, column: "mainColumn", - onResize: saveColumnSizes, + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { + saveColumnSizes(); + }), height: null }); let prop_h = LocalPreferences.get("properties_height_rel"); @@ -1614,9 +1618,9 @@ window.addEventListener("DOMContentLoaded", () => { paddingHorizontal: 0, width: loadWindowWidth(id, 500), height: loadWindowHeight(id, 460), - onResize: () => { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - }, + }), onContentLoaded: () => { const fileInput = $(`${id}_iframe`).contentDocument.getElementById("fileselect"); fileInput.files = droppedFiles; @@ -1658,9 +1662,9 @@ window.addEventListener("DOMContentLoaded", () => { paddingHorizontal: 0, width: loadWindowWidth(id, 500), height: loadWindowHeight(id, 600), - onResize: () => { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - } + }) }); } }); diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 5c177bb1c..5c87c4ccc 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -119,14 +119,9 @@ window.qBittorrent.DynamicTable ??= (() => { } }; - this.resizeDebounceTimer = -1; - const resizeDebouncer = (entries) => { - clearTimeout(this.resizeDebounceTimer); - this.resizeDebounceTimer = setTimeout(() => { - resizeFn(entries); - this.resizeDebounceTimer = -1; - }, 100); - }; + const resizeDebouncer = window.qBittorrent.Misc.createDebounceHandler(100, (entries) => { + resizeFn(entries); + }); const resizeObserver = new ResizeObserver(resizeDebouncer); resizeObserver.observe(parentPanel, { box: "border-box" }); diff --git a/src/webui/www/private/scripts/misc.js b/src/webui/www/private/scripts/misc.js index 0dd3bb901..422ac33f7 100644 --- a/src/webui/www/private/scripts/misc.js +++ b/src/webui/www/private/scripts/misc.js @@ -32,6 +32,7 @@ window.qBittorrent ??= {}; window.qBittorrent.Misc ??= (() => { const exports = () => { return { + createDebounceHandler: createDebounceHandler, friendlyUnit: friendlyUnit, friendlyDuration: friendlyDuration, friendlyPercentage: friendlyPercentage, @@ -50,6 +51,18 @@ window.qBittorrent.Misc ??= (() => { }; }; + const createDebounceHandler = (delay, func) => { + let timer = -1; + return (...params) => { + clearTimeout(timer); + timer = setTimeout(() => { + func(...params); + + timer = -1; + }, delay); + }; + }; + /* * JS counterpart of the function in src/misc.cpp */ diff --git a/src/webui/www/private/scripts/mocha-init.js b/src/webui/www/private/scripts/mocha-init.js index c19cc545e..483837785 100644 --- a/src/webui/www/private/scripts/mocha-init.js +++ b/src/webui/www/private/scripts/mocha-init.js @@ -142,9 +142,9 @@ const initializeWindows = function() { paddingHorizontal: 0, width: loadWindowWidth(id, 500), height: loadWindowHeight(id, 600), - onResize: function() { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - } + }) }); updateMainData(); }; @@ -171,9 +171,9 @@ const initializeWindows = function() { paddingHorizontal: 0, width: loadWindowWidth(id, 700), height: loadWindowHeight(id, 600), - onResize: function() { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - } + }) }); }); @@ -195,9 +195,9 @@ const initializeWindows = function() { paddingHorizontal: 0, width: loadWindowWidth(id, 500), height: loadWindowHeight(id, 460), - onResize: function() { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - } + }) }); updateMainData(); }); @@ -367,9 +367,9 @@ const initializeWindows = function() { padding: 10, width: loadWindowWidth(id, 275), height: loadWindowHeight(id, 370), - onResize: function() { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - } + }) }); }; @@ -1191,9 +1191,9 @@ const initializeWindows = function() { padding: 10, width: loadWindowWidth(id, 550), height: loadWindowHeight(id, 360), - onResize: function() { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - } + }) }); }); diff --git a/src/webui/www/private/scripts/search.js b/src/webui/www/private/scripts/search.js index 5d44efd79..b9942b98c 100644 --- a/src/webui/www/private/scripts/search.js +++ b/src/webui/www/private/scripts/search.js @@ -502,9 +502,9 @@ window.qBittorrent.Search ??= (() => { paddingHorizontal: 0, width: loadWindowWidth(id, 600), height: loadWindowHeight(id, 360), - onResize: function() { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - }, + }), onBeforeBuild: function() { loadSearchPlugins(); }, diff --git a/src/webui/www/private/views/rss.html b/src/webui/www/private/views/rss.html index c5b43b52d..a02f97caa 100644 --- a/src/webui/www/private/views/rss.html +++ b/src/webui/www/private/views/rss.html @@ -837,9 +837,9 @@ maximizable: false, width: loadWindowWidth(id, 800), height: loadWindowHeight(id, 650), - onResize: () => { + onResize: window.qBittorrent.Misc.createDebounceHandler(500, (e) => { saveWindowSize(id); - }, + }), resizeLimit: { "x": [800, 2500], "y": [500, 2000] From 98623b2cf720d22a580c922136cdbc46d75428dd Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Sat, 10 Aug 2024 01:33:13 +0800 Subject: [PATCH 4/4] WebUI: use passive event handlers These kind of event handlers can be asynchronously dispatched, freeing up the main thread for lag-free operation. --- src/webui/www/private/scripts/contextmenu.js | 4 ++-- src/webui/www/private/scripts/dynamicTable.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webui/www/private/scripts/contextmenu.js b/src/webui/www/private/scripts/contextmenu.js index ad1b92d02..13d28625b 100644 --- a/src/webui/www/private/scripts/contextmenu.js +++ b/src/webui/www/private/scripts/contextmenu.js @@ -156,7 +156,7 @@ window.qBittorrent.ContextMenu ??= (() => { this.hide(); this.touchStartAt = performance.now(); this.touchStartEvent = e; - }); + }, { passive: true }); elem.addEventListener("touchend", (e) => { const now = performance.now(); const touchStartAt = this.touchStartAt; @@ -168,7 +168,7 @@ window.qBittorrent.ContextMenu ??= (() => { const isTargetUnchanged = (Math.abs(e.event.pageX - touchStartEvent.event.pageX) <= 10) && (Math.abs(e.event.pageY - touchStartEvent.event.pageY) <= 10); if (((now - touchStartAt) >= this.options.touchTimer) && isTargetUnchanged) this.triggerMenu(touchStartEvent, elem); - }); + }, { passive: true }); }, addTarget: function(t) { diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index 5c87c4ccc..d87272893 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -274,7 +274,7 @@ window.qBittorrent.DynamicTable ??= (() => { const th = ths[i]; th.addEventListener("mousemove", mouseMoveFn); th.addEventListener("mouseout", mouseOutFn); - th.addEventListener("touchend", onTouch); + th.addEventListener("touchend", onTouch, { passive: true }); th.makeResizable({ modifiers: { x: "", @@ -762,7 +762,7 @@ window.qBittorrent.DynamicTable ??= (() => { this._this.deselectAll(); this._this.selectRow(this.rowId); } - }); + }, { passive: true }); tr.addEventListener("keydown", function(event) { switch (event.key) { case "up":