/// Fill listener — receives credentials from the service worker and fills form fields. /// /// Uses the native value setter trick to work with React/Vue controlled inputs /// that override the value property. /// Set up a listener for fill_credentials messages from the service worker. export function setupFillListener(): void { chrome.runtime.onMessage.addListener( (message: { type: string; username: string; password: string }, _sender: chrome.runtime.MessageSender, sendResponse: (response: { ok: boolean }) => void) => { if (message.type !== 'fill_credentials') return false; fillFields(message.username, message.password); sendResponse({ ok: true }); return false; }, ); } /// Fill username and password fields on the page. /// /// Finds the first visible password field and its associated username field, /// then sets their values using the native setter trick for React/Vue compat. export function fillFields(username: string, password: string): void { const pwField = document.querySelector('input[type="password"]'); if (!pwField) return; // Set the password. setNativeValue(pwField, password); // Find the username field (same logic as detector). if (username) { const usernameField = findUsernameForFill(pwField); if (usernameField) { setNativeValue(usernameField, username); } } } /// Use the native HTMLInputElement.value setter to bypass React/Vue wrappers. /// Then dispatch input and change events so the framework picks up the change. function setNativeValue(input: HTMLInputElement, value: string): void { const nativeSetter = Object.getOwnPropertyDescriptor( HTMLInputElement.prototype, 'value', )?.set; if (nativeSetter) { nativeSetter.call(input, value); } else { input.value = value; } input.dispatchEvent(new Event('input', { bubbles: true })); input.dispatchEvent(new Event('change', { bubbles: true })); } /// Find the username field associated with a password field (simplified version for fill). function findUsernameForFill(pwField: HTMLInputElement): HTMLInputElement | null { const form = pwField.closest('form'); const scope = form ?? document; const inputs = scope.querySelectorAll('input'); // Priority: autocomplete > type=email > name pattern > preceding text input. for (const input of inputs) { if (input === pwField) continue; if (input.autocomplete === 'username' || input.autocomplete === 'email') return input; } for (const input of inputs) { if (input === pwField) continue; if (input.type === 'email') return input; } const pattern = /user|email|login|account/i; for (const input of inputs) { if (input === pwField || input.type === 'hidden' || input.type === 'password') continue; if (pattern.test(input.name) || pattern.test(input.id)) return input; } const allInputs = Array.from(inputs); const pwIndex = allInputs.indexOf(pwField); for (let i = pwIndex - 1; i >= 0; i--) { const input = allInputs[i]; if (input.type === 'hidden' || input.type === 'password' || input.type === 'submit') continue; if (input.offsetWidth > 0 && input.offsetHeight > 0) return input; } return null; }