Skip to content

Commit 9e8d4e4

Browse files
committed
[api-minor] Attempt to support fetching the raw data of the PDF document from the PDFDocumentLoadingTask-instance (issue 15085)
The new API-functionality will allow a PDF document to be downloaded in the viewer e.g. while the PasswordPrompt is open, or in cases when document initialization failed. Normally the raw data of the PDF document would be accessed via the `PDFDocumentProxy.prototype.getData` method, however in these cases the `PDFDocumentProxy`-instance isn't available.
1 parent d1d88cc commit 9e8d4e4

3 files changed

Lines changed: 92 additions & 30 deletions

File tree

src/display/api.js

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -629,39 +629,48 @@ const isValidExplicitDest = _isValidExplicitDest.bind(
629629
class PDFDocumentLoadingTask {
630630
static #docId = 0;
631631

632-
constructor() {
633-
this._capability = Promise.withResolvers();
634-
this._transport = null;
635-
this._worker = null;
632+
/**
633+
* @private
634+
*/
635+
_capability = Promise.withResolvers();
636636

637-
/**
638-
* Unique identifier for the document loading task.
639-
* @type {string}
640-
*/
641-
this.docId = `d${PDFDocumentLoadingTask.#docId++}`;
637+
/**
638+
* @private
639+
*/
640+
_transport = null;
642641

643-
/**
644-
* Whether the loading task is destroyed or not.
645-
* @type {boolean}
646-
*/
647-
this.destroyed = false;
642+
/**
643+
* @private
644+
*/
645+
_worker = null;
648646

649-
/**
650-
* Callback to request a password if a wrong or no password was provided.
651-
* The callback receives two parameters: a function that should be called
652-
* with the new password, and a reason (see {@link PasswordResponses}).
653-
* @type {function}
654-
*/
655-
this.onPassword = null;
647+
/**
648+
* Unique identifier for the document loading task.
649+
* @type {string}
650+
*/
651+
docId = `d${PDFDocumentLoadingTask.#docId++}`;
656652

657-
/**
658-
* Callback to be able to monitor the loading progress of the PDF file
659-
* (necessary to implement e.g. a loading bar).
660-
* The callback receives an {@link OnProgressParameters} argument.
661-
* @type {function}
662-
*/
663-
this.onProgress = null;
664-
}
653+
/**
654+
* Whether the loading task is destroyed or not.
655+
* @type {boolean}
656+
*/
657+
destroyed = false;
658+
659+
/**
660+
* Callback to request a password if a wrong or no password was provided.
661+
* The callback receives two parameters: a function that should be called
662+
* with the new password, and a reason (see {@link PasswordResponses}).
663+
* @type {function}
664+
*/
665+
onPassword = null;
666+
667+
/**
668+
* Callback to be able to monitor the loading progress of the PDF file
669+
* (necessary to implement e.g. a loading bar).
670+
* The callback receives an {@link OnProgressParameters} argument.
671+
* @type {function}
672+
*/
673+
onProgress = null;
665674

666675
/**
667676
* Promise for document loading task completion.
@@ -699,6 +708,16 @@ class PDFDocumentLoadingTask {
699708
this._worker?.destroy();
700709
this._worker = null;
701710
}
711+
712+
/**
713+
* Attempt to fetch the raw data of the PDF document, when e.g.
714+
* - An exception was thrown during document initialization.
715+
* - An `onPassword` callback is delaying initialization.
716+
* @returns {Promise<Uint8Array>}
717+
*/
718+
async getData() {
719+
return this._transport.getData();
720+
}
702721
}
703722

704723
/**

test/unit/api_spec.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -828,6 +828,47 @@ describe("api", function () {
828828

829829
await loadingTask.destroy();
830830
});
831+
832+
it("gets data, on failure, from `PDFDocumentLoadingTask`-instance", async function () {
833+
const typedArrayPdf = await DefaultFileReaderFactory.fetch({
834+
path: TEST_PDFS_PATH + "issue6010_1.pdf",
835+
});
836+
837+
// Sanity check to make sure that we fetched the entire PDF file.
838+
expect(typedArrayPdf instanceof Uint8Array).toEqual(true);
839+
expect(typedArrayPdf.length).toEqual(1116);
840+
841+
const loadingTask = getDocument(typedArrayPdf.slice());
842+
expect(loadingTask instanceof PDFDocumentLoadingTask).toEqual(true);
843+
844+
let passwordData = null;
845+
// Attach the callback that is used to request a password;
846+
// similarly to how the default viewer handles passwords.
847+
loadingTask.onPassword = async (updatePassword, reason) => {
848+
passwordData = await loadingTask.getData();
849+
850+
updatePassword(new Error("Should reject the loadingTask."));
851+
};
852+
853+
try {
854+
await loadingTask.promise;
855+
856+
// Shouldn't get here.
857+
expect(false).toEqual(true);
858+
} catch (ex) {
859+
expect(ex instanceof PasswordException).toEqual(true);
860+
expect(ex.code).toEqual(PasswordResponses.NEED_PASSWORD);
861+
}
862+
863+
// Ensure that the raw PDF document can be fetched while
864+
// an `onPassword` callback is delaying initialization...
865+
expect(passwordData).toEqual(typedArrayPdf);
866+
// ... and once an exception has stopped initialization.
867+
const data = await loadingTask.getData();
868+
expect(data).toEqual(typedArrayPdf);
869+
870+
await loadingTask.destroy();
871+
});
831872
});
832873

833874
describe("PDFWorker", function () {

web/app.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,9 @@ const PDFViewerApplication = {
11531153
async download() {
11541154
let data;
11551155
try {
1156-
data = await this.pdfDocument.getData();
1156+
data = await (this.pdfDocument
1157+
? this.pdfDocument.getData()
1158+
: this.pdfLoadingTask.getData());
11571159
} catch {
11581160
// When the PDF document isn't ready, simply download using the URL.
11591161
}

0 commit comments

Comments
 (0)