Skip to content

Commit ecb09d6

Browse files
committed
Add the current loading percentage to the onPassword callback
The percentage calculation is currently "spread out" across various viewer functionality, which we can avoid by having the API handle that instead. Also, remove the `this.#lastProgress` special-case[1] and just register a "normal" `fullReader.onProgress` callback unconditionally. Once `headersReady` is resolved the callback can simply be removed when not needed, since the "worst" thing that could theoretically happen is that the loadingBar (in the viewer) updates sooner this way. In practice though, since `fullReader.read` cannot return data until `headersReady` is resolved, this change is not actually observable in the API. --- [1] This was added in PR 8617, close to a decade ago, but it's not obvious to me that it was ever necessary to implement it that way.
1 parent 4ca205b commit ecb09d6

File tree

6 files changed

+94
-113
lines changed

6 files changed

+94
-113
lines changed

examples/mobile-viewer/viewer.mjs

Lines changed: 47 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -46,19 +46,13 @@ const PDFViewerApplication = {
4646
* @returns {Promise} - Returns the promise, which is resolved when document
4747
* is opened.
4848
*/
49-
open(params) {
49+
async open(params) {
5050
if (this.pdfLoadingTask) {
51-
// We need to destroy already opened document
52-
return this.close().then(
53-
function () {
54-
// ... and repeat the open() call.
55-
return this.open(params);
56-
}.bind(this)
57-
);
51+
// We need to destroy already opened document.
52+
await this.close();
5853
}
5954

60-
const url = params.url;
61-
const self = this;
55+
const { url } = params;
6256
this.setTitleUsingUrl(url);
6357

6458
// Loading document.
@@ -70,24 +64,22 @@ const PDFViewerApplication = {
7064
});
7165
this.pdfLoadingTask = loadingTask;
7266

73-
loadingTask.onProgress = function (progressData) {
74-
self.progress(progressData.loaded / progressData.total);
75-
};
67+
loadingTask.onProgress = evt => this.progress(evt.percent);
7668

7769
return loadingTask.promise.then(
78-
function (pdfDocument) {
70+
pdfDocument => {
7971
// Document loaded, specifying document for the viewer.
80-
self.pdfDocument = pdfDocument;
81-
self.pdfViewer.setDocument(pdfDocument);
82-
self.pdfLinkService.setDocument(pdfDocument);
83-
self.pdfHistory.initialize({
72+
this.pdfDocument = pdfDocument;
73+
this.pdfViewer.setDocument(pdfDocument);
74+
this.pdfLinkService.setDocument(pdfDocument);
75+
this.pdfHistory.initialize({
8476
fingerprint: pdfDocument.fingerprints[0],
8577
});
8678

87-
self.loadingBar.hide();
88-
self.setTitleUsingMetadata(pdfDocument);
79+
this.loadingBar.hide();
80+
this.setTitleUsingMetadata(pdfDocument);
8981
},
90-
function (reason) {
82+
reason => {
9183
let key = "pdfjs-loading-error";
9284
if (reason instanceof pdfjsLib.InvalidPDFException) {
9385
key = "pdfjs-invalid-file-error";
@@ -96,10 +88,10 @@ const PDFViewerApplication = {
9688
? "pdfjs-missing-file-error"
9789
: "pdfjs-unexpected-response-error";
9890
}
99-
self.l10n.get(key).then(msg => {
100-
self.error(msg, { message: reason?.message });
91+
this.l10n.get(key).then(msg => {
92+
this.error(msg, { message: reason.message });
10193
});
102-
self.loadingBar.hide();
94+
this.loadingBar.hide();
10395
}
10496
);
10597
},
@@ -109,9 +101,9 @@ const PDFViewerApplication = {
109101
* @returns {Promise} - Returns the promise, which is resolved when all
110102
* destruction is completed.
111103
*/
112-
close() {
104+
async close() {
113105
if (!this.pdfLoadingTask) {
114-
return Promise.resolve();
106+
return;
115107
}
116108

117109
const promise = this.pdfLoadingTask.destroy();
@@ -128,7 +120,7 @@ const PDFViewerApplication = {
128120
}
129121
}
130122

131-
return promise;
123+
await promise;
132124
},
133125

134126
get loadingBar() {
@@ -152,48 +144,36 @@ const PDFViewerApplication = {
152144
this.setTitle(title);
153145
},
154146

155-
setTitleUsingMetadata(pdfDocument) {
156-
const self = this;
157-
pdfDocument.getMetadata().then(function (data) {
158-
const info = data.info,
159-
metadata = data.metadata;
160-
self.documentInfo = info;
161-
self.metadata = metadata;
162-
163-
// Provides some basic debug information
164-
console.log(
165-
"PDF " +
166-
pdfDocument.fingerprints[0] +
167-
" [" +
168-
info.PDFFormatVersion +
169-
" " +
170-
(info.Producer || "-").trim() +
171-
" / " +
172-
(info.Creator || "-").trim() +
173-
"]" +
174-
" (PDF.js: " +
175-
(pdfjsLib.version || "-") +
176-
")"
177-
);
178-
179-
let pdfTitle;
180-
if (metadata && metadata.has("dc:title")) {
181-
const title = metadata.get("dc:title");
182-
// Ghostscript sometimes returns 'Untitled', so prevent setting the
183-
// title to 'Untitled.
184-
if (title !== "Untitled") {
185-
pdfTitle = title;
186-
}
187-
}
147+
async setTitleUsingMetadata(pdfDocument) {
148+
const { info, metadata } = await pdfDocument.getMetadata();
149+
this.documentInfo = info;
150+
this.metadata = metadata;
151+
152+
// Provides some basic debug information
153+
console.log(
154+
`PDF ${pdfDocument.fingerprints[0]} [${info.PDFFormatVersion} ` +
155+
`${(metadata?.get("pdf:producer") || info.Producer || "-").trim()} / ` +
156+
`${(metadata?.get("xmp:creatortool") || info.Creator || "-").trim()}` +
157+
`] (PDF.js: ${pdfjsLib.version || "?"} [${pdfjsLib.build || "?"}])`
158+
);
188159

189-
if (!pdfTitle && info && info.Title) {
190-
pdfTitle = info.Title;
160+
let pdfTitle;
161+
if (metadata && metadata.has("dc:title")) {
162+
const title = metadata.get("dc:title");
163+
// Ghostscript sometimes returns 'Untitled', so prevent setting the
164+
// title to 'Untitled.
165+
if (title !== "Untitled") {
166+
pdfTitle = title;
191167
}
168+
}
192169

193-
if (pdfTitle) {
194-
self.setTitle(pdfTitle + " - " + document.title);
195-
}
196-
});
170+
if (!pdfTitle && info && info.Title) {
171+
pdfTitle = info.Title;
172+
}
173+
174+
if (pdfTitle) {
175+
this.setTitle(pdfTitle + " - " + document.title);
176+
}
197177
},
198178

199179
setTitle: function pdfViewSetTitle(title) {
@@ -223,8 +203,7 @@ const PDFViewerApplication = {
223203
console.error(`${message}\n\n${moreInfoText.join("\n")}`);
224204
},
225205

226-
progress: function pdfViewProgress(level) {
227-
const percent = Math.round(level * 100);
206+
progress(percent) {
228207
// Updating the bar if value increases.
229208
if (percent > this.loadingBar.percent || isNaN(percent)) {
230209
this.loadingBar.percent = percent;

src/display/api.js

Lines changed: 19 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
getVerbosityLevel,
2626
info,
2727
isNodeJS,
28+
MathClamp,
2829
RenderingIntentFlag,
2930
setVerbosityLevel,
3031
shadow,
@@ -525,6 +526,8 @@ function getDocument(src = {}) {
525526
* @typedef {Object} OnProgressParameters
526527
* @property {number} loaded - Currently loaded number of bytes.
527528
* @property {number} total - Total number of bytes in the PDF file.
529+
* @property {number} percent - Currently loaded percentage, as an integer value
530+
* in the [0, 100] range. If `total` is undefined, the percentage is `NaN`.
528531
*/
529532

530533
/**
@@ -2396,8 +2399,6 @@ class WorkerTransport {
23962399

23972400
#fullReader = null;
23982401

2399-
#lastProgress = null;
2400-
24012402
#methodPromises = new Map();
24022403

24032404
#networkStream = null;
@@ -2492,6 +2493,14 @@ class WorkerTransport {
24922493
return promise;
24932494
}
24942495

2496+
#onProgress({ loaded, total }) {
2497+
this.loadingTask.onProgress?.({
2498+
loaded,
2499+
total,
2500+
percent: MathClamp(Math.round((loaded / total) * 100), 0, 100),
2501+
});
2502+
}
2503+
24952504
get annotationStorage() {
24962505
return shadow(this, "annotationStorage", new AnnotationStorage());
24972506
}
@@ -2624,12 +2633,10 @@ class WorkerTransport {
26242633
"GetReader - no `BasePDFStream` instance available."
26252634
);
26262635
this.#fullReader = this.#networkStream.getFullReader();
2627-
this.#fullReader.onProgress = evt => {
2628-
this.#lastProgress = {
2629-
loaded: evt.loaded,
2630-
total: evt.total,
2631-
};
2632-
};
2636+
// If stream or range turn out to be disabled, once `headersReady` is
2637+
// resolved, this is our only way to report loading progress.
2638+
this.#fullReader.onProgress = evt => this.#onProgress(evt);
2639+
26332640
sink.onPull = () => {
26342641
this.#fullReader
26352642
.read()
@@ -2669,20 +2676,9 @@ class WorkerTransport {
26692676
const { isStreamingSupported, isRangeSupported, contentLength } =
26702677
this.#fullReader;
26712678

2672-
// If stream or range are disabled, it's our only way to report
2673-
// loading progress.
2674-
if (!isStreamingSupported || !isRangeSupported) {
2675-
if (this.#lastProgress) {
2676-
loadingTask.onProgress?.(this.#lastProgress);
2677-
}
2678-
this.#fullReader.onProgress = evt => {
2679-
loadingTask.onProgress?.({
2680-
loaded: evt.loaded,
2681-
total: evt.total,
2682-
});
2683-
};
2679+
if (isStreamingSupported && isRangeSupported) {
2680+
this.#fullReader.onProgress = null; // See comment in "GetReader" above.
26842681
}
2685-
26862682
return { isStreamingSupported, isRangeSupported, contentLength };
26872683
});
26882684

@@ -2779,10 +2775,7 @@ class WorkerTransport {
27792775
messageHandler.on("DataLoaded", data => {
27802776
// For consistency: Ensure that progress is always reported when the
27812777
// entire PDF file has been loaded, regardless of how it was fetched.
2782-
loadingTask.onProgress?.({
2783-
loaded: data.length,
2784-
total: data.length,
2785-
});
2778+
this.#onProgress({ loaded: data.length, total: data.length });
27862779

27872780
this.downloadInfoCapability.resolve(data);
27882781
});
@@ -2905,10 +2898,7 @@ class WorkerTransport {
29052898
if (this.destroyed) {
29062899
return; // Ignore any pending requests if the worker was terminated.
29072900
}
2908-
loadingTask.onProgress?.({
2909-
loaded: data.loaded,
2910-
total: data.total,
2911-
});
2901+
this.#onProgress(data);
29122902
});
29132903

29142904
messageHandler.on("FetchBinaryData", async data => {

test/unit/api_spec.js

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -160,14 +160,18 @@ describe("api", function () {
160160
progressReportedCapability.resolve(progressData);
161161
};
162162

163-
const data = await Promise.all([
164-
progressReportedCapability.promise,
163+
const [pdfDoc, progress] = await Promise.all([
165164
loadingTask.promise,
165+
progressReportedCapability.promise,
166166
]);
167167

168-
expect(data[0].loaded / data[0].total >= 0).toEqual(true);
169-
expect(data[1] instanceof PDFDocumentProxy).toEqual(true);
170-
expect(loadingTask).toEqual(data[1].loadingTask);
168+
expect(pdfDoc instanceof PDFDocumentProxy).toEqual(true);
169+
expect(pdfDoc.loadingTask).toBe(loadingTask);
170+
171+
expect(progress.loaded).toBeGreaterThanOrEqual(0);
172+
expect(progress.total).toEqual(basicApiFileLength);
173+
expect(progress.percent).toBeGreaterThanOrEqual(0);
174+
expect(progress.percent).toBeLessThanOrEqual(100);
171175

172176
await loadingTask.destroy();
173177
});
@@ -218,12 +222,17 @@ describe("api", function () {
218222
progressReportedCapability.resolve(data);
219223
};
220224

221-
const data = await Promise.all([
225+
const [pdfDoc, progress] = await Promise.all([
222226
loadingTask.promise,
223227
progressReportedCapability.promise,
224228
]);
225-
expect(data[0] instanceof PDFDocumentProxy).toEqual(true);
226-
expect(data[1].loaded / data[1].total).toEqual(1);
229+
230+
expect(pdfDoc instanceof PDFDocumentProxy).toEqual(true);
231+
expect(pdfDoc.loadingTask).toBe(loadingTask);
232+
233+
expect(progress.loaded).toEqual(basicApiFileLength);
234+
expect(progress.total).toEqual(basicApiFileLength);
235+
expect(progress.percent).toEqual(100);
227236

228237
// Check that the TypedArray was transferred.
229238
expect(typedArrayPdf.length).toEqual(0);

web/app.js

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1236,9 +1236,7 @@ const PDFViewerApplication = {
12361236
this.passwordPrompt.open();
12371237
};
12381238

1239-
loadingTask.onProgress = ({ loaded, total }) => {
1240-
this.progress(loaded / total);
1241-
};
1239+
loadingTask.onProgress = evt => this.progress(evt.percent);
12421240

12431241
return loadingTask.promise.then(
12441242
pdfDocument => {
@@ -1374,8 +1372,7 @@ const PDFViewerApplication = {
13741372
return message;
13751373
},
13761374

1377-
progress(level) {
1378-
const percent = Math.round(level * 100);
1375+
progress(percent) {
13791376
// When we transition from full request to range requests, it's possible
13801377
// that we discard some of the loaded data. This can cause the loading
13811378
// bar to move backwards. So prevent this by only updating the bar if it

web/firefoxcom.js

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

16-
import { isPdfFile, PDFDataRangeTransport } from "pdfjs-lib";
16+
import { isPdfFile, MathClamp, PDFDataRangeTransport } from "pdfjs-lib";
1717
import { AppOptions } from "./app_options.js";
1818
import { BaseExternalServices } from "./external_services.js";
1919
import { BasePreferences } from "./preferences.js";
@@ -627,7 +627,13 @@ class ExternalServices extends BaseExternalServices {
627627
pdfDataRangeTransport?.onDataProgressiveDone();
628628
break;
629629
case "progress":
630-
viewerApp.progress(args.loaded / args.total);
630+
const percent = MathClamp(
631+
Math.round((args.loaded / args.total) * 100),
632+
0,
633+
100
634+
);
635+
636+
viewerApp.progress(percent);
631637
break;
632638
case "complete":
633639
if (!args.data) {

web/ui_utils.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ class ProgressBar {
709709
}
710710

711711
set percent(val) {
712-
this.#percent = MathClamp(val, 0, 100);
712+
this.#percent = val;
713713

714714
if (isNaN(val)) {
715715
this.#classList.add("indeterminate");

0 commit comments

Comments
 (0)