Phenomenon
In Chrome on Android, input events may fire when a contenteditable element gains or loses focus, even without any actual content changes. This behavior can lead to unintended side effects in applications that rely on input events to detect content modifications.
Reproduction Steps
- Open Chrome browser on an Android device.
- Create a
contenteditableelement. - Add an event listener for
inputevents that logs to console. - Tap on the
contenteditableelement to focus it. - Observe the console - an
inputevent may fire even though no content was changed. - Tap outside the element to blur it.
- Observe the console - an
inputevent may fire again.
Observed Behavior
- Input Event on Focus: An
inputevent fires when the element gains focus, even though no content was changed. - Input Event on Blur: An
inputevent may fire when the element loses focus, even though no content was changed. - False Positive Events: These events are false positives - they indicate content changes that didnโt actually occur.
- Android-Specific: This issue is specific to Chrome on Android.
Expected Behavior
inputevents should only fire when content actually changes.- Focus and blur events should not trigger
inputevents. - Applications should be able to rely on
inputevents to detect actual content modifications.
Impact
- False Positive Events: Input events fire without actual content changes.
- Unintended Side Effects: Applications may trigger save operations, validation, or other actions unnecessarily.
- Performance Issues: Unnecessary processing triggered by false input events.
- User Experience: Applications may behave unexpectedly.
Browser Comparison
- Chrome Android: This issue occurs.
- Chrome Desktop: Not affected.
- Firefox Android: Not affected.
- Safari iOS: Not affected.
Notes and Possible Workarounds
Filter False Input Events
const editor = document.querySelector('[contenteditable]');
let lastContent = editor.innerHTML;
let isFocusing = false;
let isBlurring = false;
editor.addEventListener('focus', () => {
isFocusing = true;
lastContent = editor.innerHTML;
setTimeout(() => {
isFocusing = false;
}, 100);
});
editor.addEventListener('blur', () => {
isBlurring = true;
setTimeout(() => {
isBlurring = false;
}, 100);
});
editor.addEventListener('input', (e) => {
// Ignore input events during focus/blur
if (isFocusing || isBlurring) {
return;
}
// Check if content actually changed
const currentContent = editor.innerHTML;
if (currentContent === lastContent) {
return;
}
lastContent = currentContent;
handleContentChange(currentContent);
});
Use MutationObserver Instead
const editor = document.querySelector('[contenteditable]');
let isUserInput = false;
editor.addEventListener('beforeinput', () => {
isUserInput = true;
});
editor.addEventListener('input', () => {
isUserInput = false;
});
const observer = new MutationObserver((mutations) => {
if (isUserInput) {
handleContentChange(editor.innerHTML);
}
});
observer.observe(editor, {
childList: true,
subtree: true,
characterData: true
});
References
- Chromium Bug: Input events on focus/blur
- Chrome Android specific behavior with input events