Skip to content

Add continuous scrolling for multi-page documents - #699

Open
aljazceru wants to merge 14 commits into
GrapheneOS:mainfrom
aljazceru:continuous-scroll-upstream
Open

Add continuous scrolling for multi-page documents#699
aljazceru wants to merge 14 commits into
GrapheneOS:mainfrom
aljazceru:continuous-scroll-upstream

Conversation

@aljazceru

@aljazceru aljazceru commented Jul 15, 2026

Copy link
Copy Markdown

Summary

  • render pages in a continuous scrolling container and lazily render the visible window
  • track the most-visible page and keep late-page jumps pending until their wrapper exists
  • coalesce pinch render requests so large documents remain responsive
  • add fit-mode and continuous-scrolling controls

Why

Large documents exposed two races in the continuous renderer. Repeated pinch events could queue enough WebView/JavaScript work to delay state reads, and a late-page jump could run before progressive page setup created the target wrapper. The scroll handler could then overwrite the requested page.

Zoom rendering now keeps one request in flight and coalesces pending updates. Page requests remain sticky until the target is laid out. Rendering and relayout are limited to the visible page window.

Validation

  • npm test -- --reporter=dot — 14 tests passed
  • npx eslint .
  • ./gradlew :app:lintDebug :app:assembleDebug :app:assembleDebugAndroidTest --no-daemon
  • ANDROID_SERIAL=5A011JEBF06589 ./gradlew connectedDebugAndroidTest --no-daemon — 77 tests passed on a Pixel 9a
  • git diff --check

@aljazceru
aljazceru force-pushed the continuous-scroll-upstream branch from 7798537 to e38cae2 Compare July 15, 2026 11:53
@aljazceru
aljazceru marked this pull request as ready for review July 15, 2026 15:54
@rdevshp

rdevshp commented Jul 15, 2026

Copy link
Copy Markdown

I tested your PR using app/src/androidTest/assets/test-multipage.pdf . There are panning issues when zoomed in when the viewer is at page 4 of the test pdf file.

# Conflicts:
#	app/src/androidTest/kotlin/app/grapheneos/pdfviewer/test/PdfViewerMenuStateTest.kt
#	app/src/androidTest/kotlin/app/grapheneos/pdfviewer/util/PdfViewerRobot.kt
#	app/src/androidTest/kotlin/app/grapheneos/pdfviewer/util/PdfViewerTestUtils.kt
#	app/src/main/java/app/grapheneos/pdfviewer/PdfViewer.java
#	app/src/main/java/app/grapheneos/pdfviewer/viewModel/PdfViewModel.kt
#	app/src/main/res/menu/pdf_viewer.xml
#	viewer/js/index.js
@aljazceru

Copy link
Copy Markdown
Author

@rdevshp the issue should be addressed now, could you confirm

@rdevshp

rdevshp commented Jul 22, 2026

Copy link
Copy Markdown

@rdevshp the issue should be addressed now, could you confirm

There are still panning issues when zoomed in at page 4 of test-multipage.pdf. When I swipe left, the viewer would very often suddenly become a blank page.

Also when I swipe left/right, it would go to the next/previous page, but I don't think that is desirable for the continuous scrolling mode.

@rdevshp

rdevshp commented Aug 10, 2026

Copy link
Copy Markdown

@aljazceru Are you still working on this PR?

@aljazceru

Copy link
Copy Markdown
Author

@rdevshp yes sorry, been busy, i have updates i'll push in the next days

# Conflicts:
#	app/src/androidTest/kotlin/app/grapheneos/pdfviewer/test/PdfViewerEdgeToEdgeTest.kt
#	app/src/androidTest/kotlin/app/grapheneos/pdfviewer/util/PdfViewerRobot.kt
#	app/src/main/java/app/grapheneos/pdfviewer/PdfJsChannel.kt
#	app/src/main/java/app/grapheneos/pdfviewer/PdfViewerScreen.kt
#	app/src/main/java/app/grapheneos/pdfviewer/viewModel/PdfViewModel.kt
#	app/src/main/res/values/strings.xml
#	viewer/css/pdf_viewer.css
#	viewer/js/index.js
#	viewer/js/index.test.js
Recovered from the stale .worktrees/upstream-port working tree (never
committed to any branch):
- index.js: expand page-wrapper to canvas width for over-wide (zoomed)
  pages so the whole canvas area is scrollable
- index.test.js: assert wrapper width tracks over-wide canvas
- PdfViewerScreen: disable fling page-jump while in continuous mode
- Robot: performSwipeLeft helper (Direction import)
- MultiPageRenderTest: horizontalFling_continuousModeDoesNotChangePage;
  drive zoom test through the custom-zoom menu UI (300%)
- NavigationTest: run the fling-navigation test in single-page mode,
  since continuous mode now pans instead of jumping pages
Restores the product decision from 'fix panning issues' (3ba62c6):
new documents open in fit-width (mode 2), not fit-page (mode 1).
Caught by PdfViewerPageFitModeTest.fitWidthMode_isDefaultForNewDocument
failing on the Pixel 9a during the manual device test pass.
Zoom changes and other re-renders cleared the visible canvas before the
asynchronous pdf.js render produced the new bitmap, leaving a blank page
for the duration of the render. On slower devices (CI emulator) that
window is long enough to be clearly visible — the exact 'suddenly a
blank page' symptom from the PR review when panning a zoomed page.

Render into an offscreen canvas and swap the finished bitmap into the
visible canvas in one step instead:
- renderPageContent renders into p.renderCanvas and copies it to the
  visible canvas when the render task completes
- rerenderVisible no longer clears near pages; it just invalidates the
  rendered flag so the old frame stays visible until the swap
- clearPage frees the offscreen target along with the visible canvas
- new JS test: the visible canvas must never drop to width 0 while a
  re-render is in flight

Fixes the CI failure in zoomedLastPage_canPanToBothHorizontalEdges
('Canvas was cleared while its zoomed content was visible') and the
blank-page panning report on the upstream PR.
In onRenderPage's zoom path, relayoutAll() resized every page wrapper and
rerenderVisible() ran while the scroll position still described the old
layout. The page under the viewport therefore measured as 'far' in the
new geometry and was cleared, while unrelated pages nearer the stale
offset were rendered instead — the viewport went blank exactly when a
zoomed page re-rendered. On fast devices the follow-up render hides the
gap; on slower ones (CI emulator, and per the upstream review report,
real devices while panning) the blank page is clearly visible.

Move the focus-preserving scrollBy ahead of rerenderVisible so
near/far decisions are made against the re-anchored scroll position.

Reproduced and verified on a local API 36 x86_64 emulator:
zoomedLastPage_canPanToBothHorizontalEdges now passes, full
instrumented suite green.
@aljazceru
aljazceru force-pushed the continuous-scroll-upstream branch from d3b29f6 to f106a2b Compare August 25, 2026 11:25
@aljazceru

Copy link
Copy Markdown
Author

@rdevshp the issue should be addressed now, could you confirm

There are still panning issues when zoomed in at page 4 of test-multipage.pdf. When I swipe left, the viewer would very often suddenly become a blank page.

Also when I swipe left/right, it would go to the next/previous page, but I don't think that is desirable for the continuous scrolling mode.

both issues are addressed with the latest commit @rdevshp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants