Add an abstract BasePDFStream class, that all the old IPDFStream implementations inherit from

Given that there's no less than *five* different, but very similar, implementations this helps reduce code duplication and simplifies maintenance.

Also, spotted during rebasing, pass the `enableHWA` option "correctly" (i.e. as part of the existing `transportParams`) to the `WorkerTransport`-class to keep the constructor simpler.
This commit is contained in:
Jonas Jenwald 2026-01-30 08:01:40 +01:00
parent a80f10ff1a
commit 4a8fb4dde1
10 changed files with 194 additions and 314 deletions

View File

@ -272,17 +272,17 @@ class ChunkedStreamManager {
_requestsByChunk = new Map();
constructor(pdfNetworkStream, args) {
constructor(pdfStream, args) {
this.length = args.length;
this.chunkSize = args.rangeChunkSize;
this.stream = new ChunkedStream(this.length, this.chunkSize, this);
this.pdfNetworkStream = pdfNetworkStream;
this.pdfStream = pdfStream;
this.disableAutoFetch = args.disableAutoFetch;
this.msgHandler = args.msgHandler;
}
sendRequest(begin, end) {
const rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
const rangeReader = this.pdfStream.getRangeReader(begin, end);
let chunks = [];
return new Promise((resolve, reject) => {
@ -530,7 +530,7 @@ class ChunkedStreamManager {
abort(reason) {
this.aborted = true;
this.pdfNetworkStream?.cancelAllRequests(reason);
this.pdfStream?.cancelAllRequests(reason);
for (const capability of this._promisesByRequest.values()) {
capability.reject(reason);

View File

@ -219,23 +219,23 @@ class WorkerMessageHandler {
return new LocalPdfManager(pdfManagerArgs);
}
const pdfStream = new PDFWorkerStream(handler),
fullRequest = pdfStream.getFullReader();
const pdfStream = new PDFWorkerStream({ msgHandler: handler }),
fullReader = pdfStream.getFullReader();
const pdfManagerCapability = Promise.withResolvers();
let newPdfManager,
cachedChunks = [],
loaded = 0;
fullRequest.headersReady
fullReader.headersReady
.then(function () {
if (!fullRequest.isRangeSupported) {
if (!fullReader.isRangeSupported) {
return;
}
pdfManagerArgs.source = pdfStream;
pdfManagerArgs.length = fullRequest.contentLength;
pdfManagerArgs.length = fullReader.contentLength;
// We don't need auto-fetch when streaming is enabled.
pdfManagerArgs.disableAutoFetch ||= fullRequest.isStreamingSupported;
pdfManagerArgs.disableAutoFetch ||= fullReader.isStreamingSupported;
newPdfManager = new NetworkPdfManager(pdfManagerArgs);
// There may be a chance that `newPdfManager` is not initialized for
@ -282,10 +282,10 @@ class WorkerMessageHandler {
}
loaded += value.byteLength;
if (!fullRequest.isStreamingSupported) {
if (!fullReader.isStreamingSupported) {
handler.send("DocProgress", {
loaded,
total: Math.max(loaded, fullRequest.contentLength || 0),
total: Math.max(loaded, fullReader.contentLength || 0),
});
}
@ -294,12 +294,12 @@ class WorkerMessageHandler {
} else {
cachedChunks.push(value);
}
fullRequest.read().then(readChunk, reject);
fullReader.read().then(readChunk, reject);
} catch (e) {
reject(e);
}
};
fullRequest.read().then(readChunk, reject);
fullReader.read().then(readChunk, reject);
}).catch(function (e) {
pdfManagerCapability.reject(e);
cancelXHRs = null;

View File

@ -13,55 +13,28 @@
* limitations under the License.
*/
import { assert } from "../shared/util.js";
import { BasePDFStream } from "../shared/base_pdf_stream.js";
/** @implements {IPDFStream} */
class PDFWorkerStream {
constructor(msgHandler) {
this._msgHandler = msgHandler;
this._contentLength = null;
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
getFullReader() {
assert(
!this._fullRequestReader,
"PDFWorkerStream.getFullReader can only be called once."
);
this._fullRequestReader = new PDFWorkerStreamReader(this._msgHandler);
return this._fullRequestReader;
}
getRangeReader(begin, end) {
const reader = new PDFWorkerStreamRangeReader(begin, end, this._msgHandler);
this._rangeRequestReaders.push(reader);
return reader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
}
class PDFWorkerStream extends BasePDFStream {
constructor(source) {
super(source, PDFWorkerStreamReader, PDFWorkerStreamRangeReader);
}
}
/** @implements {IPDFStreamReader} */
class PDFWorkerStreamReader {
constructor(msgHandler) {
this._msgHandler = msgHandler;
constructor(stream) {
const { msgHandler } = stream._source;
this.onProgress = null;
this._contentLength = null;
this._isRangeSupported = false;
this._isStreamingSupported = false;
const readableStream = this._msgHandler.sendWithStream("GetReader");
const readableStream = msgHandler.sendWithStream("GetReader");
this._reader = readableStream.getReader();
this._headersReady = this._msgHandler
this._headersReady = msgHandler
.sendWithPromise("ReaderHeadersReady")
.then(data => {
this._isStreamingSupported = data.isStreamingSupported;
@ -103,10 +76,10 @@ class PDFWorkerStreamReader {
/** @implements {IPDFStreamRangeReader} */
class PDFWorkerStreamRangeReader {
constructor(begin, end, msgHandler) {
this._msgHandler = msgHandler;
constructor(stream, begin, end) {
const { msgHandler } = stream._source;
const readableStream = this._msgHandler.sendWithStream("GetRangeReader", {
const readableStream = msgHandler.sendWithStream("GetRangeReader", {
begin,
end,
});

View File

@ -440,6 +440,7 @@ function getDocument(src = {}) {
ownerDocument,
pdfBug,
styleElement,
enableHWA,
loadingParams: {
disableAutoFetch,
enableXfa,
@ -463,7 +464,8 @@ function getDocument(src = {}) {
let networkStream;
if (rangeTransport) {
networkStream = new PDFDataTransportStream(rangeTransport, {
networkStream = new PDFDataTransportStream({
pdfDataRangeTransport: rangeTransport,
disableRange,
disableStream,
});
@ -508,8 +510,7 @@ function getDocument(src = {}) {
task,
networkStream,
transportParams,
transportFactory,
enableHWA
transportFactory
);
task._transport = transport;
messageHandler.send("Ready", null);
@ -2391,8 +2392,16 @@ class PDFWorker {
* @ignore
*/
class WorkerTransport {
downloadInfoCapability = Promise.withResolvers();
#fullReader = null;
#lastProgress = null;
#methodPromises = new Map();
#networkStream = null;
#pageCache = new Map();
#pagePromises = new Map();
@ -2403,21 +2412,17 @@ class WorkerTransport {
#pagesMapper = PagesMapper.instance;
constructor(
messageHandler,
loadingTask,
networkStream,
params,
factory,
enableHWA
) {
constructor(messageHandler, loadingTask, networkStream, params, factory) {
this.messageHandler = messageHandler;
this.loadingTask = loadingTask;
this.#networkStream = networkStream;
this.commonObjs = new PDFObjects();
this.fontLoader = new FontLoader({
ownerDocument: params.ownerDocument,
styleElement: params.styleElement,
});
this.enableHWA = params.enableHWA;
this.loadingParams = params.loadingParams;
this._params = params;
@ -2430,12 +2435,6 @@ class WorkerTransport {
this.destroyed = false;
this.destroyCapability = null;
this._networkStream = networkStream;
this._fullReader = null;
this._lastProgress = null;
this.downloadInfoCapability = Promise.withResolvers();
this.enableHWA = enableHWA;
this.setupMessageHandler();
this.#pagesMapper.addListener(this.#updateCaches.bind(this));
@ -2604,7 +2603,7 @@ class WorkerTransport {
this.filterFactory.destroy();
TextLayer.cleanup();
this._networkStream?.cancelAllRequests(
this.#networkStream?.cancelAllRequests(
new AbortException("Worker was terminated.")
);
@ -2621,18 +2620,18 @@ class WorkerTransport {
messageHandler.on("GetReader", (data, sink) => {
assert(
this._networkStream,
"GetReader - no `IPDFStream` instance available."
this.#networkStream,
"GetReader - no `BasePDFStream` instance available."
);
this._fullReader = this._networkStream.getFullReader();
this._fullReader.onProgress = evt => {
this._lastProgress = {
this.#fullReader = this.#networkStream.getFullReader();
this.#fullReader.onProgress = evt => {
this.#lastProgress = {
loaded: evt.loaded,
total: evt.total,
};
};
sink.onPull = () => {
this._fullReader
this.#fullReader
.read()
.then(function ({ value, done }) {
if (done) {
@ -2653,7 +2652,7 @@ class WorkerTransport {
};
sink.onCancel = reason => {
this._fullReader.cancel(reason);
this.#fullReader.cancel(reason);
sink.ready.catch(readyReason => {
if (this.destroyed) {
@ -2665,18 +2664,18 @@ class WorkerTransport {
});
messageHandler.on("ReaderHeadersReady", async data => {
await this._fullReader.headersReady;
await this.#fullReader.headersReady;
const { isStreamingSupported, isRangeSupported, contentLength } =
this._fullReader;
this.#fullReader;
// If stream or range are disabled, it's our only way to report
// loading progress.
if (!isStreamingSupported || !isRangeSupported) {
if (this._lastProgress) {
loadingTask.onProgress?.(this._lastProgress);
if (this.#lastProgress) {
loadingTask.onProgress?.(this.#lastProgress);
}
this._fullReader.onProgress = evt => {
this.#fullReader.onProgress = evt => {
loadingTask.onProgress?.({
loaded: evt.loaded,
total: evt.total,
@ -2689,16 +2688,16 @@ class WorkerTransport {
messageHandler.on("GetRangeReader", (data, sink) => {
assert(
this._networkStream,
"GetRangeReader - no `IPDFStream` instance available."
this.#networkStream,
"GetRangeReader - no `BasePDFStream` instance available."
);
const rangeReader = this._networkStream.getRangeReader(
const rangeReader = this.#networkStream.getRangeReader(
data.begin,
data.end
);
// When streaming is enabled, it's possible that the data requested here
// has already been fetched via the `_fullRequestReader` implementation.
// has already been fetched via the `#fullReader` implementation.
// However, given that the PDF data is loaded asynchronously on the
// main-thread and then sent via `postMessage` to the worker-thread,
// it may not have been available during parsing (hence the attempt to
@ -2706,7 +2705,7 @@ class WorkerTransport {
//
// To avoid wasting time and resources here, we'll thus *not* dispatch
// range requests if the data was already loaded but has not been sent to
// the worker-thread yet (which will happen via the `_fullRequestReader`).
// the worker-thread yet (which will happen via the `#fullReader`).
if (!rangeReader) {
sink.close();
return;
@ -2950,7 +2949,7 @@ class WorkerTransport {
isPureXfa: !!this._htmlForXfa,
numPages: this._numPages,
annotationStorage: map,
filename: this._fullReader?.filename ?? null,
filename: this.#fullReader?.filename ?? null,
},
transfer
)
@ -3118,8 +3117,8 @@ class WorkerTransport {
.then(results => ({
info: results[0],
metadata: results[1] ? new Metadata(results[1]) : null,
contentDispositionFilename: this._fullReader?.filename ?? null,
contentLength: this._fullReader?.contentLength ?? null,
contentDispositionFilename: this.#fullReader?.filename ?? null,
contentLength: this.#fullReader?.contentLength ?? null,
hasStructTree: results[2],
}));
this.#methodPromises.set(name, promise);

View File

@ -13,7 +13,7 @@
* limitations under the License.
*/
import { AbortException, assert, warn } from "../shared/util.js";
import { AbortException, warn } from "../shared/util.js";
import {
createHeaders,
createResponseError,
@ -22,6 +22,7 @@ import {
validateRangeRequestCapabilities,
validateResponseStatus,
} from "./network_utils.js";
import { BasePDFStream } from "../shared/base_pdf_stream.js";
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
throw new Error(
@ -51,47 +52,13 @@ function getArrayBuffer(val) {
return new Uint8Array(val).buffer;
}
/** @implements {IPDFStream} */
class PDFFetchStream {
class PDFFetchStream extends BasePDFStream {
_responseOrigin = null;
constructor(source) {
this.source = source;
super(source, PDFFetchStreamReader, PDFFetchStreamRangeReader);
this.isHttp = /^https?:/i.test(source.url);
this.headers = createHeaders(this.isHttp, source.httpHeaders);
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
get _progressiveDataLength() {
return this._fullRequestReader?._loaded ?? 0;
}
getFullReader() {
assert(
!this._fullRequestReader,
"PDFFetchStream.getFullReader can only be called once."
);
this._fullRequestReader = new PDFFetchStreamReader(this);
return this._fullRequestReader;
}
getRangeReader(begin, end) {
if (end <= this._progressiveDataLength) {
return null;
}
const reader = new PDFFetchStreamRangeReader(this, begin, end);
this._rangeRequestReaders.push(reader);
return reader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
}
}
}
@ -102,7 +69,7 @@ class PDFFetchStreamReader {
this._reader = null;
this._loaded = 0;
this._filename = null;
const source = stream.source;
const source = stream._source;
this._withCredentials = source.withCredentials || false;
this._contentLength = source.length;
this._headersCapability = Promise.withResolvers();
@ -205,7 +172,7 @@ class PDFFetchStreamRangeReader {
constructor(stream, begin, end) {
this._stream = stream;
this._reader = null;
const source = stream.source;
const source = stream._source;
this._withCredentials = source.withCredentials || false;
this._readCapability = Promise.withResolvers();

View File

@ -21,6 +21,7 @@ import {
getResponseOrigin,
validateRangeRequestCapabilities,
} from "./network_utils.js";
import { BasePDFStream } from "../shared/base_pdf_stream.js";
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
throw new Error(
@ -35,18 +36,13 @@ function getArrayBuffer(val) {
return typeof val !== "string" ? val : stringToBytes(val).buffer;
}
/** @implements {IPDFStream} */
class PDFNetworkStream {
class PDFNetworkStream extends BasePDFStream {
#pendingRequests = new WeakMap();
_fullRequestReader = null;
_rangeRequestReaders = [];
_responseOrigin = null;
constructor(source) {
this._source = source;
super(source, PDFNetworkStreamFullReader, PDFNetworkStreamRangeReader);
this.url = source.url;
this.isHttp = /^https?:/i.test(this.url);
this.headers = createHeaders(this.isHttp, source.httpHeaders);
@ -160,38 +156,18 @@ class PDFNetworkStream {
}
}
getFullReader() {
assert(
!this._fullRequestReader,
"PDFNetworkStream.getFullReader can only be called once."
);
this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this);
return this._fullRequestReader;
}
getRangeReader(begin, end) {
const reader = new PDFNetworkStreamRangeRequestReader(this, begin, end);
reader.onClosed = () => {
const i = this._rangeRequestReaders.indexOf(reader);
if (i >= 0) {
this._rangeRequestReaders.splice(i, 1);
}
};
this._rangeRequestReaders.push(reader);
return reader;
}
const reader = super.getRangeReader(begin, end);
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
if (reader) {
reader.onClosed = () => this._rangeReaders.delete(reader);
}
return reader;
}
}
/** @implements {IPDFStreamReader} */
class PDFNetworkStreamFullRequestReader {
class PDFNetworkStreamFullReader {
constructor(stream) {
this._stream = stream;
const { disableRange, length, rangeChunkSize } = stream._source;
@ -355,7 +331,7 @@ class PDFNetworkStreamFullRequestReader {
}
/** @implements {IPDFStreamRangeReader} */
class PDFNetworkStreamRangeRequestReader {
class PDFNetworkStreamRangeReader {
onClosed = null;
constructor(stream, begin, end) {
@ -398,7 +374,7 @@ class PDFNetworkStreamRangeRequestReader {
requestCapability.resolve({ value: undefined, done: true });
}
this._requests.length = 0;
this.onClosed?.(this);
this.onClosed?.();
}
_onError(status) {
@ -435,7 +411,7 @@ class PDFNetworkStreamRangeRequestReader {
this._requests.length = 0;
this._stream._abortRequest(this._requestXhr);
this.onClosed?.(this);
this.onClosed?.();
}
}

View File

@ -15,6 +15,7 @@
/* globals process */
import { AbortException, assert, warn } from "../shared/util.js";
import { BasePDFStream } from "../shared/base_pdf_stream.js";
import { createResponseError } from "./network_utils.js";
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
@ -60,47 +61,14 @@ function getArrayBuffer(val) {
return new Uint8Array(val).buffer;
}
class PDFNodeStream {
class PDFNodeStream extends BasePDFStream {
constructor(source) {
this.source = source;
super(source, PDFNodeStreamFsFullReader, PDFNodeStreamFsRangeReader);
this.url = parseUrlOrPath(source.url);
assert(
this.url.protocol === "file:",
"PDFNodeStream only supports file:// URLs."
);
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
get _progressiveDataLength() {
return this._fullRequestReader?._loaded ?? 0;
}
getFullReader() {
assert(
!this._fullRequestReader,
"PDFNodeStream.getFullReader can only be called once."
);
this._fullRequestReader = new PDFNodeStreamFsFullReader(this);
return this._fullRequestReader;
}
getRangeReader(begin, end) {
if (end <= this._progressiveDataLength) {
return null;
}
const rangeReader = new PDFNodeStreamFsRangeReader(this, begin, end);
this._rangeRequestReaders.push(rangeReader);
return rangeReader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
for (const reader of this._rangeRequestReaders.slice(0)) {
reader.cancel(reason);
}
}
}
@ -111,7 +79,7 @@ class PDFNodeStreamFsFullReader {
constructor(stream) {
this.onProgress = null;
const source = stream.source;
const source = stream._source;
this._contentLength = source.length; // optional
this._loaded = 0;
this._filename = null;

View File

@ -13,39 +13,42 @@
* limitations under the License.
*/
/** @typedef {import("../interfaces").IPDFStream} IPDFStream */
/** @typedef {import("../interfaces").IPDFStreamReader} IPDFStreamReader */
// eslint-disable-next-line max-len
/** @typedef {import("../interfaces").IPDFStreamRangeReader} IPDFStreamRangeReader */
import { assert } from "../shared/util.js";
import { BasePDFStream } from "../shared/base_pdf_stream.js";
import { isPdfFile } from "./display_utils.js";
/** @implements {IPDFStream} */
class PDFDataTransportStream {
constructor(
pdfDataRangeTransport,
{ disableRange = false, disableStream = false }
) {
assert(
pdfDataRangeTransport,
'PDFDataTransportStream - missing required "pdfDataRangeTransport" argument.'
function getArrayBuffer(val) {
// Prevent any possible issues by only transferring a Uint8Array that
// completely "utilizes" its underlying ArrayBuffer.
return val instanceof Uint8Array && val.byteLength === val.buffer.byteLength
? val.buffer
: new Uint8Array(val).buffer;
}
class PDFDataTransportStream extends BasePDFStream {
_pdfDataRangeTransport = null;
_queuedChunks = [];
constructor(source) {
super(
source,
PDFDataTransportStreamReader,
PDFDataTransportStreamRangeReader
);
const { pdfDataRangeTransport, disableRange, disableStream } = source;
const { length, initialData, progressiveDone, contentDispositionFilename } =
pdfDataRangeTransport;
this._queuedChunks = [];
this._progressiveDone = progressiveDone;
this._contentDispositionFilename = contentDispositionFilename;
if (initialData?.length > 0) {
// Prevent any possible issues by only transferring a Uint8Array that
// completely "utilizes" its underlying ArrayBuffer.
const buffer =
initialData instanceof Uint8Array &&
initialData.byteLength === initialData.buffer.byteLength
? initialData.buffer
: new Uint8Array(initialData).buffer;
const buffer = getArrayBuffer(initialData);
this._queuedChunks.push(buffer);
}
@ -54,139 +57,89 @@ class PDFDataTransportStream {
this._isRangeSupported = !disableRange;
this._contentLength = length;
this._fullRequestReader = null;
this._rangeReaders = [];
pdfDataRangeTransport.addRangeListener((begin, chunk) => {
this._onReceiveData({ begin, chunk });
this.#onReceiveData(begin, chunk);
});
pdfDataRangeTransport.addProgressListener((loaded, total) => {
this._onProgress({ loaded, total });
if (total !== undefined) {
this._fullReader?.onProgress?.({ loaded, total });
}
});
pdfDataRangeTransport.addProgressiveReadListener(chunk => {
this._onReceiveData({ chunk });
this.#onReceiveData(/* begin = */ undefined, chunk);
});
pdfDataRangeTransport.addProgressiveDoneListener(() => {
this._onProgressiveDone();
this._fullReader?.progressiveDone();
this._progressiveDone = true;
});
pdfDataRangeTransport.transportReady();
}
_onReceiveData({ begin, chunk }) {
// Prevent any possible issues by only transferring a Uint8Array that
// completely "utilizes" its underlying ArrayBuffer.
const buffer =
chunk instanceof Uint8Array &&
chunk.byteLength === chunk.buffer.byteLength
? chunk.buffer
: new Uint8Array(chunk).buffer;
#onReceiveData(begin, chunk) {
const buffer = getArrayBuffer(chunk);
if (begin === undefined) {
if (this._fullRequestReader) {
this._fullRequestReader._enqueue(buffer);
if (this._fullReader) {
this._fullReader._enqueue(buffer);
} else {
this._queuedChunks.push(buffer);
}
} else {
const found = this._rangeReaders.some(function (rangeReader) {
if (rangeReader._begin !== begin) {
return false;
}
rangeReader._enqueue(buffer);
return true;
});
const rangeReader = this._rangeReaders
.keys()
.find(r => r._begin === begin);
assert(
found,
"_onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."
rangeReader,
"#onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."
);
}
}
get _progressiveDataLength() {
return this._fullRequestReader?._loaded ?? 0;
}
_onProgress(evt) {
if (evt.total !== undefined) {
this._fullRequestReader?.onProgress?.({
loaded: evt.loaded,
total: evt.total,
});
}
}
_onProgressiveDone() {
this._fullRequestReader?.progressiveDone();
this._progressiveDone = true;
}
_removeRangeReader(reader) {
const i = this._rangeReaders.indexOf(reader);
if (i >= 0) {
this._rangeReaders.splice(i, 1);
rangeReader._enqueue(buffer);
}
}
getFullReader() {
assert(
!this._fullRequestReader,
"PDFDataTransportStream.getFullReader can only be called once."
);
const queuedChunks = this._queuedChunks;
const reader = super.getFullReader();
this._queuedChunks = null;
return new PDFDataTransportStreamReader(
this,
queuedChunks,
this._progressiveDone,
this._contentDispositionFilename
);
return reader;
}
getRangeReader(begin, end) {
if (end <= this._progressiveDataLength) {
return null;
const reader = super.getRangeReader(begin, end);
if (reader) {
reader.onDone = () => this._rangeReaders.delete(reader);
this._pdfDataRangeTransport.requestDataRange(begin, end);
}
const reader = new PDFDataTransportStreamRangeReader(this, begin, end);
this._pdfDataRangeTransport.requestDataRange(begin, end);
this._rangeReaders.push(reader);
return reader;
}
cancelAllRequests(reason) {
this._fullRequestReader?.cancel(reason);
super.cancelAllRequests(reason);
for (const reader of this._rangeReaders.slice(0)) {
reader.cancel(reason);
}
this._pdfDataRangeTransport.abort();
}
}
/** @implements {IPDFStreamReader} */
class PDFDataTransportStreamReader {
constructor(
stream,
queuedChunks,
progressiveDone = false,
contentDispositionFilename = null
) {
constructor(stream) {
this._stream = stream;
this._done = progressiveDone || false;
this._filename = isPdfFile(contentDispositionFilename)
? contentDispositionFilename
this._done = stream._progressiveDone || false;
this._filename = isPdfFile(stream._contentDispositionFilename)
? stream._contentDispositionFilename
: null;
this._queuedChunks = queuedChunks || [];
this._queuedChunks = stream._queuedChunks || [];
this._loaded = 0;
for (const chunk of this._queuedChunks) {
this._loaded += chunk.byteLength;
}
this._requests = [];
this._headersReady = Promise.resolve();
stream._fullRequestReader = this;
this.onProgress = null;
}
@ -255,6 +208,8 @@ class PDFDataTransportStreamReader {
/** @implements {IPDFStreamRangeReader} */
class PDFDataTransportStreamRangeReader {
onDone = null;
constructor(stream, begin, end) {
this._stream = stream;
this._begin = begin;
@ -279,7 +234,7 @@ class PDFDataTransportStreamRangeReader {
this._requests.length = 0;
}
this._done = true;
this._stream._removeRangeReader(this);
this.onDone?.();
}
async read() {
@ -302,7 +257,7 @@ class PDFDataTransportStreamRangeReader {
requestCapability.resolve({ value: undefined, done: true });
}
this._requests.length = 0;
this._stream._removeRangeReader(this);
this.onDone?.();
}
}

View File

@ -13,40 +13,83 @@
* limitations under the License.
*/
import { assert, unreachable } from "./util.js";
/**
* Interface that represents PDF data transport. If possible, it allows
* progressively load entire or fragment of the PDF binary data.
*
* @interface
*/
class IPDFStream {
class BasePDFStream {
#PDFStreamReader = null;
#PDFStreamRangeReader = null;
_fullReader = null;
_rangeReaders = new Set();
_source = null;
constructor(source, PDFStreamReader, PDFStreamRangeReader) {
if (
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
this.constructor === BasePDFStream
) {
unreachable("Cannot initialize BasePDFStream.");
}
this._source = source;
this.#PDFStreamReader = PDFStreamReader;
this.#PDFStreamRangeReader = PDFStreamRangeReader;
}
get _progressiveDataLength() {
return this._fullReader?._loaded ?? 0;
}
/**
* Gets a reader for the entire PDF data.
* @returns {IPDFStreamReader}
* @returns {BasePDFStreamReader}
*/
getFullReader() {
return null;
assert(
!this._fullReader,
"BasePDFStream.getFullReader can only be called once."
);
return (this._fullReader = new this.#PDFStreamReader(this));
}
/**
* Gets a reader for the range of the PDF data.
*
* NOTE: Currently this method is only expected to be invoked *after*
* the `IPDFStreamReader.prototype.headersReady` promise has resolved.
* the `BasePDFStreamReader.prototype.headersReady` promise has resolved.
*
* @param {number} begin - the start offset of the data.
* @param {number} end - the end offset of the data.
* @returns {IPDFStreamRangeReader}
* @returns {BasePDFStreamRangeReader}
*/
getRangeReader(begin, end) {
return null;
if (end <= this._progressiveDataLength) {
return null;
}
const reader = new this.#PDFStreamRangeReader(this, begin, end);
this._rangeReaders.add(reader);
return reader;
}
/**
* Cancels all opened reader and closes all their opened requests.
* @param {Object} reason - the reason for cancelling
*/
cancelAllRequests(reason) {}
cancelAllRequests(reason) {
this._fullReader?.cancel(reason);
// Always create a copy of the rangeReaders.
for (const reader of new Set(this._rangeReaders)) {
reader.cancel(reason);
}
}
}
/**
@ -152,4 +195,4 @@ class IPDFStreamRangeReader {
cancel(reason) {}
}
export { IPDFStream, IPDFStreamRangeReader, IPDFStreamReader };
export { BasePDFStream, IPDFStreamRangeReader, IPDFStreamReader };

View File

@ -16,8 +16,7 @@
import { AbortException, isNodeJS } from "../../src/shared/util.js";
import { getCrossOriginHostname, TestPdfsServer } from "./test_utils.js";
// Common tests to verify behavior across implementations of the IPDFStream
// interface:
// Common tests to verify behavior across `BasePDFStream` implementations:
// - PDFNetworkStream by network_spec.js
// - PDFFetchStream by fetch_stream_spec.js
async function testCrossOriginRedirects({