mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-06-25 01:25:51 +02:00
Replace the IDownloadManager interface with an abstract BaseDownloadManager class
This should help reduce the maintenance burden of the code, since you no longer need to remember to update separate code when touching the different `DownloadManager` classes.
This commit is contained in:
parent
ff7f87fc21
commit
839c257f87
@ -231,7 +231,7 @@ function createWebpackAlias(defines) {
|
||||
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
|
||||
libraryAlias["display-network"] = "src/display/network.js";
|
||||
|
||||
viewerAlias["web-download_manager"] = "web/download_manager.js";
|
||||
viewerAlias["web-download_manager"] = "web/chromecom.js";
|
||||
viewerAlias["web-external_services"] = "web/chromecom.js";
|
||||
viewerAlias["web-null_l10n"] = "web/l10n.js";
|
||||
viewerAlias["web-preferences"] = "web/chromecom.js";
|
||||
|
||||
@ -18,8 +18,6 @@
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../../web/interfaces").IDownloadManager} IDownloadManager */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../../web/struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
||||
@ -57,7 +55,7 @@ const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
|
||||
* @property {Object} data
|
||||
* @property {HTMLDivElement} layer
|
||||
* @property {PDFLinkService} linkService
|
||||
* @property {IDownloadManager} [downloadManager]
|
||||
* @property {BaseDownloadManager} [downloadManager]
|
||||
* @property {AnnotationStorage} [annotationStorage]
|
||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||
* for annotation icons. Include trailing slash.
|
||||
@ -3736,7 +3734,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
|
||||
* @property {Array} annotations
|
||||
* @property {PDFPageProxy} page
|
||||
* @property {PDFLinkService} linkService
|
||||
* @property {IDownloadManager} [downloadManager]
|
||||
* @property {BaseDownloadManager} [downloadManager]
|
||||
* @property {AnnotationStorage} [annotationStorage]
|
||||
* @property {string} [imageResourcesPath] - Path for image resources, mainly
|
||||
* for annotation icons. Include trailing slash.
|
||||
|
||||
@ -18,7 +18,6 @@
|
||||
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */
|
||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
|
||||
// eslint-disable-next-line max-len
|
||||
@ -43,7 +42,7 @@ import { PresentationModeState } from "./ui_utils.js";
|
||||
* for annotation icons. Include trailing slash.
|
||||
* @property {boolean} renderForms
|
||||
* @property {PDFLinkService} linkService
|
||||
* @property {IDownloadManager} [downloadManager]
|
||||
* @property {BaseDownloadManager} [downloadManager]
|
||||
* @property {boolean} [enableComment]
|
||||
* @property {boolean} [enableScripting]
|
||||
* @property {Promise<boolean>} [hasJSActionsPromise]
|
||||
|
||||
103
web/base_download_manager.js
Normal file
103
web/base_download_manager.js
Normal file
@ -0,0 +1,103 @@
|
||||
/* Copyright 2013 Mozilla Foundation
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isPdfFile } from "pdfjs-lib";
|
||||
|
||||
class BaseDownloadManager {
|
||||
#openBlobUrls = new WeakMap();
|
||||
|
||||
constructor() {
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||
this.constructor === BaseDownloadManager
|
||||
) {
|
||||
throw new Error("Cannot initialize BaseDownloadManager.");
|
||||
}
|
||||
}
|
||||
|
||||
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
|
||||
throw new Error("Not implemented: _triggerDownload");
|
||||
}
|
||||
|
||||
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||
throw new Error("Not implemented: _getOpenDataUrl");
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {string} filename
|
||||
* @param {string} [contentType]
|
||||
*/
|
||||
downloadData(data, filename, contentType) {
|
||||
const blobUrl = URL.createObjectURL(
|
||||
new Blob([data], { type: contentType })
|
||||
);
|
||||
|
||||
this._triggerDownload(
|
||||
blobUrl,
|
||||
/* originalUrl = */ blobUrl,
|
||||
filename,
|
||||
/* isAttachment = */ true
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {string} filename
|
||||
* @param {string | null} [dest]
|
||||
* @returns {boolean} Indicating if the data was opened.
|
||||
*/
|
||||
openOrDownloadData(data, filename, dest = null) {
|
||||
const isPdfData = isPdfFile(filename);
|
||||
const contentType = isPdfData ? "application/pdf" : "";
|
||||
|
||||
if (isPdfData) {
|
||||
let blobUrl;
|
||||
try {
|
||||
blobUrl = this.#openBlobUrls.getOrInsertComputed(data, () =>
|
||||
URL.createObjectURL(new Blob([data], { type: contentType }))
|
||||
);
|
||||
const viewerUrl = this._getOpenDataUrl(blobUrl, filename, dest);
|
||||
|
||||
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(data);
|
||||
}
|
||||
}
|
||||
|
||||
this.downloadData(data, filename, contentType);
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {string} url
|
||||
* @param {string} filename
|
||||
*/
|
||||
download(data, url, filename) {
|
||||
const blobUrl = data
|
||||
? URL.createObjectURL(new Blob([data], { type: "application/pdf" }))
|
||||
: null;
|
||||
|
||||
this._triggerDownload(blobUrl, /* originalUrl = */ url, filename);
|
||||
}
|
||||
}
|
||||
|
||||
export { BaseDownloadManager };
|
||||
@ -17,6 +17,7 @@
|
||||
import { AppOptions } from "./app_options.js";
|
||||
import { BaseExternalServices } from "./external_services.js";
|
||||
import { BasePreferences } from "./preferences.js";
|
||||
import { DownloadManager as GenericDownloadManager } from "./download_manager.js";
|
||||
import { GenericL10n } from "./genericl10n.js";
|
||||
import { GenericScripting } from "./generic_scripting.js";
|
||||
import { SignatureStorage } from "./generic_signature_storage.js";
|
||||
@ -310,6 +311,25 @@ function setReferer(url, callback) {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This "should" really extend the `BaseDownloadManager` class,
|
||||
* however doing it this way instead reduces code duplication.
|
||||
*/
|
||||
class DownloadManager extends GenericDownloadManager {
|
||||
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||
// In the Chrome extension, the URL is rewritten using the history API
|
||||
// in viewer.js, so an absolute URL must be generated.
|
||||
let url =
|
||||
chrome.runtime.getURL("/content/web/viewer.html") +
|
||||
"?file=" +
|
||||
encodeURIComponent(blobUrl + "#" + filename);
|
||||
if (dest) {
|
||||
url += `#${escape(dest)}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
// chrome.storage.sync is not supported in every Chromium-derivate.
|
||||
// Note: The background page takes care of migrating values from
|
||||
// chrome.storage.local to chrome.storage.sync when needed.
|
||||
@ -437,4 +457,4 @@ class MLManager {
|
||||
}
|
||||
}
|
||||
|
||||
export { ExternalServices, initCom, MLManager, Preferences };
|
||||
export { DownloadManager, ExternalServices, initCom, MLManager, Preferences };
|
||||
|
||||
@ -13,9 +13,8 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
||||
|
||||
import { createValidAbsoluteUrl, isPdfFile } from "pdfjs-lib";
|
||||
import { BaseDownloadManager } from "./base_download_manager.js";
|
||||
import { createValidAbsoluteUrl } from "pdfjs-lib";
|
||||
|
||||
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
|
||||
throw new Error(
|
||||
@ -24,101 +23,41 @@ if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
|
||||
);
|
||||
}
|
||||
|
||||
function download(blobUrl, filename) {
|
||||
const a = document.createElement("a");
|
||||
if (!a.click) {
|
||||
throw new Error('DownloadManager: "a.click()" is not supported.');
|
||||
}
|
||||
a.href = blobUrl;
|
||||
a.target = "_parent";
|
||||
// Use a.download if available. This increases the likelihood that
|
||||
// the file is downloaded instead of opened by another PDF plugin.
|
||||
if ("download" in a) {
|
||||
a.download = filename;
|
||||
}
|
||||
// <a> must be in the document for recent Firefox versions,
|
||||
// otherwise .click() is ignored.
|
||||
(document.body || document.documentElement).append(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
|
||||
/**
|
||||
* @implements {IDownloadManager}
|
||||
*/
|
||||
class DownloadManager {
|
||||
#openBlobUrls = new WeakMap();
|
||||
|
||||
downloadData(data, filename, contentType) {
|
||||
const blobUrl = URL.createObjectURL(
|
||||
new Blob([data], { type: contentType })
|
||||
);
|
||||
download(blobUrl, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} Indicating if the data was opened.
|
||||
*/
|
||||
openOrDownloadData(data, filename, dest = null) {
|
||||
const isPdfData = isPdfFile(filename);
|
||||
const contentType = isPdfData ? "application/pdf" : "";
|
||||
|
||||
if (
|
||||
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("COMPONENTS")) &&
|
||||
isPdfData
|
||||
) {
|
||||
let blobUrl = this.#openBlobUrls.get(data);
|
||||
if (!blobUrl) {
|
||||
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
|
||||
this.#openBlobUrls.set(data, 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);
|
||||
}
|
||||
if (dest) {
|
||||
viewerUrl += `#${escape(dest)}`;
|
||||
}
|
||||
|
||||
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(data);
|
||||
class DownloadManager extends BaseDownloadManager {
|
||||
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
|
||||
if (!blobUrl && !isAttachment) {
|
||||
// Fallback to downloading non-attachments by their URL.
|
||||
if (!createValidAbsoluteUrl(originalUrl, "http://example.com")) {
|
||||
throw new Error(`_triggerDownload - not a valid URL: ${originalUrl}`);
|
||||
}
|
||||
blobUrl = originalUrl + "#pdfjs.action=download";
|
||||
}
|
||||
|
||||
this.downloadData(data, filename, contentType);
|
||||
return false;
|
||||
const a = document.createElement("a");
|
||||
a.href = blobUrl;
|
||||
a.target = "_parent";
|
||||
// Use a.download if available. This increases the likelihood that
|
||||
// the file is downloaded instead of opened by another PDF plugin.
|
||||
if ("download" in a) {
|
||||
a.download = filename;
|
||||
}
|
||||
// <a> must be in the document for recent Firefox versions,
|
||||
// otherwise .click() is ignored.
|
||||
(document.body || document.documentElement).append(a);
|
||||
a.click();
|
||||
a.remove();
|
||||
}
|
||||
|
||||
download(data, url, filename) {
|
||||
let blobUrl;
|
||||
if (data) {
|
||||
blobUrl = URL.createObjectURL(
|
||||
new Blob([data], { type: "application/pdf" })
|
||||
);
|
||||
} else {
|
||||
if (!createValidAbsoluteUrl(url, "http://example.com")) {
|
||||
console.error(`download - not a valid URL: ${url}`);
|
||||
return;
|
||||
}
|
||||
blobUrl = url + "#pdfjs.action=download";
|
||||
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("COMPONENTS")) {
|
||||
throw new Error("Opening data is not supported in `COMPONENTS` builds.");
|
||||
}
|
||||
download(blobUrl, filename);
|
||||
// The current URL is the viewer, let's use it and append the file.
|
||||
let url = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
|
||||
if (dest) {
|
||||
url += `#${escape(dest)}`;
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,8 +13,9 @@
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { isPdfFile, MathClamp, PDFDataRangeTransport } from "pdfjs-lib";
|
||||
import { MathClamp, PDFDataRangeTransport } from "pdfjs-lib";
|
||||
import { AppOptions } from "./app_options.js";
|
||||
import { BaseDownloadManager } from "./base_download_manager.js";
|
||||
import { BaseExternalServices } from "./external_services.js";
|
||||
import { BasePreferences } from "./preferences.js";
|
||||
import { DEFAULT_SCALE_VALUE } from "./ui_utils.js";
|
||||
@ -80,69 +81,25 @@ class FirefoxCom {
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadManager {
|
||||
#openBlobUrls = new WeakMap();
|
||||
|
||||
downloadData(data, filename, contentType) {
|
||||
const blobUrl = URL.createObjectURL(
|
||||
new Blob([data], { type: contentType })
|
||||
);
|
||||
|
||||
class DownloadManager extends BaseDownloadManager {
|
||||
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
|
||||
FirefoxCom.request("download", {
|
||||
blobUrl,
|
||||
originalUrl: blobUrl,
|
||||
originalUrl,
|
||||
filename,
|
||||
isAttachment: true,
|
||||
isAttachment,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {boolean} Indicating if the data was opened.
|
||||
*/
|
||||
openOrDownloadData(data, filename, dest = null) {
|
||||
const isPdfData = isPdfFile(filename);
|
||||
const contentType = isPdfData ? "application/pdf" : "";
|
||||
|
||||
if (isPdfData) {
|
||||
let blobUrl = this.#openBlobUrls.get(data);
|
||||
if (!blobUrl) {
|
||||
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
|
||||
this.#openBlobUrls.set(data, blobUrl);
|
||||
}
|
||||
// Let Firefox's content handler catch the URL and display the PDF.
|
||||
// NOTE: This cannot use a query string for the filename, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1632644#c5
|
||||
let viewerUrl = blobUrl + "#filename=" + encodeURIComponent(filename);
|
||||
if (dest) {
|
||||
viewerUrl += `&filedest=${escape(dest)}`;
|
||||
}
|
||||
|
||||
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(data);
|
||||
}
|
||||
_getOpenDataUrl(blobUrl, filename, dest = null) {
|
||||
// Let Firefox's content handler catch the URL and display the PDF.
|
||||
// NOTE: This cannot use a query string for the filename, see
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=1632644#c5
|
||||
let url = blobUrl + "#filename=" + encodeURIComponent(filename);
|
||||
if (dest) {
|
||||
url += `&filedest=${escape(dest)}`;
|
||||
}
|
||||
|
||||
this.downloadData(data, filename, contentType);
|
||||
return false;
|
||||
}
|
||||
|
||||
download(data, url, filename) {
|
||||
const blobUrl = data
|
||||
? URL.createObjectURL(new Blob([data], { type: "application/pdf" }))
|
||||
: null;
|
||||
|
||||
FirefoxCom.request("download", {
|
||||
blobUrl,
|
||||
originalUrl: url,
|
||||
filename,
|
||||
});
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -44,31 +44,4 @@ class IRenderableView {
|
||||
async draw() {}
|
||||
}
|
||||
|
||||
/**
|
||||
* @interface
|
||||
*/
|
||||
class IDownloadManager {
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {string} filename
|
||||
* @param {string} [contentType]
|
||||
*/
|
||||
downloadData(data, filename, contentType) {}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {string} filename
|
||||
* @param {string | null} [dest]
|
||||
* @returns {boolean} Indicating if the data was opened.
|
||||
*/
|
||||
openOrDownloadData(data, filename, dest = null) {}
|
||||
|
||||
/**
|
||||
* @param {Uint8Array} data
|
||||
* @param {string} url
|
||||
* @param {string} filename
|
||||
*/
|
||||
download(data, url, filename) {}
|
||||
}
|
||||
|
||||
export { IDownloadManager, IRenderableView };
|
||||
export { IRenderableView };
|
||||
|
||||
@ -423,7 +423,7 @@ class PDFLinkService {
|
||||
}
|
||||
// Support opening of PDF attachments in the Firefox PDF Viewer,
|
||||
// which uses a couple of non-standard hash parameters; refer to
|
||||
// `DownloadManager.openOrDownloadData` in the firefoxcom.js file.
|
||||
// `DownloadManager._getOpenDataUrl` in the firefoxcom.js file.
|
||||
if (!params.has("filename") || !params.has("filedest")) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -20,7 +20,6 @@
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("../src/display/optional_content_config").OptionalContentConfig} OptionalContentConfig */
|
||||
/** @typedef {import("./event_utils").EventBus} EventBus */
|
||||
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
|
||||
// eslint-disable-next-line max-len
|
||||
/** @typedef {import("./pdf_find_controller").PDFFindController} PDFFindController */
|
||||
// eslint-disable-next-line max-len
|
||||
@ -89,7 +88,7 @@ function isValidAnnotationEditorMode(mode) {
|
||||
* @property {HTMLDivElement} [viewer] - The viewer element.
|
||||
* @property {EventBus} eventBus - The application event bus.
|
||||
* @property {PDFLinkService} [linkService] - The navigation/linking service.
|
||||
* @property {IDownloadManager} [downloadManager] - The download manager
|
||||
* @property {BaseDownloadManager} [downloadManager] - The download manager
|
||||
* component.
|
||||
* @property {PDFFindController} [findController] - The find controller
|
||||
* component.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user