mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-04-10 07:14:04 +02:00
The `PDFDataTransportStream` constructor has always registered exactly one listener for each type of data that an `PDFDataRangeTransport` instance can receive. Given that an end-user of the `PDFDataRangeTransport` class will supply data through its `onData...` methods, it's also somewhat difficult to understand why additional end-user registered listeners would be needed (since the data is already, by definition, available to the user). Furthermore, since TypedArray data is being transferred nowadays it's not even clear that multiple listeners (of the same kind) would generally work. All in all, let's simplify this old code a little bit by using *a single* (internal) listener in the `PDFDataRangeTransport` class.
258 lines
6.4 KiB
JavaScript
258 lines
6.4 KiB
JavaScript
/* Copyright 2012 Mozilla Foundation
|
|
*
|
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
* you may not use this file except in compliance with the License.
|
|
* You may obtain a copy of the License at
|
|
*
|
|
* http://www.apache.org/licenses/LICENSE-2.0
|
|
*
|
|
* Unless required by applicable law or agreed to in writing, software
|
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
* See the License for the specific language governing permissions and
|
|
* limitations under the License.
|
|
*/
|
|
|
|
import {
|
|
BasePDFStream,
|
|
BasePDFStreamRangeReader,
|
|
BasePDFStreamReader,
|
|
} from "../shared/base_pdf_stream.js";
|
|
import { assert } from "../shared/util.js";
|
|
import { isPdfFile } from "./display_utils.js";
|
|
|
|
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;
|
|
}
|
|
|
|
function endRequests() {
|
|
for (const capability of this._requests) {
|
|
capability.resolve({ value: undefined, done: true });
|
|
}
|
|
this._requests.length = 0;
|
|
}
|
|
|
|
class PDFDataTransportStream extends BasePDFStream {
|
|
_progressiveDone = false;
|
|
|
|
_queuedChunks = [];
|
|
|
|
constructor(source) {
|
|
super(
|
|
source,
|
|
PDFDataTransportStreamReader,
|
|
PDFDataTransportStreamRangeReader
|
|
);
|
|
const { pdfDataRangeTransport } = source;
|
|
const { initialData, progressiveDone } = pdfDataRangeTransport;
|
|
|
|
if (initialData?.length > 0) {
|
|
const buffer = getArrayBuffer(initialData);
|
|
this._queuedChunks.push(buffer);
|
|
}
|
|
this._progressiveDone = progressiveDone;
|
|
|
|
const listener = args => {
|
|
switch (args.type) {
|
|
case "range":
|
|
case "progressiveRead":
|
|
this.#onReceiveData(args.begin, args.chunk);
|
|
break;
|
|
case "progressiveDone":
|
|
this._fullReader?.progressiveDone();
|
|
this._progressiveDone = true;
|
|
break;
|
|
}
|
|
};
|
|
pdfDataRangeTransport.transportReady(listener);
|
|
}
|
|
|
|
#onReceiveData(begin, chunk) {
|
|
const buffer = getArrayBuffer(chunk);
|
|
|
|
if (begin === undefined) {
|
|
if (this._fullReader) {
|
|
this._fullReader._enqueue(buffer);
|
|
} else {
|
|
this._queuedChunks.push(buffer);
|
|
}
|
|
} else {
|
|
const rangeReader = this._rangeReaders
|
|
.keys()
|
|
.find(r => r._begin === begin);
|
|
|
|
assert(
|
|
rangeReader,
|
|
"#onReceiveData - no `PDFDataTransportStreamRangeReader` instance found."
|
|
);
|
|
rangeReader._enqueue(buffer);
|
|
}
|
|
}
|
|
|
|
getFullReader() {
|
|
const reader = super.getFullReader();
|
|
this._queuedChunks = null;
|
|
return reader;
|
|
}
|
|
|
|
getRangeReader(begin, end) {
|
|
const reader = super.getRangeReader(begin, end);
|
|
|
|
if (reader) {
|
|
reader.onDone = () => this._rangeReaders.delete(reader);
|
|
|
|
this._source.pdfDataRangeTransport.requestDataRange(begin, end);
|
|
}
|
|
return reader;
|
|
}
|
|
|
|
cancelAllRequests(reason) {
|
|
super.cancelAllRequests(reason);
|
|
|
|
this._source.pdfDataRangeTransport.abort();
|
|
}
|
|
}
|
|
|
|
class PDFDataTransportStreamReader extends BasePDFStreamReader {
|
|
#endRequests = endRequests.bind(this);
|
|
|
|
_done = false;
|
|
|
|
_queuedChunks = null;
|
|
|
|
_requests = [];
|
|
|
|
constructor(stream) {
|
|
super(stream);
|
|
const { pdfDataRangeTransport, disableRange, disableStream } =
|
|
stream._source;
|
|
const { length, contentDispositionFilename } = pdfDataRangeTransport;
|
|
|
|
this._queuedChunks = stream._queuedChunks || [];
|
|
for (const chunk of this._queuedChunks) {
|
|
this._loaded += chunk.byteLength;
|
|
}
|
|
this._done = stream._progressiveDone;
|
|
|
|
this._contentLength = length;
|
|
this._isStreamingSupported = !disableStream;
|
|
this._isRangeSupported = !disableRange;
|
|
|
|
if (isPdfFile(contentDispositionFilename)) {
|
|
this._filename = contentDispositionFilename;
|
|
}
|
|
this._headersCapability.resolve();
|
|
|
|
// Report loading progress when there is `initialData`, and `_enqueue` has
|
|
// not been invoked, but with a small delay to give an `onProgress` callback
|
|
// a chance to be registered first.
|
|
const loaded = this._loaded;
|
|
Promise.resolve().then(() => {
|
|
if (loaded > 0 && this._loaded === loaded) {
|
|
this._callOnProgress();
|
|
}
|
|
});
|
|
}
|
|
|
|
_enqueue(chunk) {
|
|
if (this._done) {
|
|
return; // Ignore new data.
|
|
}
|
|
if (this._requests.length > 0) {
|
|
const capability = this._requests.shift();
|
|
capability.resolve({ value: chunk, done: false });
|
|
} else {
|
|
this._queuedChunks.push(chunk);
|
|
}
|
|
this._loaded += chunk.byteLength;
|
|
this._callOnProgress();
|
|
}
|
|
|
|
async read() {
|
|
if (this._queuedChunks.length > 0) {
|
|
const chunk = this._queuedChunks.shift();
|
|
return { value: chunk, done: false };
|
|
}
|
|
if (this._done) {
|
|
return { value: undefined, done: true };
|
|
}
|
|
const capability = Promise.withResolvers();
|
|
this._requests.push(capability);
|
|
return capability.promise;
|
|
}
|
|
|
|
cancel(reason) {
|
|
this._done = true;
|
|
this.#endRequests();
|
|
}
|
|
|
|
progressiveDone() {
|
|
this._done ||= true;
|
|
|
|
if (this._queuedChunks.length === 0) {
|
|
this.#endRequests();
|
|
}
|
|
}
|
|
}
|
|
|
|
class PDFDataTransportStreamRangeReader extends BasePDFStreamRangeReader {
|
|
#endRequests = endRequests.bind(this);
|
|
|
|
onDone = null;
|
|
|
|
_begin = -1;
|
|
|
|
_done = false;
|
|
|
|
_queuedChunk = null;
|
|
|
|
_requests = [];
|
|
|
|
constructor(stream, begin, end) {
|
|
super(stream, begin, end);
|
|
this._begin = begin;
|
|
}
|
|
|
|
_enqueue(chunk) {
|
|
if (this._done) {
|
|
return; // ignore new data
|
|
}
|
|
if (this._requests.length === 0) {
|
|
this._queuedChunk = chunk;
|
|
} else {
|
|
const capability = this._requests.shift();
|
|
capability.resolve({ value: chunk, done: false });
|
|
|
|
this.#endRequests();
|
|
}
|
|
this._done = true;
|
|
this.onDone?.();
|
|
}
|
|
|
|
async read() {
|
|
if (this._queuedChunk) {
|
|
const chunk = this._queuedChunk;
|
|
this._queuedChunk = null;
|
|
return { value: chunk, done: false };
|
|
}
|
|
if (this._done) {
|
|
return { value: undefined, done: true };
|
|
}
|
|
const capability = Promise.withResolvers();
|
|
this._requests.push(capability);
|
|
return capability.promise;
|
|
}
|
|
|
|
cancel(reason) {
|
|
this._done = true;
|
|
this.#endRequests();
|
|
this.onDone?.();
|
|
}
|
|
}
|
|
|
|
export { endRequests, PDFDataTransportStream };
|