mirror of
https://github.com/mozilla/pdf.js.git
synced 2025-04-28 23:28:16 +02:00
Merge pull request #8617 from mukulmishra18/network-streaming
Adds Streams API support for networking task of PDF.js project.
This commit is contained in:
commit
343b4dc2b6
9 changed files with 481 additions and 238 deletions
|
@ -1,601 +0,0 @@
|
|||
/* 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 {
|
||||
assert, createPromiseCapability, globalScope, isInt, MissingPDFException,
|
||||
UnexpectedResponseException
|
||||
} from '../shared/util';
|
||||
import { setPDFNetworkStreamClass } from './worker';
|
||||
|
||||
if (typeof PDFJSDev !== 'undefined' && PDFJSDev.test('FIREFOX || MOZCENTRAL')) {
|
||||
throw new Error('Module "./network" shall not ' +
|
||||
'be used with FIREFOX or MOZCENTRAL build.');
|
||||
}
|
||||
|
||||
var OK_RESPONSE = 200;
|
||||
var PARTIAL_CONTENT_RESPONSE = 206;
|
||||
|
||||
function NetworkManager(url, args) {
|
||||
this.url = url;
|
||||
args = args || {};
|
||||
this.isHttp = /^https?:/i.test(url);
|
||||
this.httpHeaders = (this.isHttp && args.httpHeaders) || {};
|
||||
this.withCredentials = args.withCredentials || false;
|
||||
this.getXhr = args.getXhr ||
|
||||
function NetworkManager_getXhr() {
|
||||
return new XMLHttpRequest();
|
||||
};
|
||||
|
||||
this.currXhrId = 0;
|
||||
this.pendingRequests = Object.create(null);
|
||||
this.loadedRequests = Object.create(null);
|
||||
}
|
||||
|
||||
function getArrayBuffer(xhr) {
|
||||
var data = xhr.response;
|
||||
if (typeof data !== 'string') {
|
||||
return data;
|
||||
}
|
||||
var length = data.length;
|
||||
var array = new Uint8Array(length);
|
||||
for (var i = 0; i < length; i++) {
|
||||
array[i] = data.charCodeAt(i) & 0xFF;
|
||||
}
|
||||
return array.buffer;
|
||||
}
|
||||
|
||||
var supportsMozChunked =
|
||||
typeof PDFJSDev !== 'undefined' && PDFJSDev.test('CHROME') ? false :
|
||||
(function supportsMozChunkedClosure() {
|
||||
try {
|
||||
var x = new XMLHttpRequest();
|
||||
// Firefox 37- required .open() to be called before setting responseType.
|
||||
// https://bugzilla.mozilla.org/show_bug.cgi?id=707484
|
||||
// Even though the URL is not visited, .open() could fail if the URL is
|
||||
// blocked, e.g. via the connect-src CSP directive or the NoScript addon.
|
||||
// When this error occurs, this feature detection method will mistakenly
|
||||
// report that moz-chunked-arraybuffer is not supported in Firefox 37-.
|
||||
x.open('GET', globalScope.location.href);
|
||||
x.responseType = 'moz-chunked-arraybuffer';
|
||||
return x.responseType === 'moz-chunked-arraybuffer';
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
})();
|
||||
|
||||
NetworkManager.prototype = {
|
||||
requestRange: function NetworkManager_requestRange(begin, end, listeners) {
|
||||
var args = {
|
||||
begin,
|
||||
end,
|
||||
};
|
||||
for (var prop in listeners) {
|
||||
args[prop] = listeners[prop];
|
||||
}
|
||||
return this.request(args);
|
||||
},
|
||||
|
||||
requestFull: function NetworkManager_requestFull(listeners) {
|
||||
return this.request(listeners);
|
||||
},
|
||||
|
||||
request: function NetworkManager_request(args) {
|
||||
var xhr = this.getXhr();
|
||||
var xhrId = this.currXhrId++;
|
||||
var pendingRequest = this.pendingRequests[xhrId] = {
|
||||
xhr,
|
||||
};
|
||||
|
||||
xhr.open('GET', this.url);
|
||||
xhr.withCredentials = this.withCredentials;
|
||||
for (var property in this.httpHeaders) {
|
||||
var value = this.httpHeaders[property];
|
||||
if (typeof value === 'undefined') {
|
||||
continue;
|
||||
}
|
||||
xhr.setRequestHeader(property, value);
|
||||
}
|
||||
if (this.isHttp && 'begin' in args && 'end' in args) {
|
||||
var rangeStr = args.begin + '-' + (args.end - 1);
|
||||
xhr.setRequestHeader('Range', 'bytes=' + rangeStr);
|
||||
pendingRequest.expectedStatus = 206;
|
||||
} else {
|
||||
pendingRequest.expectedStatus = 200;
|
||||
}
|
||||
|
||||
var useMozChunkedLoading = supportsMozChunked && !!args.onProgressiveData;
|
||||
if (useMozChunkedLoading) {
|
||||
xhr.responseType = 'moz-chunked-arraybuffer';
|
||||
pendingRequest.onProgressiveData = args.onProgressiveData;
|
||||
pendingRequest.mozChunked = true;
|
||||
} else {
|
||||
xhr.responseType = 'arraybuffer';
|
||||
}
|
||||
|
||||
if (args.onError) {
|
||||
xhr.onerror = function(evt) {
|
||||
args.onError(xhr.status);
|
||||
};
|
||||
}
|
||||
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
|
||||
xhr.onprogress = this.onProgress.bind(this, xhrId);
|
||||
|
||||
pendingRequest.onHeadersReceived = args.onHeadersReceived;
|
||||
pendingRequest.onDone = args.onDone;
|
||||
pendingRequest.onError = args.onError;
|
||||
pendingRequest.onProgress = args.onProgress;
|
||||
|
||||
xhr.send(null);
|
||||
|
||||
return xhrId;
|
||||
},
|
||||
|
||||
onProgress: function NetworkManager_onProgress(xhrId, evt) {
|
||||
var pendingRequest = this.pendingRequests[xhrId];
|
||||
if (!pendingRequest) {
|
||||
// Maybe abortRequest was called...
|
||||
return;
|
||||
}
|
||||
|
||||
if (pendingRequest.mozChunked) {
|
||||
var chunk = getArrayBuffer(pendingRequest.xhr);
|
||||
pendingRequest.onProgressiveData(chunk);
|
||||
}
|
||||
|
||||
var onProgress = pendingRequest.onProgress;
|
||||
if (onProgress) {
|
||||
onProgress(evt);
|
||||
}
|
||||
},
|
||||
|
||||
onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
|
||||
var pendingRequest = this.pendingRequests[xhrId];
|
||||
if (!pendingRequest) {
|
||||
// Maybe abortRequest was called...
|
||||
return;
|
||||
}
|
||||
|
||||
var xhr = pendingRequest.xhr;
|
||||
if (xhr.readyState >= 2 && pendingRequest.onHeadersReceived) {
|
||||
pendingRequest.onHeadersReceived();
|
||||
delete pendingRequest.onHeadersReceived;
|
||||
}
|
||||
|
||||
if (xhr.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(xhrId in this.pendingRequests)) {
|
||||
// The XHR request might have been aborted in onHeadersReceived()
|
||||
// callback, in which case we should abort request
|
||||
return;
|
||||
}
|
||||
|
||||
delete this.pendingRequests[xhrId];
|
||||
|
||||
// success status == 0 can be on ftp, file and other protocols
|
||||
if (xhr.status === 0 && this.isHttp) {
|
||||
if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var xhrStatus = xhr.status || OK_RESPONSE;
|
||||
|
||||
// From http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.35.2:
|
||||
// "A server MAY ignore the Range header". This means it's possible to
|
||||
// get a 200 rather than a 206 response from a range request.
|
||||
var ok_response_on_range_request =
|
||||
xhrStatus === OK_RESPONSE &&
|
||||
pendingRequest.expectedStatus === PARTIAL_CONTENT_RESPONSE;
|
||||
|
||||
if (!ok_response_on_range_request &&
|
||||
xhrStatus !== pendingRequest.expectedStatus) {
|
||||
if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
this.loadedRequests[xhrId] = true;
|
||||
|
||||
var chunk = getArrayBuffer(xhr);
|
||||
if (xhrStatus === PARTIAL_CONTENT_RESPONSE) {
|
||||
var rangeHeader = xhr.getResponseHeader('Content-Range');
|
||||
var matches = /bytes (\d+)-(\d+)\/(\d+)/.exec(rangeHeader);
|
||||
var begin = parseInt(matches[1], 10);
|
||||
pendingRequest.onDone({
|
||||
begin,
|
||||
chunk,
|
||||
});
|
||||
} else if (pendingRequest.onProgressiveData) {
|
||||
pendingRequest.onDone(null);
|
||||
} else if (chunk) {
|
||||
pendingRequest.onDone({
|
||||
begin: 0,
|
||||
chunk,
|
||||
});
|
||||
} else if (pendingRequest.onError) {
|
||||
pendingRequest.onError(xhr.status);
|
||||
}
|
||||
},
|
||||
|
||||
hasPendingRequests: function NetworkManager_hasPendingRequests() {
|
||||
for (var xhrId in this.pendingRequests) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
getRequestXhr: function NetworkManager_getXhr(xhrId) {
|
||||
return this.pendingRequests[xhrId].xhr;
|
||||
},
|
||||
|
||||
isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
|
||||
return !!(this.pendingRequests[xhrId].onProgressiveData);
|
||||
},
|
||||
|
||||
isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
|
||||
return xhrId in this.pendingRequests;
|
||||
},
|
||||
|
||||
isLoadedRequest: function NetworkManager_isLoadedRequest(xhrId) {
|
||||
return xhrId in this.loadedRequests;
|
||||
},
|
||||
|
||||
abortAllRequests: function NetworkManager_abortAllRequests() {
|
||||
for (var xhrId in this.pendingRequests) {
|
||||
this.abortRequest(xhrId | 0);
|
||||
}
|
||||
},
|
||||
|
||||
abortRequest: function NetworkManager_abortRequest(xhrId) {
|
||||
var xhr = this.pendingRequests[xhrId].xhr;
|
||||
delete this.pendingRequests[xhrId];
|
||||
xhr.abort();
|
||||
},
|
||||
};
|
||||
|
||||
/** @implements {IPDFStream} */
|
||||
function PDFNetworkStream(options) {
|
||||
this._options = options;
|
||||
var source = options.source;
|
||||
this._manager = new NetworkManager(source.url, {
|
||||
httpHeaders: source.httpHeaders,
|
||||
withCredentials: source.withCredentials,
|
||||
});
|
||||
this._rangeChunkSize = source.rangeChunkSize;
|
||||
this._fullRequestReader = null;
|
||||
this._rangeRequestReaders = [];
|
||||
}
|
||||
|
||||
PDFNetworkStream.prototype = {
|
||||
_onRangeRequestReaderClosed:
|
||||
function PDFNetworkStream_onRangeRequestReaderClosed(reader) {
|
||||
var i = this._rangeRequestReaders.indexOf(reader);
|
||||
if (i >= 0) {
|
||||
this._rangeRequestReaders.splice(i, 1);
|
||||
}
|
||||
},
|
||||
|
||||
getFullReader: function PDFNetworkStream_getFullReader() {
|
||||
assert(!this._fullRequestReader);
|
||||
this._fullRequestReader =
|
||||
new PDFNetworkStreamFullRequestReader(this._manager, this._options);
|
||||
return this._fullRequestReader;
|
||||
},
|
||||
|
||||
getRangeReader: function PDFNetworkStream_getRangeReader(begin, end) {
|
||||
var reader = new PDFNetworkStreamRangeRequestReader(this._manager,
|
||||
begin, end);
|
||||
reader.onClosed = this._onRangeRequestReaderClosed.bind(this);
|
||||
this._rangeRequestReaders.push(reader);
|
||||
return reader;
|
||||
},
|
||||
|
||||
cancelAllRequests: function PDFNetworkStream_cancelAllRequests(reason) {
|
||||
if (this._fullRequestReader) {
|
||||
this._fullRequestReader.cancel(reason);
|
||||
}
|
||||
var readers = this._rangeRequestReaders.slice(0);
|
||||
readers.forEach(function (reader) {
|
||||
reader.cancel(reason);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/** @implements {IPDFStreamReader} */
|
||||
function PDFNetworkStreamFullRequestReader(manager, options) {
|
||||
this._manager = manager;
|
||||
|
||||
var source = options.source;
|
||||
var args = {
|
||||
onHeadersReceived: this._onHeadersReceived.bind(this),
|
||||
onProgressiveData: source.disableStream ? null :
|
||||
this._onProgressiveData.bind(this),
|
||||
onDone: this._onDone.bind(this),
|
||||
onError: this._onError.bind(this),
|
||||
onProgress: this._onProgress.bind(this),
|
||||
};
|
||||
this._url = source.url;
|
||||
this._fullRequestId = manager.requestFull(args);
|
||||
this._headersReceivedCapability = createPromiseCapability();
|
||||
this._disableRange = options.disableRange || false;
|
||||
this._contentLength = source.length; // optional
|
||||
this._rangeChunkSize = source.rangeChunkSize;
|
||||
if (!this._rangeChunkSize && !this._disableRange) {
|
||||
this._disableRange = true;
|
||||
}
|
||||
|
||||
this._isStreamingSupported = false;
|
||||
this._isRangeSupported = false;
|
||||
|
||||
this._cachedChunks = [];
|
||||
this._requests = [];
|
||||
this._done = false;
|
||||
this._storedError = undefined;
|
||||
|
||||
this.onProgress = null;
|
||||
}
|
||||
|
||||
PDFNetworkStreamFullRequestReader.prototype = {
|
||||
_validateRangeRequestCapabilities: function
|
||||
PDFNetworkStreamFullRequestReader_validateRangeRequestCapabilities() {
|
||||
|
||||
if (this._disableRange) {
|
||||
return false;
|
||||
}
|
||||
|
||||
var networkManager = this._manager;
|
||||
if (!networkManager.isHttp) {
|
||||
return false;
|
||||
}
|
||||
var fullRequestXhrId = this._fullRequestId;
|
||||
var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
|
||||
if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var contentEncoding =
|
||||
fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity';
|
||||
if (contentEncoding !== 'identity') {
|
||||
return false;
|
||||
}
|
||||
|
||||
var length = fullRequestXhr.getResponseHeader('Content-Length');
|
||||
length = parseInt(length, 10);
|
||||
if (!isInt(length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._contentLength = length; // setting right content length
|
||||
|
||||
if (length <= 2 * this._rangeChunkSize) {
|
||||
// The file size is smaller than the size of two chunks, so it does
|
||||
// not make any sense to abort the request and retry with a range
|
||||
// request.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
|
||||
_onHeadersReceived:
|
||||
function PDFNetworkStreamFullRequestReader_onHeadersReceived() {
|
||||
|
||||
if (this._validateRangeRequestCapabilities()) {
|
||||
this._isRangeSupported = true;
|
||||
}
|
||||
|
||||
var networkManager = this._manager;
|
||||
var fullRequestXhrId = this._fullRequestId;
|
||||
if (networkManager.isStreamingRequest(fullRequestXhrId)) {
|
||||
// We can continue fetching when progressive loading is enabled,
|
||||
// and we don't need the autoFetch feature.
|
||||
this._isStreamingSupported = true;
|
||||
} else if (this._isRangeSupported) {
|
||||
// NOTE: by cancelling the full request, and then issuing range
|
||||
// requests, there will be an issue for sites where you can only
|
||||
// request the pdf once. However, if this is the case, then the
|
||||
// server should not be returning that it can support range
|
||||
// requests.
|
||||
networkManager.abortRequest(fullRequestXhrId);
|
||||
}
|
||||
|
||||
this._headersReceivedCapability.resolve();
|
||||
},
|
||||
|
||||
_onProgressiveData:
|
||||
function PDFNetworkStreamFullRequestReader_onProgressiveData(chunk) {
|
||||
if (this._requests.length > 0) {
|
||||
var requestCapability = this._requests.shift();
|
||||
requestCapability.resolve({ value: chunk, done: false, });
|
||||
} else {
|
||||
this._cachedChunks.push(chunk);
|
||||
}
|
||||
},
|
||||
|
||||
_onDone: function PDFNetworkStreamFullRequestReader_onDone(args) {
|
||||
if (args) {
|
||||
this._onProgressiveData(args.chunk);
|
||||
}
|
||||
this._done = true;
|
||||
if (this._cachedChunks.length > 0) {
|
||||
return;
|
||||
}
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({ value: undefined, done: true, });
|
||||
});
|
||||
this._requests = [];
|
||||
},
|
||||
|
||||
_onError: function PDFNetworkStreamFullRequestReader_onError(status) {
|
||||
var url = this._url;
|
||||
var exception;
|
||||
if (status === 404 || status === 0 && /^file:/.test(url)) {
|
||||
exception = new MissingPDFException('Missing PDF "' + url + '".');
|
||||
} else {
|
||||
exception = new UnexpectedResponseException(
|
||||
'Unexpected server response (' + status +
|
||||
') while retrieving PDF "' + url + '".', status);
|
||||
}
|
||||
this._storedError = exception;
|
||||
this._headersReceivedCapability.reject(exception);
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.reject(exception);
|
||||
});
|
||||
this._requests = [];
|
||||
this._cachedChunks = [];
|
||||
},
|
||||
|
||||
_onProgress: function PDFNetworkStreamFullRequestReader_onProgress(data) {
|
||||
if (this.onProgress) {
|
||||
this.onProgress({
|
||||
loaded: data.loaded,
|
||||
total: data.lengthComputable ? data.total : this._contentLength,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get isRangeSupported() {
|
||||
return this._isRangeSupported;
|
||||
},
|
||||
|
||||
get isStreamingSupported() {
|
||||
return this._isStreamingSupported;
|
||||
},
|
||||
|
||||
get contentLength() {
|
||||
return this._contentLength;
|
||||
},
|
||||
|
||||
get headersReady() {
|
||||
return this._headersReceivedCapability.promise;
|
||||
},
|
||||
|
||||
read: function PDFNetworkStreamFullRequestReader_read() {
|
||||
if (this._storedError) {
|
||||
return Promise.reject(this._storedError);
|
||||
}
|
||||
if (this._cachedChunks.length > 0) {
|
||||
var chunk = this._cachedChunks.shift();
|
||||
return Promise.resolve({ value: chunk, done: false, });
|
||||
}
|
||||
if (this._done) {
|
||||
return Promise.resolve({ value: undefined, done: true, });
|
||||
}
|
||||
var requestCapability = createPromiseCapability();
|
||||
this._requests.push(requestCapability);
|
||||
return requestCapability.promise;
|
||||
},
|
||||
|
||||
cancel: function PDFNetworkStreamFullRequestReader_cancel(reason) {
|
||||
this._done = true;
|
||||
this._headersReceivedCapability.reject(reason);
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({ value: undefined, done: true, });
|
||||
});
|
||||
this._requests = [];
|
||||
if (this._manager.isPendingRequest(this._fullRequestId)) {
|
||||
this._manager.abortRequest(this._fullRequestId);
|
||||
}
|
||||
this._fullRequestReader = null;
|
||||
},
|
||||
};
|
||||
|
||||
/** @implements {IPDFStreamRangeReader} */
|
||||
function PDFNetworkStreamRangeRequestReader(manager, begin, end) {
|
||||
this._manager = manager;
|
||||
var args = {
|
||||
onDone: this._onDone.bind(this),
|
||||
onProgress: this._onProgress.bind(this),
|
||||
};
|
||||
this._requestId = manager.requestRange(begin, end, args);
|
||||
this._requests = [];
|
||||
this._queuedChunk = null;
|
||||
this._done = false;
|
||||
|
||||
this.onProgress = null;
|
||||
this.onClosed = null;
|
||||
}
|
||||
|
||||
PDFNetworkStreamRangeRequestReader.prototype = {
|
||||
_close: function PDFNetworkStreamRangeRequestReader_close() {
|
||||
if (this.onClosed) {
|
||||
this.onClosed(this);
|
||||
}
|
||||
},
|
||||
|
||||
_onDone: function PDFNetworkStreamRangeRequestReader_onDone(data) {
|
||||
var chunk = data.chunk;
|
||||
if (this._requests.length > 0) {
|
||||
var requestCapability = this._requests.shift();
|
||||
requestCapability.resolve({ value: chunk, done: false, });
|
||||
} else {
|
||||
this._queuedChunk = chunk;
|
||||
}
|
||||
this._done = true;
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({ value: undefined, done: true, });
|
||||
});
|
||||
this._requests = [];
|
||||
this._close();
|
||||
},
|
||||
|
||||
_onProgress: function PDFNetworkStreamRangeRequestReader_onProgress(evt) {
|
||||
if (!this.isStreamingSupported && this.onProgress) {
|
||||
this.onProgress({
|
||||
loaded: evt.loaded,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
get isStreamingSupported() {
|
||||
return false; // TODO allow progressive range bytes loading
|
||||
},
|
||||
|
||||
read: function PDFNetworkStreamRangeRequestReader_read() {
|
||||
if (this._queuedChunk !== null) {
|
||||
var chunk = this._queuedChunk;
|
||||
this._queuedChunk = null;
|
||||
return Promise.resolve({ value: chunk, done: false, });
|
||||
}
|
||||
if (this._done) {
|
||||
return Promise.resolve({ value: undefined, done: true, });
|
||||
}
|
||||
var requestCapability = createPromiseCapability();
|
||||
this._requests.push(requestCapability);
|
||||
return requestCapability.promise;
|
||||
},
|
||||
|
||||
cancel: function PDFNetworkStreamRangeRequestReader_cancel(reason) {
|
||||
this._done = true;
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({ value: undefined, done: true, });
|
||||
});
|
||||
this._requests = [];
|
||||
if (this._manager.isPendingRequest(this._requestId)) {
|
||||
this._manager.abortRequest(this._requestId);
|
||||
}
|
||||
this._close();
|
||||
},
|
||||
};
|
||||
|
||||
setPDFNetworkStreamClass(PDFNetworkStream);
|
||||
|
||||
export {
|
||||
PDFNetworkStream,
|
||||
NetworkManager,
|
||||
};
|
|
@ -198,218 +198,120 @@ IPDFStreamRangeReader.prototype = {
|
|||
|
||||
/** @implements {IPDFStream} */
|
||||
var PDFWorkerStream = (function PDFWorkerStreamClosure() {
|
||||
function PDFWorkerStream(params, msgHandler) {
|
||||
this._queuedChunks = [];
|
||||
var initialData = params.initialData;
|
||||
if (initialData && initialData.length > 0) {
|
||||
this._queuedChunks.push(initialData);
|
||||
}
|
||||
function PDFWorkerStream(msgHandler) {
|
||||
this._msgHandler = msgHandler;
|
||||
|
||||
this._isRangeSupported = !(params.disableRange);
|
||||
this._isStreamingSupported = !(params.disableStream);
|
||||
this._contentLength = params.length;
|
||||
|
||||
this._contentLength = null;
|
||||
this._fullRequestReader = null;
|
||||
this._rangeReaders = [];
|
||||
|
||||
msgHandler.on('OnDataRange', this._onReceiveData.bind(this));
|
||||
msgHandler.on('OnDataProgress', this._onProgress.bind(this));
|
||||
this._rangeRequestReaders = [];
|
||||
}
|
||||
PDFWorkerStream.prototype = {
|
||||
_onReceiveData: function PDFWorkerStream_onReceiveData(args) {
|
||||
if (args.begin === undefined) {
|
||||
if (this._fullRequestReader) {
|
||||
this._fullRequestReader._enqueue(args.chunk);
|
||||
} else {
|
||||
this._queuedChunks.push(args.chunk);
|
||||
}
|
||||
} else {
|
||||
var found = this._rangeReaders.some(function (rangeReader) {
|
||||
if (rangeReader._begin !== args.begin) {
|
||||
return false;
|
||||
}
|
||||
rangeReader._enqueue(args.chunk);
|
||||
return true;
|
||||
});
|
||||
assert(found);
|
||||
}
|
||||
},
|
||||
|
||||
_onProgress: function PDFWorkerStream_onProgress(evt) {
|
||||
if (this._rangeReaders.length > 0) {
|
||||
// Reporting to first range reader.
|
||||
var firstReader = this._rangeReaders[0];
|
||||
if (firstReader.onProgress) {
|
||||
firstReader.onProgress({ loaded: evt.loaded, });
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
_removeRangeReader: function PDFWorkerStream_removeRangeReader(reader) {
|
||||
var i = this._rangeReaders.indexOf(reader);
|
||||
if (i >= 0) {
|
||||
this._rangeReaders.splice(i, 1);
|
||||
}
|
||||
},
|
||||
|
||||
getFullReader: function PDFWorkerStream_getFullReader() {
|
||||
getFullReader() {
|
||||
assert(!this._fullRequestReader);
|
||||
var queuedChunks = this._queuedChunks;
|
||||
this._queuedChunks = null;
|
||||
return new PDFWorkerStreamReader(this, queuedChunks);
|
||||
this._fullRequestReader = new PDFWorkerStreamReader(this._msgHandler);
|
||||
return this._fullRequestReader;
|
||||
},
|
||||
|
||||
getRangeReader: function PDFWorkerStream_getRangeReader(begin, end) {
|
||||
var reader = new PDFWorkerStreamRangeReader(this, begin, end);
|
||||
this._msgHandler.send('RequestDataRange', { begin, end, });
|
||||
this._rangeReaders.push(reader);
|
||||
getRangeReader(begin, end) {
|
||||
let reader = new PDFWorkerStreamRangeReader(begin, end, this._msgHandler);
|
||||
this._rangeRequestReaders.push(reader);
|
||||
return reader;
|
||||
},
|
||||
|
||||
cancelAllRequests: function PDFWorkerStream_cancelAllRequests(reason) {
|
||||
cancelAllRequests(reason) {
|
||||
if (this._fullRequestReader) {
|
||||
this._fullRequestReader.cancel(reason);
|
||||
}
|
||||
var readers = this._rangeReaders.slice(0);
|
||||
readers.forEach(function (rangeReader) {
|
||||
rangeReader.cancel(reason);
|
||||
let readers = this._rangeRequestReaders.slice(0);
|
||||
readers.forEach(function (reader) {
|
||||
reader.cancel(reason);
|
||||
});
|
||||
},
|
||||
};
|
||||
|
||||
/** @implements {IPDFStreamReader} */
|
||||
function PDFWorkerStreamReader(stream, queuedChunks) {
|
||||
this._stream = stream;
|
||||
this._done = false;
|
||||
this._queuedChunks = queuedChunks || [];
|
||||
this._requests = [];
|
||||
this._headersReady = Promise.resolve();
|
||||
stream._fullRequestReader = this;
|
||||
function PDFWorkerStreamReader(msgHandler) {
|
||||
this._msgHandler = msgHandler;
|
||||
|
||||
this.onProgress = null; // not used
|
||||
this._contentLength = null;
|
||||
this._isRangeSupported = false;
|
||||
this._isStreamingSupported = false;
|
||||
|
||||
let readableStream = this._msgHandler.sendWithStream('GetReader');
|
||||
|
||||
this._reader = readableStream.getReader();
|
||||
|
||||
this._headersReady = this._msgHandler.sendWithPromise('ReaderHeadersReady').
|
||||
then((data) => {
|
||||
this._isStreamingSupported = data.isStreamingSupported;
|
||||
this._isRangeSupported = data.isRangeSupported;
|
||||
this._contentLength = data.contentLength;
|
||||
});
|
||||
}
|
||||
PDFWorkerStreamReader.prototype = {
|
||||
_enqueue: function PDFWorkerStreamReader_enqueue(chunk) {
|
||||
if (this._done) {
|
||||
return; // ignore new data
|
||||
}
|
||||
if (this._requests.length > 0) {
|
||||
var requestCapability = this._requests.shift();
|
||||
requestCapability.resolve({ value: chunk, done: false, });
|
||||
return;
|
||||
}
|
||||
this._queuedChunks.push(chunk);
|
||||
},
|
||||
|
||||
get headersReady() {
|
||||
return this._headersReady;
|
||||
},
|
||||
|
||||
get isRangeSupported() {
|
||||
return this._stream._isRangeSupported;
|
||||
get contentLength() {
|
||||
return this._contentLength;
|
||||
},
|
||||
|
||||
get isStreamingSupported() {
|
||||
return this._stream._isStreamingSupported;
|
||||
return this._isStreamingSupported;
|
||||
},
|
||||
|
||||
get contentLength() {
|
||||
return this._stream._contentLength;
|
||||
get isRangeSupported() {
|
||||
return this._isRangeSupported;
|
||||
},
|
||||
|
||||
read: function PDFWorkerStreamReader_read() {
|
||||
if (this._queuedChunks.length > 0) {
|
||||
var chunk = this._queuedChunks.shift();
|
||||
return Promise.resolve({ value: chunk, done: false, });
|
||||
}
|
||||
if (this._done) {
|
||||
return Promise.resolve({ value: undefined, done: true, });
|
||||
}
|
||||
var requestCapability = createPromiseCapability();
|
||||
this._requests.push(requestCapability);
|
||||
return requestCapability.promise;
|
||||
},
|
||||
|
||||
cancel: function PDFWorkerStreamReader_cancel(reason) {
|
||||
this._done = true;
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({ value: undefined, done: true, });
|
||||
read() {
|
||||
return this._reader.read().then(function({ value, done, }) {
|
||||
if (done) {
|
||||
return { value: undefined, done: true, };
|
||||
}
|
||||
// `value` is wrapped into Uint8Array, we need to
|
||||
// unwrap it to ArrayBuffer for further processing.
|
||||
return { value: value.buffer, done: false, };
|
||||
});
|
||||
this._requests = [];
|
||||
},
|
||||
|
||||
cancel(reason) {
|
||||
this._reader.cancel(reason);
|
||||
},
|
||||
};
|
||||
|
||||
/** @implements {IPDFStreamRangeReader} */
|
||||
function PDFWorkerStreamRangeReader(stream, begin, end) {
|
||||
this._stream = stream;
|
||||
this._begin = begin;
|
||||
this._end = end;
|
||||
this._queuedChunk = null;
|
||||
this._requests = [];
|
||||
this._done = false;
|
||||
|
||||
function PDFWorkerStreamRangeReader(begin, end, msgHandler) {
|
||||
this._msgHandler = msgHandler;
|
||||
this.onProgress = null;
|
||||
|
||||
let readableStream = this._msgHandler.sendWithStream('GetRangeReader',
|
||||
{ begin, end, });
|
||||
|
||||
this._reader = readableStream.getReader();
|
||||
}
|
||||
PDFWorkerStreamRangeReader.prototype = {
|
||||
_enqueue: function PDFWorkerStreamRangeReader_enqueue(chunk) {
|
||||
if (this._done) {
|
||||
return; // ignore new data
|
||||
}
|
||||
if (this._requests.length === 0) {
|
||||
this._queuedChunk = chunk;
|
||||
} else {
|
||||
var requestsCapability = this._requests.shift();
|
||||
requestsCapability.resolve({ value: chunk, done: false, });
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({ value: undefined, done: true, });
|
||||
});
|
||||
this._requests = [];
|
||||
}
|
||||
this._done = true;
|
||||
this._stream._removeRangeReader(this);
|
||||
},
|
||||
|
||||
get isStreamingSupported() {
|
||||
return false;
|
||||
},
|
||||
|
||||
read: function PDFWorkerStreamRangeReader_read() {
|
||||
if (this._queuedChunk) {
|
||||
return Promise.resolve({ value: this._queuedChunk, done: false, });
|
||||
}
|
||||
if (this._done) {
|
||||
return Promise.resolve({ value: undefined, done: true, });
|
||||
}
|
||||
var requestCapability = createPromiseCapability();
|
||||
this._requests.push(requestCapability);
|
||||
return requestCapability.promise;
|
||||
read() {
|
||||
return this._reader.read().then(function({ value, done, }) {
|
||||
if (done) {
|
||||
return { value: undefined, done: true, };
|
||||
}
|
||||
return { value: value.buffer, done: false, };
|
||||
});
|
||||
},
|
||||
|
||||
cancel: function PDFWorkerStreamRangeReader_cancel(reason) {
|
||||
this._done = true;
|
||||
this._requests.forEach(function (requestCapability) {
|
||||
requestCapability.resolve({ value: undefined, done: true, });
|
||||
});
|
||||
this._requests = [];
|
||||
this._stream._removeRangeReader(this);
|
||||
cancel(reason) {
|
||||
this._reader.cancel(reason);
|
||||
},
|
||||
};
|
||||
|
||||
return PDFWorkerStream;
|
||||
})();
|
||||
|
||||
/** @type IPDFStream */
|
||||
var PDFNetworkStream;
|
||||
|
||||
/**
|
||||
* Sets PDFNetworkStream class to be used as alternative PDF data transport.
|
||||
* @param {IPDFStream} cls - the PDF data transport.
|
||||
*/
|
||||
function setPDFNetworkStreamClass(cls) {
|
||||
PDFNetworkStream = cls;
|
||||
}
|
||||
|
||||
var WorkerMessageHandler = {
|
||||
setup(handler, port) {
|
||||
var testMessageProcessed = false;
|
||||
|
@ -536,16 +438,9 @@ var WorkerMessageHandler = {
|
|||
return pdfManagerCapability.promise;
|
||||
}
|
||||
|
||||
var pdfStream;
|
||||
var pdfStream, cachedChunks = [];
|
||||
try {
|
||||
if (source.chunkedViewerLoading) {
|
||||
pdfStream = new PDFWorkerStream(source, handler);
|
||||
} else {
|
||||
if (!PDFNetworkStream) {
|
||||
throw new Error('./network module is not loaded');
|
||||
}
|
||||
pdfStream = new PDFNetworkStream(data);
|
||||
}
|
||||
pdfStream = new PDFWorkerStream(handler);
|
||||
} catch (ex) {
|
||||
pdfManagerCapability.reject(ex);
|
||||
return pdfManagerCapability.promise;
|
||||
|
@ -553,18 +448,6 @@ var WorkerMessageHandler = {
|
|||
|
||||
var fullRequest = pdfStream.getFullReader();
|
||||
fullRequest.headersReady.then(function () {
|
||||
if (!fullRequest.isStreamingSupported ||
|
||||
!fullRequest.isRangeSupported) {
|
||||
// If stream or range are disabled, it's our only way to report
|
||||
// loading progress.
|
||||
fullRequest.onProgress = function (evt) {
|
||||
handler.send('DocProgress', {
|
||||
loaded: evt.loaded,
|
||||
total: evt.total,
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
if (!fullRequest.isRangeSupported) {
|
||||
return;
|
||||
}
|
||||
|
@ -580,6 +463,15 @@ var WorkerMessageHandler = {
|
|||
disableAutoFetch,
|
||||
rangeChunkSize: source.rangeChunkSize,
|
||||
}, evaluatorOptions, docBaseUrl);
|
||||
// There may be a chance that `pdfManager` is not initialized
|
||||
// for first few runs of `readchunk` block of code. Be sure
|
||||
// to send all cached chunks, if any, to chunked_stream via
|
||||
// pdf_manager.
|
||||
for (let i = 0; i < cachedChunks.length; i++) {
|
||||
pdfManager.sendProgressiveData(cachedChunks[i]);
|
||||
}
|
||||
|
||||
cachedChunks = [];
|
||||
pdfManagerCapability.resolve(pdfManager);
|
||||
cancelXHRs = null;
|
||||
}).catch(function (reason) {
|
||||
|
@ -587,7 +479,7 @@ var WorkerMessageHandler = {
|
|||
cancelXHRs = null;
|
||||
});
|
||||
|
||||
var cachedChunks = [], loaded = 0;
|
||||
var loaded = 0;
|
||||
var flushChunks = function () {
|
||||
var pdfFile = arraysToBytes(cachedChunks);
|
||||
if (source.length && pdfFile.length !== source.length) {
|
||||
|
@ -969,7 +861,6 @@ if (typeof window === 'undefined' && !isNodeJS() &&
|
|||
}
|
||||
|
||||
export {
|
||||
setPDFNetworkStreamClass,
|
||||
WorkerTask,
|
||||
WorkerMessageHandler,
|
||||
};
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue