[api-minor] Support the Content-Disposition filename in the Firefox PDF Viewer (bug 1694556, PR 9379 follow-up)

As can be seen [in the mozilla-central code](https://searchfox.org/mozilla-central/rev/a6db3bd67367aa9ddd9505690cab09b47e65a762/toolkit/components/pdfjs/content/PdfStreamConverter.jsm#1222-1225), we're already getting the Content-Disposition filename. However, that data isn't passed through to the viewer nor to the `PDFDataTransportStream`-implementation, which explains why it's currently being ignored.

*Please note:* This will also require a small mozilla-central patch, see https://bugzilla.mozilla.org/show_bug.cgi?id=1694556, to forward the necessary data to the viewer.
This commit is contained in:
Jonas Jenwald 2021-02-24 13:02:58 +01:00
parent 061637d3f4
commit 6fd899dc44
10 changed files with 55 additions and 30 deletions

View File

@ -334,6 +334,7 @@ function getDocument(src) {
length: params.length, length: params.length,
initialData: params.initialData, initialData: params.initialData,
progressiveDone: params.progressiveDone, progressiveDone: params.progressiveDone,
contentDispositionFilename: params.contentDispositionFilename,
disableRange: params.disableRange, disableRange: params.disableRange,
disableStream: params.disableStream, disableStream: params.disableStream,
}, },
@ -401,6 +402,8 @@ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
source.length = pdfDataRangeTransport.length; source.length = pdfDataRangeTransport.length;
source.initialData = pdfDataRangeTransport.initialData; source.initialData = pdfDataRangeTransport.initialData;
source.progressiveDone = pdfDataRangeTransport.progressiveDone; source.progressiveDone = pdfDataRangeTransport.progressiveDone;
source.contentDispositionFilename =
pdfDataRangeTransport.contentDispositionFilename;
} }
return worker.messageHandler return worker.messageHandler
.sendWithPromise("GetDocRequest", { .sendWithPromise("GetDocRequest", {
@ -554,11 +557,18 @@ class PDFDataRangeTransport {
* @param {number} length * @param {number} length
* @param {Uint8Array} initialData * @param {Uint8Array} initialData
* @param {boolean} [progressiveDone] * @param {boolean} [progressiveDone]
* @param {string} [contentDispositionFilename]
*/ */
constructor(length, initialData, progressiveDone = false) { constructor(
length,
initialData,
progressiveDone = false,
contentDispositionFilename = null
) {
this.length = length; this.length = length;
this.initialData = initialData; this.initialData = initialData;
this.progressiveDone = progressiveDone; this.progressiveDone = progressiveDone;
this.contentDispositionFilename = contentDispositionFilename;
this._rangeListeners = []; this._rangeListeners = [];
this._progressListeners = []; this._progressListeners = [];

View File

@ -451,6 +451,10 @@ function addLinkAttributes(link, { url, target, rel, enabled = true } = {}) {
link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL; link.rel = typeof rel === "string" ? rel : DEFAULT_LINK_REL;
} }
function isPdfFile(filename) {
return typeof filename === "string" && /\.pdf$/i.test(filename);
}
/** /**
* Gets the file name from a given URL. * Gets the file name from a given URL.
* @param {string} url * @param {string} url
@ -652,6 +656,7 @@ export {
DOMSVGFactory, DOMSVGFactory,
getFilenameFromUrl, getFilenameFromUrl,
isFetchSupported, isFetchSupported,
isPdfFile,
isValidFetchUrl, isValidFetchUrl,
LinkTarget, LinkTarget,
loadScript, loadScript,

View File

@ -19,6 +19,7 @@ import {
UnexpectedResponseException, UnexpectedResponseException,
} from "../shared/util.js"; } from "../shared/util.js";
import { getFilenameFromContentDispositionHeader } from "./content_disposition.js"; import { getFilenameFromContentDispositionHeader } from "./content_disposition.js";
import { isPdfFile } from "./display_utils.js";
function validateRangeRequestCapabilities({ function validateRangeRequestCapabilities({
getResponseHeader, getResponseHeader,
@ -70,7 +71,7 @@ function extractFilenameFromHeader(getResponseHeader) {
filename = decodeURIComponent(filename); filename = decodeURIComponent(filename);
} catch (ex) {} } catch (ex) {}
} }
if (/\.pdf$/i.test(filename)) { if (isPdfFile(filename)) {
return filename; return filename;
} }
} }
@ -82,11 +83,7 @@ function createResponseStatusError(status, url) {
return new MissingPDFException('Missing PDF "' + url + '".'); return new MissingPDFException('Missing PDF "' + url + '".');
} }
return new UnexpectedResponseException( return new UnexpectedResponseException(
"Unexpected server response (" + `Unexpected server response (${status}) while retrieving PDF "${url}".`,
status +
') while retrieving PDF "' +
url +
'".',
status status
); );
} }

View File

@ -14,6 +14,7 @@
*/ */
import { assert, createPromiseCapability } from "../shared/util.js"; import { assert, createPromiseCapability } from "../shared/util.js";
import { isPdfFile } from "./display_utils.js";
/** @implements {IPDFStream} */ /** @implements {IPDFStream} */
class PDFDataTransportStream { class PDFDataTransportStream {
@ -25,6 +26,8 @@ class PDFDataTransportStream {
this._queuedChunks = []; this._queuedChunks = [];
this._progressiveDone = params.progressiveDone || false; this._progressiveDone = params.progressiveDone || false;
this._contentDispositionFilename =
params.contentDispositionFilename || null;
const initialData = params.initialData; const initialData = params.initialData;
if (initialData?.length > 0) { if (initialData?.length > 0) {
@ -125,7 +128,8 @@ class PDFDataTransportStream {
return new PDFDataTransportStreamReader( return new PDFDataTransportStreamReader(
this, this,
queuedChunks, queuedChunks,
this._progressiveDone this._progressiveDone,
this._contentDispositionFilename
); );
} }
@ -153,10 +157,17 @@ class PDFDataTransportStream {
/** @implements {IPDFStreamReader} */ /** @implements {IPDFStreamReader} */
class PDFDataTransportStreamReader { class PDFDataTransportStreamReader {
constructor(stream, queuedChunks, progressiveDone = false) { constructor(
stream,
queuedChunks,
progressiveDone = false,
contentDispositionFilename = null
) {
this._stream = stream; this._stream = stream;
this._done = progressiveDone || false; this._done = progressiveDone || false;
this._filename = null; this._filename = isPdfFile(contentDispositionFilename)
? contentDispositionFilename
: null;
this._queuedChunks = queuedChunks || []; this._queuedChunks = queuedChunks || [];
this._loaded = 0; this._loaded = 0;
for (const chunk of this._queuedChunks) { for (const chunk of this._queuedChunks) {

View File

@ -18,6 +18,7 @@ import {
addLinkAttributes, addLinkAttributes,
getFilenameFromUrl, getFilenameFromUrl,
isFetchSupported, isFetchSupported,
isPdfFile,
isValidFetchUrl, isValidFetchUrl,
LinkTarget, LinkTarget,
loadScript, loadScript,
@ -128,6 +129,7 @@ export {
// From "./display/display_utils.js": // From "./display/display_utils.js":
addLinkAttributes, addLinkAttributes,
getFilenameFromUrl, getFilenameFromUrl,
isPdfFile,
LinkTarget, LinkTarget,
loadScript, loadScript,
PDFDateString, PDFDateString,

View File

@ -44,6 +44,7 @@ import {
getFilenameFromUrl, getFilenameFromUrl,
GlobalWorkerOptions, GlobalWorkerOptions,
InvalidPDFException, InvalidPDFException,
isPdfFile,
LinkTarget, LinkTarget,
loadScript, loadScript,
MissingPDFException, MissingPDFException,
@ -727,7 +728,10 @@ const PDFViewerApplication = {
onOpenWithTransport: (url, length, transport) => { onOpenWithTransport: (url, length, transport) => {
this.open(url, { length, range: transport }); this.open(url, { length, range: transport });
}, },
onOpenWithData: data => { onOpenWithData: (data, contentDispositionFilename) => {
if (isPdfFile(contentDispositionFilename)) {
this._contentDispositionFilename = contentDispositionFilename;
}
this.open(data); this.open(data);
}, },
onOpenWithURL: (url, length, originalUrl) => { onOpenWithURL: (url, length, originalUrl) => {
@ -1744,7 +1748,7 @@ const PDFViewerApplication = {
} }
this.documentInfo = info; this.documentInfo = info;
this.metadata = metadata; this.metadata = metadata;
this._contentDispositionFilename = contentDispositionFilename; this._contentDispositionFilename ??= contentDispositionFilename;
this._contentLength ??= contentLength; // See `getDownloadInfo`-call above. this._contentLength ??= contentLength; // See `getDownloadInfo`-call above.
// Provides some basic debug information // Provides some basic debug information

View File

@ -13,8 +13,7 @@
* limitations under the License. * limitations under the License.
*/ */
import { createObjectURL, createValidAbsoluteUrl } from "pdfjs-lib"; import { createObjectURL, createValidAbsoluteUrl, isPdfFile } 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")) {
@ -68,10 +67,10 @@ class DownloadManager {
* @returns {boolean} Indicating if the data was opened. * @returns {boolean} Indicating if the data was opened.
*/ */
openOrDownloadData(element, data, filename) { openOrDownloadData(element, data, filename) {
const isPdfFile = PdfFileRegExp.test(filename); const isPdfData = isPdfFile(filename);
const contentType = isPdfFile ? "application/pdf" : ""; const contentType = isPdfData ? "application/pdf" : "";
if (isPdfFile && !viewerCompatibilityParams.disableCreateObjectURL) { if (isPdfData && !viewerCompatibilityParams.disableCreateObjectURL) {
let blobUrl = this._openBlobUrls.get(element); let blobUrl = this._openBlobUrls.get(element);
if (!blobUrl) { if (!blobUrl) {
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType })); blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));

View File

@ -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 { isPdfFile, 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(
@ -129,10 +129,10 @@ class DownloadManager {
* @returns {boolean} Indicating if the data was opened. * @returns {boolean} Indicating if the data was opened.
*/ */
openOrDownloadData(element, data, filename) { openOrDownloadData(element, data, filename) {
const isPdfFile = PdfFileRegExp.test(filename); const isPdfData = isPdfFile(filename);
const contentType = isPdfFile ? "application/pdf" : ""; const contentType = isPdfData ? "application/pdf" : "";
if (isPdfFile) { if (isPdfData) {
let blobUrl = this._openBlobUrls.get(element); let blobUrl = this._openBlobUrls.get(element);
if (!blobUrl) { if (!blobUrl) {
blobUrl = URL.createObjectURL(new Blob([data], { type: contentType })); blobUrl = URL.createObjectURL(new Blob([data], { type: contentType }));
@ -332,7 +332,8 @@ class FirefoxExternalServices extends DefaultExternalServices {
pdfDataRangeTransport = new FirefoxComDataRangeTransport( pdfDataRangeTransport = new FirefoxComDataRangeTransport(
args.length, args.length,
args.data, args.data,
args.done args.done,
args.filename
); );
callbacks.onOpenWithTransport( callbacks.onOpenWithTransport(
@ -367,7 +368,7 @@ class FirefoxExternalServices extends DefaultExternalServices {
callbacks.onError(args.errorCode); callbacks.onError(args.errorCode);
break; break;
} }
callbacks.onOpenWithData(args.data); callbacks.onOpenWithData(args.data, args.filename);
break; break;
} }
}); });

View File

@ -119,8 +119,8 @@ 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 content = item.content,
const filename = getFilenameFromUrl(item.filename); filename = getFilenameFromUrl(item.filename);
const div = document.createElement("div"); const div = document.createElement("div");
div.className = "treeItem"; div.className = "treeItem";

View File

@ -69,9 +69,6 @@ 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) {
@ -1062,7 +1059,6 @@ export {
normalizeWheelEventDirection, normalizeWheelEventDirection,
NullL10n, NullL10n,
parseQueryString, parseQueryString,
PdfFileRegExp,
PresentationModeState, PresentationModeState,
ProgressBar, ProgressBar,
RendererType, RendererType,