Skip to content

EPUB: currentLocation can adopt a transient position computed while the app is inactive #841

Description

@raphi011

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?

  1. Open a paginated reflowable publication in the Test App and add a log in the delegate's locationDidChange.
  2. 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).
  3. Foreground the app.
  4. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions