diff options
author | varjolintu <sami.vanttinen@protonmail.com> | 2020-06-28 11:47:29 +0300 |
---|---|---|
committer | varjolintu <sami.vanttinen@protonmail.com> | 2020-07-06 16:13:27 +0300 |
commit | 8f8dd522f8384b301d11550ec04dac6ae82a4db3 (patch) | |
tree | 37d7376516655fb3b2313c39a3b2a18b646ceb91 /keepassxc-browser | |
parent | d518662696a7965477039f161a2bb4e10137b0e9 (diff) |
Fix high CPU usage
Diffstat (limited to 'keepassxc-browser')
-rwxr-xr-x | keepassxc-browser/content/keepassxc-browser.js | 128 | ||||
-rw-r--r-- | keepassxc-browser/content/pwgen.js | 26 | ||||
-rw-r--r-- | keepassxc-browser/content/totp-field.js | 21 | ||||
-rw-r--r-- | keepassxc-browser/content/ui.js | 18 | ||||
-rw-r--r-- | keepassxc-browser/content/username-field.js | 19 |
5 files changed, 154 insertions, 58 deletions
diff --git a/keepassxc-browser/content/keepassxc-browser.js b/keepassxc-browser/content/keepassxc-browser.js index 91b6d8a..b6685f2 100755 --- a/keepassxc-browser/content/keepassxc-browser.js +++ b/keepassxc-browser/content/keepassxc-browser.js @@ -44,6 +44,11 @@ let _documentURL = document.location.href; // These are executed in each frame browser.runtime.onMessage.addListener(async function(req, sender) { if ('action' in req) { + // Don't allow any actions if the site is ignored + if (kpxc.siteIgnored()) { + return Promise.resolve(); + } + if (req.action === 'fill_user_pass_with_specific_login') { kpxc.fillWithSpecificLogin(req.id); } else if (req.action === 'fill_username_password') { @@ -275,6 +280,7 @@ kpxcFields.traverseParents = function(element, predicate, resultFn = () => true, return resultFn(f); } } + return defaultValFn(); }; @@ -318,20 +324,26 @@ kpxcFields.isSearchField = function(target) { }; kpxcFields.isVisible = function(field) { - // Check for parent opacity - if (kpxcFields.traverseParents(field, f => f.style.opacity === '0')) { + // Check element position and size + const rect = field.getBoundingClientRect(); + if (rect.x < 0 + || rect.y < 0 + || rect.width < 8 + || rect.x > document.body.offsetWidth + || rect.y > document.body.offsetHeight + || rect.height < 8) { return false; } // Check CSS visibility const fieldStyle = getComputedStyle(field); - if (fieldStyle.visibility && (fieldStyle.visibility === 'hidden' || fieldStyle.visibility === 'collapse')) { + if (fieldStyle.visibility && (fieldStyle.visibility === 'hidden' || fieldStyle.visibility === 'collapse') + || fieldStyle.opacity === '0') { return false; } - // Check element position and size - const rect = field.getBoundingClientRect(); - if (rect.x < 0 || rect.y < 0 || rect.width < 8 || rect.height < 8) { + // Check for parent opacity + if (kpxcFields.traverseParents(field, f => f.style.opacity === '0')) { return false; } @@ -595,6 +607,16 @@ kpxcFields.getPasswordField = function(usernameId, checkDisabled) { }; kpxcFields.prepareCombinations = async function(combinations) { + if (combinations.length === 0) { + return; + } + + // Only request this once if there are combinations available + let passwordFilled; + if (combinations.length > 0) { + passwordFilled = await kpxc.passwordFilled(); + } + for (const c of combinations) { const pwField = _f(c.password); // Needed for auto-complete: don't overwrite manually filled-in password field @@ -611,7 +633,7 @@ kpxcFields.prepareCombinations = async function(combinations) { // If no username field is found, handle the single password field as such const usernameField = c.username ? _f(c.username) : field; - if (kpxc.settings.showLoginFormIcon && await kpxc.passwordFilled() === false) { + if (kpxc.settings.showLoginFormIcon && passwordFilled === false) { kpxcUsernameIcons.newIcon(usernameField, _databaseState); } @@ -683,6 +705,7 @@ kpxcObserverHelper.inputTypes = [ ]; // Ignores all nodes that doesn't contain elements +// Also ignore few Youtube-specific custom nodeNames kpxcObserverHelper.ignoredNode = function(target) { if (target.nodeType === Node.ATTRIBUTE_NODE || target.nodeType === Node.TEXT_NODE @@ -690,13 +713,20 @@ kpxcObserverHelper.ignoredNode = function(target) { || target.nodeType === Node.PROCESSING_INSTRUCTION_NODE || target.nodeType === Node.COMMENT_NODE || target.nodeType === Node.DOCUMENT_TYPE_NODE - || target.nodeType === Node.NOTATION_NODE) { + || target.nodeType === Node.NOTATION_NODE + || target.nodeName === 'HTML' + || target.nodeName === 'LINK' + || target.nodeName === 'HEAD' + || target.nodeName === 'VIDEO' + || target.nodeName.startsWith('YTMUSIC') + || target.nodeName.startsWith('YT-')) { return true; } + return false; }; -kpxcObserverHelper.getInputs = function(target) { +kpxcObserverHelper.getInputs = function(target, ignoreVisibility = false) { // Ignores target element if it's not an element node if (kpxcObserverHelper.ignoredNode(target)) { return []; @@ -705,7 +735,9 @@ kpxcObserverHelper.getInputs = function(target) { // Filter out any input fields with type 'hidden' right away const inputFields = []; Array.from(target.getElementsByTagName('input')).forEach(e => { - if (e.type !== 'hidden') { + if (e.type !== 'hidden' + && !e.disabled + && !kpxcObserverHelper.hasKpxcClass(e)) { inputFields.push(e); } }); @@ -724,9 +756,15 @@ kpxcObserverHelper.getInputs = function(target) { for (const field of inputFields) { const type = field.getLowerCaseAttribute('type'); - if (kpxcObserverHelper.inputTypes.includes(type) && kpxcFields.isVisible(field)) { - kpxcFields.setUniqueId(field); - inputs.push(field); + if (ignoreVisibility) { + if (kpxcObserverHelper.inputTypes.includes(type)) { + inputs.push(field); + } + } else { + if (kpxcObserverHelper.inputTypes.includes(type) && kpxcFields.isVisible(field)) { + kpxcFields.setUniqueId(field); + inputs.push(field); + } } } return inputs; @@ -745,15 +783,27 @@ kpxcObserverHelper.getId = function(target) { return `kpxc${target.clientTop}${target.clientLeft}${target.clientWidth}${target.clientHeight}`; }; +kpxcObserverHelper.hasKpxcClass = function(target) { + if (!target.className + || !target.className.includes('kpxc')) { + return false; + } + + return target.className.includes('kpxc'); +}; + kpxcObserverHelper.ignoredElement = function(target) { + if (kpxcObserverHelper.ignoredNode(target)) { + return true; + } + // Ignore elements that do not have a className (including SVG) if (typeof target.className !== 'string') { return true; } // Ignore KeePassXC-Browser classes - if (target.className && target.className !== undefined - && target.className.includes('kpxc')) { + if (kpxcObserverHelper.hasKpxcClass(target)) { return true; } @@ -780,7 +830,7 @@ kpxcObserverHelper.handleObserverAdd = function(target) { if (Object.keys(kpxc.settings).length === 0) { kpxc.init(); } else { - kpxc.initCredentialFields(true, inputs); + kpxc.handleCredentialFields(inputs); } } }; @@ -790,11 +840,13 @@ kpxcObserverHelper.handleObserverRemove = function(target) { return; } - const inputs = kpxcObserverHelper.getInputs(target); + const inputs = kpxcObserverHelper.getInputs(target, true); if (inputs.length === 0) { return; } + kpxc.deleteHiddenIcons(); + // Remove target element id from the list const id = kpxcObserverHelper.getId(target); if (_observerIds.includes(id)) { @@ -824,7 +876,7 @@ kpxc.url = null; kpxc.submitUrl = null; kpxc.credentials = []; -const initcb = async function() { +const initContentScript = async function() { try { const settings = await browser.runtime.sendMessage({ action: 'load_settings' @@ -873,13 +925,13 @@ const initcb = async function() { }; if (document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll)) { - initcb(); + initContentScript(); } else { - document.addEventListener('DOMContentLoaded', initcb); + document.addEventListener('DOMContentLoaded', initContentScript); } kpxc.init = function() { - initcb(); + initContentScript(); }; // Detects DOM changes in the document @@ -896,10 +948,7 @@ kpxc.initObserver = function() { for (const mut of mutations) { // Skip text nodes and base HTML element - if (mut.target.nodeType === Node.TEXT_NODE - || mut.target.nodeName === 'HTML' - || mut.target.nodeName === 'LINK' - || mut.target.nodeName === 'HEAD') { + if (kpxcObserverHelper.ignoredNode(mut.target)) { continue; } @@ -911,7 +960,7 @@ kpxc.initObserver = function() { // Check if some class is changed that holds a form or input field(s). Ignore large forms. const formInput = mut.target.querySelector('form input'); if (mut.attributeName === 'class' && formInput !== null && formInput.form.length < 20) { - kpxc.initCredentialFields(true); + kpxc.handleCredentialFields(Array.from(formInput.form.getElementsByTagName('input'))); continue; } @@ -1013,7 +1062,8 @@ kpxc.siteIgnored = function(condition) { return false; }; -kpxc.initCredentialFields = async function(forceCall, inputs) { +// Initializes all input fields from the whole page +kpxc.initCredentialFields = async function(forceCall) { if (_called.initCredentialFields && !forceCall) { return; } @@ -1030,11 +1080,13 @@ kpxc.initCredentialFields = async function(forceCall, inputs) { return; } - // If target input fields are not defined, get inputs from the whole document - if (inputs === undefined) { - inputs = kpxcFields.getAllFields(); - } + // Get inputs from the whole document + const inputs = kpxcFields.getAllFields(); + kpxc.handleCredentialFields(inputs); +}; +// Handles the input fields from the whole page or from dynamically added content +kpxc.handleCredentialFields = async function(inputs) { if (inputs.length === 0) { return; } @@ -1092,10 +1144,6 @@ kpxc.initPasswordGenerator = function(inputs) { kpxc.initOTPFields = function(inputs) { for (const i of inputs) { - if (!kpxcFields.isVisible(i)) { - continue; - } - const id = i.getLowerCaseAttribute('id'); const name = i.getLowerCaseAttribute('name'); const autocomplete = i.getLowerCaseAttribute('autocomplete'); @@ -1247,6 +1295,10 @@ kpxc.preparePageForMultipleCredentials = function(credentials) { // Returns the form that includes the inputField kpxc.getForm = function(inputField) { + if (inputField.form) { + return inputField.form; + } + for (const f of document.forms) { for (const e of f.elements) { if (e === inputField) { @@ -1922,6 +1974,12 @@ kpxc.switchIcons = function() { kpxcTOTPIcons.switchIcon(_databaseState); }; +kpxc.deleteHiddenIcons = function() { + kpxcUsernameIcons.deleteHiddenIcons(); + kpxcPasswordIcons.deleteHiddenIcons(); + kpxcTOTPIcons.deleteHiddenIcons(); +}; + kpxc.setPasswordFilled = function(state) { browser.runtime.sendMessage({ action: 'password_set_filled', diff --git a/keepassxc-browser/content/pwgen.js b/keepassxc-browser/content/pwgen.js index d6fc1d8..2fc8267 100644 --- a/keepassxc-browser/content/pwgen.js +++ b/keepassxc-browser/content/pwgen.js @@ -11,6 +11,10 @@ kpxcPasswordIcons.switchIcon = function(state) { kpxcPasswordIcons.icons.forEach(u => u.switchIcon(state)); }; +kpxcPasswordIcons.deleteHiddenIcons = function() { + kpxcUI.deleteHiddenIcons(kpxcPasswordIcons.icons, 'kpxc-password-field'); +}; + class PasswordIcon extends Icon { constructor(useIcons, field, inputs, pos, databaseState) { @@ -18,22 +22,25 @@ class PasswordIcon extends Icon { this.useIcons = useIcons; this.databaseState = databaseState; - this.initField(field, inputs, pos); - kpxcUI.monitorIconPosition(this); + if (this.initField(field, inputs, pos)) { + kpxcUI.monitorIconPosition(this); + } } } PasswordIcon.prototype.initField = function(field, inputs, pos) { - if (!field || field.readOnly) { - return; + if (!field + || field.readOnly + || field.offsetWidth < MINIMUM_INPUT_FIELD_WIDTH) { + return false; } - if (field.getAttribute('kpxc-password-generator') + if (field.getAttribute('kpxc-password-field') || (field.hasAttribute('kpxc-defined') && field.getAttribute('kpxc-defined') !== 'password')) { - return; + return false; } - field.setAttribute('kpxc-password-generator', true); + field.setAttribute('kpxc-password-field', true); if (this.useIcons) { // Observer the visibility @@ -58,6 +65,7 @@ PasswordIcon.prototype.initField = function(field, inputs, pos) { } field.setAttribute('kpxc-pwgen-next-field-exists', found); + return true; }; PasswordIcon.prototype.createIcon = function(field) { @@ -117,7 +125,7 @@ kpxcPasswordDialog.dialog = null; kpxcPasswordDialog.titleBar = null; kpxcPasswordDialog.removeIcon = function(field) { - if (field.getAttribute('kpxc-password-generator')) { + if (field.getAttribute('kpxc-password-field')) { const pwgenIcons = document.querySelectorAll('.kpxc-pwgen-icon'); for (const i of pwgenIcons) { if (i.getAttribute('kpxc-pwgen-field-id') === field.getAttribute('data-kpxc-id')) { @@ -233,7 +241,7 @@ kpxcPasswordDialog.trigger = function() { kpxcPasswordDialog.showDialog = function(field, icon) { if (!kpxcFields.isVisible(field)) { document.body.removeChild(icon); - field.removeAttribute('kpxc-password-generator'); + field.removeAttribute('kpxc-password-field'); return; } diff --git a/keepassxc-browser/content/totp-field.js b/keepassxc-browser/content/totp-field.js index f715448..e09595c 100644 --- a/keepassxc-browser/content/totp-field.js +++ b/keepassxc-browser/content/totp-field.js @@ -1,6 +1,5 @@ 'use strict'; -const MINIMUM_SIZE = 60; const ignoreRegex = /(zip|postal).*code/i; const ignoredTypes = [ 'email', 'password', 'username' ]; @@ -15,6 +14,10 @@ kpxcTOTPIcons.switchIcon = function(state) { kpxcTOTPIcons.icons.forEach(u => u.switchIcon(state)); }; +kpxcTOTPIcons.deleteHiddenIcons = function() { + kpxcUI.deleteHiddenIcons(kpxcTOTPIcons.icons, 'kpxc-totp-field'); +}; + class TOTPFieldIcon extends Icon { constructor(field, databaseState = DatabaseState.DISCONNECTED, forced = false) { @@ -23,8 +26,9 @@ class TOTPFieldIcon extends Icon { this.inputField = null; this.databaseState = databaseState; - this.initField(field, forced); - kpxcUI.monitorIconPosition(this); + if (this.initField(field, forced)) { + kpxcUI.monitorIconPosition(this); + } } } @@ -35,20 +39,20 @@ TOTPFieldIcon.prototype.initField = function(field, forced) { if (!forced) { if (ignoredTypes.some(t => t === field.type) + || field.offsetWidth < MINIMUM_INPUT_FIELD_WIDTH + || field.size < 2 + || (field.maxLength > 0 && (field.maxLength < 6 || field.maxLength > 8)) || ignoredTypes.some(t => t === field.autocomplete) || field.getAttribute('kpxc-totp-field') === 'true' || (field.hasAttribute('kpxc-defined') && field.getAttribute('kpxc-defined') !== 'totp') - || field.offsetWidth < MINIMUM_SIZE - || field.size < 2 - || (field.maxLength > 0 && (field.maxLength < 6 || field.maxLength > 8)) || field.id.match(ignoreRegex) || field.name.match(ignoreRegex) || field.readOnly) { - return; + return false; } } else { if (field.getAttribute('kpxc-totp-field') === 'true') { - return; + return false; } } @@ -61,6 +65,7 @@ TOTPFieldIcon.prototype.initField = function(field, forced) { this.createIcon(field); this.inputField = field; + return false; }; TOTPFieldIcon.prototype.createIcon = function(field) { diff --git a/keepassxc-browser/content/ui.js b/keepassxc-browser/content/ui.js index 923e7e1..a7e7c3a 100644 --- a/keepassxc-browser/content/ui.js +++ b/keepassxc-browser/content/ui.js @@ -1,5 +1,7 @@ 'use strict'; +const MINIMUM_INPUT_FIELD_WIDTH = 60; + // jQuery style wrapper for querySelector() const $ = function(elem) { return document.querySelector(elem); @@ -33,6 +35,12 @@ class Icon { this.icon.style.filter = 'saturate(0%)'; } } + + removeIcon(attr) { + this.inputField.removeAttribute(attr); + this.shadowRoot.removeChild(this.icon); + document.body.removeChild(this.shadowRoot.host); + } } const kpxcUI = {}; @@ -101,6 +109,16 @@ kpxcUI.setIconPosition = function(icon, field) { } }; +kpxcUI.deleteHiddenIcons = function(iconList, attr) { + for (const icon of iconList) { + if (icon.inputField && !kpxcFields.isVisible(icon.inputField)) { + const index = iconList.indexOf(icon); + icon.removeIcon(attr); + iconList.splice(index, 1); + } + } +}; + /** * Detects if the input field appears or disappears -> show/hide the icon * - boundingClientRect with slightly (< -10) negative values -> hidden diff --git a/keepassxc-browser/content/username-field.js b/keepassxc-browser/content/username-field.js index 738276d..f7964d9 100644 --- a/keepassxc-browser/content/username-field.js +++ b/keepassxc-browser/content/username-field.js @@ -11,6 +11,10 @@ kpxcUsernameIcons.switchIcon = function(state) { kpxcUsernameIcons.icons.forEach(u => u.switchIcon(state)); }; +kpxcUsernameIcons.deleteHiddenIcons = function() { + kpxcUI.deleteHiddenIcons(kpxcUsernameIcons.icons, 'kpxc-username-field'); +}; + class UsernameFieldIcon extends Icon { constructor(field, databaseState = DatabaseState.DISCONNECTED) { @@ -19,8 +23,9 @@ class UsernameFieldIcon extends Icon { this.icon = null; this.inputField = null; - this.initField(field); - kpxcUI.monitorIconPosition(this); + if (this.initField(field)) { + kpxcUI.monitorIconPosition(this); + } } switchIcon(state) { @@ -36,12 +41,13 @@ class UsernameFieldIcon extends Icon { UsernameFieldIcon.prototype.initField = function(field) { if (!field + || field.offsetWidth < MINIMUM_INPUT_FIELD_WIDTH + || field.readOnly || field.getAttribute('kpxc-username-field') === 'true' || field.getAttribute('kpxc-totp-field') === 'true' || (field.hasAttribute('kpxc-defined') && field.getAttribute('kpxc-defined') !== 'username') - || !kpxcFields.isVisible(field) - || field.readOnly) { - return; + || !kpxcFields.isVisible(field)) { + return false; } field.setAttribute('kpxc-username-field', 'true'); @@ -53,11 +59,12 @@ UsernameFieldIcon.prototype.initField = function(field) { this.createIcon(field); this.inputField = field; + return true; }; UsernameFieldIcon.prototype.createIcon = function(target) { // Remove any existing password generator icons from the input field - if (target.getAttribute('kpxc-password-generator')) { + if (target.getAttribute('kpxc-password-field')) { kpxcPasswordDialog.removeIcon(target); } |