PDF Viewer Embedder

Embeds PDFs directly in the page instead of downloading, with fallback and cleanup

  1. // ==UserScript==
  2. // @name PDF Viewer Embedder
  3. // @namespace *
  4. // @version 1.7
  5. // @author zinchaiku
  6. // @description Embeds PDFs directly in the page instead of downloading, with fallback and cleanup
  7. // @match *://*/*
  8. // @grant none
  9. // @license MIT
  10. // ==/UserScript==
  11.  
  12. (function () {
  13. 'use strict';
  14.  
  15. let currentBlobUrl = null;
  16.  
  17. function showPDF(srcUrl, isBlob = false) {
  18. const existing = document.getElementById('tampermonkey-pdf-viewer');
  19. if (existing) existing.remove();
  20.  
  21. const overlay = document.createElement('div');
  22. overlay.id = 'tampermonkey-pdf-viewer';
  23. overlay.style.position = 'fixed';
  24. overlay.style.top = 0;
  25. overlay.style.left = 0;
  26. overlay.style.width = '100vw';
  27. overlay.style.height = '100vh';
  28. overlay.style.background = 'rgba(0,0,0,0.8)';
  29. overlay.style.zIndex = 99999;
  30. overlay.style.display = 'flex';
  31. overlay.style.alignItems = 'center';
  32. overlay.style.justifyContent = 'center';
  33.  
  34. const iframe = document.createElement('iframe');
  35. iframe.src = srcUrl;
  36. iframe.style.width = '80vw';
  37. iframe.style.height = '90vh';
  38. iframe.style.border = 'none';
  39. iframe.style.background = '#fff';
  40. overlay.appendChild(iframe);
  41.  
  42. // Fallback if iframe fails to load (e.g. X-Frame-Options)
  43. iframe.onerror = () => {
  44. overlay.remove();
  45. if (isBlob && currentBlobUrl) {
  46. URL.revokeObjectURL(currentBlobUrl);
  47. currentBlobUrl = null;
  48. }
  49. window.open(srcUrl, '_blank');
  50. };
  51.  
  52. // Close button
  53. const closeBtn = document.createElement('button');
  54. closeBtn.textContent = 'Close PDF';
  55. closeBtn.style.position = 'absolute';
  56. closeBtn.style.top = '20px';
  57. closeBtn.style.right = '200px';
  58. closeBtn.style.zIndex = 100000;
  59. closeBtn.style.padding = '2px 6px';
  60. closeBtn.style.fontSize = '1.2em';
  61. closeBtn.onclick = () => {
  62. overlay.remove();
  63. if (isBlob && currentBlobUrl) {
  64. URL.revokeObjectURL(currentBlobUrl);
  65. currentBlobUrl = null;
  66. }
  67. };
  68. overlay.appendChild(closeBtn);
  69.  
  70. // "Open in New Tab" button
  71. const newTabBtn = document.createElement('button');
  72. newTabBtn.textContent = 'Open in New Tab';
  73. newTabBtn.style.position = 'absolute';
  74. newTabBtn.style.top = '20px';
  75. newTabBtn.style.right = '40px';
  76. newTabBtn.style.zIndex = 100000;
  77. newTabBtn.style.padding = '2px 6px';
  78. newTabBtn.style.fontSize = '1.2em';
  79. newTabBtn.onclick = () => {
  80. window.open(srcUrl, '_blank');
  81. };
  82. overlay.appendChild(newTabBtn);
  83.  
  84. document.body.appendChild(overlay);
  85.  
  86. // Track for cleanup
  87. if (isBlob) {
  88. currentBlobUrl = srcUrl;
  89. } else {
  90. currentBlobUrl = null;
  91. }
  92. }
  93.  
  94. function isPDFLink(href) {
  95. try {
  96. const url = new URL(href, window.location.href);
  97. return url.pathname.toLowerCase().endsWith('.pdf');
  98. } catch {
  99. return false;
  100. }
  101. }
  102.  
  103. function attachPDFInterceptors() {
  104. document.querySelectorAll('a[href]').forEach(link => {
  105. const href = link.getAttribute('href');
  106. if (!href || link.dataset.tmPdfBound) return;
  107.  
  108. const isWrapper = href.includes('_download.html');
  109. const isDirectPDF = isPDFLink(href);
  110.  
  111. if (isWrapper || isDirectPDF) {
  112. link.dataset.tmPdfBound = "1";
  113.  
  114. link.addEventListener('click', function (e) {
  115. e.preventDefault();
  116.  
  117. if (isDirectPDF) {
  118. showPDF(link.href);
  119. } else {
  120. fetch(link.href, { credentials: 'include' })
  121. .then(res => {
  122. const contentType = res.headers.get('Content-Type') || '';
  123. if (contentType.includes('pdf')) {
  124. return res.blob().then(blob => {
  125. const blobUrl = URL.createObjectURL(blob);
  126. showPDF(blobUrl, true);
  127. });
  128. } else {
  129. window.location.href = link.href;
  130. }
  131. })
  132. .catch(err => {
  133. alert("Could not open PDF: " + err);
  134. window.location.href = link.href;
  135. });
  136. }
  137. });
  138. }
  139. });
  140. }
  141.  
  142. window.addEventListener('keydown', (e) => {
  143. if (e.key === 'Escape') {
  144. const viewer = document.getElementById('tampermonkey-pdf-viewer');
  145. if (viewer) {
  146. viewer.remove();
  147. if (currentBlobUrl) {
  148. URL.revokeObjectURL(currentBlobUrl);
  149. currentBlobUrl = null;
  150. }
  151. }
  152. }
  153. });
  154.  
  155. attachPDFInterceptors();
  156. const observer = new MutationObserver(attachPDFInterceptors);
  157. observer.observe(document.body, { childList: true, subtree: true });
  158. })();