Chrome Extension (Manifest V3) that intercepts PDF navigations and opens a custom viewer where you can move through the document sentence-by-sentence with Tab / Shift+Tab.
npm install
npm run build dist/ will contain everything Chrome needs: index.html, the bundled
JS, manifest.json, background.js, and pdf.worker.min.mjs.
- Go to
chrome://extensions. - Enable Developer mode (top-right toggle).
- Click Load unpacked and select the
dist/folder. - For local
file://PDFs: click Details on the extension card and turn on "Allow access to file URLs." This is a manual, per-user Chrome security toggle — no manifest permission can grant it automatically.
Once loaded, open any PDF link (or a local file via
file:///path/to/doc.pdf) and it will redirect straight into the custom
viewer.
Listens on chrome.webNavigation.onBeforeNavigate for the tab's top-level
frame (frameId === 0, so PDFs embedded in an <iframe> inside a normal
page are left alone). If the URL's path ends in .pdf (checked
case-insensitively with a regex, since Chrome's own pathSuffix URL filter
is case-sensitive), it redirects the tab to:
chrome-extension://<id>/index.html?file=<encoded-pdf-url>&viewerMarker=1
Two loop guards prevent infinite redirects: skip if the URL already targets
the extension origin, or if it already contains viewerMarker=1.
Reads the file query param, loads the PDF offline with pdfjs-dist and a
locally bundled worker (/pdf.worker.min.mjs), extracts text page-by-page,
then splits it into sentences with /(?<=[.!?])\s+/. Scanned/image-only
PDFs with no text layer show a clear error instead of a blank screen.
Once parsing completes, Tab advances to the next sentence and Shift+Tab goes back. Both wrap at the boundaries. The active sentence is highlighted and scrolled into view automatically.
- Local PDFs require "Allow access to file URLs" in extension Details — this is not a bug.
- Sentence splitting is a heuristic; abbreviations, decimals, and ellipses may be split incorrectly. Good enough for navigation, not a perfect tokenizer.
npm run dev # Vite dev server (copies worker via predev)
npm run preview # Preview the production build