diff --git a/browser-extension/src/entrypoints/contentScript/Popup.ts b/browser-extension/src/entrypoints/contentScript/Popup.ts index b8898f03f..c17d28a70 100644 --- a/browser-extension/src/entrypoints/contentScript/Popup.ts +++ b/browser-extension/src/entrypoints/contentScript/Popup.ts @@ -315,27 +315,7 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden }; // Add click listener with capture and prevent removal. - createButton.addEventListener('click', handleCreateClick, { - capture: true, - passive: false - }); - - // Backup click handling using mousedown/mouseup if needed. - let isMouseDown = false; - createButton.addEventListener('mousedown', (e) => { - e.preventDefault(); - e.stopPropagation(); - isMouseDown = true; - }, { capture: true }); - - createButton.addEventListener('mouseup', (e) => { - e.preventDefault(); - e.stopPropagation(); - if (isMouseDown) { - handleCreateClick(e); - } - isMouseDown = false; - }, { capture: true }); + addReliableClickHandler(createButton, handleCreateClick); // Create search input. const searchInput = document.createElement('input'); @@ -358,10 +338,18 @@ export function createAutofillPopup(input: HTMLInputElement, credentials: Creden `; - closeButton.addEventListener('click', async () => { + /** + * Handle close button click + */ + const handleCloseClick = async (e: Event) : Promise => { + e.preventDefault(); + e.stopPropagation(); + e.stopImmediatePropagation(); await disableAutoShowPopup(); removeExistingPopup(rootContainer); - }); + }; + + addReliableClickHandler(closeButton, handleCloseClick); actionContainer.appendChild(searchInput); actionContainer.appendChild(createButton); @@ -1378,3 +1366,35 @@ function getValidServiceUrl(): string { return ''; } } + +/** + * Add click handler with mousedown/mouseup backup for better click reliability in shadow DOM. + * + * Some websites due to their design cause the AliasVault autofill to re-trigger when clicking + * outside of the input field, which causes the AliasVault popup to close before the click event + * is registered. This is a workaround to ensure the click event is always registered. + */ +function addReliableClickHandler(element: HTMLElement, handler: (e: Event) => void): void { + // Add primary click listener with capture and prevent removal + element.addEventListener('click', handler, { + capture: true, + passive: false + }); + + // Backup click handling using mousedown/mouseup if needed + let isMouseDown = false; + element.addEventListener('mousedown', (e) => { + e.preventDefault(); + e.stopPropagation(); + isMouseDown = true; + }, { capture: true }); + + element.addEventListener('mouseup', (e) => { + e.preventDefault(); + e.stopPropagation(); + if (isMouseDown) { + handler(e); + } + isMouseDown = false; + }, { capture: true }); +} diff --git a/browser-extension/src/utils/formDetector/FormDetector.ts b/browser-extension/src/utils/formDetector/FormDetector.ts index 327a6615b..c1d544feb 100644 --- a/browser-extension/src/utils/formDetector/FormDetector.ts +++ b/browser-extension/src/utils/formDetector/FormDetector.ts @@ -46,7 +46,7 @@ export class FormDetector { } return true; } - + // Check for display:none if (style.display === 'none') { // Cache and return false for this element and all its parents @@ -57,7 +57,7 @@ export class FormDetector { } return false; } - + // Check for visibility:hidden if (style.visibility === 'hidden') { // Cache and return false for this element and all its parents @@ -68,7 +68,7 @@ export class FormDetector { } return false; } - + // Check for opacity:0 if (parseFloat(style.opacity) === 0) { // Cache and return false for this element and all its parents @@ -101,7 +101,16 @@ export class FormDetector { * Detect login forms on the page based on the clicked element. */ public containsLoginForm(): boolean { - const formWrapper = this.clickedElement?.closest('form') ?? this.document.body; + let formWrapper = this.clickedElement?.closest('form, [role="dialog"]') as HTMLElement | null; + if (formWrapper?.getAttribute('role') === 'dialog') { + // If we hit a dialog, search for form only within the dialog + formWrapper = formWrapper.querySelector('form') as HTMLElement | null ?? formWrapper; + } + + if (!formWrapper) { + // If no form or dialog found, fallback to document.body + formWrapper = this.document.body as HTMLElement; + } /** * Sanity check: if form contains more than 150 inputs, don't process as this is likely not a login form. diff --git a/docs/misc/dev/browser-extensions.md b/docs/misc/dev/browser-extensions.md index a9fed4285..7369a33a0 100644 --- a/docs/misc/dev/browser-extensions.md +++ b/docs/misc/dev/browser-extensions.md @@ -112,4 +112,5 @@ The following websites have been known to cause issues in the past (but should b | https://vault.bitwarden.com/#/login | Autofill password not detected (input not long enough), manually typing in works | | https://login.microsoftonline.com/ | Password gets reset after autofill | | https://mijn.ing.nl/login/ | Autofill doesn't detect input fields and AliasVault autofill icon placement is off | +| https://github.com/lanedirt/AliasVault/issues | The "New issue -> Blank Issue" title field causes the autofill to trigger because of a parent form (outside of the role=modal div) |