From e95c8ad4502405db2005f8db3cf1740a033bb12d Mon Sep 17 00:00:00 2001 From: varjolintu Date: Wed, 1 Jan 2020 18:34:01 +0200 Subject: [PATCH] Performance adjustments --- keepassxc-browser/_locales/en/messages.json | 8 ++ keepassxc-browser/background/page.js | 13 +- .../content/keepassxc-browser.js | 129 +++++++++++------- keepassxc-browser/content/ui.js | 10 ++ keepassxc-browser/options/options.html | 10 ++ 5 files changed, 117 insertions(+), 53 deletions(-) diff --git a/keepassxc-browser/_locales/en/messages.json b/keepassxc-browser/_locales/en/messages.json index 1b55928..0e4fbb9 100644 --- a/keepassxc-browser/_locales/en/messages.json +++ b/keepassxc-browser/_locales/en/messages.json @@ -575,6 +575,14 @@ "message": "Activate username field icons.", "description": "Activate username field icons textbox text." }, + "optionsUseObserver": { + "message": "Use dynamic input field detection.", + "description": "Use dynamic input field detection checkbox text." + }, + "optionsUseObserverHelpText": { + "message": "Useful with pages that use popups or dynamic dialogs. Disable the feature to reduce CPU load on slower machines. When disabled credentials can be only inserted using keyboard shortcuts, the context menu or the extension popup.", + "description": "Use dynamic input field detection help text." + }, "optionsCheckboxOTPIcons": { "message": "Activate 2FA/OTP field icons.", "description": "Activate OTP field icons textbox text." diff --git a/keepassxc-browser/background/page.js b/keepassxc-browser/background/page.js index c87ca68..3d9e4a0 100755 --- a/keepassxc-browser/background/page.js +++ b/keepassxc-browser/background/page.js @@ -17,6 +17,7 @@ const defaultSettings = { showLoginNotifications: true, showNotifications: true, showOTPIcon: true, + useObserver: true, usePasswordGeneratorIcons: false }; @@ -76,6 +77,10 @@ page.initSettings = async function() { page.settings.defaultGroupAlwaysAsk = defaultSettings.defaultGroupAlwaysAsk; } + if (!('redirectAllowance' in page.settings)) { + page.settings.redirectAllowance = defaultSettings.redirectAllowance; + } + if (!('saveDomainOnly' in page.settings)) { page.settings.saveDomainOnly = defaultSettings.saveDomainOnly; } @@ -100,8 +105,12 @@ page.initSettings = async function() { page.settings.usePasswordGeneratorIcons = defaultSettings.usePasswordGeneratorIcons; } - if (!('redirectAllowance' in page.settings)) { - page.settings.redirectAllowance = defaultSettings.redirectAllowance; + if (!('useObserver' in page.settings)) { + page.settings.useObserver = defaultSettings.useObserver; + } + + if (!('usePasswordGeneratorIcons' in page.settings)) { + page.settings.usePasswordGeneratorIcons = defaultSettings.usePasswordGeneratorIcons; } await browser.storage.local.set({ 'settings': page.settings }); diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index 9162e99..eeba6e2 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -24,6 +24,7 @@ _called.manualFillRequested = ManualFill.NONE; let _singleInputEnabledForPage = false; let _databaseClosed = true; const _maximumInputs = 100; +const _maximumMutations = 200; // Count of detected form fields on the page var _detectedFields = 0; @@ -712,7 +713,7 @@ kpxcObserverHelper.handleObserverAdd = function(target) { if (Object.keys(kpxc.settings).length === 0) { kpxc.init(); } else { - kpxc.initCredentialFields(true); + kpxc.initCredentialFields(true, inputs); } } }; @@ -747,53 +748,6 @@ kpxcObserverHelper.detectURLChange = function() { MutationObserver = window.MutationObserver || window.WebKitMutationObserver; -// Detects DOM changes in the document -const observer = new MutationObserver(function(mutations, obs) { - if (document.visibilityState === 'hidden') { - return; - } - - for (const mut of mutations) { - // Skip text nodes - if (mut.target.nodeType === Node.TEXT_NODE) { - continue; - } - - // Check document URL change and detect new fields - kpxcObserverHelper.detectURLChange(); - - // Handle attributes only if CSS display is modified - if (mut.type === 'attributes') { - // Check if some class is changed that folds a form or input field(s) - if (mut.attributeName === 'class' && mut.target.querySelectorAll('form input').length > 0) { - kpxc.initCredentialFields(true); - continue; - } - - const newValue = mut.target.getAttribute(mut.attributeName); - if (newValue && (newValue.includes('display') || newValue.includes('z-index'))) { - if (mut.target.style.display !== 'none') { - kpxcObserverHelper.handleObserverAdd(mut.target); - } else { - kpxcObserverHelper.handleObserverRemove(mut.target); - } - } - } else if (mut.type === 'childList') { - kpxcObserverHelper.handleObserverAdd((mut.addedNodes.length > 0) ? mut.addedNodes[0] : mut.target); - kpxcObserverHelper.handleObserverRemove((mut.removedNodes.length > 0) ? mut.removedNodes[0] : mut.target); - } - } -}); - -// define what element should be observed by the observer -// and what types of mutations trigger the callback -observer.observe(document, { - subtree: true, - attributes: true, - childList: true, - characterData: true, - attributeFilter: [ 'style', 'class' ] -}); const kpxc = {}; @@ -811,6 +765,16 @@ const initcb = async function() { }); kpxc.settings = settings; + + // Don't initialize MutationObserver if the site is ignored + if (kpxc.siteIgnored()) { + return; + } + + if (kpxc.settings.useObserver) { + kpxc.initObserver(); + } + await kpxc.initCredentialFields(); // Retrieve submitted credentials if available. @@ -851,6 +815,61 @@ kpxc.init = function() { initcb(); }; +// Detects DOM changes in the document +kpxc.initObserver = function() { + const observer = new MutationObserver(function(mutations, obs) { + if (document.visibilityState === 'hidden' || kpxcUI.mouseDown) { + return; + } + + // Limit the mutation handling + if (mutations.length > _maximumMutations) { + mutations.slice(0, _maximumMutations); + } + + for (const mut of mutations) { + // Skip text nodes + if (mut.target.nodeType === Node.TEXT_NODE) { + continue; + } + + // Check document URL change and detect new fields + kpxcObserverHelper.detectURLChange(); + + // Handle attributes only if CSS display is modified + if (mut.type === 'attributes') { + // Check if some class is changed that folds a form or input field(s) + if (mut.attributeName === 'class' && mut.target.querySelectorAll('form input').length > 0) { + kpxc.initCredentialFields(true); + continue; + } + + const newValue = mut.target.getAttribute(mut.attributeName); + if (newValue && (newValue.includes('display') || newValue.includes('z-index'))) { + if (mut.target.style.display !== 'none') { + kpxcObserverHelper.handleObserverAdd(mut.target); + } else { + kpxcObserverHelper.handleObserverRemove(mut.target); + } + } + } else if (mut.type === 'childList') { + kpxcObserverHelper.handleObserverAdd((mut.addedNodes.length > 0) ? mut.addedNodes[0] : mut.target); + kpxcObserverHelper.handleObserverRemove((mut.removedNodes.length > 0) ? mut.removedNodes[0] : mut.target); + } + } + }); + + // define what element should be observed by the observer + // and what types of mutations trigger the callback + observer.observe(document, { + subtree: true, + attributes: true, + childList: true, + characterData: true, + attributeFilter: [ 'style', 'class' ] + }); +}; + // Clears all from the content and background scripts, including autocomplete kpxc.clearAllFromPage = function() { kpxcEvents.clearCredentials(); @@ -920,7 +939,7 @@ kpxc.siteIgnored = function(condition) { return false; }; -kpxc.initCredentialFields = async function(forceCall) { +kpxc.initCredentialFields = async function(forceCall, inputs) { if (_called.initCredentialFields && !forceCall) { return; } @@ -937,7 +956,11 @@ kpxc.initCredentialFields = async function(forceCall) { return; } - const inputs = kpxcFields.getAllFields(); + // If target input fields are not defined, get inputs from the whole document + if (inputs === undefined) { + inputs = kpxcFields.getAllFields(); + } + if (inputs.length === 0) { return; } @@ -989,9 +1012,13 @@ kpxc.initCredentialFields = async function(forceCall) { }; kpxc.initPasswordGenerator = function(inputs) { + if (!kpxc.settings.usePasswordGeneratorIcons) { + return; + } + for (let i = 0; i < inputs.length; i++) { if (inputs[i] && inputs[i].getLowerCaseAttribute('type') === 'password') { - kpxcPasswordIcons.newIcon(kpxc.settings.usePasswordGeneratorIcons, inputs[i], inputs, i, _databaseClosed); + kpxcPasswordIcons.newIcon(true, inputs[i], inputs, i, _databaseClosed); } } }; diff --git a/keepassxc-browser/content/ui.js b/keepassxc-browser/content/ui.js index 29e7235..44c7576 100644 --- a/keepassxc-browser/content/ui.js +++ b/keepassxc-browser/content/ui.js @@ -36,6 +36,7 @@ class Icon { }; const kpxcUI = {}; +kpxcUI.mouseDown = false; // Wrapper for creating elements kpxcUI.createElement = function(type, classes, attributes, textContent) { @@ -167,6 +168,10 @@ const initColorTheme = function(elem) { // Enables dragging document.addEventListener('mousemove', function(e) { + if (!kpxcUI.mouseDown) { + return; + } + if (kpxcPasswordDialog.selected === kpxcPasswordDialog.titleBar) { const xPos = e.clientX - kpxcPasswordDialog.diffX; const yPos = e.clientY - kpxcPasswordDialog.diffY; @@ -188,9 +193,14 @@ document.addEventListener('mousemove', function(e) { } }); +document.addEventListener('mousedown', function() { + kpxcUI.mouseDown = true; +}); + document.addEventListener('mouseup', function() { kpxcPasswordDialog.selected = null; kpxcDefine.selected = null; + kpxcUI.mouseDown = false; }); HTMLDivElement.prototype.appendMultiple = function(...args) { diff --git a/keepassxc-browser/options/options.html b/keepassxc-browser/options/options.html index 2b5e2a4..3679347 100644 --- a/keepassxc-browser/options/options.html +++ b/keepassxc-browser/options/options.html @@ -77,6 +77,16 @@


+

+

+ + + + +
+