Move the NetworkManager functionality into the PDFNetworkStream class

The `NetworkManager` is very old code at this point, and it predates the introduction of the streaming functionality by many years.
To simplify things, especially with upcoming re-factoring patches, let's move this functionality into private (and semi-private) methods in the `PDFNetworkStream` class to avoid having to deal with too many different scopes.
This commit is contained in:
Jonas Jenwald 2026-01-25 12:41:33 +01:00
parent 4d9301fceb
commit eaf605d720

View File

@ -35,19 +35,27 @@ function getArrayBuffer(val) {
return typeof val !== "string" ? val : stringToBytes(val).buffer; return typeof val !== "string" ? val : stringToBytes(val).buffer;
} }
class NetworkManager { /** @implements {IPDFStream} */
class PDFNetworkStream {
#pendingRequests = new WeakMap(); #pendingRequests = new WeakMap();
_fullRequestReader = null;
_rangeRequestReaders = [];
_responseOrigin = null; _responseOrigin = null;
constructor({ url, httpHeaders, withCredentials }) { constructor(source) {
this.url = url; this._source = source;
this.isHttp = /^https?:/i.test(url); this.url = source.url;
this.headers = createHeaders(this.isHttp, httpHeaders); this.isHttp = /^https?:/i.test(this.url);
this.withCredentials = withCredentials || false; this.headers = createHeaders(this.isHttp, source.httpHeaders);
} }
request(args) { /**
* @ignore
*/
_request(args) {
const xhr = new XMLHttpRequest(); const xhr = new XMLHttpRequest();
const pendingRequest = { const pendingRequest = {
validateStatus: null, validateStatus: null,
@ -59,7 +67,7 @@ class NetworkManager {
this.#pendingRequests.set(xhr, pendingRequest); this.#pendingRequests.set(xhr, pendingRequest);
xhr.open("GET", this.url); xhr.open("GET", this.url);
xhr.withCredentials = this.withCredentials; xhr.withCredentials = this._source.withCredentials;
for (const [key, val] of this.headers) { for (const [key, val] of this.headers) {
xhr.setRequestHeader(key, val); xhr.setRequestHeader(key, val);
} }
@ -141,51 +149,34 @@ class NetworkManager {
} }
} }
// Abort the request, if it's pending. /**
abortRequest(xhr) { * Abort the request, if it's pending.
* @ignore
*/
_abortRequest(xhr) {
if (this.#pendingRequests.has(xhr)) { if (this.#pendingRequests.has(xhr)) {
this.#pendingRequests.delete(xhr); this.#pendingRequests.delete(xhr);
xhr.abort(); xhr.abort();
} }
} }
}
/** @implements {IPDFStream} */
class PDFNetworkStream {
constructor(source) {
this._source = source;
this._manager = new NetworkManager(source);
this._rangeChunkSize = source.rangeChunkSize;
this._fullRequestReader = null;
this._rangeRequestReaders = [];
}
_onRangeRequestReaderClosed(reader) {
const i = this._rangeRequestReaders.indexOf(reader);
if (i >= 0) {
this._rangeRequestReaders.splice(i, 1);
}
}
getFullReader() { getFullReader() {
assert( assert(
!this._fullRequestReader, !this._fullRequestReader,
"PDFNetworkStream.getFullReader can only be called once." "PDFNetworkStream.getFullReader can only be called once."
); );
this._fullRequestReader = new PDFNetworkStreamFullRequestReader( this._fullRequestReader = new PDFNetworkStreamFullRequestReader(this);
this._manager,
this._source
);
return this._fullRequestReader; return this._fullRequestReader;
} }
getRangeReader(begin, end) { getRangeReader(begin, end) {
const reader = new PDFNetworkStreamRangeRequestReader( const reader = new PDFNetworkStreamRangeRequestReader(this, begin, end);
this._manager, reader.onClosed = () => {
begin, const i = this._rangeRequestReaders.indexOf(reader);
end if (i >= 0) {
); this._rangeRequestReaders.splice(i, 1);
reader.onClosed = this._onRangeRequestReaderClosed.bind(this); }
};
this._rangeRequestReaders.push(reader); this._rangeRequestReaders.push(reader);
return reader; return reader;
} }
@ -201,20 +192,20 @@ class PDFNetworkStream {
/** @implements {IPDFStreamReader} */ /** @implements {IPDFStreamReader} */
class PDFNetworkStreamFullRequestReader { class PDFNetworkStreamFullRequestReader {
constructor(manager, source) { constructor(stream) {
this._manager = manager; this._stream = stream;
const { disableRange, length, rangeChunkSize } = stream._source;
this._url = source.url; this._fullRequestXhr = stream._request({
this._fullRequestXhr = manager.request({
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: this._onProgress.bind(this), onProgress: this._onProgress.bind(this),
}); });
this._headersCapability = Promise.withResolvers(); this._headersCapability = Promise.withResolvers();
this._disableRange = source.disableRange || false; this._disableRange = disableRange || false;
this._contentLength = source.length; // Optional this._contentLength = length; // Optional
this._rangeChunkSize = source.rangeChunkSize; this._rangeChunkSize = rangeChunkSize;
if (!this._rangeChunkSize && !this._disableRange) { if (!this._rangeChunkSize && !this._disableRange) {
this._disableRange = true; this._disableRange = true;
} }
@ -232,11 +223,10 @@ class PDFNetworkStreamFullRequestReader {
} }
_onHeadersReceived() { _onHeadersReceived() {
const stream = this._stream;
const fullRequestXhr = this._fullRequestXhr; const fullRequestXhr = this._fullRequestXhr;
this._manager._responseOrigin = getResponseOrigin( stream._responseOrigin = getResponseOrigin(fullRequestXhr.responseURL);
fullRequestXhr.responseURL
);
const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders(); const rawResponseHeaders = fullRequestXhr.getAllResponseHeaders();
const responseHeaders = new Headers( const responseHeaders = new Headers(
@ -255,7 +245,7 @@ class PDFNetworkStreamFullRequestReader {
const { allowRangeRequests, suggestedLength } = const { allowRangeRequests, suggestedLength } =
validateRangeRequestCapabilities({ validateRangeRequestCapabilities({
responseHeaders, responseHeaders,
isHttp: this._manager.isHttp, isHttp: stream.isHttp,
rangeChunkSize: this._rangeChunkSize, rangeChunkSize: this._rangeChunkSize,
disableRange: this._disableRange, disableRange: this._disableRange,
}); });
@ -273,7 +263,7 @@ class PDFNetworkStreamFullRequestReader {
// requests, there will be an issue for sites where you can only // requests, there will be an issue for sites where you can only
// request the pdf once. However, if this is the case, then the // request the pdf once. However, if this is the case, then the
// server should not be returning that it can support range requests. // server should not be returning that it can support range requests.
this._manager.abortRequest(fullRequestXhr); stream._abortRequest(fullRequestXhr);
} }
this._headersCapability.resolve(); this._headersCapability.resolve();
@ -297,7 +287,7 @@ class PDFNetworkStreamFullRequestReader {
} }
_onError(status) { _onError(status) {
this._storedError = createResponseError(status, this._url); this._storedError = createResponseError(status, this._stream.url);
this._headersCapability.reject(this._storedError); this._headersCapability.reject(this._storedError);
for (const requestCapability of this._requests) { for (const requestCapability of this._requests) {
requestCapability.reject(this._storedError); requestCapability.reject(this._storedError);
@ -359,18 +349,19 @@ class PDFNetworkStreamFullRequestReader {
} }
this._requests.length = 0; this._requests.length = 0;
this._manager.abortRequest(this._fullRequestXhr); this._stream._abortRequest(this._fullRequestXhr);
this._fullRequestXhr = null; this._fullRequestXhr = null;
} }
} }
/** @implements {IPDFStreamRangeReader} */ /** @implements {IPDFStreamRangeReader} */
class PDFNetworkStreamRangeRequestReader { class PDFNetworkStreamRangeRequestReader {
constructor(manager, begin, end) { onClosed = null;
this._manager = manager;
this._url = manager.url; constructor(stream, begin, end) {
this._requestXhr = manager.request({ this._stream = stream;
this._requestXhr = stream._request({
begin, begin,
end, end,
onHeadersReceived: this._onHeadersReceived.bind(this), onHeadersReceived: this._onHeadersReceived.bind(this),
@ -384,24 +375,19 @@ class PDFNetworkStreamRangeRequestReader {
this._storedError = undefined; this._storedError = undefined;
this.onProgress = null; this.onProgress = null;
this.onClosed = null;
} }
_onHeadersReceived() { _onHeadersReceived() {
const responseOrigin = getResponseOrigin(this._requestXhr?.responseURL); const responseOrigin = getResponseOrigin(this._requestXhr?.responseURL);
if (responseOrigin !== this._manager._responseOrigin) { if (responseOrigin !== this._stream._responseOrigin) {
this._storedError = new Error( this._storedError = new Error(
`Expected range response-origin "${responseOrigin}" to match "${this._manager._responseOrigin}".` `Expected range response-origin "${responseOrigin}" to match "${this._stream._responseOrigin}".`
); );
this._onError(0); this._onError(0);
} }
} }
_close() {
this.onClosed?.(this);
}
_onDone(chunk) { _onDone(chunk) {
if (this._requests.length > 0) { if (this._requests.length > 0) {
const requestCapability = this._requests.shift(); const requestCapability = this._requests.shift();
@ -414,11 +400,11 @@ class PDFNetworkStreamRangeRequestReader {
requestCapability.resolve({ value: undefined, done: true }); requestCapability.resolve({ value: undefined, done: true });
} }
this._requests.length = 0; this._requests.length = 0;
this._close(); this.onClosed?.(this);
} }
_onError(status) { _onError(status) {
this._storedError ??= createResponseError(status, this._url); this._storedError ??= createResponseError(status, this._stream.url);
for (const requestCapability of this._requests) { for (const requestCapability of this._requests) {
requestCapability.reject(this._storedError); requestCapability.reject(this._storedError);
} }
@ -460,8 +446,8 @@ class PDFNetworkStreamRangeRequestReader {
} }
this._requests.length = 0; this._requests.length = 0;
this._manager.abortRequest(this._requestXhr); this._stream._abortRequest(this._requestXhr);
this._close(); this.onClosed?.(this);
} }
} }