mirror of
https://github.com/keepassxreboot/keepassxc-browser.git
synced 2026-03-11 08:54:43 +00:00
Merge pull request #920 from keepassxreboot/fix/high_cpu_usage
Fix high CPU usage
This commit is contained in:
commit
7cf428c3d3
5 changed files with 154 additions and 58 deletions
|
|
@ -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',
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue