Skip to content

Commit 839c257

Browse files
committed
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.
1 parent ff7f87f commit 839c257

10 files changed

Lines changed: 176 additions & 188 deletions

gulpfile.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -231,7 +231,7 @@ function createWebpackAlias(defines) {
231231
libraryAlias["display-fetch_stream"] = "src/display/fetch_stream.js";
232232
libraryAlias["display-network"] = "src/display/network.js";
233233

234-
viewerAlias["web-download_manager"] = "web/download_manager.js";
234+
viewerAlias["web-download_manager"] = "web/chromecom.js";
235235
viewerAlias["web-external_services"] = "web/chromecom.js";
236236
viewerAlias["web-null_l10n"] = "web/l10n.js";
237237
viewerAlias["web-preferences"] = "web/chromecom.js";

src/display/annotation_layer.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
// eslint-disable-next-line max-len
1919
/** @typedef {import("../../web/text_accessibility.js").TextAccessibilityManager} TextAccessibilityManager */
2020
// eslint-disable-next-line max-len
21-
/** @typedef {import("../../web/interfaces").IDownloadManager} IDownloadManager */
22-
// eslint-disable-next-line max-len
2321
/** @typedef {import("../src/display/editor/tools.js").AnnotationEditorUIManager} AnnotationEditorUIManager */
2422
// eslint-disable-next-line max-len
2523
/** @typedef {import("../../web/struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
@@ -57,7 +55,7 @@ const TIMEZONE_OFFSET = new Date().getTimezoneOffset() * 60 * 1000;
5755
* @property {Object} data
5856
* @property {HTMLDivElement} layer
5957
* @property {PDFLinkService} linkService
60-
* @property {IDownloadManager} [downloadManager]
58+
* @property {BaseDownloadManager} [downloadManager]
6159
* @property {AnnotationStorage} [annotationStorage]
6260
* @property {string} [imageResourcesPath] - Path for image resources, mainly
6361
* for annotation icons. Include trailing slash.
@@ -3736,7 +3734,7 @@ class FileAttachmentAnnotationElement extends AnnotationElement {
37363734
* @property {Array} annotations
37373735
* @property {PDFPageProxy} page
37383736
* @property {PDFLinkService} linkService
3739-
* @property {IDownloadManager} [downloadManager]
3737+
* @property {BaseDownloadManager} [downloadManager]
37403738
* @property {AnnotationStorage} [annotationStorage]
37413739
* @property {string} [imageResourcesPath] - Path for image resources, mainly
37423740
* for annotation icons. Include trailing slash.

web/annotation_layer_builder.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
/** @typedef {import("../src/display/display_utils").PageViewport} PageViewport */
1919
// eslint-disable-next-line max-len
2020
/** @typedef {import("../src/display/annotation_storage").AnnotationStorage} AnnotationStorage */
21-
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
2221
// eslint-disable-next-line max-len
2322
/** @typedef {import("./struct_tree_layer_builder.js").StructTreeLayerBuilder} StructTreeLayerBuilder */
2423
// eslint-disable-next-line max-len
@@ -43,7 +42,7 @@ import { PresentationModeState } from "./ui_utils.js";
4342
* for annotation icons. Include trailing slash.
4443
* @property {boolean} renderForms
4544
* @property {PDFLinkService} linkService
46-
* @property {IDownloadManager} [downloadManager]
45+
* @property {BaseDownloadManager} [downloadManager]
4746
* @property {boolean} [enableComment]
4847
* @property {boolean} [enableScripting]
4948
* @property {Promise<boolean>} [hasJSActionsPromise]

web/base_download_manager.js

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
/* Copyright 2013 Mozilla Foundation
2+
*
3+
* Licensed under the Apache License, Version 2.0 (the "License");
4+
* you may not use this file except in compliance with the License.
5+
* You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software
10+
* distributed under the License is distributed on an "AS IS" BASIS,
11+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
* See the License for the specific language governing permissions and
13+
* limitations under the License.
14+
*/
15+
16+
import { isPdfFile } from "pdfjs-lib";
17+
18+
class BaseDownloadManager {
19+
#openBlobUrls = new WeakMap();
20+
21+
constructor() {
22+
if (
23+
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
24+
this.constructor === BaseDownloadManager
25+
) {
26+
throw new Error("Cannot initialize BaseDownloadManager.");
27+
}
28+
}
29+
30+
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
31+
throw new Error("Not implemented: _triggerDownload");
32+
}
33+
34+
_getOpenDataUrl(blobUrl, filename, dest = null) {
35+
throw new Error("Not implemented: _getOpenDataUrl");
36+
}
37+
38+
/**
39+
* @param {Uint8Array} data
40+
* @param {string} filename
41+
* @param {string} [contentType]
42+
*/
43+
downloadData(data, filename, contentType) {
44+
const blobUrl = URL.createObjectURL(
45+
new Blob([data], { type: contentType })
46+
);
47+
48+
this._triggerDownload(
49+
blobUrl,
50+
/* originalUrl = */ blobUrl,
51+
filename,
52+
/* isAttachment = */ true
53+
);
54+
}
55+
56+
/**
57+
* @param {Uint8Array} data
58+
* @param {string} filename
59+
* @param {string | null} [dest]
60+
* @returns {boolean} Indicating if the data was opened.
61+
*/
62+
openOrDownloadData(data, filename, dest = null) {
63+
const isPdfData = isPdfFile(filename);
64+
const contentType = isPdfData ? "application/pdf" : "";
65+
66+
if (isPdfData) {
67+
let blobUrl;
68+
try {
69+
blobUrl = this.#openBlobUrls.getOrInsertComputed(data, () =>
70+
URL.createObjectURL(new Blob([data], { type: contentType }))
71+
);
72+
const viewerUrl = this._getOpenDataUrl(blobUrl, filename, dest);
73+
74+
window.open(viewerUrl);
75+
return true;
76+
} catch (ex) {
77+
console.error("openOrDownloadData:", ex);
78+
// Release the `blobUrl`, since opening it failed, and fallback to
79+
// downloading the PDF file.
80+
URL.revokeObjectURL(blobUrl);
81+
this.#openBlobUrls.delete(data);
82+
}
83+
}
84+
85+
this.downloadData(data, filename, contentType);
86+
return false;
87+
}
88+
89+
/**
90+
* @param {Uint8Array} data
91+
* @param {string} url
92+
* @param {string} filename
93+
*/
94+
download(data, url, filename) {
95+
const blobUrl = data
96+
? URL.createObjectURL(new Blob([data], { type: "application/pdf" }))
97+
: null;
98+
99+
this._triggerDownload(blobUrl, /* originalUrl = */ url, filename);
100+
}
101+
}
102+
103+
export { BaseDownloadManager };

