125 lines
3.4 KiB
JavaScript
125 lines
3.4 KiB
JavaScript
// Clavitor Content Script
|
|
|
|
// Detect login forms and notify background
|
|
function detectForms() {
|
|
const forms = document.querySelectorAll('form');
|
|
let loginForms = 0;
|
|
|
|
forms.forEach(form => {
|
|
const hasPassword = form.querySelector('input[type="password"]');
|
|
const hasUsername = form.querySelector('input[type="text"], input[type="email"], input[name*="user"], input[name*="email"], input[name*="login"]');
|
|
|
|
if (hasPassword || hasUsername) {
|
|
loginForms++;
|
|
}
|
|
});
|
|
|
|
// Also check for standalone password fields
|
|
const standalonePasswords = document.querySelectorAll('input[type="password"]:not(form input)');
|
|
loginForms += standalonePasswords.length;
|
|
|
|
chrome.runtime.sendMessage({ action: 'formsDetected', count: loginForms });
|
|
|
|
return loginForms;
|
|
}
|
|
|
|
// Get all form fields for mapping
|
|
function getFormFields() {
|
|
const fields = [];
|
|
const inputs = document.querySelectorAll('input:not([type="hidden"]):not([type="submit"]):not([type="button"])');
|
|
|
|
inputs.forEach(input => {
|
|
const label = findLabel(input);
|
|
fields.push({
|
|
selector: getSelector(input),
|
|
label: label,
|
|
type: input.type,
|
|
name: input.name,
|
|
placeholder: input.placeholder,
|
|
autocomplete: input.autocomplete
|
|
});
|
|
});
|
|
|
|
return fields;
|
|
}
|
|
|
|
// Find label for an input
|
|
function findLabel(input) {
|
|
// Check for associated label
|
|
if (input.id) {
|
|
const label = document.querySelector(`label[for="${input.id}"]`);
|
|
if (label) return label.textContent.trim();
|
|
}
|
|
|
|
// Check for wrapping label
|
|
const parent = input.closest('label');
|
|
if (parent) {
|
|
const text = parent.textContent.replace(input.value, '').trim();
|
|
if (text) return text;
|
|
}
|
|
|
|
// Check aria-label
|
|
if (input.getAttribute('aria-label')) {
|
|
return input.getAttribute('aria-label');
|
|
}
|
|
|
|
// Use placeholder or name as fallback
|
|
return input.placeholder || input.name || input.type;
|
|
}
|
|
|
|
// Generate a unique selector for an element
|
|
function getSelector(el) {
|
|
if (el.id) return '#' + el.id;
|
|
if (el.name) return `[name="${el.name}"]`;
|
|
|
|
// Build a path selector
|
|
const path = [];
|
|
while (el && el !== document.body) {
|
|
let selector = el.tagName.toLowerCase();
|
|
if (el.className) {
|
|
selector += '.' + el.className.trim().split(/\s+/).join('.');
|
|
}
|
|
path.unshift(selector);
|
|
el = el.parentElement;
|
|
}
|
|
return path.join(' > ');
|
|
}
|
|
|
|
// Fill fields by selector
|
|
function fillFields(fields) {
|
|
Object.entries(fields).forEach(([label, selector]) => {
|
|
try {
|
|
const el = document.querySelector(selector);
|
|
if (el) {
|
|
el.value = label; // label here is actually the value
|
|
el.dispatchEvent(new Event('input', { bubbles: true }));
|
|
el.dispatchEvent(new Event('change', { bubbles: true }));
|
|
}
|
|
} catch (e) {
|
|
console.error('Clavitor: Failed to fill field', selector, e);
|
|
}
|
|
});
|
|
}
|
|
|
|
// Listen for messages from background
|
|
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
|
|
if (request.action === 'fillFields') {
|
|
fillFields(request.fields);
|
|
sendResponse({ success: true });
|
|
}
|
|
|
|
if (request.action === 'getFormFields') {
|
|
const fields = getFormFields();
|
|
sendResponse({ success: true, fields });
|
|
}
|
|
});
|
|
|
|
// Detect forms on page load
|
|
setTimeout(detectForms, 500);
|
|
|
|
// Re-detect on dynamic content changes
|
|
const observer = new MutationObserver(() => {
|
|
detectForms();
|
|
});
|
|
observer.observe(document.body, { childList: true, subtree: true });
|