perf(webui): make the UI usable with password-manager extensions
Password managers (Bitwarden/1Password/LastPass) treat this app's many API-key/ token/secret fields as login forms and re-scan the whole, constantly-mutating DOM on every change — pegging the main thread for seconds and making hover/click/ scroll feel laggy. Two mitigations (measured to make the app usable with the extension enabled): - Tag all inputs with data-bwignore / data-1p-ignore / data-lpignore so the managers skip them (no autofill detection work). - Rate-monitor equalizer: skip DOM writes while it's off-screen (offsetParent null). All pages stay mounted, so updating the hidden grid still triggered the managers' MutationObserver on every backend rate-monitor event for no benefit. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
89fe7703fa
commit
0fe0a4c45b
2 changed files with 30 additions and 0 deletions
|
|
@ -65,6 +65,14 @@ function _handleRateMonitorUpdate(data) {
|
|||
const grid = document.getElementById('rate-monitor-grid');
|
||||
if (!grid) return;
|
||||
|
||||
// Skip DOM writes while the equalizer is off-screen (you're on another page).
|
||||
// All pages stay mounted, so updating a hidden grid still fires every
|
||||
// MutationObserver on the document — including password-manager extensions
|
||||
// that re-scan the WHOLE DOM on each mutation — for zero visible benefit.
|
||||
// offsetParent is null when an ancestor is display:none. The next update that
|
||||
// arrives while the dashboard is visible renders normally.
|
||||
if (grid.offsetParent === null) return;
|
||||
|
||||
// The dashboard rate monitor uses the equalizer-bar visual — a
|
||||
// vertical-bar VU-meter row that fits any service count without
|
||||
// an orphan grid cell. Detail page / mobile breakpoints keep the
|
||||
|
|
|
|||
|
|
@ -143,6 +143,25 @@ function handleMetadataSourceChange(event) {
|
|||
}
|
||||
|
||||
let _settingsInitialized = false;
|
||||
// Tell password-manager extensions (Bitwarden / 1Password / LastPass) to ignore
|
||||
// this app's credential inputs. The settings page is full of API-key / token /
|
||||
// secret fields; password managers treat them as login forms and re-scan the
|
||||
// whole (large, constantly-mutating) DOM on every change, which can peg the main
|
||||
// thread for seconds. These attributes make them skip the fields entirely.
|
||||
function _markCredentialFieldsNoAutofill(root) {
|
||||
const scope = root || document;
|
||||
scope.querySelectorAll('input, textarea').forEach((el) => {
|
||||
if (el.dataset.bwignore !== undefined) return; // already tagged
|
||||
el.setAttribute('data-bwignore', ''); // Bitwarden
|
||||
el.setAttribute('data-1p-ignore', ''); // 1Password
|
||||
el.setAttribute('data-lpignore', 'true'); // LastPass
|
||||
if (!el.getAttribute('autocomplete')) el.setAttribute('autocomplete', 'off');
|
||||
});
|
||||
}
|
||||
// Run once on load (inputs exist from page load — all pages are mounted).
|
||||
if (document.readyState !== 'loading') _markCredentialFieldsNoAutofill();
|
||||
else document.addEventListener('DOMContentLoaded', () => _markCredentialFieldsNoAutofill());
|
||||
|
||||
function initializeSettings() {
|
||||
// This function is called when the settings page is loaded.
|
||||
// It attaches event listeners to all interactive elements on the page.
|
||||
|
|
@ -151,6 +170,9 @@ function initializeSettings() {
|
|||
if (_settingsInitialized) return;
|
||||
_settingsInitialized = true;
|
||||
|
||||
// Re-tag in case any inputs were added dynamically since page load.
|
||||
_markCredentialFieldsNoAutofill(document.getElementById('settings-page'));
|
||||
|
||||
// Accent color listeners (live preview + custom picker toggle)
|
||||
initAccentColorListeners();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue