Describe the bug
EPUBNavigatorViewController's debounced updateCurrentLocation runs whenever a spread reports a change, including while the application is inactive. While backgrounded, iOS renders the app switcher snapshots (running appearance/layout passes for both color schemes) and WebKit may reload or re-lay out the web views — so a location computed in that window can reflect a transient scroll position the user never saw. It is adopted into currentLocation and delivered via navigator(_:locationDidChange:), misleading any observer persisting the reading progression.
This is a follow-up to #839 (closed with a corrected analysis: the drift there was app-side, but the instrumentation showed inactive-window emissions being adopted) and adjacent to the restore family #645 / #321 / #309.
Expectation: locations are only adopted from a layout the user can actually see, the same contract needsReloadSpreadsOnActive already applies to spread reloads.
How to reproduce?
- Open a paginated reflowable publication in the Test App and add a log in the delegate's
locationDidChange.
- Background the app (with the system appearance such that the both-appearance snapshot pass triggers a layout/appearance change, or force a WebKit reload via a memory warning while backgrounded).
- Foreground the app.
- Observe
locationDidChange emissions during/after the inactive window carrying a position that was never on screen.
Readium version
develop (post-3.10.0, at aa00f91)
OS version
iOS 26.3 (device and Simulator)
Testing device
iPhone (physical) + iPhone 17 Pro (Simulator)
Proposal
Gate the debounced updateCurrentLocation on the existing isActive flag, symmetric to needsReloadSpreadsOnActive. Because it goes through execute(when:), the pending update is deferred, not dropped — it runs once against the settled layout after didBecomeActive.
Secondary (docs-only): document the page-start bias of the firstVisibleElementLocator() + go(to:) capture-and-restore idiom — restoring lands on the page where the element begins, one page back whenever the visible page starts mid-element.
Reference implementation (draft on my fork, happy to open it here once the approach is agreed): raphi011#6 — one-line predicate change + protocol doc + CHANGELOG; full package suite green.
Describe the bug
EPUBNavigatorViewController's debouncedupdateCurrentLocationruns whenever a spread reports a change, including while the application is inactive. While backgrounded, iOS renders the app switcher snapshots (running appearance/layout passes for both color schemes) and WebKit may reload or re-lay out the web views — so a location computed in that window can reflect a transient scroll position the user never saw. It is adopted intocurrentLocationand delivered vianavigator(_:locationDidChange:), misleading any observer persisting the reading progression.This is a follow-up to #839 (closed with a corrected analysis: the drift there was app-side, but the instrumentation showed inactive-window emissions being adopted) and adjacent to the restore family #645 / #321 / #309.
Expectation: locations are only adopted from a layout the user can actually see, the same contract
needsReloadSpreadsOnActivealready applies to spread reloads.How to reproduce?
locationDidChange.locationDidChangeemissions during/after the inactive window carrying a position that was never on screen.Readium version
develop(post-3.10.0, at aa00f91)OS version
iOS 26.3 (device and Simulator)
Testing device
iPhone (physical) + iPhone 17 Pro (Simulator)
Proposal
Gate the debounced
updateCurrentLocationon the existingisActiveflag, symmetric toneedsReloadSpreadsOnActive. Because it goes throughexecute(when:), the pending update is deferred, not dropped — it runs once against the settled layout afterdidBecomeActive.Secondary (docs-only): document the page-start bias of the
firstVisibleElementLocator()+go(to:)capture-and-restore idiom — restoring lands on the page where the element begins, one page back whenever the visible page starts mid-element.Reference implementation (draft on my fork, happy to open it here once the approach is agreed): raphi011#6 — one-line predicate change + protocol doc + CHANGELOG; full package suite green.