web/chromecom.js

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import { AppOptions } from "./app_options.js";
1818
import { BaseExternalServices } from "./external_services.js";
1919
import { BasePreferences } from "./preferences.js";
20+
import { DownloadManager as GenericDownloadManager } from "./download_manager.js";
2021
import { GenericL10n } from "./genericl10n.js";
2122
import { GenericScripting } from "./generic_scripting.js";
2223
import { SignatureStorage } from "./generic_signature_storage.js";
@@ -310,6 +311,25 @@ function setReferer(url, callback) {
310311
}
311312
}
312313

314+
/**
315+
* This "should" really extend the `BaseDownloadManager` class,
316+
* however doing it this way instead reduces code duplication.
317+
*/
318+
class DownloadManager extends GenericDownloadManager {
319+
_getOpenDataUrl(blobUrl, filename, dest = null) {
320+
// In the Chrome extension, the URL is rewritten using the history API
321+
// in viewer.js, so an absolute URL must be generated.
322+
let url =
323+
chrome.runtime.getURL("/content/web/viewer.html") +
324+
"?file=" +
325+
encodeURIComponent(blobUrl + "#" + filename);
326+
if (dest) {
327+
url += `#${escape(dest)}`;
328+
}
329+
return url;
330+
}
331+
}
332+
313333
// chrome.storage.sync is not supported in every Chromium-derivate.
314334
// Note: The background page takes care of migrating values from
315335
// chrome.storage.local to chrome.storage.sync when needed.
@@ -437,4 +457,4 @@ class MLManager {
437457
}
438458
}
439459

440-
export { ExternalServices, initCom, MLManager, Preferences };
460+
export { DownloadManager, ExternalServices, initCom, MLManager, Preferences };

web/download_manager.js

Lines changed: 31 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,8 @@
1313
* limitations under the License.
1414
*/
1515

16-
/** @typedef {import("./interfaces").IDownloadManager} IDownloadManager */
17-
18-
import { createValidAbsoluteUrl, isPdfFile } from "pdfjs-lib";
16+
import { BaseDownloadManager } from "./base_download_manager.js";
17+
import { createValidAbsoluteUrl } from "pdfjs-lib";
1918

2019
if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
2120
throw new Error(
@@ -24,101 +23,41 @@ if (typeof PDFJSDev !== "undefined" && !PDFJSDev.test("CHROME || GENERIC")) {
2423
);
2524
}
2625

27-
function download(blobUrl, filename) {
28-
const a = document.createElement("a");
29-
if (!a.click) {
30-
throw new Error('DownloadManager: "a.click()" is not supported.');
31-
}
32-
a.href = blobUrl;
33-
a.target = "_parent";
34-
// Use a.download if available. This increases the likelihood that
35-
// the file is downloaded instead of opened by another PDF plugin.
36-
if ("download" in a) {
37-
a.download = filename;
38-
}
39-
// <a> must be in the document for recent Firefox versions,
40-
// otherwise .click() is ignored.
41-
(document.body || document.documentElement).append(a);
42-
a.click();
43-
a.remove();
44-
}
45-
46-
/**
47-
* @implements {IDownloadManager}
48-
*/
49-
class DownloadManager {
50-
#openBlobUrls = new WeakMap();
51-
52-
downloadData(data, filename, contentType) {
53-
const blobUrl = URL.createObjectURL(
54-
new Blob([data], { type: contentType })
55-
);
56-
download(blobUrl, filename);
57-
}
58-
59-
/**
60-
* @returns {boolean} Indicating if the data was opened.
61-
*/
62-
openOrDownloadData(data, filename, dest = null) {
63-
const isPdfData = isPdfFile(filename);
64-
const contentType = isPdfData ? "application/pdf" : "";
65-
66-
if (
67-
(typeof PDFJSDev === "undefined" || !PDFJSDev.test("COMPONENTS")) &&
68-
isPdfData
69-
) {
70-
let blobUrl = this.#openBlobUrls.get(data);
71-
if (!blobUrl) {
72-
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
73-
this.#openBlobUrls.set(data, blobUrl);
74-
}
75-
let viewerUrl;
76-
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
77-
// The current URL is the viewer, let's use it and append the file.
78-
viewerUrl = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
79-
} else if (PDFJSDev.test("CHROME")) {
80-
// In the Chrome extension, the URL is rewritten using the history API
81-
// in viewer.js, so an absolute URL must be generated.
82-
viewerUrl =
83-
// eslint-disable-next-line no-undef
84-
chrome.runtime.getURL("/content/web/viewer.html") +
85-
"?file=" +
86-
encodeURIComponent(blobUrl + "#" + filename);
87-
}
88-
if (dest) {
89-
viewerUrl += `#${escape(dest)}`;
90-
}
91-
92-
try {
93-
window.open(viewerUrl);
94-
return true;
95-
} catch (ex) {
96-
console.error("openOrDownloadData:", ex);
97-
// Release the `blobUrl`, since opening it failed, and fallback to
98-
// downloading the PDF file.
99-
URL.revokeObjectURL(blobUrl);
100-
this.#openBlobUrls.delete(data);
26+
class DownloadManager extends BaseDownloadManager {
27+
_triggerDownload(blobUrl, originalUrl, filename, isAttachment = false) {
28+
if (!blobUrl && !isAttachment) {
29+
// Fallback to downloading non-attachments by their URL.
30+
if (!createValidAbsoluteUrl(originalUrl, "http://example.com")) {
31+
throw new Error(`_triggerDownload - not a valid URL: ${originalUrl}`);
10132
}
33+
blobUrl = originalUrl + "#pdfjs.action=download";
10234
}
10335

104-
this.downloadData(data, filename, contentType);
105-
return false;
36+
const a = document.createElement("a");
37+
a.href = blobUrl;
38+
a.target = "_parent";
39+
// Use a.download if available. This increases the likelihood that
40+
// the file is downloaded instead of opened by another PDF plugin.
41+
if ("download" in a) {
42+
a.download = filename;
43+
}
44+
// <a> must be in the document for recent Firefox versions,
45+
// otherwise .click() is ignored.
46+
(document.body || document.documentElement).append(a);
47+
a.click();
48+
a.remove();
10649
}
10750

108-
download(data, url, filename) {
109-
let blobUrl;
110-
if (data) {
111-
blobUrl = URL.createObjectURL(
112-
new Blob([data], { type: "application/pdf" })
113-
);
114-
} else {
115-
if (!createValidAbsoluteUrl(url, "http://example.com")) {
116-
console.error(`download - not a valid URL: ${url}`);
117-
return;
118-
}
119-
blobUrl = url + "#pdfjs.action=download";
51+
_getOpenDataUrl(blobUrl, filename, dest = null) {
52+
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("COMPONENTS")) {
53+
throw new Error("Opening data is not supported in `COMPONENTS` builds.");
54+
}
55+
// The current URL is the viewer, let's use it and append the file.
56+
let url = "?file=" + encodeURIComponent(blobUrl + "#" + filename);
57+
if (dest) {
58+
url += `#${escape(dest)}`;
12059
}
121-
download(blobUrl, filename);
60+
return url;
12261
}
12362
}
12463

0 commit comments

Comments
 (0)