diff --git a/ui/components/auth/oss/sign-in-form.tsx b/ui/components/auth/oss/sign-in-form.tsx index 7a9f53edfb..4cf6cd9090 100644 --- a/ui/components/auth/oss/sign-in-form.tsx +++ b/ui/components/auth/oss/sign-in-form.tsx @@ -16,6 +16,7 @@ import { Button } from "@/components/shadcn"; import { useToast } from "@/components/shadcn"; import { CustomInput } from "@/components/shadcn/custom"; import { Form } from "@/components/shadcn/form"; +import { stripPasswordManagerHighlight } from "@/lib/password-manager"; import { SignInFormData, signInSchema } from "@/types"; export const SignInForm = ({ @@ -146,6 +147,7 @@ export const SignInForm = ({
+ */ +export const stripPasswordManagerHighlight = ( + container: HTMLElement | null, +): (() => void) | void => { + if (!container) return; + + // removeAttribute on an element without the attribute is a no-op and emits no + // mutation record, so re-firing the observer on our own removal can't loop. + const strip = (element: Element) => + element.removeAttribute(ONEPASSWORD_FILLED_ATTR); + + container.querySelectorAll(`[${ONEPASSWORD_FILLED_ATTR}]`).forEach(strip); + + const observer = new MutationObserver((mutations) => { + for (const { target } of mutations) { + if (target instanceof Element) strip(target); + } + }); + + observer.observe(container, { + subtree: true, + attributes: true, + attributeFilter: [ONEPASSWORD_FILLED_ATTR], + }); + + return () => observer.disconnect(); +}; diff --git a/ui/styles/globals.css b/ui/styles/globals.css index df435a8872..b69db8ebac 100644 --- a/ui/styles/globals.css +++ b/ui/styles/globals.css @@ -476,6 +476,27 @@ } } +/* Neutralize the browser's native autofill highlight (Chromium/WebKit paint a + forced blue/yellow background on :autofill). box-shadow inset masks it; the + long transition stops the native color from flashing before the mask lands. + Kept UNLAYERED + !important: the mask is a box-shadow and collides with + shadcn's focus:ring (also a box-shadow, in @layer utilities) — a layered + rule can't win that, so this sits outside the cascade layers. */ +input:autofill, +input:autofill:hover, +input:autofill:focus, +input:autofill:active, +input:-webkit-autofill, +input:-webkit-autofill:hover, +input:-webkit-autofill:focus, +input:-webkit-autofill:active { + -webkit-text-fill-color: var(--text-neutral-secondary) !important; + -webkit-box-shadow: 0 0 0 1000px var(--bg-input-primary) inset !important; + box-shadow: 0 0 0 1000px var(--bg-input-primary) inset !important; + caret-color: var(--text-neutral-secondary); + transition: background-color 5000s ease-in-out 0s; +} + /* Override vaul's injected user-select: none to allow text selection in drawers */ @media (hover: hover) and (pointer: fine) { [data-vaul-drawer][data-vaul-drawer] {