├── .gitignore ├── TODO.md ├── README.md ├── manifest.json ├── patches ├── pdfjs-pinch-gestures.js └── pdfjs-pinch-gestures-larsneo.js ├── Makefile ├── resource-delivery.js ├── icon.svg ├── preserve-referer.js ├── pdfHandler.js └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | *.xpi 2 | content 3 | -------------------------------------------------------------------------------- /TODO.md: -------------------------------------------------------------------------------- 1 | - Review the permissions we request after wanted features are fixed and unwanted features are removed. Most probably some permissions can go. 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Android PDF.js 2 | ==================== 3 | 4 | Addon for "Firefox for Android". 5 | 6 | Provides a way to view PDF files directly in the browser without cluttering up your "Downloads" directory. It uses [Mozilla PDF.js](https://github.com/mozilla/pdf.js) to directly view PDF files. This is the same PDF viewer that is already built into the "Desktop Firefox" browser. 7 | 8 | Main repository: https://github.com/M-Reimer/android-pdf-js. 9 | 10 | AMO: https://addons.mozilla.org/en-US/android/addon/android-pdf-js/ 11 | 12 | Hacking: Do a [temporary install](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox). 13 | 14 | Debugging on "Desktop Firefox" works, but you have to set *pdfjs.disabled* to *true* in *about:config*. 15 | 16 | Building: [make](https://www.gnu.org/software/make/) 17 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "manifest_version": 2, 3 | "name": "Android PDF.js", 4 | "version": "2.5.0", 5 | "description": "Opens PDF files on Firefox for Android using PDF.js.", 6 | 7 | "applications": { 8 | "gecko": { 9 | "id": "{7a4b4ee5-e5af-4a20-b179-d42ec510f3e8}", 10 | "strict_min_version": "70.0" 11 | } 12 | }, 13 | 14 | "icons": { 15 | "128": "icon.svg", 16 | "48": "icon.svg", 17 | "16": "icon.svg" 18 | }, 19 | "permissions": [ 20 | "webRequest", "webRequestBlocking", 21 | "", 22 | "tabs", 23 | "webNavigation", 24 | "storage" 25 | ], 26 | "content_security_policy": "script-src 'self'; object-src 'none'", 27 | "background": { 28 | "scripts": [ 29 | "resource-delivery.js", 30 | "preserve-referer.js", 31 | "pdfHandler.js" 32 | ] 33 | }, 34 | "web_accessible_resources": [ 35 | "content/web/viewer.css", 36 | "content/web/locale/*", 37 | "content/web/cmaps/*" 38 | ], 39 | "incognito": "not_allowed" 40 | } 41 | -------------------------------------------------------------------------------- /patches/pdfjs-pinch-gestures.js: -------------------------------------------------------------------------------- 1 | 2 | // Pinch gesture workaround for Firefox for Android 3 | // https://github.com/M-Reimer/android-pdf-js 4 | (function AndroidPDFJSPinchGesture() { 5 | const STEP_DIST = 50; 6 | 7 | function PinchGestureDist(aTouches) { 8 | return Math.sqrt(Math.pow((aTouches[1].pageX - aTouches[0].pageX),2)+Math.pow((aTouches[1].pageY - aTouches[0].pageY),2)); 9 | } 10 | 11 | let starttouches = undefined; 12 | let endtouches = undefined; 13 | document.addEventListener('touchstart',function(e) { 14 | if (e.touches.length === 2) 15 | starttouches = e.touches; 16 | },false); 17 | document.addEventListener('touchmove', function (e) { 18 | if (e.touches.length === 2) 19 | endtouches = e.touches; 20 | }); 21 | document.addEventListener('touchend', function (e) { 22 | if (e.touches.length === 0 && starttouches && endtouches) { 23 | const d = PinchGestureDist(endtouches) - PinchGestureDist(starttouches); 24 | 25 | for (let loop = 0; loop < Math.abs(Math.floor(d / STEP_DIST)); loop++) { 26 | if (d < 0) 27 | document.getElementById('zoomOut').click(); 28 | else if (d > 0) 29 | document.getElementById('zoomIn').click(); 30 | } 31 | 32 | starttouches = undefined; 33 | endtouches = undefined; 34 | } 35 | }); 36 | })(); 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | # -*- Mode: Makefile -*- 2 | # 3 | # Makefile for Android PDF.js 4 | # 5 | 6 | FILES = manifest.json \ 7 | icon.svg \ 8 | resource-delivery.js \ 9 | pdfHandler.js \ 10 | preserve-referer.js 11 | 12 | ADDON = android-pdfjs 13 | 14 | VERSION = $(shell sed -n 's/^ "version": "\([^"]\+\).*/\1/p' manifest.json) 15 | PDFJS_VERSION = 2.14.305 16 | 17 | ANDROIDDEVICE = $(shell adb devices | cut -s -d$$'\t' -f1 | head -n1) 18 | 19 | trunk: $(ADDON)-trunk.xpi 20 | 21 | release: $(ADDON)-$(VERSION).xpi 22 | @echo "" 23 | @echo "This Add-on contains Mozilla PDF.js as third-party library" 24 | @echo "Path in Add-on: content/build" 25 | @echo "Official source archive: https://github.com/mozilla/pdf.js/releases/download/v$(PDFJS_VERSION)/pdfjs-$(PDFJS_VERSION)-dist.zip" 26 | 27 | %.xpi: $(FILES) content 28 | @zip -r9 - $^ > $@ 29 | 30 | # I don't want to mess with building PDF.js on my own. 31 | # This gives us a "web build" of PDF.js without any browser specific messaging. 32 | # We also add a community patch for pinch gestures here. 33 | content: 34 | wget "https://github.com/mozilla/pdf.js/releases/download/v$(PDFJS_VERSION)/pdfjs-$(PDFJS_VERSION)-dist.zip" 35 | rm -rf content.build 36 | unzip "pdfjs-$(PDFJS_VERSION)-dist.zip" -d "content.build" 37 | rm content.build/web/compressed.tracemonkey-pldi-09.pdf 38 | rm content.build/web/*.js.map 39 | rm content.build/build/*.js.map 40 | rm "pdfjs-$(PDFJS_VERSION)-dist.zip" 41 | 42 | cat patches/pdfjs-pinch-gestures-larsneo.js >> content.build/web/viewer.js 43 | mv content.build content 44 | 45 | clean: 46 | rm -f $(ADDON)-*.xpi 47 | rm -rf content 48 | 49 | # Starts local debug session 50 | run: content 51 | web-ext run --bc --pref=pdfjs.disabled=true -u "https://github.com/mozilla/pdf.js/blob/gh-pages/web/compressed.tracemonkey-pldi-09.pdf" 52 | 53 | # Starts debug session on connected Android device 54 | arun: content 55 | @if [ -z "$(ANDROIDDEVICE)" ]; then \ 56 | echo "No android devices found!"; \ 57 | else \ 58 | web-ext run --target=firefox-android --firefox-apk=org.mozilla.fenix --android-device="$(ANDROIDDEVICE)"; \ 59 | fi 60 | -------------------------------------------------------------------------------- /patches/pdfjs-pinch-gestures-larsneo.js: -------------------------------------------------------------------------------- 1 | // Pinch gesture workaround for Firefox for Android 2 | // https://gist.github.com/larsneo/bb75616e9426ae589f50e8c8411020f6 3 | // https://github.com/M-Reimer/android-pdf-js 4 | 5 | (function AndroidPDFJSPinchGesture() { 6 | let pinchZoomEnabled = false; 7 | function enablePinchZoom(pdfViewer) { 8 | let startX = 0, startY = 0; 9 | let initialPinchDistance = 0; 10 | let pinchScale = 1; 11 | const viewer = document.getElementById("viewer"); 12 | const container = document.getElementById("viewerContainer"); 13 | const reset = () => { startX = startY = initialPinchDistance = 0; pinchScale = 1; }; 14 | // Prevent native iOS page zoom 15 | //document.addEventListener("touchmove", (e) => { if (e.scale !== 1) { e.preventDefault(); } }, { passive: false }); 16 | document.addEventListener("touchstart", (e) => { 17 | if (e.touches.length > 1) { 18 | startX = (e.touches[0].pageX + e.touches[1].pageX) / 2; 19 | startY = (e.touches[0].pageY + e.touches[1].pageY) / 2; 20 | initialPinchDistance = Math.hypot((e.touches[1].pageX - e.touches[0].pageX), (e.touches[1].pageY - e.touches[0].pageY)); 21 | } else { 22 | initialPinchDistance = 0; 23 | } 24 | }); 25 | document.addEventListener("touchmove", (e) => { 26 | if (initialPinchDistance <= 0 || e.touches.length < 2) { return; } 27 | if (e.scale !== 1) { e.preventDefault(); } 28 | const pinchDistance = Math.hypot((e.touches[1].pageX - e.touches[0].pageX), (e.touches[1].pageY - e.touches[0].pageY)); 29 | const originX = startX + container.scrollLeft; 30 | const originY = startY + container.scrollTop; 31 | pinchScale = pinchDistance / initialPinchDistance; 32 | viewer.style.transform = `scale(${pinchScale})`; 33 | viewer.style.transformOrigin = `${originX}px ${originY}px`; 34 | }, { passive: false }); 35 | document.addEventListener("touchend", (e) => { 36 | if (initialPinchDistance <= 0) { return; } 37 | viewer.style.transform = `none`; 38 | viewer.style.transformOrigin = `unset`; 39 | PDFViewerApplication.pdfViewer.currentScale *= pinchScale; 40 | const rect = container.getBoundingClientRect(); 41 | const dx = startX - rect.left; 42 | const dy = startY - rect.top; 43 | container.scrollLeft += dx * (pinchScale - 1); 44 | container.scrollTop += dy * (pinchScale - 1); 45 | reset(); 46 | }); 47 | } 48 | document.addEventListener('webviewerloaded', () => { 49 | if (!pinchZoomEnabled) { 50 | pinchZoomEnabled = true; 51 | enablePinchZoom(); 52 | } 53 | }); 54 | })(); 55 | -------------------------------------------------------------------------------- /resource-delivery.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Manuel Reimer 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | 17 | 'use strict'; 18 | 19 | /* Firefox Add-ons and their ugly workarounds... 20 | * Some information about this file and what it is meant for 21 | * 22 | * Unlike Chrome, where each Add-on uses its own Add-on UUID for its internal 23 | * resource URLs, Mozilla decided that it may be better to generate a random 24 | * UUID on installation of each Add-on to "prevent websites from fingerprinting 25 | * a browser by examining the extensions it has installed" (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/web_accessible_resources) 26 | * 27 | * Unfortunately this will lead to a much bigger problem once you use these 28 | * resource URLs in page content, as we want to do with PDF.js. Now a website 29 | * can check for this URL and get the unique UUID, which was generated while 30 | * Add-on installation, to uniquely identify this specific browser and track it. 31 | * (https://bugzilla.mozilla.org/show_bug.cgi?id=1372288) 32 | * 33 | * To reduce this risk somewhat, I've placed the resource files to my web server 34 | * but as I don't even want to provide the slightest possibility for me to log 35 | * any usage, accesses to my webserver are interrupted by the "onBeforeRequest" 36 | * listener below to redirect them back to the files shipped with this Add-on. 37 | * 38 | * It is still possible to detect that my Add-on is installed using several 39 | * methods but at least we no longer have the worldwide unique UUID in URLs. 40 | */ 41 | 42 | // This is the prefix on my server. Loads from this are interrupted below to 43 | // serve the files directly from the Add-on built in resources. 44 | const RESOURCE_URL = "https://www.m-reimer.de/android-pdf-js/content/web/"; 45 | 46 | chrome.webRequest.onBeforeRequest.addListener( 47 | function(details) { 48 | if (!details.url.startsWith(RESOURCE_URL)) 49 | return; 50 | 51 | // Some security checks. 52 | let path = details.url.substr(RESOURCE_URL.length); 53 | if (path != "viewer.css" && 54 | !path.match(/^images\/[a-zA-Z0-9-]+\.svg$/) && 55 | path != "locale/locale.properties" && 56 | !path.match(/^locale\/[a-zA-Z-]+\/viewer\.properties$/) && 57 | !path.match(/^\/?cmaps\/[a-zA-Z0-9-]+\.bcmap$/)){ 58 | return; 59 | } 60 | 61 | // cmaps has another leading slash which causes trouble 62 | path = path.replace(/^\//, ""); 63 | 64 | return {redirectUrl: browser.runtime.getURL("content/web/" + path)}; 65 | }, 66 | { 67 | urls: [ 68 | RESOURCE_URL + "*" 69 | ], 70 | types: ['main_frame', 'image', 'xmlhttprequest', 'stylesheet'], 71 | }, 72 | ['blocking']); 73 | -------------------------------------------------------------------------------- /icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 6 | 9 | 12 | 18 | 24 | 25 | 28 | 31 | 34 | 37 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /preserve-referer.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2015 Mozilla Foundation 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | */ 16 | /* import-globals-from pdfHandler.js */ 17 | 18 | 'use strict'; 19 | 20 | // Remembers the request headers for every http(s) page request for the duration 21 | // of the request. 22 | var g_requestHeaders = {}; 23 | // g_referrers[tabId][frameId][url] = referrer of PDF frame. 24 | var g_referrers = {}; 25 | 26 | (function() { 27 | var requestFilter = { 28 | urls: [''], 29 | types: ['main_frame', 'sub_frame', 'object'], 30 | }; 31 | chrome.webRequest.onSendHeaders.addListener(function(details) { 32 | g_requestHeaders[details.requestId] = details.requestHeaders; 33 | }, requestFilter, ['requestHeaders']); 34 | chrome.webRequest.onBeforeRedirect.addListener(forgetHeaders, requestFilter); 35 | chrome.webRequest.onCompleted.addListener(forgetHeaders, requestFilter); 36 | chrome.webRequest.onErrorOccurred.addListener(forgetHeaders, requestFilter); 37 | function forgetHeaders(details) { 38 | delete g_requestHeaders[details.requestId]; 39 | } 40 | })(); 41 | 42 | /** 43 | * @param {object} details - onHeadersReceived event data. 44 | */ 45 | function saveReferer(details) { 46 | var referer = g_requestHeaders[details.requestId] && 47 | getHeaderFromHeaders(g_requestHeaders[details.requestId], 'referer'); 48 | referer = referer && referer.value || ''; 49 | if (!g_referrers[details.tabId]) { 50 | g_referrers[details.tabId] = {}; 51 | } 52 | if (!g_referrers[details.tabId][details.frameId]) { 53 | g_referrers[details.tabId][details.frameId] = {}; 54 | } 55 | g_referrers[details.tabId][details.frameId][details.url] = referer; 56 | } 57 | 58 | chrome.tabs.onRemoved.addListener(function(tabId) { 59 | delete g_referrers[tabId]; 60 | }); 61 | 62 | 63 | // Add the Referer header to the "fetch" request done by PDF.js 64 | browser.webRequest.onBeforeSendHeaders.addListener( 65 | function(details) { 66 | // Nothing to do if no Referer was saved 67 | var referer = g_referrers[details.tabId] 68 | && g_referrers[details.tabId][details.frameId] 69 | && g_referrers[details.tabId][details.frameId][details.url] 70 | || ''; 71 | if (referer === '') 72 | return; 73 | 74 | // Add or modify Referer header 75 | let found = false; 76 | details.requestHeaders.forEach(function(header){ 77 | if (header.name.toLowerCase() == "referer") { 78 | header.value = referer; 79 | found = true; 80 | } 81 | }); 82 | if (!found) 83 | details.requestHeaders.push({"name": "Referer", "value": referer}); 84 | 85 | return {requestHeaders: details.requestHeaders}; 86 | }, 87 | { 88 | urls: [ 89 | '' 90 | ], 91 | types: ['xmlhttprequest'], 92 | }, 93 | ['blocking', "requestHeaders"]); 94 | -------------------------------------------------------------------------------- /pdfHandler.js: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright 2021 Manuel Reimer 3 | Copyright 2012 Mozilla Foundation 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | */ 17 | /* import-globals-from preserve-referer.js */ 18 | 19 | 'use strict'; 20 | 21 | 22 | // This function is meant to bypass the "POST blocker" for "known good URLs" 23 | // Note: If this function returns true, then we handle this URL even if it was 24 | // requested via "HTTP POST" *but* PDF.js will fetch it via "HTTP GET"! 25 | function PostWhitelistedURL(aURL) { 26 | if (aURL.startsWith("https://www.pollin.de/productdownloads/")) 27 | return true; 28 | return false; 29 | } 30 | 31 | 32 | /** 33 | * @param {Object} details First argument of the webRequest.onHeadersReceived 34 | * event. The property "url" is read. 35 | * @return {boolean} True if the PDF file should be downloaded. 36 | */ 37 | function isPdfDownloadable(details) { 38 | if (details.url.includes('pdfjs.action=download')) { 39 | return true; 40 | } 41 | // Display the PDF viewer regardless of the Content-Disposition header if the 42 | // file is displayed in the main frame, since most often users want to view 43 | // a PDF, and servers are often misconfigured. 44 | // If the query string contains "=download", do not unconditionally force the 45 | // viewer to open the PDF, but first check whether the Content-Disposition 46 | // header specifies an attachment. This allows sites like Google Drive to 47 | // operate correctly (#6106). 48 | if (details.type === 'main_frame' && !details.url.includes('=download')) { 49 | return false; 50 | } 51 | var cdHeader = (details.responseHeaders && 52 | getHeaderFromHeaders(details.responseHeaders, 'content-disposition')); 53 | return (cdHeader && /^attachment/i.test(cdHeader.value)); 54 | } 55 | 56 | /** 57 | * Get the header from the list of headers for a given name. 58 | * @param {Array} headers responseHeaders of webRequest.onHeadersReceived 59 | * @return {undefined|{name: string, value: string}} The header, if found. 60 | */ 61 | function getHeaderFromHeaders(headers, headerName) { 62 | for (var i = 0; i < headers.length; ++i) { 63 | var header = headers[i]; 64 | if (header.name.toLowerCase() === headerName) { 65 | return header; 66 | } 67 | } 68 | } 69 | 70 | /** 71 | * Check if the request is a PDF file. 72 | * @param {Object} details First argument of the webRequest.onHeadersReceived 73 | * event. The properties "responseHeaders" and "url" 74 | * are read. 75 | * @return {boolean} True if the resource is a PDF file. 76 | */ 77 | function isPdfFile(details) { 78 | var header = getHeaderFromHeaders(details.responseHeaders, 'content-type'); 79 | if (header) { 80 | var headerValue = header.value.toLowerCase().split(';', 1)[0].trim(); 81 | if (headerValue === 'application/pdf') { 82 | return true; 83 | } 84 | if (headerValue === 'application/octet-stream') { 85 | if (details.url.toLowerCase().indexOf('.pdf') > 0) { 86 | return true; 87 | } 88 | var cdHeader = 89 | getHeaderFromHeaders(details.responseHeaders, 'content-disposition'); 90 | if (cdHeader && /\.pdf(["']|$)/i.test(cdHeader.value)) { 91 | return true; 92 | } 93 | } 94 | } 95 | } 96 | 97 | /** 98 | * Takes a set of headers, and set "Content-Disposition: attachment". 99 | * @param {Object} details First argument of the webRequest.onHeadersReceived 100 | * event. The property "responseHeaders" is read and 101 | * modified if needed. 102 | * @return {Object|undefined} The return value for the onHeadersReceived event. 103 | * Object with key "responseHeaders" if the headers 104 | * have been modified, undefined otherwise. 105 | */ 106 | function getHeadersWithContentDispositionAttachment(details) { 107 | var headers = details.responseHeaders; 108 | var cdHeader = getHeaderFromHeaders(headers, 'content-disposition'); 109 | if (!cdHeader) { 110 | cdHeader = { name: 'Content-Disposition', }; 111 | headers.push(cdHeader); 112 | } 113 | if (!/^attachment/i.test(cdHeader.value)) { 114 | cdHeader.value = 'attachment' + cdHeader.value.replace(/^[^;]+/i, ''); 115 | return { responseHeaders: headers, }; 116 | } 117 | } 118 | 119 | /** 120 | * Helper to read a file shipped with our Add-on. 121 | * @param aPath Path to the file inside our Add-on 122 | * @return Promise which will be fulfilled with the file contents. 123 | */ 124 | async function getAddonFile(aPath) { 125 | const url = browser.runtime.getURL(aPath); 126 | const response = await fetch(url); 127 | return await response.text(); 128 | } 129 | 130 | /** 131 | * Helper to get a script file for embedding directly into the HTML file. 132 | * Uses "getAddonFile()" to get the file content and removes sourceMappingURL 133 | * @param aPath Path to the file inside our Add-on 134 | * @return Promise which will be fulfilled with the file contents. 135 | */ 136 | async function getAddonScriptForEmbed(aPath) { 137 | let content = await getAddonFile(aPath); 138 | return content.replace(/^\/\/# sourceMappingURL=[a-z.]+\.js\.map/m, ''); 139 | } 140 | 141 | /** 142 | * Helper to get a data:-URL for a script file 143 | * Uses "getAddonScriptForEmbed" to get the file and formats it as data:-URL 144 | * @param aPath Path to the file inside our Add-on 145 | * @return Promise which will be fulfilled with the data:-URL. 146 | */ 147 | async function getAddonScriptDataURL(aPath) { 148 | let content = await getAddonScriptForEmbed(aPath); 149 | return "data:application/javascript;base64," + btoa(unescape(encodeURIComponent(content))); 150 | } 151 | 152 | /** 153 | * The following function creates a viewer HTML file with all scripts embedded. 154 | * This is to avoid web-accessible scripts for security reasons. 155 | * This function also slightly patches PDF.js to use its own URL as the PDF URL. 156 | * The built HTML code is cached and reused. 157 | * @return Promise which will be fulfilled with the viewer HTML 158 | */ 159 | let viewer_html_cache = false; 160 | async function getHTML() { 161 | if (!viewer_html_cache) { 162 | let txt_viewer_js = await getAddonScriptForEmbed('content/web/viewer.js'); 163 | txt_viewer_js = txt_viewer_js.replace( 164 | '../build/pdf.worker.js', 165 | await getAddonScriptDataURL('content/build/pdf.worker.js') 166 | ).replace( 167 | '../build/pdf.sandbox.js', 168 | await getAddonScriptDataURL('content/build/pdf.sandbox.js') 169 | ).replace( 170 | '../web/cmaps/', 171 | RESOURCE_URL + '/cmaps/' 172 | ).replace( 173 | '"compressed.tracemonkey-pldi-09.pdf"', 174 | 'document.location.href' 175 | ).replace( // Hide the actual URL parameters from PDF.js 176 | 'const queryString = document.location.search.substring(1);', 177 | 'const queryString = "";' 178 | ); 179 | 180 | let txt_html = await getAddonFile('content/web/viewer.html'); 181 | txt_html = txt_html.replace( 182 | '', 183 | '' 184 | ).replace( 185 | '', 186 | '' 187 | ).replace( 188 | 'href="viewer.css"', 189 | 'href="' + RESOURCE_URL + 'viewer.css"' 190 | ).replace( 191 | 'locale/locale.properties', 192 | RESOURCE_URL + 'locale/locale.properties' 193 | ); 194 | 195 | viewer_html_cache = txt_html; 196 | } 197 | 198 | return viewer_html_cache 199 | } 200 | 201 | 202 | chrome.webRequest.onHeadersReceived.addListener( 203 | function(details) { 204 | if (details.method !== 'GET' && !PostWhitelistedURL(details.url)) 205 | return; 206 | 207 | if (!isPdfFile(details)) { 208 | return; 209 | } 210 | if (isPdfDownloadable(details)) { 211 | // Force download by ensuring that Content-Disposition: attachment is set 212 | return getHeadersWithContentDispositionAttachment(details); 213 | } 214 | 215 | // Implemented in preserve-referer.js 216 | saveReferer(details); 217 | 218 | const filter = browser.webRequest.filterResponseData(details.requestId); 219 | filter.onstart = async () => { 220 | const encoder = new TextEncoder(); 221 | filter.write(encoder.encode(await getHTML())); 222 | filter.close(); 223 | } 224 | return { responseHeaders: [ { name: "Content-Type", value: "text/html" }, 225 | { name: "Content-Security-Policy", value: "default-src 'self' https://www.m-reimer.de data: blob:; script-src 'unsafe-inline' data: blob:; style-src https://www.m-reimer.de 'unsafe-inline'; object-src 'none';"} ]}; 226 | }, 227 | { 228 | urls: [ 229 | '' 230 | ], 231 | types: ['main_frame', 'sub_frame', 'object'], 232 | }, 233 | ['blocking', 'responseHeaders']); 234 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | --------------------------------------------------------------------------------