const ext = globalThis.browser ?? globalThis.chrome; const isNodeTestEnv = typeof module !== "undefined" && module.exports; const usePromiseAPI = typeof globalThis.browser !== "undefined"; function runtimeSend(message) { if (usePromiseAPI) { return ext.runtime.sendMessage(message); } return new Promise((resolve, reject) => { ext.runtime.sendMessage(message, (response) => { const error = ext.runtime.lastError; if (error) { reject(new Error(error.message)); return; } resolve(response); }); }); } function isVisibleInput(input) { if (!(input instanceof HTMLInputElement)) { return false; } if (input.disabled || input.readOnly) { return false; } const style = window.getComputedStyle(input); if (style.display === "none" || style.visibility === "hidden") { return false; } return input.offsetParent !== null || style.position === "fixed"; } function normalizeRole(rawRole) { switch (String(rawRole || "").trim().toLowerCase()) { case "password": return "password"; default: return "username"; } } function describeFieldRole(input) { const type = String(input?.getAttribute?.("type") || "").toLowerCase(); if (type === "password") { return "password"; } const autocomplete = String(input?.autocomplete || "").toLowerCase(); if (autocomplete.includes("username") || autocomplete.includes("email")) { return "username"; } return "username"; } function isUsernameCandidate(input) { if (!isVisibleInput(input)) { return false; } return describeFieldRole(input) === "username"; } function isPasswordCandidate(input) { return isVisibleInput(input) && describeFieldRole(input) === "password"; } function dispatchFillEvents(input) { if (typeof InputEvent === "function") { input.dispatchEvent(new InputEvent("input", { bubbles: true, data: input.value, inputType: "insertText" })); } else { input.dispatchEvent(new Event("input", { bubbles: true })); } input.dispatchEvent(new Event("change", { bubbles: true })); } function setInputValue(input, value) { const prototype = Object.getPrototypeOf(input); const descriptor = prototype ? Object.getOwnPropertyDescriptor(prototype, "value") : null; if (descriptor?.set) { descriptor.set.call(input, value); return; } input.value = value; } function visibleInputs(scope) { return Array.from(scope.querySelectorAll("input")).filter(isVisibleInput); } function resolveFormInputs(anchorInput) { if (anchorInput?.form instanceof HTMLFormElement) { return visibleInputs(anchorInput.form); } return visibleInputs(document); } function firstVisiblePassword(scope) { return visibleInputs(scope).find(isPasswordCandidate) || null; } function firstVisibleUsername(scope) { return visibleInputs(scope).find(isUsernameCandidate) || null; } function associatedFieldsForAnchor(anchorInput) { const scopeInputs = resolveFormInputs(anchorInput); const passwordInput = scopeInputs.find(isPasswordCandidate) || firstVisiblePassword(document); const usernameInScope = scopeInputs.filter(isUsernameCandidate); let usernameInput = usernameInScope[0] || null; if (passwordInput && usernameInScope.length !== 0) { const priorSibling = usernameInScope.find((input) => typeof input.compareDocumentPosition === "function" && Boolean(input.compareDocumentPosition(passwordInput) & Node.DOCUMENT_POSITION_FOLLOWING) ); usernameInput = priorSibling || usernameInScope[0] || null; } if (!usernameInput) { usernameInput = firstVisibleUsername(document); } return { usernameInput, passwordInput }; } function buildFieldDescriptor(input, role) { if (!(input instanceof HTMLInputElement)) { return null; } const normalizedRole = normalizeRole(role || describeFieldRole(input)); const form = input.form instanceof HTMLFormElement ? input.form : null; const scope = form || document; const inputs = visibleInputs(scope); const fieldIndex = inputs.indexOf(input); const forms = Array.from(document.forms || []); return { role: normalizedRole, formIndex: form ? forms.indexOf(form) : -1, fieldIndex, id: String(input.id || ""), name: String(input.name || ""), autocomplete: String(input.autocomplete || "").toLowerCase() }; } function resolveFieldDescriptor(descriptor) { if (!descriptor || typeof descriptor !== "object") { return null; } const normalizedRole = normalizeRole(descriptor.role); const forms = Array.from(document.forms || []); const form = Number.isInteger(descriptor.formIndex) && descriptor.formIndex >= 0 ? forms[descriptor.formIndex] || null : null; const scope = form || document; const inputs = visibleInputs(scope); if (Number.isInteger(descriptor.fieldIndex) && descriptor.fieldIndex >= 0 && descriptor.fieldIndex < inputs.length) { const candidate = inputs[descriptor.fieldIndex]; if (describeFieldRole(candidate) === normalizedRole) { return candidate; } } if (descriptor.id) { const byID = scope.querySelector(`#${CSS.escape(descriptor.id)}`); if (byID instanceof HTMLInputElement && isVisibleInput(byID) && describeFieldRole(byID) === normalizedRole) { return byID; } } if (descriptor.name) { const byName = visibleInputs(scope).find((input) => input.name === descriptor.name && describeFieldRole(input) === normalizedRole); if (byName) { return byName; } } if (normalizedRole === "password") { return firstVisiblePassword(scope) || firstVisiblePassword(document); } return firstVisibleUsername(scope) || firstVisibleUsername(document); } function chooseFillTargets(targetDescriptor) { const anchorInput = resolveFieldDescriptor(targetDescriptor) || (document.activeElement instanceof HTMLInputElement ? document.activeElement : null); const associated = associatedFieldsForAnchor(anchorInput); if (normalizeRole(targetDescriptor?.role) === "password" && anchorInput instanceof HTMLInputElement) { return { usernameInput: associated.usernameInput, passwordInput: isPasswordCandidate(anchorInput) ? anchorInput : associated.passwordInput, anchorInput }; } if (normalizeRole(targetDescriptor?.role) === "username" && anchorInput instanceof HTMLInputElement) { return { usernameInput: isUsernameCandidate(anchorInput) ? anchorInput : associated.usernameInput, passwordInput: associated.passwordInput, anchorInput }; } return { usernameInput: associated.usernameInput, passwordInput: associated.passwordInput, anchorInput: anchorInput || associated.passwordInput || associated.usernameInput || null }; } function scanLoginFields() { const activeElement = document.activeElement instanceof HTMLInputElement ? document.activeElement : null; const activeUsable = activeElement && isVisibleInput(activeElement) ? activeElement : null; const targets = chooseFillTargets(buildFieldDescriptor(activeUsable, describeFieldRole(activeUsable))); const anchorInput = activeUsable || targets.passwordInput || targets.usernameInput; const focusTarget = buildFieldDescriptor(anchorInput, describeFieldRole(anchorInput)); const allVisible = visibleInputs(document); const roles = allVisible .filter((input) => isUsernameCandidate(input) || isPasswordCandidate(input)) .map((input) => { const descriptor = buildFieldDescriptor(input, describeFieldRole(input)); return `${descriptor.formIndex}:${descriptor.fieldIndex}:${descriptor.role}`; }); return { pageHasLoginForm: Boolean(targets.usernameInput || targets.passwordInput), usernameInput: targets.usernameInput, passwordInput: targets.passwordInput, anchorInput, focusTarget, signature: roles.join("|") }; } function fillCredential(credential, targetDescriptor) { const { passwordInput, usernameInput } = chooseFillTargets(targetDescriptor); if (usernameInput && credential.username) { usernameInput.focus(); setInputValue(usernameInput, credential.username); dispatchFillEvents(usernameInput); } if (passwordInput && credential.password) { passwordInput.focus(); setInputValue(passwordInput, credential.password); dispatchFillEvents(passwordInput); } if (!usernameInput && !passwordInput) { return { ok: false, error: "No fillable username or password fields were found." }; } return { ok: true }; } function domainLabel(rawURL) { try { return new URL(rawURL).host || ""; } catch (_error) { return ""; } } function inlineMatchSummary(match) { const parts = []; if (match.username) { parts.push(match.username); } if (match.url) { const host = domainLabel(match.url); if (host) { parts.push(host); } } if (Array.isArray(match.path) && match.path.length !== 0) { parts.push(match.path.join(" / ")); } return parts.join(" ยท ") || "No username"; } function shouldShowInlineOverlay(state, hasTarget, suppressed) { if (suppressed || !hasTarget) { return false; } return Boolean( state?.pageHasLoginForm && ( state?.pendingFill || (state?.configured && state?.success && !state?.status?.locked && Array.isArray(state?.matches) && state.matches.length > 0) ) ); } const contentTestExports = { normalizeRole, describeFieldRole, buildFieldDescriptor, resolveFieldDescriptor, chooseFillTargets, inlineMatchSummary, domainLabel, shouldShowInlineOverlay }; if (isNodeTestEnv) { module.exports = contentTestExports; } else { let pageState = { configured: true, success: true, matches: [], pageHasLoginForm: false, pendingFill: false, error: "", focusTarget: null }; let chooserOpen = false; let inlineSuppressed = false; let refreshTimer = null; let lastReportedSignature = ""; let lastReportedTarget = ""; const root = document.createElement("div"); root.id = "keepassgo-inline-root"; root.setAttribute("aria-live", "polite"); const shadow = root.attachShadow({ mode: "open" }); shadow.innerHTML = `