Merge pull request #5263 from yurydelendik/stream

Implement streaming using moz-chunk-arraybuffer
This commit is contained in:
Brendan Dahl 2014-09-25 16:40:28 -07:00
commit 9c56c6f9f6
10 changed files with 282 additions and 85 deletions

View File

@ -50,6 +50,12 @@
"type": "boolean", "type": "boolean",
"default": false "default": false
}, },
"disableStream": {
"title": "Disable streaming for requests",
"description": "Whether to disable streaming for requests (not recommended).",
"type": "boolean",
"default": false
},
"disableAutoFetch": { "disableAutoFetch": {
"type": "boolean", "type": "boolean",
"default": false "default": false

View File

@ -178,6 +178,7 @@ function makeContentReadable(obj, window) {
function PdfDataListener(length) { function PdfDataListener(length) {
this.length = length; // less than 0, if length is unknown this.length = length; // less than 0, if length is unknown
this.data = new Uint8Array(length >= 0 ? length : 0x10000); this.data = new Uint8Array(length >= 0 ? length : 0x10000);
this.position = 0;
this.loaded = 0; this.loaded = 0;
} }
@ -200,6 +201,11 @@ PdfDataListener.prototype = {
this.loaded = willBeLoaded; this.loaded = willBeLoaded;
this.onprogress(this.loaded, this.length >= 0 ? this.length : void(0)); this.onprogress(this.loaded, this.length >= 0 ? this.length : void(0));
}, },
readData: function PdfDataListener_readData() {
var data = this.data.subarray(this.position, this.loaded);
this.position = this.loaded;
return data;
},
getData: function PdfDataListener_getData() { getData: function PdfDataListener_getData() {
var data = this.data; var data = this.data;
if (this.loaded != data.length) if (this.loaded != data.length)
@ -523,11 +529,13 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
*/ */
function RangedChromeActions( function RangedChromeActions(
domWindow, contentDispositionFilename, originalRequest, domWindow, contentDispositionFilename, originalRequest,
dataListener) { rangeEnabled, streamingEnabled, dataListener) {
ChromeActions.call(this, domWindow, contentDispositionFilename); ChromeActions.call(this, domWindow, contentDispositionFilename);
this.dataListener = dataListener; this.dataListener = dataListener;
this.originalRequest = originalRequest; this.originalRequest = originalRequest;
this.rangeEnabled = rangeEnabled;
this.streamingEnabled = streamingEnabled;
this.pdfUrl = originalRequest.URI.spec; this.pdfUrl = originalRequest.URI.spec;
this.contentLength = originalRequest.contentLength; this.contentLength = originalRequest.contentLength;
@ -585,20 +593,46 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
proto.constructor = RangedChromeActions; proto.constructor = RangedChromeActions;
proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() { proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
this.originalRequest.cancel(Cr.NS_BINDING_ABORTED); var self = this;
this.originalRequest = null; var data;
if (!this.streamingEnabled) {
this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
this.originalRequest = null;
data = this.dataListener.getData();
this.dataListener = null;
} else {
data = this.dataListener.readData();
this.dataListener.onprogress = function (loaded, total) {
self.domWindow.postMessage({
pdfjsLoadAction: 'progressiveRead',
loaded: loaded,
total: total,
chunk: self.dataListener.readData()
}, '*');
};
this.dataListener.oncomplete = function () {
delete self.dataListener;
};
}
this.domWindow.postMessage({ this.domWindow.postMessage({
pdfjsLoadAction: 'supportsRangedLoading', pdfjsLoadAction: 'supportsRangedLoading',
rangeEnabled: this.rangeEnabled,
streamingEnabled: this.streamingEnabled,
pdfUrl: this.pdfUrl, pdfUrl: this.pdfUrl,
length: this.contentLength, length: this.contentLength,
data: this.dataListener.getData() data: data
}, '*'); }, '*');
this.dataListener = null;
return true; return true;
}; };
proto.requestDataRange = function RangedChromeActions_requestDataRange(args) { proto.requestDataRange = function RangedChromeActions_requestDataRange(args) {
if (!this.rangeEnabled) {
return;
}
var begin = args.begin; var begin = args.begin;
var end = args.end; var end = args.end;
var domWindow = this.domWindow; var domWindow = this.domWindow;
@ -840,7 +874,8 @@ PdfStreamConverter.prototype = {
} catch (e) {} } catch (e) {}
var rangeRequest = false; var rangeRequest = false;
if (isHttpRequest) { var hash = aRequest.URI.ref;
if (isHttpRequest && !getBoolPref(PREF_PREFIX + '.disableRange', false)) {
var contentEncoding = 'identity'; var contentEncoding = 'identity';
try { try {
contentEncoding = aRequest.getResponseHeader('Content-Encoding'); contentEncoding = aRequest.getResponseHeader('Content-Encoding');
@ -851,12 +886,13 @@ PdfStreamConverter.prototype = {
acceptRanges = aRequest.getResponseHeader('Accept-Ranges'); acceptRanges = aRequest.getResponseHeader('Accept-Ranges');
} catch (e) {} } catch (e) {}
var hash = aRequest.URI.ref;
rangeRequest = contentEncoding === 'identity' && rangeRequest = contentEncoding === 'identity' &&
acceptRanges === 'bytes' && acceptRanges === 'bytes' &&
aRequest.contentLength >= 0 && aRequest.contentLength >= 0 &&
hash.indexOf('disableRange=true') < 0; hash.toLowerCase().indexOf('disablerange=true') < 0;
} }
var streamRequest = !getBoolPref(PREF_PREFIX + '.disableStream', false) &&
hash.toLowerCase().indexOf('disablestream=true') < 0;
aRequest.QueryInterface(Ci.nsIChannel); aRequest.QueryInterface(Ci.nsIChannel);
@ -914,12 +950,13 @@ PdfStreamConverter.prototype = {
// may have changed during a redirect. // may have changed during a redirect.
var domWindow = getDOMWindow(channel); var domWindow = getDOMWindow(channel);
var actions; var actions;
if (rangeRequest) { if (rangeRequest || streamRequest) {
actions = new RangedChromeActions( actions = new RangedChromeActions(
domWindow, contentDispositionFilename, aRequest, dataListener); domWindow, contentDispositionFilename, aRequest,
rangeRequest, streamRequest, dataListener);
} else { } else {
actions = new StandardChromeActions( actions = new StandardChromeActions(
domWindow, contentDispositionFilename, dataListener); domWindow, contentDispositionFilename, dataListener);
} }
var requestListener = new RequestListener(actions); var requestListener = new RequestListener(actions);
domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {

View File

@ -30,7 +30,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
this.numChunksLoaded = 0; this.numChunksLoaded = 0;
this.numChunks = Math.ceil(length / chunkSize); this.numChunks = Math.ceil(length / chunkSize);
this.manager = manager; this.manager = manager;
this.initialDataLength = 0; this.progressiveDataLength = 0;
this.lastSuccessfulEnsureByteChunk = -1; // a single-entry cache this.lastSuccessfulEnsureByteChunk = -1; // a single-entry cache
} }
@ -41,7 +41,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
getMissingChunks: function ChunkedStream_getMissingChunks() { getMissingChunks: function ChunkedStream_getMissingChunks() {
var chunks = []; var chunks = [];
for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) { for (var chunk = 0, n = this.numChunks; chunk < n; ++chunk) {
if (!(chunk in this.loadedChunks)) { if (!this.loadedChunks[chunk]) {
chunks.push(chunk); chunks.push(chunk);
} }
} }
@ -73,21 +73,29 @@ var ChunkedStream = (function ChunkedStreamClosure() {
var curChunk; var curChunk;
for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) { for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
if (!(curChunk in this.loadedChunks)) { if (!this.loadedChunks[curChunk]) {
this.loadedChunks[curChunk] = true; this.loadedChunks[curChunk] = true;
++this.numChunksLoaded; ++this.numChunksLoaded;
} }
} }
}, },
onReceiveInitialData: function ChunkedStream_onReceiveInitialData(data) { onReceiveProgressiveData:
this.bytes.set(data); function ChunkedStream_onReceiveProgressiveData(data) {
this.initialDataLength = data.length; var position = this.progressiveDataLength;
var endChunk = (this.end === data.length ? var beginChunk = Math.floor(position / this.chunkSize);
this.numChunks : Math.floor(data.length / this.chunkSize));
for (var i = 0; i < endChunk; i++) { this.bytes.set(new Uint8Array(data), position);
this.loadedChunks[i] = true; position += data.byteLength;
++this.numChunksLoaded; this.progressiveDataLength = position;
var endChunk = position >= this.end ? this.numChunks :
Math.floor(position / this.chunkSize);
var curChunk;
for (curChunk = beginChunk; curChunk < endChunk; ++curChunk) {
if (!this.loadedChunks[curChunk]) {
this.loadedChunks[curChunk] = true;
++this.numChunksLoaded;
}
} }
}, },
@ -97,7 +105,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
return; return;
} }
if (!(chunk in this.loadedChunks)) { if (!this.loadedChunks[chunk]) {
throw new MissingDataException(pos, pos + 1); throw new MissingDataException(pos, pos + 1);
} }
this.lastSuccessfulEnsureByteChunk = chunk; this.lastSuccessfulEnsureByteChunk = chunk;
@ -108,7 +116,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
return; return;
} }
if (end <= this.initialDataLength) { if (end <= this.progressiveDataLength) {
return; return;
} }
@ -116,7 +124,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
var beginChunk = Math.floor(begin / chunkSize); var beginChunk = Math.floor(begin / chunkSize);
var endChunk = Math.floor((end - 1) / chunkSize) + 1; var endChunk = Math.floor((end - 1) / chunkSize) + 1;
for (var chunk = beginChunk; chunk < endChunk; ++chunk) { for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) { if (!this.loadedChunks[chunk]) {
throw new MissingDataException(begin, end); throw new MissingDataException(begin, end);
} }
} }
@ -125,13 +133,13 @@ var ChunkedStream = (function ChunkedStreamClosure() {
nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) { nextEmptyChunk: function ChunkedStream_nextEmptyChunk(beginChunk) {
var chunk, n; var chunk, n;
for (chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) { for (chunk = beginChunk, n = this.numChunks; chunk < n; ++chunk) {
if (!(chunk in this.loadedChunks)) { if (!this.loadedChunks[chunk]) {
return chunk; return chunk;
} }
} }
// Wrap around to beginning // Wrap around to beginning
for (chunk = 0; chunk < beginChunk; ++chunk) { for (chunk = 0; chunk < beginChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) { if (!this.loadedChunks[chunk]) {
return chunk; return chunk;
} }
} }
@ -139,7 +147,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
}, },
hasChunk: function ChunkedStream_hasChunk(chunk) { hasChunk: function ChunkedStream_hasChunk(chunk) {
return chunk in this.loadedChunks; return !!this.loadedChunks[chunk];
}, },
get length() { get length() {
@ -238,7 +246,7 @@ var ChunkedStream = (function ChunkedStreamClosure() {
var endChunk = Math.floor((this.end - 1) / chunkSize) + 1; var endChunk = Math.floor((this.end - 1) / chunkSize) + 1;
var missingChunks = []; var missingChunks = [];
for (var chunk = beginChunk; chunk < endChunk; ++chunk) { for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
if (!(chunk in this.loadedChunks)) { if (!this.loadedChunks[chunk]) {
missingChunks.push(chunk); missingChunks.push(chunk);
} }
} }
@ -300,28 +308,16 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
this.chunksNeededByRequest = {}; this.chunksNeededByRequest = {};
this.requestsByChunk = {}; this.requestsByChunk = {};
this.callbacksByRequest = {}; this.callbacksByRequest = {};
this.progressiveDataLength = 0;
this._loadedStreamCapability = createPromiseCapability(); this._loadedStreamCapability = createPromiseCapability();
if (args.initialData) { if (args.initialData) {
this.setInitialData(args.initialData); this.onReceiveData({chunk: args.initialData});
} }
} }
ChunkedStreamManager.prototype = { ChunkedStreamManager.prototype = {
setInitialData: function ChunkedStreamManager_setInitialData(data) {
this.stream.onReceiveInitialData(data);
if (this.stream.allChunksLoaded()) {
this._loadedStreamCapability.resolve(this.stream);
} else if (this.msgHandler) {
this.msgHandler.send('DocProgress', {
loaded: data.length,
total: this.length
});
}
},
onLoadedStream: function ChunkedStreamManager_getLoadedStream() { onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
return this._loadedStreamCapability.promise; return this._loadedStreamCapability.promise;
}, },
@ -459,13 +455,21 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
onReceiveData: function ChunkedStreamManager_onReceiveData(args) { onReceiveData: function ChunkedStreamManager_onReceiveData(args) {
var chunk = args.chunk; var chunk = args.chunk;
var begin = args.begin; var isProgressive = args.begin === undefined;
var begin = isProgressive ? this.progressiveDataLength : args.begin;
var end = begin + chunk.byteLength; var end = begin + chunk.byteLength;
var beginChunk = this.getBeginChunk(begin); var beginChunk = Math.floor(begin / this.chunkSize);
var endChunk = this.getEndChunk(end); var endChunk = end < this.length ? Math.floor(end / this.chunkSize) :
Math.ceil(end / this.chunkSize);
if (isProgressive) {
this.stream.onReceiveProgressiveData(chunk);
this.progressiveDataLength = end;
} else {
this.stream.onReceiveData(begin, chunk);
}
this.stream.onReceiveData(begin, chunk);
if (this.stream.allChunksLoaded()) { if (this.stream.allChunksLoaded()) {
this._loadedStreamCapability.resolve(this.stream); this._loadedStreamCapability.resolve(this.stream);
} }

View File

@ -68,11 +68,11 @@ var NetworkManager = (function NetworkManagerClosure() {
return data; return data;
} }
var length = data.length; var length = data.length;
var buffer = new Uint8Array(length); var array = new Uint8Array(length);
for (var i = 0; i < length; i++) { for (var i = 0; i < length; i++) {
buffer[i] = data.charCodeAt(i) & 0xFF; array[i] = data.charCodeAt(i) & 0xFF;
} }
return buffer; return array.buffer;
} }
NetworkManager.prototype = { NetworkManager.prototype = {
@ -87,11 +87,11 @@ var NetworkManager = (function NetworkManagerClosure() {
return this.request(args); return this.request(args);
}, },
requestFull: function NetworkManager_requestRange(listeners) { requestFull: function NetworkManager_requestFull(listeners) {
return this.request(listeners); return this.request(listeners);
}, },
request: function NetworkManager_requestRange(args) { request: function NetworkManager_request(args) {
var xhr = this.getXhr(); var xhr = this.getXhr();
var xhrId = this.currXhrId++; var xhrId = this.currXhrId++;
var pendingRequest = this.pendingRequests[xhrId] = { var pendingRequest = this.pendingRequests[xhrId] = {
@ -115,27 +115,54 @@ var NetworkManager = (function NetworkManagerClosure() {
pendingRequest.expectedStatus = 200; pendingRequest.expectedStatus = 200;
} }
xhr.responseType = 'arraybuffer'; if (args.onProgressiveData) {
xhr.responseType = 'moz-chunked-arraybuffer';
if (args.onProgress) { if (xhr.responseType === 'moz-chunked-arraybuffer') {
xhr.onprogress = args.onProgress; pendingRequest.onProgressiveData = args.onProgressiveData;
pendingRequest.mozChunked = true;
} else {
xhr.responseType = 'arraybuffer';
}
} else {
xhr.responseType = 'arraybuffer';
} }
if (args.onError) { if (args.onError) {
xhr.onerror = function(evt) { xhr.onerror = function(evt) {
args.onError(xhr.status); args.onError(xhr.status);
}; };
} }
xhr.onreadystatechange = this.onStateChange.bind(this, xhrId); xhr.onreadystatechange = this.onStateChange.bind(this, xhrId);
xhr.onprogress = this.onProgress.bind(this, xhrId);
pendingRequest.onHeadersReceived = args.onHeadersReceived; pendingRequest.onHeadersReceived = args.onHeadersReceived;
pendingRequest.onDone = args.onDone; pendingRequest.onDone = args.onDone;
pendingRequest.onError = args.onError; pendingRequest.onError = args.onError;
pendingRequest.onProgress = args.onProgress;
xhr.send(null); xhr.send(null);
return xhrId; 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) { onStateChange: function NetworkManager_onStateChange(xhrId, evt) {
var pendingRequest = this.pendingRequests[xhrId]; var pendingRequest = this.pendingRequests[xhrId];
if (!pendingRequest) { if (!pendingRequest) {
@ -196,6 +223,8 @@ var NetworkManager = (function NetworkManagerClosure() {
begin: begin, begin: begin,
chunk: chunk chunk: chunk
}); });
} else if (pendingRequest.onProgressiveData) {
pendingRequest.onDone(null);
} else { } else {
pendingRequest.onDone({ pendingRequest.onDone({
begin: 0, begin: 0,
@ -215,6 +244,10 @@ var NetworkManager = (function NetworkManagerClosure() {
return this.pendingRequests[xhrId].xhr; return this.pendingRequests[xhrId].xhr;
}, },
isStreamingRequest: function NetworkManager_isStreamingRequest(xhrId) {
return !!(this.pendingRequests[xhrId].onProgressiveData);
},
isPendingRequest: function NetworkManager_isPendingRequest(xhrId) { isPendingRequest: function NetworkManager_isPendingRequest(xhrId) {
return xhrId in this.pendingRequests; return xhrId in this.pendingRequests;
}, },

View File

@ -65,6 +65,10 @@ var BasePdfManager = (function BasePdfManagerClosure() {
return new NotImplementedException(); return new NotImplementedException();
}, },
sendProgressiveData: function BasePdfManager_sendProgressiveData(chunk) {
return new NotImplementedException();
},
updatePassword: function BasePdfManager_updatePassword(password) { updatePassword: function BasePdfManager_updatePassword(password) {
this.pdfDocument.xref.password = this.password = password; this.pdfDocument.xref.password = this.password = password;
if (this._passwordChangedCapability) { if (this._passwordChangedCapability) {
@ -201,6 +205,11 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() {
this.streamManager.requestAllChunks(); this.streamManager.requestAllChunks();
}; };
NetworkPdfManager.prototype.sendProgressiveData =
function NetworkPdfManager_sendProgressiveData(chunk) {
this.streamManager.onReceiveData({ chunk: chunk });
};
NetworkPdfManager.prototype.onLoadedStream = NetworkPdfManager.prototype.onLoadedStream =
function NetworkPdfManager_getLoadedStream() { function NetworkPdfManager_getLoadedStream() {
return this.streamManager.onLoadedStream(); return this.streamManager.onLoadedStream();

View File

@ -16,7 +16,7 @@
*/ */
/* globals PDFJS, createPromiseCapability, LocalPdfManager, NetworkPdfManager, /* globals PDFJS, createPromiseCapability, LocalPdfManager, NetworkPdfManager,
NetworkManager, isInt, RANGE_CHUNK_SIZE, MissingPDFException, NetworkManager, isInt, RANGE_CHUNK_SIZE, MissingPDFException,
UnexpectedResponseException, PasswordException, Promise, UnexpectedResponseException, PasswordException, Promise, warn,
PasswordResponses, InvalidPDFException, UnknownErrorException, PasswordResponses, InvalidPDFException, UnknownErrorException,
XRefParseException, Ref, info, globalScope, error, MessageHandler */ XRefParseException, Ref, info, globalScope, error, MessageHandler */
@ -86,6 +86,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
httpHeaders: source.httpHeaders, httpHeaders: source.httpHeaders,
withCredentials: source.withCredentials withCredentials: source.withCredentials
}); });
var cachedChunks = [];
var fullRequestXhrId = networkManager.requestFull({ var fullRequestXhrId = networkManager.requestFull({
onHeadersReceived: function onHeadersReceived() { onHeadersReceived: function onHeadersReceived() {
if (disableRange) { if (disableRange) {
@ -116,11 +117,18 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
return; return;
} }
// NOTE: by cancelling the full request, and then issuing range if (networkManager.isStreamingRequest(fullRequestXhrId)) {
// requests, there will be an issue for sites where you can only // We can continue fetching when progressive loading is enabled,
// request the pdf once. However, if this is the case, then the // and we don't need the autoFetch feature.
// server should not be returning that it can support range requests. source.disableAutoFetch = true;
networkManager.abortRequest(fullRequestXhrId); } else {
// 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);
}
try { try {
pdfManager = new NetworkPdfManager(source, handler); pdfManager = new NetworkPdfManager(source, handler);
@ -130,10 +138,44 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
} }
}, },
onProgressiveData: PDFJS.disableStream ? null :
function onProgressiveData(chunk) {
if (!pdfManager) {
cachedChunks.push(chunk);
return;
}
pdfManager.sendProgressiveData(chunk);
},
onDone: function onDone(args) { onDone: function onDone(args) {
if (pdfManager) {
return; // already processed
}
var pdfFile;
if (args === null) {
// TODO add some streaming manager, e.g. for unknown length files.
// The data was returned in the onProgressiveData, combining...
var pdfFileLength = 0, pos = 0;
cachedChunks.forEach(function (chunk) {
pdfFileLength += chunk.byteLength;
});
if (source.length && pdfFileLength !== source.length) {
warn('reported HTTP length is different from actual');
}
var pdfFileArray = new Uint8Array(pdfFileLength);
cachedChunks.forEach(function (chunk) {
pdfFileArray.set(new Uint8Array(chunk), pos);
pos += chunk.byteLength;
});
pdfFile = pdfFileArray.buffer;
} else {
pdfFile = args.chunk;
}
// the data is array, instantiating directly from it // the data is array, instantiating directly from it
try { try {
pdfManager = new LocalPdfManager(args.chunk, source.password); pdfManager = new LocalPdfManager(pdfFile, source.password);
pdfManagerCapability.resolve(); pdfManagerCapability.resolve();
} catch (ex) { } catch (ex) {
pdfManagerCapability.reject(ex); pdfManagerCapability.reject(ex);
@ -228,6 +270,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
PDFJS.cMapPacked = data.cMapPacked === true; PDFJS.cMapPacked = data.cMapPacked === true;
getPdfManager(data).then(function () { getPdfManager(data).then(function () {
handler.send('PDFManagerReady', null);
pdfManager.onLoadedStream().then(function(stream) { pdfManager.onLoadedStream().then(function(stream) {
handler.send('DataLoaded', { length: stream.bytes.byteLength }); handler.send('DataLoaded', { length: stream.bytes.byteLength });
}); });

View File

@ -86,6 +86,14 @@ PDFJS.workerSrc = (PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc);
PDFJS.disableRange = (PDFJS.disableRange === undefined ? PDFJS.disableRange = (PDFJS.disableRange === undefined ?
false : PDFJS.disableRange); false : PDFJS.disableRange);
/**
* Disable streaming of PDF file data. By default PDF.js attempts to load PDF
* in chunks. This default behavior can be disabled.
* @var {boolean}
*/
PDFJS.disableStream = (PDFJS.disableStream === undefined ?
false : PDFJS.disableStream);
/** /**
* Disable pre-fetching of PDF file data. When range requests are enabled PDF.js * Disable pre-fetching of PDF file data. When range requests are enabled PDF.js
* will automatically keep fetching more data even if it isn't needed to display * will automatically keep fetching more data even if it isn't needed to display
@ -851,6 +859,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
}); });
}); });
pdfDataRangeTransport.addProgressiveReadListener(function(chunk) {
messageHandler.send('OnDataRange', {
chunk: chunk
});
});
messageHandler.on('RequestDataRange', messageHandler.on('RequestDataRange',
function transportDataRange(data) { function transportDataRange(data) {
pdfDataRangeTransport.requestDataRange(data.begin, data.end); pdfDataRangeTransport.requestDataRange(data.begin, data.end);
@ -911,6 +925,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.downloadInfoCapability.resolve(data); this.downloadInfoCapability.resolve(data);
}, this); }, this);
messageHandler.on('PDFManagerReady', function transportPage(data) {
if (this.pdfDataRangeTransport) {
this.pdfDataRangeTransport.transportReady();
}
}, this);
messageHandler.on('StartRenderPage', function transportRender(data) { messageHandler.on('StartRenderPage', function transportRender(data) {
var page = this.pageCache[data.pageIndex]; var page = this.pageCache[data.pageIndex];

View File

@ -167,6 +167,21 @@ if (typeof PDFJS === 'undefined') {
// The worker will be using XHR, so we can save time and disable worker. // The worker will be using XHR, so we can save time and disable worker.
PDFJS.disableWorker = true; PDFJS.disableWorker = true;
Object.defineProperty(xhrPrototype, 'responseType', {
get: function xmlHttpRequestGetResponseType() {
return this._responseType || 'text';
},
set: function xmlHttpRequestSetResponseType(value) {
if (value === 'text' || value === 'arraybuffer') {
this._responseType = value;
if (value === 'arraybuffer' &&
typeof this.overrideMimeType === 'function') {
this.overrideMimeType('text/plain; charset=x-user-defined');
}
}
}
});
// Support: IE9 // Support: IE9
if (typeof VBArray !== 'undefined') { if (typeof VBArray !== 'undefined') {
Object.defineProperty(xhrPrototype, 'response', { Object.defineProperty(xhrPrototype, 'response', {
@ -181,25 +196,20 @@ if (typeof PDFJS === 'undefined') {
return; return;
} }
// other browsers Object.defineProperty(xhrPrototype, 'response', {
function responseTypeSetter() { get: function xmlHttpRequestResponseGet() {
// will be only called to set "arraybuffer" if (this.responseType !== 'arraybuffer') {
this.overrideMimeType('text/plain; charset=x-user-defined'); return this.responseText;
} }
if (typeof xhr.overrideMimeType === 'function') { var text = this.responseText;
Object.defineProperty(xhrPrototype, 'responseType', var i, n = text.length;
{ set: responseTypeSetter }); var result = new Uint8Array(n);
} for (i = 0; i < n; ++i) {
function responseGetter() { result[i] = text.charCodeAt(i) & 0xFF;
var text = this.responseText; }
var i, n = text.length; return result.buffer;
var result = new Uint8Array(n);
for (i = 0; i < n; ++i) {
result[i] = text.charCodeAt(i) & 0xFF;
} }
return result.buffer; });
}
Object.defineProperty(xhrPrototype, 'response', { get: responseGetter });
})(); })();
// window.btoa (base64 encode function) ? // window.btoa (base64 encode function) ?
@ -471,6 +481,7 @@ if (typeof PDFJS === 'undefined') {
if (isSafari || isOldAndroid) { if (isSafari || isOldAndroid) {
PDFJS.disableRange = true; PDFJS.disableRange = true;
PDFJS.disableStream = true;
} }
})(); })();

View File

@ -28,6 +28,7 @@ var DEFAULT_PREFERENCES = {
enableWebGL: false, enableWebGL: false,
pdfBugEnabled: false, pdfBugEnabled: false,
disableRange: false, disableRange: false,
disableStream: false,
disableAutoFetch: false, disableAutoFetch: false,
disableFontFace: false, disableFontFace: false,
//#if B2G //#if B2G

View File

@ -516,9 +516,15 @@ var PDFView = {
//#if (FIREFOX || MOZCENTRAL) //#if (FIREFOX || MOZCENTRAL)
initPassiveLoading: function pdfViewInitPassiveLoading() { initPassiveLoading: function pdfViewInitPassiveLoading() {
var pdfDataRangeTransportReadyResolve;
var pdfDataRangeTransportReady = new Promise(function (resolve) {
pdfDataRangeTransportReadyResolve = resolve;
});
var pdfDataRangeTransport = { var pdfDataRangeTransport = {
rangeListeners: [], rangeListeners: [],
progressListeners: [], progressListeners: [],
progressiveReadListeners: [],
ready: pdfDataRangeTransportReady,
addRangeListener: function PdfDataRangeTransport_addRangeListener( addRangeListener: function PdfDataRangeTransport_addRangeListener(
listener) { listener) {
@ -530,6 +536,11 @@ var PDFView = {
this.progressListeners.push(listener); this.progressListeners.push(listener);
}, },
addProgressiveReadListener:
function PdfDataRangeTransport_addProgressiveReadListener(listener) {
this.progressiveReadListeners.push(listener);
},
onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) { onDataRange: function PdfDataRangeTransport_onDataRange(begin, chunk) {
var listeners = this.rangeListeners; var listeners = this.rangeListeners;
for (var i = 0, n = listeners.length; i < n; ++i) { for (var i = 0, n = listeners.length; i < n; ++i) {
@ -538,10 +549,26 @@ var PDFView = {
}, },
onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) { onDataProgress: function PdfDataRangeTransport_onDataProgress(loaded) {
var listeners = this.progressListeners; this.ready.then(function () {
for (var i = 0, n = listeners.length; i < n; ++i) { var listeners = this.progressListeners;
listeners[i](loaded); for (var i = 0, n = listeners.length; i < n; ++i) {
} listeners[i](loaded);
}
}.bind(this));
},
onDataProgressiveRead:
function PdfDataRangeTransport_onDataProgress(chunk) {
this.ready.then(function () {
var listeners = this.progressiveReadListeners;
for (var i = 0, n = listeners.length; i < n; ++i) {
listeners[i](chunk);
}
}.bind(this));
},
transportReady: function PdfDataRangeTransport_transportReady() {
pdfDataRangeTransportReadyResolve();
}, },
requestDataRange: function PdfDataRangeTransport_requestDataRange( requestDataRange: function PdfDataRangeTransport_requestDataRange(
@ -574,6 +601,9 @@ var PDFView = {
case 'rangeProgress': case 'rangeProgress':
pdfDataRangeTransport.onDataProgress(args.loaded); pdfDataRangeTransport.onDataProgress(args.loaded);
break; break;
case 'progressiveRead':
pdfDataRangeTransport.onDataProgressiveRead(args.chunk);
break;
case 'progress': case 'progress':
PDFView.progress(args.loaded / args.total); PDFView.progress(args.loaded / args.total);
break; break;
@ -1787,6 +1817,9 @@ function webViewerInitialized() {
if ('disablerange' in hashParams) { if ('disablerange' in hashParams) {
PDFJS.disableRange = (hashParams['disablerange'] === 'true'); PDFJS.disableRange = (hashParams['disablerange'] === 'true');
} }
if ('disablestream' in hashParams) {
PDFJS.disableStream = (hashParams['disablestream'] === 'true');
}
if ('disableautofetch' in hashParams) { if ('disableautofetch' in hashParams) {
PDFJS.disableAutoFetch = (hashParams['disableautofetch'] === 'true'); PDFJS.disableAutoFetch = (hashParams['disableautofetch'] === 'true');
} }