fix(scroll): guard against re-locking iOS Safari scroll while locked#642
fix(scroll): guard against re-locking iOS Safari scroll while locked#642blvdmitry wants to merge 1 commit into
Conversation
size-limit report 📦
|
On iOS the document lock is applied to document.body (position: fixed). The 'already locked' check in lockScroll inspects documentElement, so a nested lock re-entered lockSafariScroll, where window.scrollX/Y now read 0 because the body is already fixed. That overwrote the cached original top/left/position with the fixed values, so releasing restored the body to position: fixed permanently (page stuck). Bail out with a no-op unlock when the body is already fixed. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01We9NqptZjfbvXRL4hE1xri
27c3491 to
b3994ec
Compare
|
Closing this as redundant after the rest of the scroll-lock cluster. The critical symptom this PR targeted — a nested iOS document lock overwriting the cached body originals with the already-fixed values, leaving the page permanently unscrollable — is now prevented by #641. The remaining concern (a second document lock re-entering That guard is also fragile — it would misfire if anything else set the body to Generated by Claude Code |
Problem
On iOS the document scroll lock is applied to
document.body(position: fixed), but the "already locked" guard inlockScrollchecksdocument.documentElement.style.overflow. So on iOS a nested lock (e.g. a modal opening a dropdown) is not recognised as already-locked and re-enterslockSafariScroll. At that point:window.scrollX/scrollYread0(the body is already fixed),styleCache.set(document.body, …)overwrites the cached originaltop/left/positionwith the already-fixed values.When the outer lock is released it restores the body to
position: fixed, leaving the page permanently unscrollable until reload.Fix
Bail out with a no-op unlock when the body is already fixed, so the second lock doesn't re-capture the zeroed scroll position or clobber the cache:
const lockSafariScroll = () => { + // Already locked (e.g. a nested overlay): the body is fixed and window.scrollX/Y + // now read 0, so re-capturing here would overwrite the saved originals with the + // already-fixed values and lose the real scroll position. Return a no-op unlock. + if (document.body.style.position === "fixed") { + return () => {}; + } + const viewport = window.visualViewport;Verification
tsc --noEmit(utilities) → 0 errors.oxlint→ clean.How to test
position: fixed) and scroll position is lost. After: scroll is restored correctly to the original position.Found during a full package bug review. Complementary to the lock ref-counting PR (which prevents the double-entry at the
lockScrolllevel); this adds defense-in-depth insidelockSafariScrollitself.Generated by Claude Code