fix(extension): stop the new tab page stealing the address bar's caret - #6606
Draft
tsahimatsliah wants to merge 1 commit into
Draft
fix(extension): stop the new tab page stealing the address bar's caret#6606tsahimatsliah wants to merge 1 commit into
tsahimatsliah wants to merge 1 commit into
Conversation
Clicking Chrome's address bar from the new tab page sometimes left it unable to accept typing: the caret landed there, and a `focus()` call in the page pulled it straight back out. `document.hasFocus()` is false in exactly that state, so the guard refuses programmatic focus while the page does not hold the keyboard. User gestures are unaffected — clicking or tabbing into the page hands focus over before any handler runs. react-modal's scoped focus is the clearest source: it arms on every window blur (which is what clicking the address bar is), pulls focus into its modal element on the next focus event, never removes its capture-phase `focus` listener, and skips teardown when a modal unmounts before its open state commits — leaving the pull armed with no modal on screen. That also explains why opening and closing any modal cleared the symptom. Radix focus scopes and the ad measurement frames have their own variants, so the guard refuses the move rather than chasing each one. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Changes
Fixes a long-standing, roughly-once-a-day bug on the extension's new tab page: you click Chrome's address bar, the caret lands there, and typing does nothing. The page scrolls and clicks normally throughout. Opening any post modal clears it.
That last detail is the tell. The cause is a focus steal: something in the page waits for the window to lose focus — which is exactly what clicking the address bar is — and then calls
focus()on one of its own elements, dragging the caret back into the page. Everything typed afterwards goes into the page, where nothing is listening.The clearest source is react-modal's global focus manager (
react-modal@3.16.3,lib/helpers/focusManager.js):handleBlurarms a module-globalneedToFocusflag on everywindowblur.handleFocusthen pulls focus intomodalElementon the next focus event in the document, with no check that the page actually holds the keyboard.teardownScopedFocusnever removes that listener — it is registered ondocumentin the capture phase and removed without the capture flag, soremoveEventListenerdoesn't match.isOpenstate commits (componentWillUnmountguards onthis.state.isOpen), leavingmodalElementpointing at a node no longer on screen with the pull still armed.That is also why opening and closing a card's modal fixes it:
afterClosefinally reachesteardownScopedFocus(), which nullsmodalElementand disarms the whole thing.The fix
preventFocusSteal()runs at the top of the new tab entry, before React and before any dependency registers its own focus bookkeeping. It drops programmaticfocus()whiledocument.hasFocus()is false — true in exactly the moments the keyboard belongs to browser chrome (address bar, devtools, another window) rather than to us.User gestures are unaffected: clicking or tabbing into the page hands focus over before any handler runs, so
hasFocus()is already true by then.Why the class and not just react-modal
Two reasons worth a reviewer's attention:
FocusScope's MutationObserver refocuses its container whendocument.activeElement === document.body, which is the state while the address bar has focus) and the embedded ad measurement frames have their own variants of the same move.Refusing the move once, in one place, covers all of them. The tradeoff is a monkey-patch on
HTMLElement.prototype.focus, scoped to the new tab entry only — not the webapp, not the companion.Known gap: if the culprit turns out to be inside the ad measurement iframe, that is a separate origin and this guard cannot reach it. The tell would be the bug recurring with nothing logged. The same guard would then need to go into the measurement frame page.
Diagnostics
Outside production the guard logs a warning with a stack trace every time it blocks a steal, so the culprit names itself if it ever fires.
Events
No new tracking events.
Experiment
No new experiments.
Manual Testing
Extension only — the webapp and companion entries are untouched.
Verified: 3 new unit tests, full extension suite passes (55 tests, 7 suites), lint clean, production build succeeds. The one pre-existing
tscerror inShortcutLinks.spec.tsxis present onmainand unrelated.Note
Opened as a draft. The only real verification for a once-a-day bug is running a dev build for a few days and confirming it does not recur — and that the guard's dev warning either names a culprit or stays quiet.
🤖 Generated with Claude Code
Preview domain
https://newtab-focus-steal-dailydotdev.preview.app.daily.dev