From 6b52a04ff1e7e69ca00c5a062a6fe060dc07ff31 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Tue, 23 Jul 2024 16:34:55 +0800 Subject: [PATCH 1/4] WebUI: avoid queuing up requests `setInterval()` will always fire a new timeout regardless previous `updateRssFeedList()` has completed or not. This patch will now wait for previous request to complete before another timeout. --- src/webui/www/private/views/rss.html | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/webui/www/private/views/rss.html b/src/webui/www/private/views/rss.html index 090499c2c..1fadd9c7e 100644 --- a/src/webui/www/private/views/rss.html +++ b/src/webui/www/private/views/rss.html @@ -180,7 +180,7 @@ const serverSyncRssDataInterval = 1500; let feedData = {}; let pathByFeedId = new Map(); - let feedRefreshTimer; + let feedRefreshTimer = -1; const rssFeedTable = new window.qBittorrent.DynamicTable.RssFeedTable(); const rssArticleTable = new window.qBittorrent.DynamicTable.RssArticleTable(); @@ -293,11 +293,15 @@ }; const unload = () => { - clearInterval(feedRefreshTimer); + clearTimeout(feedRefreshTimer); + feedRefreshTimer = -1; }; const load = () => { - feedRefreshTimer = setInterval(updateRssFeedList, serverSyncRssDataInterval); + feedRefreshTimer = setTimeout(() => { + updateRssFeedList(); + load(); + }, serverSyncRssDataInterval); }; const addRSSFeed = () => { From 062904c2bd2da09e041a7568a347ad06dc1bba5f Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Tue, 23 Jul 2024 16:42:17 +0800 Subject: [PATCH 2/4] WebUI: avoid excessive checking --- src/webui/www/private/scripts/piecesbar.js | 4 ++-- src/webui/www/private/scripts/progressbar.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/webui/www/private/scripts/piecesbar.js b/src/webui/www/private/scripts/piecesbar.js index 1e4cfbca1..bfaf540e4 100644 --- a/src/webui/www/private/scripts/piecesbar.js +++ b/src/webui/www/private/scripts/piecesbar.js @@ -88,7 +88,7 @@ window.qBittorrent.PiecesBar ??= (() => { if (vals.width > 0) obj.setPieces(vals.pieces); else - setTimeout(() => { checkForParent(obj.id); }, 1); + setTimeout(() => { checkForParent(obj.id); }); return obj; } @@ -258,7 +258,7 @@ window.qBittorrent.PiecesBar ??= (() => { if (!obj) return; if (!obj.parentNode) - return setTimeout(() => { checkForParent(id); }, 1); + return setTimeout(() => { checkForParent(id); }, 100); obj.refresh(); } diff --git a/src/webui/www/private/scripts/progressbar.js b/src/webui/www/private/scripts/progressbar.js index cda2b0090..9344c4cc8 100644 --- a/src/webui/www/private/scripts/progressbar.js +++ b/src/webui/www/private/scripts/progressbar.js @@ -104,7 +104,7 @@ window.qBittorrent.ProgressBar ??= (() => { if (vals.width) obj.setValue(vals.value); else - setTimeout('ProgressBar_checkForParent("' + obj.id + '")', 1); + setTimeout('ProgressBar_checkForParent("' + obj.id + '")'); return obj; } }); @@ -144,7 +144,7 @@ window.qBittorrent.ProgressBar ??= (() => { if (!obj) return; if (!obj.parentNode) - return setTimeout('ProgressBar_checkForParent("' + id + '")', 1); + return setTimeout('ProgressBar_checkForParent("' + id + '")', 100); obj.setStyle("width", "100%"); const w = obj.offsetWidth; obj.vals.dark.setStyle("width", w); From 7131d1bd6b2d8d2b5908873e2b72b0c2ec58a5bc Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Tue, 23 Jul 2024 16:54:10 +0800 Subject: [PATCH 3/4] WebUI: listen to resize events properly The workaround is not needed now. Also added a debouncer to avoid too many transient resizing events. --- src/webui/www/private/scripts/dynamicTable.js | 57 ++++++++----------- 1 file changed, 23 insertions(+), 34 deletions(-) diff --git a/src/webui/www/private/scripts/dynamicTable.js b/src/webui/www/private/scripts/dynamicTable.js index bc4242095..2ff251804 100644 --- a/src/webui/www/private/scripts/dynamicTable.js +++ b/src/webui/www/private/scripts/dynamicTable.js @@ -91,54 +91,43 @@ window.qBittorrent.DynamicTable ??= (() => { }, setupCommonEvents: function() { - const scrollFn = function() { - $(this.dynamicTableFixedHeaderDivId).getElements("table")[0].style.left = -$(this.dynamicTableDivId).scrollLeft + "px"; - }.bind(this); + const tableDiv = $(this.dynamicTableDivId); + const tableFixedHeaderDiv = $(this.dynamicTableFixedHeaderDivId); - $(this.dynamicTableDivId).addEvent("scroll", scrollFn); + tableDiv.addEventListener("scroll", () => { + tableFixedHeaderDiv.getElements("table")[0].style.left = `${-tableDiv.scrollLeft}px`; + }); // if the table exists within a panel - if ($(this.dynamicTableDivId).getParent(".panel")) { - const resizeFn = function() { - const panel = $(this.dynamicTableDivId).getParent(".panel"); - let h = panel.getBoundingClientRect().height - $(this.dynamicTableFixedHeaderDivId).getBoundingClientRect().height; - $(this.dynamicTableDivId).style.height = h + "px"; + const parentPanel = tableDiv.getParent(".panel"); + if (parentPanel) { + const resizeFn = (entries) => { + const panel = entries[0].target; + let h = panel.getBoundingClientRect().height - tableFixedHeaderDiv.getBoundingClientRect().height; + tableDiv.style.height = `${h}px`; // Workaround due to inaccurate calculation of elements heights by browser - let n = 2; // is panel vertical scrollbar visible or does panel content not fit? while (((panel.clientWidth !== panel.offsetWidth) || (panel.clientHeight !== panel.scrollHeight)) && (n > 0)) { --n; h -= 0.5; - $(this.dynamicTableDivId).style.height = h + "px"; + tableDiv.style.height = `${h}px`; } + }; - this.lastPanelHeight = panel.getBoundingClientRect().height; - }.bind(this); + this.resizeDebounceTimer = -1; + const resizeDebouncer = (entries) => { + clearTimeout(this.resizeDebounceTimer); + this.resizeDebounceTimer = setTimeout(() => { + resizeFn(entries); + resizeDebounceTimer = -1; + }, 100); + }; - $(this.dynamicTableDivId).getParent(".panel").addEvent("resize", resizeFn); - - this.lastPanelHeight = 0; - - // Workaround. Resize event is called not always (for example it isn't called when browser window changes it's size) - - const checkResizeFn = function() { - const tableDiv = $(this.dynamicTableDivId); - - // dynamicTableDivId is not visible on the UI - if (!tableDiv) - return; - - const panel = tableDiv.getParent(".panel"); - if (this.lastPanelHeight !== panel.getBoundingClientRect().height) { - this.lastPanelHeight = panel.getBoundingClientRect().height; - panel.fireEvent("resize"); - } - }.bind(this); - - setInterval(checkResizeFn, 500); + const resizeObserver = new ResizeObserver(resizeDebouncer); + resizeObserver.observe(parentPanel, { box: "border-box" }); } }, From bf7e1516d57638774c2668e25523180045831227 Mon Sep 17 00:00:00 2001 From: Chocobo1 Date: Fri, 26 Jul 2024 04:58:21 +0800 Subject: [PATCH 4/4] WebUI: clear timer variable properly In JS the timer handle pool is reused and therefore require careful handling of it. --- src/webui/www/private/scripts/client.js | 3 ++- src/webui/www/private/scripts/prop-files.js | 5 ++++- src/webui/www/private/scripts/prop-general.js | 3 ++- src/webui/www/private/scripts/prop-peers.js | 3 ++- src/webui/www/private/scripts/prop-trackers.js | 3 ++- src/webui/www/private/scripts/prop-webseeds.js | 3 ++- src/webui/www/private/scripts/search.js | 6 ++++-- 7 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/webui/www/private/scripts/client.js b/src/webui/www/private/scripts/client.js index e578606f6..548550819 100644 --- a/src/webui/www/private/scripts/client.js +++ b/src/webui/www/private/scripts/client.js @@ -705,7 +705,7 @@ window.addEventListener("DOMContentLoaded", () => { }; })(); - let syncMainDataTimeoutID; + let syncMainDataTimeoutID = -1; let syncRequestInProgress = false; const syncMainData = function() { const url = new URI("api/v2/sync/maindata"); @@ -889,6 +889,7 @@ window.addEventListener("DOMContentLoaded", () => { return; clearTimeout(syncMainDataTimeoutID); + syncMainDataTimeoutID = -1; if (window.qBittorrent.Client.isStopped()) return; diff --git a/src/webui/www/private/scripts/prop-files.js b/src/webui/www/private/scripts/prop-files.js index 2e0d74dec..f396f0545 100644 --- a/src/webui/www/private/scripts/prop-files.js +++ b/src/webui/www/private/scripts/prop-files.js @@ -306,6 +306,8 @@ window.qBittorrent.PropFiles ??= (() => { return; clearTimeout(loadTorrentFilesDataTimer); + loadTorrentFilesDataTimer = -1; + new Request({ url: "api/v2/torrents/filePrio", method: "post", @@ -331,7 +333,7 @@ window.qBittorrent.PropFiles ??= (() => { torrentFilesTable.updateTable(false); }; - let loadTorrentFilesDataTimer; + let loadTorrentFilesDataTimer = -1; const loadTorrentFilesData = function() { if ($("prop_files").hasClass("invisible") || $("propertiesPanel_collapseToggle").hasClass("panel-expand")) { @@ -378,6 +380,7 @@ window.qBittorrent.PropFiles ??= (() => { const updateData = function() { clearTimeout(loadTorrentFilesDataTimer); + loadTorrentFilesDataTimer = -1; loadTorrentFilesData(); }; diff --git a/src/webui/www/private/scripts/prop-general.js b/src/webui/www/private/scripts/prop-general.js index 342e85fa0..2ef547f4f 100644 --- a/src/webui/www/private/scripts/prop-general.js +++ b/src/webui/www/private/scripts/prop-general.js @@ -72,7 +72,7 @@ window.qBittorrent.PropGeneral ??= (() => { piecesBar.clear(); }; - let loadTorrentDataTimer; + let loadTorrentDataTimer = -1; const loadTorrentData = function() { if ($("prop_general").hasClass("invisible") || $("propertiesPanel_collapseToggle").hasClass("panel-expand")) { @@ -250,6 +250,7 @@ window.qBittorrent.PropGeneral ??= (() => { const updateData = function() { clearTimeout(loadTorrentDataTimer); + loadTorrentDataTimer = -1; loadTorrentData(); }; diff --git a/src/webui/www/private/scripts/prop-peers.js b/src/webui/www/private/scripts/prop-peers.js index bb12d7449..aed2ea9a8 100644 --- a/src/webui/www/private/scripts/prop-peers.js +++ b/src/webui/www/private/scripts/prop-peers.js @@ -37,7 +37,7 @@ window.qBittorrent.PropPeers ??= (() => { }; const torrentPeersTable = new window.qBittorrent.DynamicTable.TorrentPeersTable(); - let loadTorrentPeersTimer; + let loadTorrentPeersTimer = -1; let syncTorrentPeersLastResponseId = 0; let show_flags = true; @@ -109,6 +109,7 @@ window.qBittorrent.PropPeers ??= (() => { const updateData = function() { clearTimeout(loadTorrentPeersTimer); + loadTorrentPeersTimer = -1; loadTorrentPeersData(); }; diff --git a/src/webui/www/private/scripts/prop-trackers.js b/src/webui/www/private/scripts/prop-trackers.js index f675075b0..c88cae879 100644 --- a/src/webui/www/private/scripts/prop-trackers.js +++ b/src/webui/www/private/scripts/prop-trackers.js @@ -39,7 +39,7 @@ window.qBittorrent.PropTrackers ??= (() => { let current_hash = ""; const torrentTrackersTable = new window.qBittorrent.DynamicTable.TorrentTrackersTable(); - let loadTrackersDataTimer; + let loadTrackersDataTimer = -1; const loadTrackersData = function() { if ($("prop_trackers").hasClass("invisible") @@ -119,6 +119,7 @@ window.qBittorrent.PropTrackers ??= (() => { const updateData = function() { clearTimeout(loadTrackersDataTimer); + loadTrackersDataTimer = -1; loadTrackersData(); }; diff --git a/src/webui/www/private/scripts/prop-webseeds.js b/src/webui/www/private/scripts/prop-webseeds.js index 0bbc90a25..5c0d4e64d 100644 --- a/src/webui/www/private/scripts/prop-webseeds.js +++ b/src/webui/www/private/scripts/prop-webseeds.js @@ -88,7 +88,7 @@ window.qBittorrent.PropWebseeds ??= (() => { let current_hash = ""; - let loadWebSeedsDataTimer; + let loadWebSeedsDataTimer = -1; const loadWebSeedsData = function() { if ($("prop_webseeds").hasClass("invisible") || $("propertiesPanel_collapseToggle").hasClass("panel-expand")) { @@ -138,6 +138,7 @@ window.qBittorrent.PropWebseeds ??= (() => { const updateData = function() { clearTimeout(loadWebSeedsDataTimer); + loadWebSeedsDataTimer = -1; loadWebSeedsData(); }; diff --git a/src/webui/www/private/scripts/search.js b/src/webui/www/private/scripts/search.js index 133319f0b..72b447b49 100644 --- a/src/webui/www/private/scripts/search.js +++ b/src/webui/www/private/scripts/search.js @@ -47,7 +47,7 @@ window.qBittorrent.Search ??= (() => { }; const searchTabIdPrefix = "Search-"; - let loadSearchPluginsTimer; + let loadSearchPluginsTimer = -1; const searchPlugins = []; let prevSearchPluginsResponse; let selectedCategory = "QBT_TR(All categories)QBT_TR[CONTEXT=SearchEngineWidget]"; @@ -207,7 +207,7 @@ window.qBittorrent.Search ??= (() => { rowId: 0, selectedRowIds: [], running: true, - loadResultsTimer: null, + loadResultsTimer: -1, sort: { column: searchResultsTable.sortedColumn, reverse: searchResultsTable.reverseSort }, }); updateSearchResultsData(searchId); @@ -507,6 +507,7 @@ window.qBittorrent.Search ??= (() => { }, onClose: function() { clearTimeout(loadSearchPluginsTimer); + loadSearchPluginsTimer = -1; } }); } @@ -569,6 +570,7 @@ window.qBittorrent.Search ??= (() => { if (state) { state.running = false; clearTimeout(state.loadResultsTimer); + state.loadResultsTimer = -1; } };