Merge pull request from Snuffleupagus/openOrDownloadData

Move the opening of PDF file attachments into the `DownloadManager`-implementations
This commit is contained in:
Tim van der Meij 2021-02-24 20:57:12 +01:00 committed by GitHub
commit 8b7dee0aae
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 97 additions and 61 deletions

@ -1982,11 +1982,11 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
* @memberof FileAttachmentAnnotationElement * @memberof FileAttachmentAnnotationElement
*/ */
_download() { _download() {
if (!this.downloadManager) { this.downloadManager?.openOrDownloadData(
warn("Download cannot be started due to unavailable download manager"); this.container,
return; this.content,
} this.filename
this.downloadManager.downloadData(this.content, this.filename, ""); );
} }
} }

@ -14,6 +14,7 @@
*/ */
import { createObjectURL, createValidAbsoluteUrl } from "pdfjs-lib"; import { createObjectURL, createValidAbsoluteUrl } from "pdfjs-lib";
import { PdfFileRegExp } from "./ui_utils.js";
import { viewerCompatibilityParams } from "./viewer_compatibility.js"; import { viewerCompatibilityParams } from "./viewer_compatibility.js";
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) { if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
@ -43,6 +44,10 @@ function download(blobUrl, filename) {
} }
class DownloadManager { class DownloadManager {
constructor() {
this._openBlobUrls = new WeakMap();
}
downloadUrl(url, filename) { downloadUrl(url, filename) {
if (!createValidAbsoluteUrl(url, "http://example.com")) { if (!createValidAbsoluteUrl(url, "http://example.com")) {
return; // restricted/invalid URL return; // restricted/invalid URL
@ -59,6 +64,49 @@ class DownloadManager {
download(blobUrl, filename); download(blobUrl, filename);
} }
/**
* @returns {boolean} Indicating if the data was opened.
*/
openOrDownloadData(element, data, filename) {
const isPdfFile = PdfFileRegExp.test(filename);
const contentType = isPdfFile ? "application/pdf" : "";
if (isPdfFile && !viewerCompatibilityParams.disableCreateObjectURL) {
let blobUrl = this._openBlobUrls.get(element);
if (!blobUrl) {
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
this._openBlobUrls.set(element, blobUrl);
}
let viewerUrl;
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
// The current URL is the viewer, let's use it and append the file.
viewerUrl = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
} else if (PDFJSDev.test("CHROME")) {
// In the Chrome extension, the URL is rewritten using the history API
// in viewer.js, so an absolute URL must be generated.
viewerUrl =
// eslint-disable-next-line no-undef
chrome.runtime.getURL("/content/web/viewer.html") +
"?file=" +
encodeURIComponent(blobUrl + "#" + filename);
}
try {
window.open(viewerUrl);
return true;
} catch (ex) {
console.error(`openOrDownloadData: ${ex}`);
// Release the `blobUrl`, since opening it failed, and fallback to
// downloading the PDF file.
URL.revokeObjectURL(blobUrl);
this._openBlobUrls.delete(element);
}
}
this.downloadData(data, filename, contentType);
return false;
}
/** /**
* @param sourceEventType {string} Used to signal what triggered the download. * @param sourceEventType {string} Used to signal what triggered the download.
* The version of PDF.js integrated with Firefox uses this to to determine * The version of PDF.js integrated with Firefox uses this to to determine

@ -14,10 +14,10 @@
*/ */
import "../extensions/firefox/tools/l10n.js"; import "../extensions/firefox/tools/l10n.js";
import { DEFAULT_SCALE_VALUE, PdfFileRegExp } from "./ui_utils.js";
import { DefaultExternalServices, PDFViewerApplication } from "./app.js"; import { DefaultExternalServices, PDFViewerApplication } from "./app.js";
import { PDFDataRangeTransport, shadow } from "pdfjs-lib"; import { PDFDataRangeTransport, shadow } from "pdfjs-lib";
import { BasePreferences } from "./preferences.js"; import { BasePreferences } from "./preferences.js";
import { DEFAULT_SCALE_VALUE } from "./ui_utils.js";
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) {
throw new Error( throw new Error(
@ -99,6 +99,10 @@ class FirefoxCom {
} }
class DownloadManager { class DownloadManager {
constructor() {
this._openBlobUrls = new WeakMap();
}
downloadUrl(url, filename) { downloadUrl(url, filename) {
FirefoxCom.request("download", { FirefoxCom.request("download", {
originalUrl: url, originalUrl: url,
@ -121,6 +125,38 @@ class DownloadManager {
}); });
} }
/**
* @returns {boolean} Indicating if the data was opened.
*/
openOrDownloadData(element, data, filename) {
const isPdfFile = PdfFileRegExp.test(filename);
const contentType = isPdfFile ? "application/pdf" : "";
if (isPdfFile) {
let blobUrl = this._openBlobUrls.get(element);
if (!blobUrl) {
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
this._openBlobUrls.set(element, blobUrl);
}
// Let Firefox's content handler catch the URL and display the PDF.
const viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
try {
window.open(viewerUrl);
return true;
} catch (ex) {
console.error(`openOrDownloadData: ${ex}`);
// Release the `blobUrl`, since opening it failed, and fallback to
// downloading the PDF file.
URL.revokeObjectURL(blobUrl);
this._openBlobUrls.delete(element);
}
}
this.downloadData(data, filename, contentType);
return false;
}
download(blob, url, filename, sourceEventType = "download") { download(blob, url, filename, sourceEventType = "download") {
const blobUrl = URL.createObjectURL(blob); const blobUrl = URL.createObjectURL(blob);

@ -15,9 +15,6 @@
import { createPromiseCapability, getFilenameFromUrl } from "pdfjs-lib"; import { createPromiseCapability, getFilenameFromUrl } from "pdfjs-lib";
import { BaseTreeViewer } from "./base_tree_viewer.js"; import { BaseTreeViewer } from "./base_tree_viewer.js";
import { viewerCompatibilityParams } from "./viewer_compatibility.js";
const PdfFileRegExp = /\.pdf$/i;
/** /**
* @typedef {Object} PDFAttachmentViewerOptions * @typedef {Object} PDFAttachmentViewerOptions
@ -91,55 +88,12 @@ class PDFAttachmentViewer extends BaseTreeViewer {
}); });
} }
/**
* NOTE: Should only be used when `URL.createObjectURL` is natively supported.
* @private
*/
_bindPdfLink(element, { content, filename }) {
let blobUrl;
element.onclick = () => {
if (!blobUrl) {
blobUrl = URL.createObjectURL(
new Blob([content], { type: "application/pdf" })
);
}
let viewerUrl;
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
// The current URL is the viewer, let's use it and append the file.
viewerUrl = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
} else if (PDFJSDev.test("MOZCENTRAL")) {
// Let Firefox's content handler catch the URL and display the PDF.
viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
} else if (PDFJSDev.test("CHROME")) {
// In the Chrome extension, the URL is rewritten using the history API
// in viewer.js, so an absolute URL must be generated.
viewerUrl =
// eslint-disable-next-line no-undef
chrome.runtime.getURL("/content/web/viewer.html") +
"?file=" +
encodeURIComponent(blobUrl + "#" + filename);
}
try {
window.open(viewerUrl);
} catch (ex) {
console.error(`_bindPdfLink: ${ex}`);
// Release the `blobUrl`, since opening it failed...
URL.revokeObjectURL(blobUrl);
blobUrl = null;
// ... and fallback to downloading the PDF file.
this.downloadManager.downloadData(content, filename, "application/pdf");
}
return false;
};
}
/** /**
* @private * @private
*/ */
_bindLink(element, { content, filename }) { _bindLink(element, { content, filename }) {
element.onclick = () => { element.onclick = () => {
const contentType = PdfFileRegExp.test(filename) ? "application/pdf" : ""; this.downloadManager.openOrDownloadData(element, content, filename);
this.downloadManager.downloadData(content, filename, contentType);
return false; return false;
}; };
} }
@ -165,20 +119,14 @@ class PDFAttachmentViewer extends BaseTreeViewer {
let attachmentsCount = 0; let attachmentsCount = 0;
for (const name of names) { for (const name of names) {
const item = attachments[name]; const item = attachments[name];
const content = item.content;
const filename = getFilenameFromUrl(item.filename); const filename = getFilenameFromUrl(item.filename);
const div = document.createElement("div"); const div = document.createElement("div");
div.className = "treeItem"; div.className = "treeItem";
const element = document.createElement("a"); const element = document.createElement("a");
if ( this._bindLink(element, { content, filename });
PdfFileRegExp.test(filename) &&
!viewerCompatibilityParams.disableCreateObjectURL
) {
this._bindPdfLink(element, { content: item.content, filename });
} else {
this._bindLink(element, { content: item.content, filename });
}
element.textContent = this._normalizeTextContent(filename); element.textContent = this._normalizeTextContent(filename);
div.appendChild(element); div.appendChild(element);

@ -69,6 +69,9 @@ const SpreadMode = {
// Used by `PDFViewerApplication`, and by the API unit-tests. // Used by `PDFViewerApplication`, and by the API unit-tests.
const AutoPrintRegExp = /\bprint\s*\(/; const AutoPrintRegExp = /\bprint\s*\(/;
// Used by the (various) `DownloadManager`-implementations.
const PdfFileRegExp = /\.pdf$/i;
// Replaces {{arguments}} with their values. // Replaces {{arguments}} with their values.
function formatL10nValue(text, args) { function formatL10nValue(text, args) {
if (!args) { if (!args) {
@ -1059,6 +1062,7 @@ export {
normalizeWheelEventDirection, normalizeWheelEventDirection,
NullL10n, NullL10n,
parseQueryString, parseQueryString,
PdfFileRegExp,
PresentationModeState, PresentationModeState,
ProgressBar, ProgressBar,
RendererType, RendererType,