Merge pull request #920 from keepassxreboot/fix/high_cpu_usage

Fix high CPU usage
This commit is contained in:
Sami Vänttinen 2020-07-07 07:26:08 +03:00 committed by GitHub
commit 7cf428c3d3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 58 deletions

View file

@ -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;
}
@ -1089,10 +1141,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');
@ -1244,6 +1292,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) {
@ -1919,6 +1971,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',

View file

@ -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;
}

View file

@ -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) {

View file

@ -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

View file

@ -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);
}