Embeds PDFs directly in the page instead of downloading
// ==UserScript==
// @name PDF Viewer Embedder
// @namespace *
// @version 1.9
// @author zinchaiku
// @description Embeds PDFs directly in the page instead of downloading
// @match *://*/*
// @grant none
// @license MIT
// ==/UserScript==
(function () {
'use strict';
let currentBlobUrl = null;
function showPDF(srcUrl, isBlob = false) {
const existing = document.getElementById('tampermonkey-pdf-viewer');
if (existing) existing.remove();
const overlay = document.createElement('div');
overlay.id = 'tampermonkey-pdf-viewer';
overlay.style.position = 'fixed';
overlay.style.top = 0;
overlay.style.left = 0;
overlay.style.width = '100vw';
overlay.style.height = '100vh';
overlay.style.background = 'rgba(0,0,0,0.8)';
overlay.style.zIndex = 99999;
overlay.style.display = 'flex';
overlay.style.alignItems = 'center';
overlay.style.justifyContent = 'center';
const iframe = document.createElement('iframe');
iframe.src = srcUrl;
iframe.style.width = '80vw';
iframe.style.height = '90vh';
iframe.style.border = 'none';
iframe.style.background = '#fff';
overlay.appendChild(iframe);
// Fallback if iframe fails to load (e.g. X-Frame-Options)
iframe.onerror = () => {
overlay.remove();
if (isBlob && currentBlobUrl) {
URL.revokeObjectURL(currentBlobUrl);
currentBlobUrl = null;
}
window.open(srcUrl, '_blank');
};
// Close button
const closeBtn = document.createElement('button');
closeBtn.textContent = 'Close PDF';
closeBtn.style.position = 'absolute';
closeBtn.style.top = '20px';
closeBtn.style.right = '200px';
closeBtn.style.zIndex = 100000;
closeBtn.style.padding = '2px 6px';
closeBtn.style.fontSize = '1.2em';
closeBtn.onclick = () => {
overlay.remove();
if (isBlob && currentBlobUrl) {
URL.revokeObjectURL(currentBlobUrl);
currentBlobUrl = null;
}
};
overlay.appendChild(closeBtn);
// "Open in New Tab" button
const newTabBtn = document.createElement('button');
newTabBtn.textContent = 'Open in New Tab';
newTabBtn.style.position = 'absolute';
newTabBtn.style.top = '20px';
newTabBtn.style.right = '40px';
newTabBtn.style.zIndex = 100000;
newTabBtn.style.padding = '2px 6px';
newTabBtn.style.fontSize = '1.2em';
newTabBtn.onclick = () => {
window.open(srcUrl, '_blank');
};
overlay.appendChild(newTabBtn);
document.body.appendChild(overlay);
// Track for cleanup
if (isBlob) {
currentBlobUrl = srcUrl;
} else {
currentBlobUrl = null;
}
}
function isPDFLink(href) {
try {
const url = new URL(href, window.location.href);
return url.pathname.toLowerCase().endsWith('.pdf');
} catch {
return false;
}
}
function isSameOrigin(href) {
try {
const url = new URL(href, window.location.href);
return url.origin === window.location.origin;
} catch {
return false;
}
}
// Links we should never intercept - anchors, JS handlers, mail/tel links,
// and links explicitly meant to open in a new tab.
function isInterceptable(link) {
const href = link.getAttribute('href');
if (!href) return false;
if (href.startsWith('#')) return false;
if (href.startsWith('javascript:')) return false;
if (href.startsWith('mailto:')) return false;
if (href.startsWith('tel:')) return false;
if (link.target === '_blank') return false;
if (link.hasAttribute('download')) return false;
try {
const url = new URL(href, window.location.href);
// Only intercept http(s) links
if (url.protocol !== 'http:' && url.protocol !== 'https:') return false;
} catch {
return false;
}
return true;
}
function attachPDFInterceptors() {
document.querySelectorAll('a[href]').forEach(link => {
if (link.dataset.tmPdfBound) return;
if (!isInterceptable(link)) return;
link.dataset.tmPdfBound = "1";
link.addEventListener('click', function (e) {
// Let modified clicks (open in new tab/window, etc.) behave normally
if (e.defaultPrevented || e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) {
return;
}
// Cross-origin: fetch() can't read Content-Type due to CORS
// (most sites don't send Access-Control-Allow-Origin), so fall
// back to an extension check instead of fetching. If it looks
// like a PDF, load it straight into the iframe; otherwise let
// the browser navigate there normally.
if (!isSameOrigin(link.href)) {
if (isPDFLink(link.href)) {
e.preventDefault();
showPDF(link.href);
}
return;
}
e.preventDefault();
fetch(link.href, { credentials: 'include' })
.then(res => {
const contentType = res.headers.get('Content-Type') || '';
if (contentType.includes('pdf')) {
return res.blob().then(blob => {
const blobUrl = URL.createObjectURL(blob);
showPDF(blobUrl, true);
});
} else {
window.location.href = link.href;
}
})
.catch(err => {
console.warn("PDF Viewer Embedder: fetch failed, navigating normally.", err);
window.location.href = link.href;
});
});
});
}
window.addEventListener('keydown', (e) => {
if (e.key === 'Escape') {
const viewer = document.getElementById('tampermonkey-pdf-viewer');
if (viewer) {
viewer.remove();
if (currentBlobUrl) {
URL.revokeObjectURL(currentBlobUrl);
currentBlobUrl = null;
}
}
}
});
attachPDFInterceptors();
const observer = new MutationObserver(attachPDFInterceptors);
observer.observe(document.body, { childList: true, subtree: true });
})();