password-store-extension/extension/foreground.js

230 lines
5.9 KiB
JavaScript

(() => {
let currentPageMatches = null;
let currentForm = null;
let successfulAttachment = false;
let focusedField = null;
function generateQuerySelector(el) {
if (el.tagName.toLowerCase() === 'html') {
return 'HTML';
}
let str = el.tagName;
str += el.id != '' ? '#' + el.id : '';
if (el.className) {
const classes = el.className.split(/\s/);
for (let i = 0; i < classes.length; i++) {
str += '.' + classes[i];
}
}
return generateQuerySelector(el.parentNode) + ' > ' + str;
}
function lookForLoginForms() {
const allPasswordInputs = document.querySelectorAll(
'input[type="password"]'
);
let loginForms = [];
allPasswordInputs.forEach((field) => {
const closestForm = field.closest('form');
if (!closestForm) {
return;
}
const existing = loginForms.find(({ form }) => form === closestForm);
if (existing) {
existing.passwordAgain = field;
return;
}
let usernameField;
const allInputs = Array.from(closestForm.querySelectorAll('input'));
const contenders = allInputs.filter((element) => {
const nameAttr = (element.getAttribute('name') || '').toLowerCase();
const idAttr = (element.getAttribute('id') || '').toLowerCase();
const closestLabel = closestForm.querySelector(
`label[for="${idAttr}"]`
);
if (closestLabel) {
const labelText = closestLabel.innerText.toLowerCase().trim();
if (
labelText.startsWith('user') ||
labelText.startsWith('email') ||
labelText.startsWith('e-mail')
) {
return true;
}
}
if (nameAttr.includes('username')) {
return true;
}
if (nameAttr.includes('email')) {
return true;
}
if (nameAttr.includes('name')) {
return true;
}
});
usernameField = contenders[0];
loginForms.push({
password: field,
username: usernameField,
form: closestForm,
});
});
if (loginForms.length) {
chrome.runtime.sendMessage({
message: 'login_forms',
payload: loginForms.map((stringify) => ({
form: generateQuerySelector(stringify.form),
password: generateQuerySelector(stringify.password),
passwordAgain: stringify.passwordAgain
? generateQuerySelector(stringify.passwordAgain)
: null,
username: stringify.username
? generateQuerySelector(stringify.username)
: null,
})),
});
}
return loginForms;
}
function createOptionsSelect(commitAutofill) {
const select = document.createElement('select');
const unopt = document.createElement('option');
unopt.innerText = 'Select autofill...';
select.appendChild(unopt);
Object.assign(select.style, {
pointerEvents: 'all',
});
currentPageMatches.forEach((match) => {
const option = document.createElement('option');
option.value = match;
option.innerText = match;
select.appendChild(option);
});
select.addEventListener('change', function () {
const val = select.value;
if (val) {
commitAutofill(val);
}
});
return select;
}
function attachLoginFormHighlight(info) {
successfulAttachment = true;
const autoFillContainer = document.createElement('div');
Object.assign(autoFillContainer.style, {
position: 'absolute',
outline: '10px solid rgb(0 170 255 / 60%)',
boxSizing: 'border-box',
pointerEvents: 'none',
borderRadius: '10px',
zIndex: '10000000',
});
document.body.appendChild(autoFillContainer);
function reposition() {
const boundingBox = info.form.getBoundingClientRect();
Object.assign(autoFillContainer.style, {
top: `${boundingBox.y - 10 + window.scrollY}px`,
left: `${boundingBox.x - 10}px`,
width: `${boundingBox.width + 20}px`,
height: `${boundingBox.height + 20}px`,
});
}
window.addEventListener('resize', reposition);
window.addEventListener('scroll', reposition);
reposition();
const select = createOptionsSelect((password) => {
currentForm = info;
chrome.runtime.sendMessage({
message: 'autofill',
payload: password,
});
});
autoFillContainer.appendChild(select);
}
function init() {
const forms = lookForLoginForms();
if (forms.length) {
forms.forEach((form) => attachLoginFormHighlight(form));
}
}
function fakeTriggers(input, value) {
const inputEvt = new Event('input');
const changeEvt = new Event('change');
input.focus();
input.value = value;
input.setAttribute('value', value);
input.dispatchEvent(inputEvt);
input.dispatchEvent(changeEvt);
input.blur();
}
chrome.runtime.onMessage.addListener(function (
request,
sender,
sendResponse
) {
if (request.message === 'has_entries') {
currentPageMatches = request.payload;
init();
sendResponse(true);
}
if (request.message === 'fill_password') {
if (request && request.payload.password) {
if (currentForm) {
fakeTriggers(currentForm.password, request.payload.password);
if (currentForm.username && request.payload.username) {
fakeTriggers(currentForm.username, request.payload.username);
}
setTimeout(() => currentForm.password.focus(), 100);
} else if (focusedField) {
fakeTriggers(focusedField, request.payload.password);
}
}
sendResponse(true);
}
});
document.addEventListener('click', function (e) {
if (!currentPageMatches) {
return;
}
focusedField = e.target;
if (successfulAttachment) {
return;
}
if (e.target && e.target.tagName === 'INPUT') {
init();
}
});
})();