keepassxc-browser/keepassxc-browser/content/keepassxc-browser.js
Sami Vänttinen d4fa2f7155 Merge pull request #741 from keepassxreboot/fix/add_submit_button_to_query
Add submit button type to query with forms
2020-02-09 18:06:20 +02:00

1853 lines
59 KiB
JavaScript
Executable file

'use strict';
const ManualFill = {
NONE: 0,
PASS: 1,
BOTH: 2
};
const acceptedOTPFields = [
'2fa',
'auth',
'challenge',
'code',
'mfa',
'otp',
'token'
];
// Contains already called method names
const _called = {};
_called.retrieveCredentials = false;
_called.clearLogins = false;
_called.manualFillRequested = ManualFill.NONE;
let _singleInputEnabledForPage = false;
let _databaseClosed = true;
const _maximumInputs = 100;
// Count of detected form fields on the page
var _detectedFields = 0;
// Element id's containing input fields detected by MutationObserver
const _observerIds = [];
// Document URL
let _documentURL = document.location.href;
// These are executed in each frame
browser.runtime.onMessage.addListener(async function(req, sender) {
if ('action' in req) {
if (req.action === 'fill_user_pass_with_specific_login') {
kpxc.fillWithSpecificLogin(req.id);
} else if (req.action === 'fill_username_password') {
_called.manualFillRequested = ManualFill.BOTH;
await kpxc.receiveCredentialsIfNecessary();
kpxc.fillInFromActiveElement(false);
} else if (req.action === 'fill_password') {
_called.manualFillRequested = ManualFill.PASS;
await kpxc.receiveCredentialsIfNecessary();
kpxc.fillInFromActiveElement(false, true); // passOnly to true
} else if (req.action === 'fill_totp') {
await kpxc.receiveCredentialsIfNecessary();
kpxc.fillInFromActiveElementTOTPOnly();
} else if (req.action === 'clear_credentials') {
kpxcEvents.clearCredentials();
return Promise.resolve();
} else if (req.action === 'activated_tab') {
kpxcEvents.triggerActivatedTab();
return Promise.resolve();
} else if (req.action === 'ignore_site') {
kpxc.ignoreSite(req.args);
} else if (req.action === 'check_database_hash' && 'hash' in req) {
kpxc.detectDatabaseChange(req.hash);
} else if (req.action === 'remember_credentials') {
kpxc.contextMenuRememberCredentials();
} else if (req.action === 'choose_credential_fields') {
kpxcDefine.init();
} else if (req.action === 'redetect_fields') {
const response = await browser.runtime.sendMessage({
action: 'load_settings'
});
kpxc.settings = response;
kpxc.initCredentialFields(true);
} else if (req.action === 'show_password_generator') {
kpxcPasswordDialog.trigger();
} else if (req.action === 'add_username_only_option') {
kpxc.addToSitePreferences();
}
}
});
function _f(fieldId) {
const inputs = document.querySelectorAll(`input[data-kpxc-id='${fieldId}']`);
return inputs.length > 0 ? inputs[0] : null;
}
function _fs(fieldId) {
const inputs = document.querySelectorAll(`input[data-kpxc-id='${fieldId}'], select[data-kpxc-id='${fieldId}']`);
return inputs.length > 0 ? inputs[0] : null;
}
const kpxcForm = {};
kpxcForm.passwordInputs = [];
kpxcForm.init = function(form, credentialFields) {
if (!form.getAttribute('kpxcForm-initialized') && (credentialFields.password || credentialFields.username)) {
form.setAttribute('kpxcForm-initialized', true);
kpxcForm.setInputFields(form, credentialFields);
form.addEventListener('submit', kpxcForm.onSubmit);
const submitButton = kpxc.getFormSubmitButton(form);
if (submitButton !== undefined) {
submitButton.addEventListener('click', kpxcForm.onSubmit);
}
}
};
kpxcForm.destroy = function(form, credentialFields) {
if (form === false && credentialFields) {
const field = _f(credentialFields.password) || _f(credentialFields.username);
if (field) {
form = kpxc.getForm(field);
if (form && form.length > 0) {
form.removeEventListener('submit', kpxcForm.onSubmit);
}
}
}
kpxcForm.passwordInputs = [];
};
kpxcForm.setInputFields = function(form, credentialFields) {
form.setAttribute('kpxcUsername', credentialFields.username);
form.setAttribute('kpxcPassword', credentialFields.password);
// Save all the password input fields from the form
kpxcForm.passwordInputs = Array.from(form.elements).filter(e => e.nodeName === 'INPUT' && e.type === 'password');
};
kpxcForm.onSubmit = async function(e) {
if (!e.isTrusted) {
return;
}
const form = this.nodeName === 'FORM' ? this : this.form;
const usernameId = form.getAttribute('kpxcUsername');
const passwordId = form.getAttribute('kpxcPassword');
const usernameField = _f(usernameId);
const passwordField = _f(passwordId);
let usernameValue = '';
let passwordValue = '';
if (usernameField) {
usernameValue = usernameField.value || usernameField.placeholder;
} else if (kpxc.credentials.length === 1) {
// Single entry found for the page, use the username of it instead of an empty one
usernameValue = kpxc.credentials[0].login;
}
// Check if the form has three password fields -> a possible password change form
if (kpxcForm.passwordInputs.length >= 2) {
passwordValue = kpxcForm.getNewPassword();
} else if (passwordField) {
// Use the combination password field instead
passwordValue = passwordField.value;
}
// Return if credentials are already found
if (kpxc.credentials.some(c => c.login === usernameValue && c.password === passwordValue)) {
return;
}
browser.runtime.sendMessage({
action: 'page_set_submitted',
args: [ true, usernameValue, passwordValue, trimURL(window.top.location.href), kpxc.credentials ]
});
// Show the banner if the page does not reload
kpxc.rememberCredentials(usernameValue, passwordValue);
};
// Retrieve new password from a form with three elements: Current, New, Repeat New
kpxcForm.getNewPassword = function() {
if (kpxcForm.passwordInputs.length < 2) {
return '';
}
// Just two password fields, current and new
if (kpxcForm.passwordInputs.length === 2 &&
kpxcForm.passwordInputs[0] !== kpxcForm.passwordInputs[1]) {
return kpxcForm.passwordInputs[1].value;
}
// Choose the last three password fields. The first ones are almost always for something else
const current = kpxcForm.passwordInputs[kpxcForm.passwordInputs.length - 3].value;
const newPass = kpxcForm.passwordInputs[kpxcForm.passwordInputs.length - 2].value;
const repeatNew = kpxcForm.passwordInputs[kpxcForm.passwordInputs.length - 1].value;
if (current !== newPass && newPass !== '' && newPass === repeatNew) {
return newPass;
} else if (current === newPass && repeatNew !== newPass) {
// Reverse form where current password is at the bottom
return repeatNew;
}
return '';
};
const kpxcFields = {};
kpxcFields.inputQueryPattern = 'input[type=\'text\'], input[type=\'email\'], input[type=\'password\'], input[type=\'tel\'], input[type=\'number\'], input[type=\'username\'], input:not([type])';
// copied from Sizzle.js
kpxcFields.rcssescape = /([\0-\x1f\x7f]|^-?\d)|^-$|[^\0-\x1f\x7f-\uFFFF\w-]/g;
kpxcFields.fcssescape = function(ch, asCodePoint) {
if (asCodePoint) {
// U+0000 NULL becomes U+FFFD REPLACEMENT CHARACTER
if (ch === '\0') {
return '\uFFFD';
}
// Control characters and (dependent upon position) numbers get escaped as code points
return ch.slice(0, -1) + '\\' + ch.charCodeAt(ch.length - 1).toString(16) + ' ';
}
// Other potentially-special ASCII characters get backslash-escaped
return '\\' + ch;
};
// Unique number as new IDs for input fields
kpxcFields.uniqueNumber = 342845638;
// Objects with combination of username + password fields
kpxcFields.combinations = [];
kpxcFields.setUniqueId = function(field) {
if (field && !field.getAttribute('data-kpxc-id')) {
// Use ID of field if it is unique
const fieldId = field.getAttribute('id');
if (fieldId) {
const foundIds = document.querySelectorAll('input#' + kpxcFields.prepareId(fieldId));
if (foundIds.length === 1) {
field.setAttribute('data-kpxc-id', fieldId);
return;
}
}
// Create own ID if no ID is set for this field
kpxcFields.uniqueNumber += 1;
field.setAttribute('data-kpxc-id', 'kpxcpw' + String(kpxcFields.uniqueNumber));
}
};
kpxcFields.prepareId = function(id) {
return (id + '').replace(kpxcFields.rcssescape, kpxcFields.fcssescape);
};
// Checks if input field is a search field. Attributes or form action containing 'search', or parent element holding
// role="search" will be identified as a search field.
kpxcFields.isSearchField = function(target) {
const attributes = target.attributes;
// Check element attributes
for (const attr of attributes) {
if ((attr.value && (attr.value.toLowerCase().includes('search')) || attr.value === 'q')) {
return true;
}
}
// Check closest form
const closestForm = kpxc.getForm(target);
if (closestForm) {
// Check form action
const formAction = closestForm.getAttribute('action');
if (formAction && (formAction.toLowerCase().includes('search') &&
!formAction.toLowerCase().includes('research'))) {
return true;
}
// Check form class and id
const closestFormId = closestForm.getAttribute('id');
const closestFormClass = closestForm.className;
if (closestFormClass && (closestForm.className.toLowerCase().includes('search') ||
(closestFormId && closestFormId.toLowerCase().includes('search') && !closestFormId.toLowerCase().includes('research')))) {
return true;
}
}
// Check parent elements for role='search'
if (target.closest('[role~=\'search\']')) {
return true;
}
return false;
};
kpxcFields.isVisible = function(field) {
const rect = field.getBoundingClientRect();
// Check CSS visibility
const fieldStyle = getComputedStyle(field);
if (fieldStyle.visibility && (fieldStyle.visibility === 'hidden' || fieldStyle.visibility === 'collapse')) {
return false;
}
// Check element position and size
if (rect.x < 0 || rect.y < 0 || rect.width < 8 || rect.height < 8) {
return false;
}
return true;
};
kpxcFields.getAllFields = function() {
const fields = [];
const inputs = kpxcObserverHelper.getInputs(document);
for (const i of inputs) {
if (kpxcFields.isVisible(i) && !kpxcFields.isSearchField(i)) {
kpxcFields.setUniqueId(i);
fields.push(i);
}
}
_detectedFields = fields.length;
// Show add username-only option for the site in popup
if (!_singleInputEnabledForPage &&
fields.length === 1 && fields[0].getLowerCaseAttribute('type') !== 'password') {
browser.runtime.sendMessage({
action: 'username_field_detected',
args: true
});
}
return fields;
};
kpxcFields.prepareVisibleFieldsWithID = function(pattern) {
const patterns = document.querySelectorAll(pattern);
for (const i of patterns) {
if (kpxcFields.isVisible(i) && i.style.visibility !== 'hidden' && i.style.visibility !== 'collapsed') {
kpxcFields.setUniqueId(i);
}
}
};
kpxcFields.getAllCombinations = function(inputs) {
const fields = [];
let uField = null;
for (const i of inputs) {
if (i) {
if (i.getLowerCaseAttribute('type') === 'password') {
const uId = (!uField || uField.length < 1) ? null : uField.getAttribute('data-kpxc-id');
const combination = {
username: uId,
password: i.getAttribute('data-kpxc-id')
};
fields.push(combination);
// Reset selected username field
uField = null;
} else {
// Username field
uField = i;
}
}
}
if (_singleInputEnabledForPage && fields.length === 0 && uField) {
const combination = {
username: uField.getAttribute('data-kpxc-id'),
password: null
};
fields.push(combination);
}
return fields;
};
kpxcFields.getCombination = function(givenType, fieldId) {
if (kpxcFields.combinations.length === 0) {
if (kpxcFields.useDefinedCredentialFields()) {
return kpxcFields.combinations[0];
}
}
// Use defined credential fields (already loaded into combinations)
const location = kpxc.getDocumentLocation();
if (kpxc.settings['defined-custom-fields'] && kpxc.settings['defined-custom-fields'][location]) {
return kpxcFields.combinations[0];
}
for (const c of kpxcFields.combinations) {
if (c[givenType] === fieldId) {
return c;
}
}
// Find new combination
let combination = {
username: null,
password: null
};
let newCombi = false;
if (givenType === 'username') {
const passwordField = kpxcFields.getPasswordField(fieldId, true);
let passwordId = null;
if (passwordField) {
passwordId = kpxcFields.prepareId(passwordField.getAttribute('data-kpxc-id'));
}
combination = {
username: fieldId,
password: passwordId
};
newCombi = true;
} else if (givenType === 'password') {
const usernameField = kpxcFields.getUsernameField(fieldId, true);
let usernameId = null;
if (usernameField) {
usernameId = kpxcFields.prepareId(usernameField.getAttribute('data-kpxc-id'));
}
combination = {
username: usernameId,
password: fieldId
};
newCombi = true;
}
if (combination.username || combination.password) {
kpxcFields.combinations.push(combination);
}
if (combination.username) {
if (kpxc.credentials.length > 0) {
kpxc.preparePageForMultipleCredentials(kpxc.credentials);
}
}
if (newCombi) {
combination.isNew = true;
}
return combination;
};
/**
* Return the username field or null if it not exists
*/
kpxcFields.getUsernameField = async function(passwordId, checkDisabled) {
const passwordField = _f(passwordId);
if (!passwordField) {
return null;
}
const form = kpxc.getForm(passwordField);
let usernameField = null;
// Search all inputs on this one form
if (form) {
const inputs = form.querySelectorAll(kpxcFields.inputQueryPattern);
for (const i of inputs) {
kpxcFields.setUniqueId(i);
if (i.getAttribute('data-kpxc-id') === passwordId) {
return false; // Break
}
if (i.getLowerCaseAttribute('type') === 'password') {
return true; // Continue
}
if (kpxc.settings.showLoginFormIcon && await kpxc.passwordFilled() === false) {
kpxcUsernameIcons.newIcon(usernameField);
}
usernameField = i;
}
} else {
// Search all inputs on page
const inputs = kpxcFields.getAllFields();
kpxc.initPasswordGenerator(inputs);
for (const i of inputs) {
if (i.getAttribute('data-kpxc-id') === passwordId) {
break;
}
if (i.getLowerCaseAttribute('type') === 'password') {
continue;
}
usernameField = i;
}
}
if (usernameField && !checkDisabled) {
const usernameId = usernameField.getAttribute('data-kpxc-id');
// Check if usernameField is already used by another combination
for (const c of kpxcFields.combinations) {
if (c.username === usernameId) {
usernameField = null;
break;
}
}
}
kpxcFields.setUniqueId(usernameField);
return usernameField;
};
/**
* Return the password field or null if it not exists
*/
kpxcFields.getPasswordField = function(usernameId, checkDisabled) {
const usernameField = _f(usernameId);
if (!usernameField) {
return null;
}
const form = kpxc.getForm(usernameField);
let passwordField = null;
// Search all inputs on this one form
if (form) {
const inputs = form.querySelectorAll('input[type=\'password\']');
if (inputs.length > 0) {
passwordField = inputs[0];
}
if (passwordField && passwordField.length < 1) {
passwordField = null;
}
kpxcPasswordIcons.newIcon(kpxc.settings.usePasswordGeneratorIcons, passwordField, [], undefined, _databaseClosed);
} else {
// Search all inputs on page
const inputs = kpxcFields.getAllFields();
kpxc.initPasswordGenerator(inputs);
let active = false;
for (const i of inputs) {
if (i.getAttribute('data-kpxc-id') === usernameId) {
active = true;
}
if (active && i.getLowerCaseAttribute('type') === 'password') {
passwordField = i;
break;
}
}
}
if (passwordField && !checkDisabled) {
const passwordId = passwordField.getAttribute('data-kpxc-id');
// Check if passwordField is already used by another combination
for (const c of kpxcFields.combinations) {
if (c.password === passwordId) {
passwordField = null;
break;
}
}
}
kpxcFields.setUniqueId(passwordField);
return passwordField;
};
kpxcFields.prepareCombinations = async function(combinations) {
for (const c of combinations) {
const pwField = _f(c.password);
// Needed for auto-complete: don't overwrite manually filled-in password field
if (pwField && !pwField.getAttribute('kpxcFields-onChange')) {
pwField.setAttribute('kpxcFields-onChange', true);
pwField.addEventListener('change', function() {
this.setAttribute('unchanged', false);
});
}
const fieldId = c.password || c.username;
const field = _f(fieldId);
// 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) {
kpxcUsernameIcons.newIcon(usernameField, _databaseClosed);
}
// Initialize form-submit for remembering credentials
if (field) {
const form = kpxc.getForm(field);
if (form && form.length > 0) {
kpxcForm.init(form, c);
}
}
}
};
kpxcFields.useDefinedCredentialFields = function() {
const location = kpxc.getDocumentLocation();
if (kpxc.settings['defined-custom-fields'] && kpxc.settings['defined-custom-fields'][location]) {
const creds = kpxc.settings['defined-custom-fields'][location];
let found = _f(creds.username) || _f(creds.password);
for (const i of creds.fields) {
if (_fs(i)) {
found = true;
break;
}
}
if (found) {
const fields = {
username: creds.username,
password: creds.password,
fields: creds.fields
};
kpxcFields.combinations = [];
kpxcFields.combinations.push(fields);
return true;
}
}
return false;
};
const kpxcObserverHelper = {};
kpxcObserverHelper.inputTypes = [
'text',
'email',
'password',
'tel',
'number',
'username', // Note: Not a standard
undefined, // Input field can be without any type. Include this and null to the list.
null
];
// Ignores all nodes that doesn't contain elements
kpxcObserverHelper.ignoredNode = function(target) {
if (target.nodeType === Node.ATTRIBUTE_NODE ||
target.nodeType === Node.TEXT_NODE ||
target.nodeType === Node.CDATA_SECTION_NODE ||
target.nodeType === Node.PROCESSING_INSTRUCTION_NODE ||
target.nodeType === Node.COMMENT_NODE ||
target.nodeType === Node.DOCUMENT_TYPE_NODE ||
target.nodeType === Node.NOTATION_NODE) {
return true;
}
return false;
};
kpxcObserverHelper.getInputs = function(target) {
// Ignores target element if it's not an element node
if (kpxcObserverHelper.ignoredNode(target)) {
return [];
}
// Filter out any input fields with type 'hidden' right away
const inputFields = [];
Array.from(target.getElementsByTagName('input')).forEach((e) => {
if (e.type !== 'hidden') {
inputFields.push(e);
}
});
if (inputFields.length === 0) {
return [];
}
// Do not allow more visible inputs than _maximumInputs (default value: 100) -> return the first 100
if (inputFields.length > _maximumInputs) {
return inputFields.slice(0, _maximumInputs);
}
// Only include input fields that match with kpxcObserverHelper.inputTypes
const inputs = [];
for (const i of inputFields) {
let type = i.getLowerCaseAttribute('type');
if (kpxcObserverHelper.inputTypes.includes(type)) {
inputs.push(i);
}
}
return inputs;
};
kpxcObserverHelper.getId = function(target) {
return target.classList.length === 0 ? target.id : target.classList;
};
kpxcObserverHelper.ignoredElement = function(target) {
// 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')) {
return true;
}
return false;
};
kpxcObserverHelper.handleObserverAdd = function(target) {
if (kpxcObserverHelper.ignoredElement(target)) {
return;
}
const inputs = kpxcObserverHelper.getInputs(target);
if (inputs.length === 0) {
return;
}
const neededLength = _detectedFields === 1 ? 0 : 1;
const id = kpxcObserverHelper.getId(target);
if (inputs.length > neededLength && !_observerIds.includes(id)) {
// Save target element id for preventing multiple calls to initCredentialsFields()
_observerIds.push(id);
// Sometimes the settings haven't been loaded before new input fields are detected
if (Object.keys(kpxc.settings).length === 0) {
kpxc.init();
} else {
kpxc.initCredentialFields(true);
}
}
};
kpxcObserverHelper.handleObserverRemove = function(target) {
if (kpxcObserverHelper.ignoredElement(target)) {
return;
}
const inputs = kpxcObserverHelper.getInputs(target);
if (inputs.length === 0) {
return;
}
// Remove target element id from the list
const id = kpxcObserverHelper.getId(target);
if (_observerIds.includes(id)) {
const index = _observerIds.indexOf(id);
if (index >= 0) {
_observerIds.splice(index, 1);
}
}
};
kpxcObserverHelper.detectURLChange = function() {
if (_documentURL !== document.location.href) {
_documentURL = document.location.href;
kpxcEvents.clearCredentials();
kpxc.initCredentialFields(true);
}
};
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 = {};
kpxc.settings = {};
kpxc.u = null;
kpxc.p = null;
kpxc.url = null;
kpxc.submitUrl = null;
kpxc.credentials = [];
const initcb = async function() {
try {
const settings = await browser.runtime.sendMessage({
action: 'load_settings'
});
kpxc.settings = settings;
await kpxc.initCredentialFields();
// Retrieve submitted credentials if available.
const creds = await browser.runtime.sendMessage({
action: 'page_get_submitted'
});
const redirectCount = await browser.runtime.sendMessage({
action: 'page_get_redirect_count'
});
if (creds && creds.submitted) {
// If username field is not set, wait for credentials in kpxc.retrieveCredentialsCallback.
if (!creds.username) {
return;
}
if (redirectCount >= kpxc.settings.redirectAllowance) {
await browser.runtime.sendMessage({
action: 'page_clear_submitted'
});
}
kpxc.rememberCredentials(creds.username, creds.password, creds.url, creds.oldCredentials);
}
} catch (err) {
console.log('Error: Cannot load extension settings');
}
};
if (document.readyState === 'complete' || (document.readyState !== 'loading' && !document.documentElement.doScroll)) {
initcb();
} else {
document.addEventListener('DOMContentLoaded', initcb);
}
kpxc.init = function() {
initcb();
};
// Clears all from the content and background scripts, including autocomplete
kpxc.clearAllFromPage = function() {
kpxcEvents.clearCredentials();
browser.runtime.sendMessage({
action: 'page_clear_logins'
});
// Switch back to default popup
browser.runtime.sendMessage({
action: 'get_status',
args: [ true ] // Set polling to true, this is an internal function call
});
};
// Switch credentials if database is changed or closed
kpxc.detectDatabaseChange = async function(response) {
_databaseClosed = true;
kpxc.clearAllFromPage();
kpxc.switchIcons(true);
if (document.visibilityState !== 'hidden') {
if (response.new !== '' && response.new !== response.old) {
_called.retrieveCredentials = false;
const settings = await browser.runtime.sendMessage({
action: 'load_settings'
});
kpxc.settings = settings;
await kpxc.initCredentialFields(true);
kpxc.switchIcons(false); // Unlocked
_databaseClosed = false;
// If user has requested a manual fill through context menu the actual credential filling
// is handled here when the opened database has been regognized. It's not a pretty hack.
if (_called.manualFillRequested && _called.manualFillRequested !== ManualFill.NONE) {
await kpxc.fillInFromActiveElement(false, _called.manualFillRequested === ManualFill.PASS);
_called.manualFillRequested = ManualFill.NONE;
}
}
}
};
// Checks if the site has been ignored using Site Preferences
kpxc.siteIgnored = function(condition) {
kpxc.initializeSitePreferences();
if (kpxc.settings.sitePreferences) {
let currentLocation;
try {
currentLocation = window.top.location.href;
} catch (err) {
// Cross-domain security error inspecting window.top.location.href.
// This catches an error when an iframe is being accessed from another (sub)domain -> use the iframe URL instead.
currentLocation = window.self.location.href;
}
const currentSetting = condition || IGNORE_FULL;
for (const site of kpxc.settings.sitePreferences) {
if (siteMatch(site.url, currentLocation) || site.url === currentLocation) {
if (site.ignore === currentSetting) {
return true;
}
_singleInputEnabledForPage = site.usernameOnly;
}
}
}
return false;
};
kpxc.initCredentialFields = async function(forceCall) {
if (_called.initCredentialFields && !forceCall) {
return;
}
_called.initCredentialFields = true;
await browser.runtime.sendMessage({
action: 'page_clear_logins',
args: _called.clearLogins
});
_called.clearLogins = true;
if (kpxc.siteIgnored()) {
return;
}
const inputs = kpxcFields.getAllFields();
if (inputs.length === 0) {
return;
}
// Update database closed status
const res = await browser.runtime.sendMessage({
action: 'get_status',
args: [ true ]
});
_databaseClosed = res.databaseClosed;
kpxcFields.prepareVisibleFieldsWithID('select');
kpxc.initPasswordGenerator(inputs);
if (kpxc.settings.showOTPIcon) {
kpxc.initOTPFields(inputs);
}
if (!kpxcFields.useDefinedCredentialFields()) {
// Get all combinations of username + password fields
kpxcFields.combinations = kpxcFields.getAllCombinations(inputs);
}
kpxcFields.prepareCombinations(kpxcFields.combinations);
if (kpxcFields.combinations.length === 0 && inputs.length === 0) {
browser.runtime.sendMessage({
action: 'show_default_browseraction'
});
return;
}
kpxc.url = document.location.origin;
kpxc.submitUrl = kpxc.getFormActionUrl(kpxcFields.combinations[0]);
// Get submitUrl for a single input
if (!kpxc.submitUrl && kpxcFields.combinations.length === 1 && inputs.length === 1) {
kpxc.submitUrl = kpxc.getFormActionUrlFromSingleInput(inputs[0]);
}
if (kpxc.settings.autoRetrieveCredentials && _called.retrieveCredentials === false && (kpxc.url && kpxc.submitUrl)) {
_called.retrieveCredentials = true;
kpxc.retrieveCredentialsCallback(await browser.runtime.sendMessage({
action: 'retrieve_credentials',
args: [ kpxc.url, kpxc.submitUrl ]
}));
} else if (_singleInputEnabledForPage) {
kpxc.preparePageForMultipleCredentials(kpxc.credentials);
}
};
kpxc.initPasswordGenerator = function(inputs) {
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);
}
}
};
kpxc.initOTPFields = function(inputs, databaseClosed) {
for (const i of inputs) {
const id = i.getLowerCaseAttribute('id');
const name = i.getLowerCaseAttribute('name');
if (acceptedOTPFields.some(f => (id && id.includes(f)) || (name && name.includes(f)))) {
kpxcTOTPIcons.newIcon(i, _databaseClosed);
}
}
};
kpxc.receiveCredentialsIfNecessary = async function() {
if (kpxc.credentials.length === 0 && _called.retrieveCredentials === false) {
const credentials = await browser.runtime.sendMessage({
action: 'retrieve_credentials',
args: [ kpxc.url, kpxc.submitUrl, true ] // Sets triggerUnlock to true
});
// If the database was locked, this is scope never met. In these cases the response is met at kpxc.detectDatabaseChange
_called.manualFillRequested = ManualFill.NONE;
await kpxc.retrieveCredentialsCallback(credentials, false);
return credentials;
}
return kpxc.credentials;
};
kpxc.retrieveCredentialsCallback = async function(credentials, dontAutoFillIn) {
if (kpxcFields.combinations.length > 0) {
kpxc.u = _f(kpxcFields.combinations[0].username);
kpxc.p = _f(kpxcFields.combinations[0].password);
}
if (credentials && credentials.length > 0) {
kpxc.credentials = credentials;
kpxc.prepareFieldsForCredentials(!dontAutoFillIn);
}
// Retrieve submitted credentials if available
const creds = await browser.runtime.sendMessage({
action: 'page_get_submitted'
});
if (creds && creds.submitted) {
await browser.runtime.sendMessage({
action: 'page_clear_submitted'
});
kpxc.rememberCredentials(creds.username, creds.password, creds.url, creds.oldCredentials);
}
};
kpxc.prepareFieldsForCredentials = function(autoFillInForSingle) {
// Only one login for this site
if (autoFillInForSingle && kpxc.settings.autoFillSingleEntry && kpxc.credentials.length === 1) {
let combination = null;
if (!kpxc.p && !kpxc.u && kpxcFields.combinations.length > 0) {
kpxc.u = _f(kpxcFields.combinations[0].username);
kpxc.p = _f(kpxcFields.combinations[0].password);
combination = kpxcFields.combinations[0];
}
if (kpxc.u) {
kpxc.setValueWithChange(kpxc.u, kpxc.credentials[0].login);
combination = kpxcFields.getCombination('username', kpxc.u);
}
if (kpxc.p) {
kpxc.setValueWithChange(kpxc.p, kpxc.credentials[0].password);
combination = kpxcFields.getCombination('password', kpxc.p);
}
if (combination) {
let list = [];
if (kpxc.fillInStringFields(combination.fields, kpxc.credentials[0].stringFields, list)) {
kpxcForm.destroy(false, { 'password': list.list[0], 'username': list.list[1] });
}
}
// Generate popup-list of usernames + descriptions
browser.runtime.sendMessage({
action: 'popup_login',
args: [ `${kpxc.credentials[0].login} (${kpxc.credentials[0].name})` ]
});
} else if (kpxc.credentials.length > 1 || (kpxc.credentials.length > 0 && (!kpxc.settings.autoFillSingleEntry || !autoFillInForSingle))) {
kpxc.preparePageForMultipleCredentials(kpxc.credentials);
}
};
kpxc.preparePageForMultipleCredentials = function(credentials) {
if (credentials.length === 0) {
return;
}
function getLoginText(credential) {
const visibleLogin = (credential.login.length > 0) ? credential.login : tr('credentialsNoUsername');
if (credential.expired && credential.expired === 'true') {
return `${visibleLogin} (${credential.name}) [${tr('credentialExpired')}]`;
}
return `${visibleLogin} (${credential.name})`;
}
// Add usernames + descriptions to autocomplete-list and popup-list
const usernames = [];
kpxcAutocomplete.elements = [];
for (let i = 0; i < credentials.length; i++) {
const loginText = getLoginText(credentials[i]);
usernames.push(loginText);
const item = {
label: loginText,
value: credentials[i].login,
loginId: i
};
kpxcAutocomplete.elements.push(item);
}
// Generate popup-list of usernames + descriptions
browser.runtime.sendMessage({
action: 'popup_login',
args: usernames
});
// Initialize autocomplete for username fields
if (kpxc.settings.autoCompleteUsernames) {
for (const i of kpxcFields.combinations) {
// Both username and password fields are visible
if (_detectedFields >= 2) {
if (_f(i.username)) {
kpxcAutocomplete.create(_f(i.username), false, kpxc.settings.autoSubmit);
}
} else if (_detectedFields === 1) {
if (_f(i.username)) {
kpxcAutocomplete.create(_f(i.username), false, kpxc.settings.autoSubmit);
}
if (_f(i.password)) {
kpxcAutocomplete.create(_f(i.password), false, kpxc.settings.autoSubmit);
}
}
}
}
};
// Returns the form that includes the inputField
kpxc.getForm = function(inputField) {
for (const f of document.forms) {
for (const e of f.elements) {
if (e === inputField) {
return f;
}
}
}
};
kpxc.getFormActionUrl = function(combination) {
if (!combination) {
return null;
}
const field = _f(combination.password) || _f(combination.username);
if (field === null) {
return null;
}
const form = kpxc.getForm(field);
let action = null;
if (form && form.length > 0) {
action = form.action;
}
if (typeof(action) !== 'string' || action === '') {
action = document.location.origin + document.location.pathname;
}
return action;
};
kpxc.getFormActionUrlFromSingleInput = function(field) {
if (!field) {
return null;
}
let action = field.formAction;
if (typeof(action) !== 'string' || action === '') {
action = document.location.origin + document.location.pathname;
}
return action;
};
// Get the form submit button instead if action URL is same as the page itself
kpxc.getFormSubmitButton = function(form) {
const action = kpxc.submitUrl || form.action;
if (action.includes(document.location.origin + document.location.pathname)) {
for (const i of form.elements) {
if (i.type === 'submit') {
return i;
}
}
}
// Try to find another button. Select the first one.
const buttons = Array.from(form.querySelectorAll('button[type=\'button\'], button[type=\'submit\'], input[type=\'button\'], button:not([type])'));
if (buttons.length > 0) {
return buttons[0];
}
return undefined;
};
kpxc.fillInCredentials = async function(combination, onlyPassword, suppressWarnings) {
const action = kpxc.getFormActionUrl(combination);
const u = _f(combination.username);
const p = _f(combination.password);
if (combination.isNew) {
// Initialize form-submit for remembering credentials
const fieldId = combination.password || combination.username;
const field = _f(fieldId);
if (field) {
const form = kpxc.getForm(field);
if (form && form.length > 0) {
kpxcForm.init(form, combination);
}
}
}
if (u) {
kpxc.u = u;
}
if (p) {
kpxc.p = p;
}
if (kpxc.url === document.location.origin && kpxc.submitUrl === action && kpxc.credentials.length > 0) {
kpxc.fillIn(combination, onlyPassword, suppressWarnings);
} else {
kpxc.url = document.location.origin;
kpxc.submitUrl = action;
const credentials = await browser.runtime.sendMessage({
action: 'retrieve_credentials',
args: [ kpxc.url, kpxc.submitUrl, true ] // Sets triggerUnlock to true
});
await kpxc.retrieveCredentialsCallback(credentials, true);
kpxc.fillIn(combination, onlyPassword, suppressWarnings);
}
};
kpxc.fillInFromActiveElement = function(suppressWarnings, passOnly = false) {
const el = document.activeElement;
if (el.tagName.toLowerCase() !== 'input') {
if (kpxcFields.combinations.length > 0) {
kpxc.fillInCredentials(kpxcFields.combinations[0], passOnly, suppressWarnings);
// Focus to the input field
const field = _f(passOnly ? kpxcFields.combinations[0].password : kpxcFields.combinations[0].username);
if (field) {
field.focus();
}
}
return;
}
kpxcFields.setUniqueId(el);
const fieldId = kpxcFields.prepareId(el.getAttribute('data-kpxc-id'));
let combination = null;
if (el.getAttribute('type') === 'password') {
combination = kpxcFields.getCombination('password', fieldId);
} else {
combination = kpxcFields.getCombination('username', fieldId);
}
if (passOnly) {
if (!_f(combination.password)) {
kpxcUI.createNotification('warning', tr('fieldsNoPasswordField'));
return;
}
}
delete combination.loginId;
kpxc.fillInCredentials(combination, passOnly, suppressWarnings);
};
kpxc.fillInFromActiveElementTOTPOnly = async function(target) {
const el = target || document.activeElement;
kpxcFields.setUniqueId(el);
const fieldId = kpxcFields.prepareId(el.getAttribute('data-kpxc-id'));
const index = await browser.runtime.sendMessage({
action: 'page_get_login_id'
});
if (index >= 0 && kpxc.credentials[index]) {
// Check the value from stringFields (to be removed)
const currentField = _fs(fieldId);
if (kpxc.credentials[index].stringFields && kpxc.credentials[index].stringFields.length > 0) {
const stringFields = kpxc.credentials[index].stringFields;
for (const s of stringFields) {
const val = s['KPH: {TOTP}'];
if (val) {
kpxc.setValue(currentField, val);
}
}
} else if (kpxc.credentials[index].totp && kpxc.credentials[index].totp.length > 0) {
kpxc.setValue(currentField, kpxc.credentials[index].totp);
}
}
};
kpxc.setValue = function(field, value) {
if (field.matches('select')) {
value = value.toLowerCase().trim();
const options = field.querySelectorAll('option');
for (const o of options) {
if (o.textContent.toLowerCase().trim() === value) {
kpxc.setValueWithChange(field, o.value);
return false;
}
}
} else {
kpxc.setValueWithChange(field, value);
}
};
kpxc.fillInStringFields = function(fields, stringFields, filledInFields) {
let filledIn = false;
filledInFields.list = [];
if (fields && stringFields && fields.length > 0 && stringFields.length > 0) {
for (let i = 0; i < fields.length; i++) {
const currentField = _fs(fields[i]);
const stringFieldValue = Object.values(stringFields[i]);
if (currentField && stringFieldValue[0]) {
kpxc.setValue(currentField, stringFieldValue[0]);
filledInFields.list.push(fields[i]);
filledIn = true;
}
}
}
return filledIn;
};
kpxc.setValueWithChange = function(field, value) {
if (kpxc.settings.respectMaxLength === true) {
const attributeMaxlength = field.getAttribute('maxlength');
if (attributeMaxlength && !isNaN(attributeMaxlength) && attributeMaxlength > 0) {
value = value.substr(0, attributeMaxlength);
}
}
field.value = value;
field.dispatchEvent(new Event('input', { 'bubbles': true }));
field.dispatchEvent(new Event('change', { 'bubbles': true }));
};
kpxc.fillWithSpecificLogin = function(id) {
if (kpxc.credentials[id]) {
let combination = null;
if (kpxc.u) {
kpxc.setValueWithChange(kpxc.u, kpxc.credentials[id].login);
combination = kpxcFields.getCombination('username', kpxc.u);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: id
});
kpxc.u.focus();
}
if (kpxc.p) {
kpxc.setValueWithChange(kpxc.p, kpxc.credentials[id].password);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: id
});
combination = kpxcFields.getCombination('password', kpxc.p);
}
let list = [];
if (kpxc.fillInStringFields(combination.fields, kpxc.credentials[id].stringFields, list)) {
kpxcForm.destroy(false, { 'password': list.list[0], 'username': list.list[1] });
}
kpxcAutocomplete.closeList();
}
};
kpxc.fillIn = function(combination, onlyPassword, suppressWarnings) {
// No credentials available
if (kpxc.credentials.length === 0 && !suppressWarnings) {
kpxcUI.createNotification('error', tr('credentialsNoLoginsFound'));
return;
}
let skipAutoSubmit = false;
const uField = _f(combination.username);
const pField = _f(combination.password);
// Exactly one pair of credentials available
if (kpxc.credentials.length === 1) {
let filledIn = false;
if (kpxc.credentials[0].skipAutoSubmit !== undefined) {
skipAutoSubmit = kpxc.credentials[0].skipAutoSubmit === 'true';
}
if (uField && (!onlyPassword || _singleInputEnabledForPage)) {
kpxc.setValueWithChange(uField, kpxc.credentials[0].login);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: 0
});
filledIn = true;
}
if (pField) {
pField.setAttribute('type', 'password');
kpxc.setValueWithChange(pField, kpxc.credentials[0].password);
pField.setAttribute('unchanged', true);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: 0
});
filledIn = true;
kpxc.setPasswordFilled(true);
}
let list = [];
if (kpxc.fillInStringFields(combination.fields, kpxc.credentials[0].stringFields, list)) {
kpxcForm.destroy(false, { 'password': list.list[0], 'username': list.list[1] });
filledIn = true;
}
if (!filledIn) {
if (!suppressWarnings) {
kpxcUI.createNotification('error', tr('fieldsFill'));
}
return;
}
} else if (combination.loginId !== undefined && kpxc.credentials[combination.loginId]) {
// Specific login ID given
let filledIn = false;
if (kpxc.credentials[0].skipAutoSubmit !== undefined) {
skipAutoSubmit = kpxc.credentials[combination.loginId].skipAutoSubmit === 'true';
}
if (uField && (!onlyPassword || _singleInputEnabledForPage)) {
kpxc.setValueWithChange(uField, kpxc.credentials[combination.loginId].login);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: combination.loginId
});
filledIn = true;
}
if (pField) {
kpxc.setValueWithChange(pField, kpxc.credentials[combination.loginId].password);
pField.setAttribute('unchanged', true);
browser.runtime.sendMessage({
action: 'page_set_login_id', args: combination.loginId
});
filledIn = true;
kpxc.setPasswordFilled(true);
}
let list = [];
if (kpxc.fillInStringFields(combination.fields, kpxc.credentials[combination.loginId].stringFields, list)) {
kpxcForm.destroy(false, { 'password': list.list[0], 'username': list.list[1] });
filledIn = true;
}
if (!filledIn) {
if (!suppressWarnings) {
kpxcUI.createNotification('error', tr('fieldsFill'));
}
return;
}
} else { // Multiple credentials available
// Check if only one password for given username exists
let countPasswords = 0;
if (uField) {
let valPassword = '';
let valUsername = '';
let valStringFields = [];
const valQueryUsername = uField.value.toLowerCase();
// Find passwords to given username (even those with empty username)
for (const c of kpxc.credentials) {
if (c.login.toLowerCase() === valQueryUsername) {
countPasswords += 1;
valPassword = c.password;
valUsername = c.login;
valStringFields = c.stringFields;
if (c.skipAutoSubmit !== undefined) {
skipAutoSubmit = c.skipAutoSubmit === 'true';
}
}
}
// For the correct notification message: 0 = no logins, X > 1 = too many logins
if (countPasswords === 0) {
countPasswords = kpxc.credentials.length;
}
// Only one mapping username found
if (countPasswords === 1) {
if (!onlyPassword) {
kpxc.setValueWithChange(uField, valUsername);
}
if (pField) {
kpxc.setValueWithChange(pField, valPassword);
pField.setAttribute('unchanged', true);
kpxc.setPasswordFilled(true);
}
let list = [];
if (kpxc.fillInStringFields(combination.fields, valStringFields, list)) {
kpxcForm.destroy(false, { 'password': list.list[0], 'username': list.list[1] });
}
}
// User has to select correct credentials by himself
if (countPasswords > 1) {
if (!suppressWarnings) {
const target = onlyPassword ? pField : uField;
if (!target) {
return;
}
if (kpxcAutocomplete.started) {
kpxcAutocomplete.showList(target);
} else {
kpxcAutocomplete.create(target, true, kpxc.settings.autoSubmit);
}
target.focus();
}
return;
} else if (countPasswords < 1) {
if (!suppressWarnings) {
kpxcUI.createNotification('error', tr('credentialsNoUsernameFound'));
}
return;
}
} else {
if (!suppressWarnings) {
const target = onlyPassword ? pField : uField;
if (!target) {
return;
}
if (kpxcAutocomplete.started) {
kpxcAutocomplete.showList(target);
} else {
kpxcAutocomplete.create(target, true, kpxc.settings.autoSubmit);
}
target.focus();
return;
}
}
}
// Close autocomplete menu after fill
kpxcAutocomplete.closeList();
// Auto-submit
if (kpxc.settings.autoSubmit && !skipAutoSubmit) {
const form = kpxc.u.form || kpxc.p.form;
const submitButton = kpxc.getFormSubmitButton(form);
if (submitButton !== undefined) {
submitButton.click();
} else {
form.submit();
}
}
};
kpxc.contextMenuRememberCredentials = async function() {
const el = document.activeElement;
if (el.tagName.toLowerCase() !== 'input') {
return;
}
kpxcFields.setUniqueId(el);
const fieldId = kpxcFields.prepareId(el.getAttribute('data-kpxc-id'));
let combination = null;
if (el.getAttribute('type') === 'password') {
combination = kpxcFields.getCombination('password', fieldId);
} else {
combination = kpxcFields.getCombination('username', fieldId);
}
let usernameValue = '';
let passwordValue = '';
const usernameField = _f(combination.username);
const passwordField = _f(combination.password);
if (usernameField) {
usernameValue = usernameField.value;
}
if (passwordField) {
passwordValue = passwordField.value;
}
const result = await kpxc.rememberCredentials(usernameValue, passwordValue);
if (result === undefined) {
kpxcUI.createNotification('error', tr('rememberNoPassword'));
return;
}
if (!result) {
kpxcUI.createNotification('warning', tr('rememberCredentialsExists'));
}
};
/**
* Gets the credential list and shows the update banner
* @param {string} usernameValue Submitted username
* @param {string} passwordValue Submitted password
* @param {string} urlValue URL of the page where password change was detected
* @param {Array} oldCredentials Credentials saved from the password change page, if available
*/
kpxc.rememberCredentials = async function(usernameValue, passwordValue, urlValue, oldCredentials) {
const credentials = (oldCredentials !== undefined && oldCredentials.length > 0) ? oldCredentials : kpxc.credentials;
// No password given or field cleaned by a site-running script
// --> no password to save
if (passwordValue === '') {
return undefined;
}
let usernameExists = false;
let nothingChanged = false;
for (const c of credentials) {
if (c.login === usernameValue && c.password === passwordValue) {
nothingChanged = true;
break;
}
if (c.login === usernameValue) {
usernameExists = true;
}
}
if (!nothingChanged) {
if (!usernameExists) {
for (const c of credentials) {
if (c.login === usernameValue) {
usernameExists = true;
break;
}
}
}
const credentialsList = [];
for (const c of credentials) {
credentialsList.push({
login: c.login,
name: c.name,
uuid: c.uuid
});
}
let url = this.action;
if (!url) {
url = kpxc.settings.saveDomainOnlyNewCreds ? document.location.origin : document.location.href;
if (url.indexOf('?') > 0) {
url = url.substring(0, url.indexOf('?'));
if (url.length < document.location.origin.length) {
url = document.location.origin;
}
}
}
urlValue = urlValue || url;
// Set usernameValue to the first one in the list, or the selected entry
if (usernameValue === '') {
if (credentialsList.length === 1) {
usernameValue = credentialsList[0].login;
} else if (credentialsList.length > 1) {
const index = await browser.runtime.sendMessage({
action: 'page_get_login_id'
});
if (index >= 0) {
usernameValue = credentialsList[index].login;
}
}
}
// Show the banner
const newCredentials = {
username: usernameValue,
password: passwordValue,
url: urlValue,
usernameExists: usernameExists,
list: credentialsList
};
kpxcBanner.create(newCredentials);
return true;
}
return false;
};
kpxc.getSite = function(sites) {
if (!sites || sites.length === 0) {
return '';
}
let site = trimURL(sites[0]);
kpxc.initializeSitePreferences();
if (slashNeededForUrl(site)) {
site += '/';
}
return site;
};
kpxc.ignoreSite = async function(sites) {
const site = kpxc.getSite(sites);
// Check if the site already exists
let siteExists = false;
for (const existingSite of kpxc.settings['sitePreferences']) {
if (existingSite.url === site) {
existingSite.ignore = IGNORE_NORMAL;
siteExists = true;
}
}
if (!siteExists) {
kpxc.settings['sitePreferences'].push({
url: site,
ignore: IGNORE_NORMAL,
usernameOnly: false
});
}
await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
};
kpxc.addToSitePreferences = async function(sites) {
kpxc.initializeSitePreferences();
// Returns a predefined URL for certain sites
const site = kpxcSites.definedURL(trimURL(window.top.location.href));
// Check if the site already exists -> update the current settings
let siteExists = false;
for (const existingSite of kpxc.settings['sitePreferences']) {
if (existingSite.url === site) {
existingSite.ignore = IGNORE_NOTHING;
existingSite.usernameOnly = true;
siteExists = true;
}
}
if (!siteExists) {
kpxc.settings['sitePreferences'].push({
url: site,
ignore: IGNORE_NOTHING,
usernameOnly: true
});
}
await browser.runtime.sendMessage({
action: 'save_settings',
args: [ kpxc.settings ]
});
browser.runtime.sendMessage({
action: 'username_field_detected',
args: false
});
};
// Delete previously created Object if it exists. It will be replaced by an Array
kpxc.initializeSitePreferences = function() {
if (kpxc.settings['sitePreferences'] !== undefined && kpxc.settings['sitePreferences'].constructor === Object) {
delete kpxc.settings['sitePreferences'];
}
if (!kpxc.settings['sitePreferences']) {
kpxc.settings['sitePreferences'] = [];
}
};
kpxc.getDocumentLocation = function() {
return kpxc.settings.saveDomainOnly ? document.location.origin : document.location.href;
};
// Sets the icons to corresponding database lock status
kpxc.switchIcons = function(locked) {
kpxcUsernameIcons.switchIcon(locked);
kpxcPasswordIcons.switchIcon(locked);
kpxcTOTPIcons.switchIcon(locked);
};
kpxc.setPasswordFilled = function(state) {
browser.runtime.sendMessage({
action: 'password_set_filled',
args: state
});
};
kpxc.passwordFilled = async function() {
return await browser.runtime.sendMessage({ action: 'password_get_filled' });
};
const kpxcEvents = {};
kpxcEvents.clearCredentials = function() {
kpxc.credentials = [];
kpxcAutocomplete.elements = [];
_called.retrieveCredentials = false;
if (kpxc.settings.autoCompleteUsernames) {
for (const c of kpxcFields.combinations) {
const uField = _f(c.username);
if (uField) {
if (uField.classList.contains('ui-autocomplete-input')) {
uField.autocomplete('destroy');
}
}
}
}
};
kpxcEvents.triggerActivatedTab = async function() {
// Doesn't run a second time because of _called.initCredentialFields set to true
kpxc.init();
// Update username field lock state
const state = await browser.runtime.sendMessage({ action: 'check_database_hash' });
kpxc.switchIcons(state === '');
// initCredentialFields calls also "retrieve_credentials", to prevent it
// check of init() was already called
if (_called.initCredentialFields && (kpxc.url && kpxc.submitUrl) && kpxc.settings.autoRetrieveCredentials) {
_called.retrieveCredentials = true;
kpxc.retrieveCredentialsCallback(await browser.runtime.sendMessage({
action: 'retrieve_credentials',
args: [ kpxc.url, kpxc.submitUrl ]
}));
}
};