Skip to content

Commit 86e0743

Browse files
Add autofill detection techniques comparison report
1 parent b7a86d2 commit 86e0743

1 file changed

Lines changed: 313 additions & 0 deletions

File tree

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
# Autofill Detection Techniques Comparison
2+
3+
This document compares different techniques for detecting browser autofill in web forms, specifically within the context of the @lambdacurry/forms library.
4+
5+
## Overview
6+
7+
Browser autofill is a convenient feature that helps users fill out forms quickly, but it can be challenging for developers to detect when it happens and style the autofilled fields appropriately. This document explores several techniques for detecting autofill and compares their effectiveness across different browsers.
8+
9+
## Techniques Compared
10+
11+
1. **Value Comparison with useRef**
12+
- Monitors input values and detects when they change without user interaction
13+
- Uses useRef to track user interaction state
14+
15+
2. **CSS Animation Detection**
16+
- Uses CSS animations that trigger only when inputs are autofilled
17+
- Listens for the animationstart event to detect when these animations run
18+
19+
3. **Form State Monitoring**
20+
- Subscribes to form state changes using React Hook Form's watch API
21+
- Detects when field values change without user interaction
22+
23+
4. **Controlled Inputs with useController**
24+
- Uses controlled inputs with React Hook Form's useController
25+
- Tracks user interactions and detects when values change without user interaction
26+
27+
## Comparison Matrix
28+
29+
| Technique | Chrome | Firefox | Safari | Edge | Implementation Complexity | Performance Impact | Reliability |
30+
|-----------|--------|---------|--------|------|--------------------------|-------------------|------------|
31+
| Value Comparison | ✅ Good | ✅ Good | ✅ Good | ✅ Good | Medium | Low | Medium |
32+
| CSS Animation | ✅ Excellent | ⚠️ Limited | ⚠️ Limited | ✅ Good | Low | Very Low | High in Chrome |
33+
| Form State Monitoring | ✅ Good | ✅ Good | ✅ Good | ✅ Good | Medium | Low | Medium |
34+
| Controlled Inputs | ✅ Excellent | ✅ Good | ✅ Good | ✅ Good | High | Medium | High |
35+
36+
## Detailed Analysis
37+
38+
### 1. Value Comparison with useRef
39+
40+
**Implementation:**
41+
```tsx
42+
const useAutofillDetection = (inputRef, value) => {
43+
const [isAutofilled, setIsAutofilled] = useState(false);
44+
const previousValueRef = useRef(value);
45+
const userInteractionRef = useRef(false);
46+
47+
// Track user interactions
48+
useEffect(() => {
49+
const element = inputRef.current;
50+
if (!element) return;
51+
52+
const handleUserInteraction = () => {
53+
userInteractionRef.current = true;
54+
setTimeout(() => {
55+
userInteractionRef.current = false;
56+
}, 50);
57+
};
58+
59+
element.addEventListener('keydown', handleUserInteraction);
60+
element.addEventListener('input', handleUserInteraction);
61+
// ... more event listeners
62+
63+
return () => {
64+
// ... cleanup
65+
};
66+
}, [inputRef]);
67+
68+
// Detect value changes that weren't triggered by user interaction
69+
useEffect(() => {
70+
if (previousValueRef.current === value) return;
71+
72+
if (value && value !== previousValueRef.current && !userInteractionRef.current) {
73+
setIsAutofilled(true);
74+
}
75+
76+
previousValueRef.current = value;
77+
}, [value]);
78+
79+
return { isAutofilled, resetAutofilled: () => setIsAutofilled(false) };
80+
};
81+
```
82+
83+
**Pros:**
84+
- Works across all major browsers
85+
- Relatively simple implementation
86+
- Low performance impact
87+
88+
**Cons:**
89+
- May have false positives if other code changes the input value
90+
- Requires tracking user interaction with multiple event listeners
91+
- Timing can be tricky (the 50ms timeout is a heuristic)
92+
93+
### 2. CSS Animation Detection
94+
95+
**Implementation:**
96+
```tsx
97+
const useAutofillAnimationDetection = (inputRef) => {
98+
const [isAutofilled, setIsAutofilled] = useState(false);
99+
100+
useEffect(() => {
101+
const element = inputRef.current;
102+
if (!element) return;
103+
104+
const handleAnimation = (event) => {
105+
if (event.animationName === 'onAutoFillStart') {
106+
setIsAutofilled(true);
107+
} else if (event.animationName === 'onAutoFillCancel') {
108+
setIsAutofilled(false);
109+
}
110+
};
111+
112+
element.addEventListener('animationstart', handleAnimation);
113+
114+
// Add CSS for animation detection
115+
const style = document.createElement('style');
116+
style.textContent = `
117+
@keyframes onAutoFillStart { from {} to {} }
118+
@keyframes onAutoFillCancel { from {} to {} }
119+
120+
input:-webkit-autofill {
121+
animation-name: onAutoFillStart;
122+
animation-fill-mode: both;
123+
}
124+
125+
input:not(:-webkit-autofill) {
126+
animation-name: onAutoFillCancel;
127+
animation-fill-mode: both;
128+
}
129+
`;
130+
document.head.appendChild(style);
131+
132+
return () => {
133+
element.removeEventListener('animationstart', handleAnimation);
134+
document.head.removeChild(style);
135+
};
136+
}, [inputRef]);
137+
138+
return { isAutofilled, resetAutofilled: () => setIsAutofilled(false) };
139+
};
140+
```
141+
142+
**Pros:**
143+
- Excellent detection in Chrome and other WebKit browsers
144+
- Very low performance impact
145+
- Immediate detection when autofill happens
146+
- Can detect both when autofill is applied and when it's removed
147+
148+
**Cons:**
149+
- Limited support in Firefox and Safari
150+
- Relies on browser-specific pseudo-classes
151+
- Requires injecting CSS into the document
152+
153+
### 3. Form State Monitoring
154+
155+
**Implementation:**
156+
```tsx
157+
const useAutofillFormState = (form, name) => {
158+
const [isAutofilled, setIsAutofilled] = useState(false);
159+
const userInteractionRef = useRef(false);
160+
const previousValueRef = useRef(form.getValues(name));
161+
const touchedRef = useRef(false);
162+
163+
// Subscribe to form state changes
164+
useEffect(() => {
165+
const subscription = form.watch((values, { name: changedField, type }) => {
166+
if (changedField !== name) return;
167+
168+
const currentValue = values[name];
169+
170+
if (currentValue === previousValueRef.current) return;
171+
172+
if (
173+
!userInteractionRef.current &&
174+
currentValue &&
175+
type !== 'change' &&
176+
type !== 'blur' &&
177+
!touchedRef.current
178+
) {
179+
setIsAutofilled(true);
180+
}
181+
182+
previousValueRef.current = currentValue;
183+
});
184+
185+
return () => subscription.unsubscribe();
186+
}, [form, name]);
187+
188+
// Track user interactions
189+
useEffect(() => {
190+
const handleUserInteraction = () => {
191+
userInteractionRef.current = true;
192+
touchedRef.current = true;
193+
194+
setTimeout(() => {
195+
userInteractionRef.current = false;
196+
}, 50);
197+
};
198+
199+
document.addEventListener('keydown', handleUserInteraction);
200+
// ... more event listeners
201+
202+
return () => {
203+
// ... cleanup
204+
};
205+
}, []);
206+
207+
return { isAutofilled, resetAutofilled: () => setIsAutofilled(false) };
208+
};
209+
```
210+
211+
**Pros:**
212+
- Works with React Hook Form's form state
213+
- Can detect autofill across multiple fields at once
214+
- Works in all major browsers
215+
216+
**Cons:**
217+
- Relies on React Hook Form's watch API, which can impact performance
218+
- More complex implementation
219+
- May have timing issues with the user interaction detection
220+
221+
### 4. Controlled Inputs with useController
222+
223+
**Implementation:**
224+
```tsx
225+
const useAutofillControlledInput = (controller) => {
226+
const [isAutofilled, setIsAutofilled] = useState(false);
227+
const [userInteracted, setUserInteracted] = useState(false);
228+
229+
// Monitor value changes from the controller
230+
useEffect(() => {
231+
const { field } = controller;
232+
233+
if (field.value && !userInteracted) {
234+
setIsAutofilled(true);
235+
}
236+
}, [controller.field.value, userInteracted]);
237+
238+
// Create handlers to track user interaction
239+
const handleUserInteraction = () => {
240+
setUserInteracted(true);
241+
242+
if (isAutofilled) {
243+
setIsAutofilled(false);
244+
}
245+
};
246+
247+
// Enhanced onChange handler
248+
const onChange = (e) => {
249+
handleUserInteraction();
250+
controller.field.onChange(e);
251+
};
252+
253+
// ... more handlers
254+
255+
return {
256+
isAutofilled,
257+
resetAutofilled: () => {
258+
setIsAutofilled(false);
259+
setUserInteracted(true);
260+
},
261+
fieldProps: {
262+
...controller.field,
263+
onChange,
264+
onBlur: /* ... */,
265+
onFocus: handleUserInteraction,
266+
// ... more props
267+
},
268+
};
269+
};
270+
```
271+
272+
**Pros:**
273+
- Most reliable detection across all browsers
274+
- Integrates well with React Hook Form's validation
275+
- Provides enhanced field props for easy integration
276+
277+
**Cons:**
278+
- Highest implementation complexity
279+
- Requires using controlled inputs, which may impact performance
280+
- More code to maintain
281+
282+
## Recommendations
283+
284+
Based on the comparison, here are our recommendations:
285+
286+
### Best Overall Solution
287+
288+
**Controlled Inputs with useController** provides the most reliable detection across all browsers and integrates well with React Hook Form. This approach is recommended for forms where accurate autofill detection is critical.
289+
290+
### Best Performance Solution
291+
292+
**CSS Animation Detection** has the lowest performance impact and works excellently in Chrome, which is the most common browser. This approach is recommended for high-performance applications where Chrome support is prioritized.
293+
294+
### Best Compatibility Solution
295+
296+
**Value Comparison with useRef** offers a good balance of compatibility, performance, and implementation complexity. This approach is recommended for applications that need to support a wide range of browsers.
297+
298+
### Implementation Strategy
299+
300+
We recommend implementing a combination of techniques:
301+
302+
1. Use **CSS Animation Detection** as the primary method for Chrome and Edge
303+
2. Fall back to **Value Comparison with useRef** for Firefox and Safari
304+
3. Provide **Controlled Inputs with useController** as an opt-in option for forms that require the highest reliability
305+
306+
This strategy provides the best balance of performance, compatibility, and reliability.
307+
308+
## Conclusion
309+
310+
Detecting browser autofill is challenging due to differences in browser implementations. Each technique has its strengths and weaknesses, and the best approach depends on the specific requirements of your application.
311+
312+
For the @lambdacurry/forms library, we recommend implementing a flexible solution that allows developers to choose the technique that best fits their needs, with sensible defaults that work well across all major browsers.
313+

0 commit comments

Comments
 (0)