-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathbackground.js
More file actions
100 lines (90 loc) · 3.22 KB
/
background.js
File metadata and controls
100 lines (90 loc) · 3.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
/**
* Freedom PDF Viewer — Background Service Worker
*
* Intercepts navigations to PDF files and redirects them to the
* built-in PDF.js viewer. This covers:
* • Clicking a PDF link (http / https)
* • Typing / pasting a PDF URL into the address bar
* • Opening a local PDF via File → Open (file://)
* • Dragging and dropping a PDF file onto a Chrome tab (file://)
*
* Embedded PDFs (lazy-loaded iframes, <object>, etc.) must NOT be
* redirected — only full-tab navigations to a .pdf URL.
*/
const VIEWER = chrome.runtime.getURL('pdfjs/web/viewer.html');
/**
* Returns true if the URL looks like a PDF and is not already
* being handled by our viewer.
*/
function isPdfNavigation(url) {
if (!url || typeof url !== 'string' || url.startsWith(VIEWER)) return false;
try {
const { pathname } = new URL(url);
return pathname.toLowerCase().endsWith('.pdf');
} catch {
// Fallback for malformed URLs
return /\.pdf($|\?|#)/i.test(url);
}
}
/**
* True only for a user-visible tab navigation to a PDF — not an iframe,
* fenced frame, or prerendered document.
* Chrome 106+ exposes `frameType`; older builds fall back to frameId/parentFrameId.
*/
function isTopLevelUserTabNavigation(details) {
if (details.documentLifecycle === 'prerender') {
return false;
}
const { frameType, frameId, parentFrameId } = details;
if (frameType === 'sub_frame' || frameType === 'fenced_frame') {
return false;
}
if (frameType === 'outermost_frame') {
return true;
}
// Chrome < 106: frameType may be undefined — use frame hierarchy only
if (frameType === undefined) {
if (frameId !== 0) {
return false;
}
if (parentFrameId === undefined || parentFrameId === null) {
return true;
}
return parentFrameId === -1;
}
return false;
}
/**
* Redirect the tab to our PDF viewer with the original URL
* encoded as the `file` query parameter that PDF.js expects.
*/
function openInViewer(tabId, url) {
// If the URL has a fragment (e.g. #page=2), keep it outside the 'file' param
// so that PDF.js can parse it correctly for its own navigation.
const [baseUrl, fragment] = url.split('#');
const redirectUrl = `${VIEWER}?file=${encodeURIComponent(baseUrl)}${fragment ? '#' + fragment : ''}`;
chrome.tabs.update(tabId, { url: redirectUrl });
}
// ── Intercept navigations ────────────────────────────────────────────────────
// onBeforeNavigate fires early — before Chrome's built-in PDF renderer
// has a chance to claim the navigation — making it ideal for interception.
chrome.webNavigation.onBeforeNavigate.addListener(
(details) => {
if (!isPdfNavigation(details.url)) {
return;
}
if (!isTopLevelUserTabNavigation(details)) {
return;
}
openInViewer(details.tabId, details.url);
},
{
url: [
// HTTP / HTTPS PDFs (web links and web drag-and-drop)
{ schemes: ['http'], urlMatches: '\\.pdf($|\\?|#)' },
{ schemes: ['https'], urlMatches: '\\.pdf($|\\?|#)' },
// Local file PDFs (file → open OR drag-and-drop from file manager)
{ schemes: ['file'], urlMatches: '\\.pdf($|\\?|#)' },
],
}
);