PDF.js Document Viewer in Chrome Extensions: Complete Implementation Guide
PDF.js is Mozilla’s JavaScript library for rendering PDF documents directly in web browsers, and it has become the gold standard for embedding PDF viewing capabilities into Chrome extensions. Whether you’re building a document reader extension, a PDF annotation tool, or integrating PDF support into your existing Chrome extension, PDF.js provides a robust, feature-rich solution that works seamlessly within the Chrome extension environment.
This comprehensive guide will walk you through everything you need to know to integrate PDF.js into your Chrome extension. We’ll cover the fundamentals of PDF.js, walk through the implementation process step by step, explore advanced features, and provide practical code examples that you can adapt for your own projects.
What is PDF.js and Why Use It in Chrome Extensions?
PDF.js is an open-source JavaScript library developed by Mozilla that renders PDF files using HTML5 Canvas and JavaScript. Unlike traditional PDF viewers that rely on native plugins, PDF.js runs entirely in JavaScript, making it perfect for Chrome extensions where security and cross-platform compatibility are paramount.
Key Benefits of PDF.js for Chrome Extensions
There are several compelling reasons to choose PDF.js for your Chrome extension’s PDF viewing needs:
Open Source and Free: PDF.js is released under the Apache License, meaning you can use it freely in both personal and commercial Chrome extensions without paying any licensing fees. The library is actively maintained by Mozilla and has a vibrant community contributing to its development.
No Native Dependencies: Since PDF.js runs entirely in JavaScript, your Chrome extension doesn’t need to rely on any native code or plugins. This means your extension will work consistently across all platforms where Chrome runs, including Windows, macOS, Linux, and Chrome OS.
Lightweight and Fast: The library is surprisingly lightweight, with the core rendering engine coming in at around 500KB. Despite its small size, it supports the vast majority of PDF features, including text rendering, vector graphics, images, and even complex features like forms and annotations.
Highly Customizable: PDF.js provides extensive APIs that allow you to customize virtually every aspect of the viewing experience. You can modify the UI, add custom controls, implement zoom functionality, and even extract text for search or processing.
Security: Running entirely in JavaScript within Chrome’s sandboxed environment, PDF.js provides excellent security for both you and your users. There’s no risk of vulnerabilities that can occur with native PDF rendering plugins.
Setting Up PDF.js in Your Chrome Extension Project
Before you can start implementing PDF viewing functionality, you need to set up PDF.js within your Chrome extension project. This section will guide you through the installation and configuration process.
Installing PDF.js
The easiest way to add PDF.js to your Chrome extension is through npm. If you’re using a build tool like webpack or Rollup in your extension project, you can install PDF.js as a dependency:
npm install pdfjs-dist
If you prefer not to use a build tool, you can download the PDF.js library directly from the official GitHub repository or use a CDN. For production Chrome extensions, it’s generally recommended to bundle PDF.js with your extension rather than relying on external CDN links.
Project Structure
For a well-organized Chrome extension with PDF.js integration, consider the following project structure:
my-pdf-extension/
├── manifest.json
├── background.js
├── content.js
├── popup/
│ ├── popup.html
│ └── popup.js
├── pdf-viewer/
│ ├── viewer.html
│ ├── viewer.js
│ └── viewer.css
└── lib/
└── pdfjs/
└── build/
└── pdf.worker.js
The key component is the pdf-viewer directory, which will contain your PDF rendering logic and UI. The lib directory stores the PDF.js library files.
Creating the Manifest Configuration
Your Chrome extension’s manifest.json file needs to be properly configured to work with PDF.js and handle PDF files correctly. Here’s an example manifest configuration:
{
"manifest_version": 3,
"name": "PDF Reader Extension",
"version": "1.0",
"description": "A powerful PDF viewer for Chrome extensions using PDF.js",
"permissions": [
"activeTab",
"downloads",
"storage"
],
"host_permissions": [
"*://*.pdf/*"
],
"action": {
"default_popup": "popup/popup.html",
"default_icon": "icons/icon.png"
},
"web_accessible_resources": [
{
"resources": ["pdf-viewer/*", "lib/*"],
"matches": ["<all_urls>"]
}
]
}
The host_permissions field is particularly important if your extension needs to access PDF files from any URL. The web_accessible_resources section allows your extension’s pages to load the PDF.js library files.
Implementing a Basic PDF Viewer Page
Now let’s implement a basic PDF viewer page using PDF.js. This will be the core functionality of your Chrome extension.
The HTML Structure
Create a file called viewer.html in your pdf-viewer directory:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>PDF Viewer</title>
<link rel="stylesheet" href="viewer.css">
</head>
<body>
<div class="pdf-container">
<div class="toolbar">
<button id="prev-page" class="toolbar-btn">Previous</button>
<span class="page-info">
Page <span id="current-page">1</span> of <span id="total-pages">-</span>
</span>
<button id="next-page" class="toolbar-btn">Next</button>
<input type="number" id="page-input" class="page-input" min="1" value="1">
<button id="zoom-out" class="toolbar-btn">-</button>
<span id="zoom-level">100%</span>
<button id="zoom-in" class="toolbar-btn">+</button>
</div>
<canvas id="pdf-canvas"></canvas>
</div>
<script src="viewer.js"></script>
</body>
</html>
The JavaScript Implementation
Now let’s create the core JavaScript file that handles PDF rendering:
// viewer.js
import * as pdfjsLib from '../lib/pdfjs/build/pdf.mjs';
// Set the worker source
pdfjsLib.GlobalWorkerOptions.workerSrc = '../lib/pdfjs/build/pdf.worker.mjs';
let pdfDoc = null;
let pageNum = 1;
let pageRendering = false;
let pageNumPending = null;
let scale = 1.0;
const canvas = document.getElementById('pdf-canvas');
const ctx = canvas.getContext('2d');
// Render a specific page
async function renderPage(num) {
pageRendering = true;
try {
const page = await pdfDoc.getPage(num);
const viewport = page.getViewport({ scale: scale });
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: ctx,
viewport: viewport
};
await page.render(renderContext).promise;
pageRendering = false;
if (pageNumPending !== null) {
renderPage(pageNumPending);
pageNumPending = null;
}
} catch (error) {
console.error('Error rendering page:', error);
pageRendering = false;
}
// Update page counters
document.getElementById('current-page').textContent = num;
document.getElementById('page-input').value = num;
}
// Queue rendering if already rendering
function queueRenderPage(num) {
if (pageRendering) {
pageNumPending = num;
} else {
renderPage(num);
}
}
// Previous page
function onPrevPage() {
if (pageNum <= 1) return;
pageNum--;
queueRenderPage(pageNum);
}
// Next page
function onNextPage() {
if (pageNum >= pdfDoc.numPages) return;
pageNum++;
queueRenderPage(pageNum);
}
// Zoom functions
function onZoomIn() {
scale += 0.1;
renderPage(pageNum);
document.getElementById('zoom-level').textContent = Math.round(scale * 100) + '%';
}
function onZoomOut() {
if (scale <= 0.1) return;
scale -= 0.1;
renderPage(pageNum);
document.getElementById('zoom-level').textContent = Math.round(scale * 100) + '%';
}
// Load PDF from URL or data
async function loadPDF(source) {
try {
const loadingTask = pdfjsLib.getDocument(source);
pdfDoc = await loadingTask.promise;
document.getElementById('total-pages').textContent = pdfDoc.numPages;
renderPage(pageNum);
} catch (error) {
console.error('Error loading PDF:', error);
}
}
// Event listeners
document.getElementById('prev-page').addEventListener('click', onPrevPage);
document.getElementById('next-page').addEventListener('click', onNextPage);
document.getElementById('zoom-in').addEventListener('click', onZoomIn);
document.getElementById('zoom-out').addEventListener('click', onZoomOut);
document.getElementById('page-input').addEventListener('change', function(e) {
const num = parseInt(e.target.value);
if (num >= 1 && num <= pdfDoc.numPages) {
pageNum = num;
renderPage(pageNum);
}
});
// Keyboard navigation
document.addEventListener('keydown', function(e) {
if (e.key === 'ArrowLeft') onPrevPage();
if (e.key === 'ArrowRight') onNextPage();
});
// Export for use in other scripts
window.PDFViewer = {
loadPDF: loadPDF
};
Basic CSS Styling
Add some basic styling to make your PDF viewer look professional:
/* viewer.css */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #525252;
}
.pdf-container {
display: flex;
flex-direction: column;
height: 100vh;
}
.toolbar {
display: flex;
align-items: center;
gap: 10px;
padding: 10px 15px;
background: #fff;
border-bottom: 1px solid #ddd;
flex-wrap: wrap;
}
.toolbar-btn {
padding: 6px 12px;
border: 1px solid #ddd;
background: #f5f5f5;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.toolbar-btn:hover {
background: #e8e8e8;
}
.page-info {
font-size: 14px;
color: #333;
}
.page-input {
width: 50px;
padding: 4px;
text-align: center;
border: 1px solid #ddd;
border-radius: 4px;
}
#zoom-level {
min-width: 50px;
text-align: center;
font-size: 14px;
}
#pdf-canvas {
display: block;
margin: 0 auto;
background: #525252;
}
Integrating with Chrome Extension APIs
To make your PDF viewer functional within a Chrome extension, you need to integrate it with Chrome’s extension APIs. This section covers common integration patterns.
Opening PDFs from Downloads
One common use case is to open PDFs that the user has downloaded. You can use the downloads API to access recently downloaded files:
// background.js
chrome.downloads.onChanged.addListener(async (downloadItem) => {
if (downloadItem.state === 'complete' &&
downloadItem.mime === 'application/pdf') {
// Get the file path
const fileUrl = `file://${downloadItem.filename}`;
// Open the PDF viewer tab
chrome.tabs.create({
url: 'pdf-viewer/viewer.html?file=' + encodeURIComponent(fileUrl)
});
}
});
Handling PDF URLs from the Current Tab
You can also add functionality to view PDFs that are linked in the current webpage:
// content.js
// Find all PDF links on the page
document.addEventListener('contextmenu', function(e) {
const link = e.target.closest('a[href$=".pdf"]');
if (link) {
e.preventDefault();
// Store the PDF URL for the extension to access
chrome.storage.local.set({ currentPdfUrl: link.href });
// Show the extension action
chrome.runtime.sendMessage({
action: 'showPdfViewer',
url: link.href
});
}
});
Reading PDF Files from Extension Storage
For smaller PDF files or those embedded within your extension, you can bundle them and read them directly:
// Reading a bundled PDF
async function loadBundledPDF() {
const response = await fetch('assets/sample.pdf');
const arrayBuffer = await response.arrayBuffer();
// Load into PDF.js
const loadingTask = pdfjsLib.getDocument({ data: arrayBuffer });
const pdf = await loadingTask.promise;
return pdf;
}
Advanced PDF.js Features for Chrome Extensions
Once you have the basic PDF viewer working, you can explore advanced features to create a more powerful document reader extension.
Text Extraction and Search
PDF.js can extract text from PDF documents, enabling search functionality within your extension:
async function extractTextFromPage(pageNum) {
const page = await pdfDoc.getPage(pageNum);
const textContent = await page.getTextContent();
const textItems = textContent.items.map(item => item.str);
return textItems.join(' ');
}
async function searchPDF(query) {
const results = [];
for (let i = 1; i <= pdfDoc.numPages; i++) {
const text = await extractTextFromPage(i);
const regex = new RegExp(query, 'gi');
const matches = text.match(regex);
if (matches) {
results.push({
page: i,
count: matches.length
});
}
}
return results;
}
Rendering Multiple Pages
For better performance with large documents, you can implement thumbnail previews or render multiple pages simultaneously:
async function renderThumbnail(pageNum, canvas, size) {
const page = await pdfDoc.getPage(pageNum);
const viewport = page.getViewport({ scale: size / page.getViewport({ scale: 1 }).width });
canvas.width = viewport.width;
canvas.height = viewport.height;
const renderContext = {
canvasContext: canvas.getContext('2d'),
viewport: viewport
};
await page.render(renderContext).promise;
}
Working with PDF Annotations
PDF.js has support for viewing and creating annotations. Here’s how you can work with them:
async function getPageAnnotations(pageNum) {
const page = await pdfDoc.getPage(pageNum);
const annotations = await page.getAnnotations();
return annotations;
}
function renderAnnotation(annotation, viewport) {
const div = document.createElement('div');
div.className = 'annotation';
const rect = viewport.convertToViewportRectangle(annotation.rect);
div.style.left = Math.min(rect[0], rect[2]) + 'px';
div.style.top = Math.min(rect[1], rect[3]) + 'px';
div.style.width = Math.abs(rect[2] - rect[0]) + 'px';
div.style.height = Math.abs(rect[3] - rect[1]) + 'px';
return div;
}
Performance Optimization Tips
When building a PDF viewer Chrome extension, performance is crucial for providing a smooth user experience. Here are some optimization strategies:
Lazy Loading Pages
Only render pages that are currently visible or about to become visible:
class LazyPDFRenderer {
constructor(pdfDoc, container) {
this.pdfDoc = pdfDoc;
this.container = container;
this.renderedPages = new Map();
this.visibleRange = { start: 1, end: 3 };
}
async renderVisiblePages() {
for (let i = this.visibleRange.start; i <= this.visibleRange.end; i++) {
if (!this.renderedPages.has(i)) {
await this.renderPage(i);
}
}
}
updateVisibleRange(start, end) {
this.visibleRange = { start, end };
this.renderVisiblePages();
}
}
Caching and Memory Management
PDF documents can be large, so proper memory management is essential:
// Clear rendered pages to free memory
function clearCache() {
const canvases = document.querySelectorAll('#pdf-canvas');
canvases.forEach(canvas => {
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
});
// Force garbage collection hint
if (window.gc) window.gc();
}
Using Web Workers
PDF.js uses web workers for parsing PDF content, which keeps the main thread responsive. Make sure the worker is properly configured:
// Using a local worker for better performance
pdfjsLib.GlobalWorkerOptions.workerSrc =
chrome.runtime.getURL('lib/pdfjs/build/pdf.worker.mjs');
Building a Complete Document Reader Extension
Now let’s put everything together to create a full-featured document reader Chrome extension. This section provides a complete implementation overview.
Extension Features
A well-rounded PDF document reader extension should include:
- PDF Viewing: Core rendering with zoom, pan, and page navigation
- Search Functionality: Full-text search across the document
- Bookmarks: Save and restore reading positions
- Recent Files: Quick access to previously viewed documents
- Annotations: Highlight and annotate text (advanced)
- Download Handling: Automatic opening of downloaded PDFs
Popup Interface
Create a simple popup that provides quick access to features:
<!-- popup/popup.html -->
<!DOCTYPE html>
<html>
<head>
<style>
body { width: 300px; padding: 15px; font-family: sans-serif; }
h2 { margin: 0 0 15px 0; font-size: 18px; }
.btn { display: block; width: 100%; padding: 10px; margin: 5px 0;
background: #4285f4; color: white; border: none;
border-radius: 4px; cursor: pointer; }
.btn:hover { background: #3367d6; }
.recent { margin-top: 15px; }
.recent-item { padding: 8px; border-bottom: 1px solid #eee;
cursor: pointer; }
.recent-item:hover { background: #f5f5f5; }
</style>
</head>
<body>
<h2>PDF Reader</h2>
<button id="open-file" class="btn">Open PDF File</button>
<button id="current-tab" class="btn">View Current Page's PDF</button>
<div class="recent">
<h3>Recent Files</h3>
<div id="recent-list"></div>
</div>
<script src="popup.js"></script>
</body>
</html>
Testing and Debugging Your PDF Extension
Proper testing is essential for Chrome extensions with PDF.js integration. Here are some strategies:
Using Chrome’s Developer Tools
Chrome provides excellent debugging capabilities for extensions:
- Background Script Debugging: Go to
chrome://extensions, enable your extension, and click “service worker” to debug background scripts - Popup Debugging: Right-click your extension icon and select “Inspect popup”
- Content Script Debugging: Use the regular DevTools when viewing extension-injected content
Common Issues and Solutions
Issue: PDF.js worker fails to load
Solution: Ensure the worker file is listed in web_accessible_resources in manifest.json
Issue: Cross-origin PDF loading fails
Solution: Add appropriate host permissions and use chrome.fileSystem API for local files
Issue: Memory leaks with large PDFs
Solution: Implement proper cleanup, dispose of canvases, and use the rendering queue to limit concurrent renders
Conclusion
Integrating PDF.js into your Chrome extension opens up powerful document viewing capabilities for your users. From basic PDF rendering to advanced features like text extraction, search, and annotations, PDF.js provides a robust foundation for building professional-grade document reader extensions.
The key to success lies in understanding how PDF.js works within the Chrome extension environment, properly configuring your manifest file, and implementing best practices for performance and user experience. With the code examples and guidance in this article, you have everything you need to create a feature-rich PDF viewer extension.
Remember to test thoroughly across different PDF formats and sizes, optimize for performance, and always keep security in mind when handling user documents. With PDF.js and Chrome extensions, you can create tools that genuinely improve how users interact with PDF documents in their browsers.
Start building your PDF viewer extension today, and take advantage of the growing demand for document reader tools in the Chrome Web Store. Your users will appreciate having a fast, secure, and feature-complete PDF viewing experience right in their browser.