mirror of
https://github.com/mozilla/pdf.js.git
synced 2026-08-02 20:37:21 +02:00
Add an abstract BasePDFStreamRangeReader class, that all the old IPDFStreamRangeReader implementations inherit from
Given that there's no less than *five* different, but very similar, implementations this helps reduce code duplication and simplifies maintenance.
This commit is contained in:
parent
54d8c5e7b4
commit
4ca205bac3
@ -15,6 +15,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
BasePDFStream,
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
BasePDFStreamReader,
|
BasePDFStreamReader,
|
||||||
} from "../shared/base_pdf_stream.js";
|
} from "../shared/base_pdf_stream.js";
|
||||||
|
|
||||||
@ -58,9 +59,11 @@ class PDFWorkerStreamReader extends BasePDFStreamReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFWorkerStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFWorkerStreamRangeReader {
|
_reader = null;
|
||||||
|
|
||||||
constructor(stream, begin, end) {
|
constructor(stream, begin, end) {
|
||||||
|
super(stream, begin, end);
|
||||||
const { msgHandler } = stream._source;
|
const { msgHandler } = stream._source;
|
||||||
|
|
||||||
const readableStream = msgHandler.sendWithStream("GetRangeReader", {
|
const readableStream = msgHandler.sendWithStream("GetRangeReader", {
|
||||||
|
|||||||
@ -16,15 +16,16 @@
|
|||||||
import { AbortException, warn } from "../shared/util.js";
|
import { AbortException, warn } from "../shared/util.js";
|
||||||
import {
|
import {
|
||||||
BasePDFStream,
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
BasePDFStreamReader,
|
BasePDFStreamReader,
|
||||||
} from "../shared/base_pdf_stream.js";
|
} from "../shared/base_pdf_stream.js";
|
||||||
import {
|
import {
|
||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
|
||||||
} from "./network_utils.js";
|
} from "./network_utils.js";
|
||||||
|
|
||||||
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
|
||||||
@ -44,6 +45,12 @@ function fetchUrl(url, headers, withCredentials, abortController) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureResponseStatus(status, url) {
|
||||||
|
if (status !== 200 && status !== 206) {
|
||||||
|
throw createResponseError(status, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function getArrayBuffer(val) {
|
function getArrayBuffer(val) {
|
||||||
if (val instanceof Uint8Array) {
|
if (val instanceof Uint8Array) {
|
||||||
return val.buffer;
|
return val.buffer;
|
||||||
@ -91,9 +98,7 @@ class PDFFetchStreamReader extends BasePDFStreamReader {
|
|||||||
.then(response => {
|
.then(response => {
|
||||||
stream._responseOrigin = getResponseOrigin(response.url);
|
stream._responseOrigin = getResponseOrigin(response.url);
|
||||||
|
|
||||||
if (!validateResponseStatus(response.status)) {
|
ensureResponseStatus(response.status, url);
|
||||||
throw createResponseError(response.status, url);
|
|
||||||
}
|
|
||||||
this._reader = response.body.getReader();
|
this._reader = response.body.getReader();
|
||||||
|
|
||||||
const responseHeaders = response.headers;
|
const responseHeaders = response.headers;
|
||||||
@ -144,35 +149,30 @@ class PDFFetchStreamReader extends BasePDFStreamReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFFetchStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFFetchStreamRangeReader {
|
_abortController = new AbortController();
|
||||||
constructor(stream, begin, end) {
|
|
||||||
this._stream = stream;
|
_readCapability = Promise.withResolvers();
|
||||||
this._reader = null;
|
|
||||||
const source = stream._source;
|
_reader = null;
|
||||||
this._withCredentials = source.withCredentials || false;
|
|
||||||
this._readCapability = Promise.withResolvers();
|
constructor(stream, begin, end) {
|
||||||
|
super(stream, begin, end);
|
||||||
|
const { url, withCredentials } = stream._source;
|
||||||
|
|
||||||
this._abortController = new AbortController();
|
|
||||||
// Always create a copy of the headers.
|
// Always create a copy of the headers.
|
||||||
const headers = new Headers(stream.headers);
|
const headers = new Headers(stream.headers);
|
||||||
headers.append("Range", `bytes=${begin}-${end - 1}`);
|
headers.append("Range", `bytes=${begin}-${end - 1}`);
|
||||||
|
|
||||||
const url = source.url;
|
fetchUrl(url, headers, withCredentials, this._abortController)
|
||||||
fetchUrl(url, headers, this._withCredentials, this._abortController)
|
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const responseOrigin = getResponseOrigin(response.url);
|
const responseOrigin = getResponseOrigin(response.url);
|
||||||
|
|
||||||
if (responseOrigin !== stream._responseOrigin) {
|
ensureResponseOrigin(responseOrigin, stream._responseOrigin);
|
||||||
throw new Error(
|
ensureResponseStatus(response.status, url);
|
||||||
`Expected range response-origin "${responseOrigin}" to match "${stream._responseOrigin}".`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!validateResponseStatus(response.status)) {
|
|
||||||
throw createResponseError(response.status, url);
|
|
||||||
}
|
|
||||||
this._readCapability.resolve();
|
|
||||||
this._reader = response.body.getReader();
|
this._reader = response.body.getReader();
|
||||||
|
|
||||||
|
this._readCapability.resolve();
|
||||||
})
|
})
|
||||||
.catch(this._readCapability.reject);
|
.catch(this._readCapability.reject);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -16,11 +16,13 @@
|
|||||||
import { assert, stringToBytes, warn } from "../shared/util.js";
|
import { assert, stringToBytes, warn } from "../shared/util.js";
|
||||||
import {
|
import {
|
||||||
BasePDFStream,
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
BasePDFStreamReader,
|
BasePDFStreamReader,
|
||||||
} from "../shared/base_pdf_stream.js";
|
} from "../shared/base_pdf_stream.js";
|
||||||
import {
|
import {
|
||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
@ -307,57 +309,59 @@ class PDFNetworkStreamReader extends BasePDFStreamReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFNetworkStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFNetworkStreamRangeReader {
|
|
||||||
onClosed = null;
|
onClosed = null;
|
||||||
|
|
||||||
|
_done = false;
|
||||||
|
|
||||||
|
_queuedChunk = null;
|
||||||
|
|
||||||
|
_requests = [];
|
||||||
|
|
||||||
|
_storedError = null;
|
||||||
|
|
||||||
constructor(stream, begin, end) {
|
constructor(stream, begin, end) {
|
||||||
this._stream = stream;
|
super(stream, begin, end);
|
||||||
|
|
||||||
this._requestXhr = stream._request({
|
this._requestXhr = stream._request({
|
||||||
begin,
|
begin,
|
||||||
end,
|
end,
|
||||||
onHeadersReceived: this._onHeadersReceived.bind(this),
|
onHeadersReceived: this.#onHeadersReceived.bind(this),
|
||||||
onDone: this._onDone.bind(this),
|
onDone: this.#onDone.bind(this),
|
||||||
onError: this._onError.bind(this),
|
onError: this.#onError.bind(this),
|
||||||
onProgress: null,
|
onProgress: null,
|
||||||
});
|
});
|
||||||
this._requests = [];
|
|
||||||
this._queuedChunk = null;
|
|
||||||
this._done = false;
|
|
||||||
this._storedError = undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_onHeadersReceived() {
|
#onHeadersReceived() {
|
||||||
const responseOrigin = getResponseOrigin(this._requestXhr?.responseURL);
|
const responseOrigin = getResponseOrigin(this._requestXhr?.responseURL);
|
||||||
|
try {
|
||||||
if (responseOrigin !== this._stream._responseOrigin) {
|
ensureResponseOrigin(responseOrigin, this._stream._responseOrigin);
|
||||||
this._storedError = new Error(
|
} catch (ex) {
|
||||||
`Expected range response-origin "${responseOrigin}" to match "${this._stream._responseOrigin}".`
|
this._storedError = ex;
|
||||||
);
|
this.#onError(0);
|
||||||
this._onError(0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onDone(chunk) {
|
#onDone(chunk) {
|
||||||
if (this._requests.length > 0) {
|
if (this._requests.length > 0) {
|
||||||
const requestCapability = this._requests.shift();
|
const capability = this._requests.shift();
|
||||||
requestCapability.resolve({ value: chunk, done: false });
|
capability.resolve({ value: chunk, done: false });
|
||||||
} else {
|
} else {
|
||||||
this._queuedChunk = chunk;
|
this._queuedChunk = chunk;
|
||||||
}
|
}
|
||||||
this._done = true;
|
this._done = true;
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
this.onClosed?.();
|
this.onClosed?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
_onError(status) {
|
#onError(status) {
|
||||||
this._storedError ??= createResponseError(status, this._stream.url);
|
this._storedError ??= createResponseError(status, this._stream.url);
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.reject(this._storedError);
|
capability.reject(this._storedError);
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
this._queuedChunk = null;
|
this._queuedChunk = null;
|
||||||
@ -375,15 +379,15 @@ class PDFNetworkStreamRangeReader {
|
|||||||
if (this._done) {
|
if (this._done) {
|
||||||
return { value: undefined, done: true };
|
return { value: undefined, done: true };
|
||||||
}
|
}
|
||||||
const requestCapability = Promise.withResolvers();
|
const capability = Promise.withResolvers();
|
||||||
this._requests.push(requestCapability);
|
this._requests.push(capability);
|
||||||
return requestCapability.promise;
|
return capability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(reason) {
|
cancel(reason) {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
|
|
||||||
|
|||||||
@ -107,15 +107,19 @@ function createResponseError(status, url) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateResponseStatus(status) {
|
function ensureResponseOrigin(rangeOrigin, origin) {
|
||||||
return status === 200 || status === 206;
|
if (rangeOrigin !== origin) {
|
||||||
|
throw new Error(
|
||||||
|
`Expected range response-origin "${rangeOrigin}" to match "${origin}".`
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export {
|
||||||
createHeaders,
|
createHeaders,
|
||||||
createResponseError,
|
createResponseError,
|
||||||
|
ensureResponseOrigin,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
getResponseOrigin,
|
getResponseOrigin,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
|
||||||
};
|
};
|
||||||
|
|||||||
@ -17,6 +17,7 @@
|
|||||||
import { AbortException, assert, warn } from "../shared/util.js";
|
import { AbortException, assert, warn } from "../shared/util.js";
|
||||||
import {
|
import {
|
||||||
BasePDFStream,
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
BasePDFStreamReader,
|
BasePDFStreamReader,
|
||||||
} from "../shared/base_pdf_stream.js";
|
} from "../shared/base_pdf_stream.js";
|
||||||
import { createResponseError } from "./network_utils.js";
|
import { createResponseError } from "./network_utils.js";
|
||||||
@ -66,7 +67,7 @@ function getArrayBuffer(val) {
|
|||||||
|
|
||||||
class PDFNodeStream extends BasePDFStream {
|
class PDFNodeStream extends BasePDFStream {
|
||||||
constructor(source) {
|
constructor(source) {
|
||||||
super(source, PDFNodeStreamReader, PDFNodeStreamFsRangeReader);
|
super(source, PDFNodeStreamReader, PDFNodeStreamRangeReader);
|
||||||
this.url = parseUrlOrPath(source.url);
|
this.url = parseUrlOrPath(source.url);
|
||||||
assert(
|
assert(
|
||||||
this.url.protocol === "file:",
|
this.url.protocol === "file:",
|
||||||
@ -142,12 +143,14 @@ class PDFNodeStreamReader extends BasePDFStreamReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class PDFNodeStreamFsRangeReader {
|
class PDFNodeStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
_readCapability = Promise.withResolvers();
|
_readCapability = Promise.withResolvers();
|
||||||
|
|
||||||
_reader = null;
|
_reader = null;
|
||||||
|
|
||||||
constructor(stream, begin, end) {
|
constructor(stream, begin, end) {
|
||||||
|
super(stream, begin, end);
|
||||||
|
|
||||||
const url = stream.url;
|
const url = stream.url;
|
||||||
const fs = process.getBuiltinModule("fs");
|
const fs = process.getBuiltinModule("fs");
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -13,11 +13,9 @@
|
|||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// eslint-disable-next-line max-len
|
|
||||||
/** @typedef {import("../interfaces").IPDFStreamRangeReader} IPDFStreamRangeReader */
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
BasePDFStream,
|
BasePDFStream,
|
||||||
|
BasePDFStreamRangeReader,
|
||||||
BasePDFStreamReader,
|
BasePDFStreamReader,
|
||||||
} from "../shared/base_pdf_stream.js";
|
} from "../shared/base_pdf_stream.js";
|
||||||
import { assert } from "../shared/util.js";
|
import { assert } from "../shared/util.js";
|
||||||
@ -187,17 +185,20 @@ class PDFDataTransportStreamReader extends BasePDFStreamReader {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @implements {IPDFStreamRangeReader} */
|
class PDFDataTransportStreamRangeReader extends BasePDFStreamRangeReader {
|
||||||
class PDFDataTransportStreamRangeReader {
|
|
||||||
onDone = null;
|
onDone = null;
|
||||||
|
|
||||||
|
_begin = -1;
|
||||||
|
|
||||||
|
_done = false;
|
||||||
|
|
||||||
|
_queuedChunk = null;
|
||||||
|
|
||||||
|
_requests = [];
|
||||||
|
|
||||||
constructor(stream, begin, end) {
|
constructor(stream, begin, end) {
|
||||||
this._stream = stream;
|
super(stream, begin, end);
|
||||||
this._begin = begin;
|
this._begin = begin;
|
||||||
this._end = end;
|
|
||||||
this._queuedChunk = null;
|
|
||||||
this._requests = [];
|
|
||||||
this._done = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_enqueue(chunk) {
|
_enqueue(chunk) {
|
||||||
@ -207,10 +208,11 @@ class PDFDataTransportStreamRangeReader {
|
|||||||
if (this._requests.length === 0) {
|
if (this._requests.length === 0) {
|
||||||
this._queuedChunk = chunk;
|
this._queuedChunk = chunk;
|
||||||
} else {
|
} else {
|
||||||
const requestsCapability = this._requests.shift();
|
const firstCapability = this._requests.shift();
|
||||||
requestsCapability.resolve({ value: chunk, done: false });
|
firstCapability.resolve({ value: chunk, done: false });
|
||||||
for (const requestCapability of this._requests) {
|
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
for (const capability of this._requests) {
|
||||||
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
}
|
}
|
||||||
@ -227,15 +229,15 @@ class PDFDataTransportStreamRangeReader {
|
|||||||
if (this._done) {
|
if (this._done) {
|
||||||
return { value: undefined, done: true };
|
return { value: undefined, done: true };
|
||||||
}
|
}
|
||||||
const requestCapability = Promise.withResolvers();
|
const capability = Promise.withResolvers();
|
||||||
this._requests.push(requestCapability);
|
this._requests.push(capability);
|
||||||
return requestCapability.promise;
|
return capability.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
cancel(reason) {
|
cancel(reason) {
|
||||||
this._done = true;
|
this._done = true;
|
||||||
for (const requestCapability of this._requests) {
|
for (const capability of this._requests) {
|
||||||
requestCapability.resolve({ value: undefined, done: true });
|
capability.resolve({ value: undefined, done: true });
|
||||||
}
|
}
|
||||||
this._requests.length = 0;
|
this._requests.length = 0;
|
||||||
this.onDone?.();
|
this.onDone?.();
|
||||||
|
|||||||
@ -198,10 +198,20 @@ class BasePDFStreamReader {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Interface for a PDF binary data fragment reader.
|
* Interface for a PDF binary data fragment reader.
|
||||||
*
|
|
||||||
* @interface
|
|
||||||
*/
|
*/
|
||||||
class IPDFStreamRangeReader {
|
class BasePDFStreamRangeReader {
|
||||||
|
_stream = null;
|
||||||
|
|
||||||
|
constructor(stream, begin, end) {
|
||||||
|
if (
|
||||||
|
(typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) &&
|
||||||
|
this.constructor === BasePDFStreamRangeReader
|
||||||
|
) {
|
||||||
|
unreachable("Cannot initialize BasePDFStreamRangeReader.");
|
||||||
|
}
|
||||||
|
this._stream = stream;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Requests a chunk of the binary data. The method returns the promise, which
|
* Requests a chunk of the binary data. The method returns the promise, which
|
||||||
* is resolved into object with properties "value" and "done". If the done
|
* is resolved into object with properties "value" and "done". If the done
|
||||||
@ -210,13 +220,17 @@ class IPDFStreamRangeReader {
|
|||||||
* set to true.
|
* set to true.
|
||||||
* @returns {Promise}
|
* @returns {Promise}
|
||||||
*/
|
*/
|
||||||
async read() {}
|
async read() {
|
||||||
|
unreachable("Abstract method `read` called");
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancels all pending read requests and closes the stream.
|
* Cancels all pending read requests and closes the stream.
|
||||||
* @param {Object} reason
|
* @param {Object} reason
|
||||||
*/
|
*/
|
||||||
cancel(reason) {}
|
cancel(reason) {
|
||||||
|
unreachable("Abstract method `cancel` called");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export { BasePDFStream, BasePDFStreamReader, IPDFStreamRangeReader };
|
export { BasePDFStream, BasePDFStreamRangeReader, BasePDFStreamReader };
|
||||||
|
|||||||
@ -18,7 +18,6 @@ import {
|
|||||||
createResponseError,
|
createResponseError,
|
||||||
extractFilenameFromHeader,
|
extractFilenameFromHeader,
|
||||||
validateRangeRequestCapabilities,
|
validateRangeRequestCapabilities,
|
||||||
validateResponseStatus,
|
|
||||||
} from "../../src/display/network_utils.js";
|
} from "../../src/display/network_utils.js";
|
||||||
import { ResponseException } from "../../src/shared/util.js";
|
import { ResponseException } from "../../src/shared/util.js";
|
||||||
|
|
||||||
@ -391,18 +390,4 @@ describe("network_utils", function () {
|
|||||||
testCreateResponseError("https://foo.com/bar.pdf", 0, false);
|
testCreateResponseError("https://foo.com/bar.pdf", 0, false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("validateResponseStatus", function () {
|
|
||||||
it("accepts valid response statuses", function () {
|
|
||||||
expect(validateResponseStatus(200)).toEqual(true);
|
|
||||||
expect(validateResponseStatus(206)).toEqual(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("rejects invalid response statuses", function () {
|
|
||||||
expect(validateResponseStatus(302)).toEqual(false);
|
|
||||||
expect(validateResponseStatus(404)).toEqual(false);
|
|
||||||
expect(validateResponseStatus(null)).toEqual(false);
|
|
||||||
expect(validateResponseStatus(undefined)).toEqual(false);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user