[Pkg-javascript-commits] [pdf.js] 220/414: Makes PDF data reading Streams API friendly.

David Prévot taffit at moszumanska.debian.org
Tue Jun 28 17:12:24 UTC 2016


This is an automated email from the git hooks/post-receive script.

taffit pushed a commit to branch master
in repository pdf.js.

commit 0d591719d9482f255782dea1c1e0d741574f7b88
Author: Yury Delendik <ydelendik at mozilla.com>
Date:   Tue Feb 9 14:55:11 2016 -0600

    Makes PDF data reading Streams API friendly.
---
 src/core/chunked_stream.js |  83 ++++---
 src/core/network.js        | 342 ++++++++++++++++++++++++++-
 src/core/pdf_manager.js    |  17 +-
 src/core/worker.js         | 563 +++++++++++++++++++++++++++++++++++----------
 src/shared/util.js         |  51 ++++
 test/unit/api_spec.js      |  93 +++++++-
 test/unit/network_spec.js  | 169 ++++++++++++++
 test/unit/unit_test.html   |   9 +-
 8 files changed, 1151 insertions(+), 176 deletions(-)

diff --git a/src/core/chunked_stream.js b/src/core/chunked_stream.js
index b76ecf4..58716e7 100644
--- a/src/core/chunked_stream.js
+++ b/src/core/chunked_stream.js
@@ -28,6 +28,8 @@
 }(this, function (exports, sharedUtil) {
 
 var MissingDataException = sharedUtil.MissingDataException;
+var arrayByteLength = sharedUtil.arrayByteLength;
+var arraysToBytes = sharedUtil.arraysToBytes;
 var assert = sharedUtil.assert;
 var createPromiseCapability = sharedUtil.createPromiseCapability;
 var isInt = sharedUtil.isInt;
@@ -279,37 +281,16 @@ var ChunkedStream = (function ChunkedStreamClosure() {
 
 var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
 
-  function ChunkedStreamManager(length, chunkSize, url, args) {
+  function ChunkedStreamManager(pdfNetworkStream, args) {
+    var chunkSize = args.rangeChunkSize;
+    var length = args.length;
     this.stream = new ChunkedStream(length, chunkSize, this);
     this.length = length;
     this.chunkSize = chunkSize;
-    this.url = url;
+    this.pdfNetworkStream = pdfNetworkStream;
+    this.url = args.url;
     this.disableAutoFetch = args.disableAutoFetch;
-    var msgHandler = this.msgHandler = args.msgHandler;
-
-    if (args.chunkedViewerLoading) {
-      msgHandler.on('OnDataRange', this.onReceiveData.bind(this));
-      msgHandler.on('OnDataProgress', this.onProgress.bind(this));
-      this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
-        msgHandler.send('RequestDataRange', { begin: begin, end: end });
-      };
-    } else {
-
-      var getXhr = function getXhr() {
-        return new XMLHttpRequest();
-      };
-      this.networkManager = new NetworkManager(this.url, {
-        getXhr: getXhr,
-        httpHeaders: args.httpHeaders,
-        withCredentials: args.withCredentials
-      });
-      this.sendRequest = function ChunkedStreamManager_sendRequest(begin, end) {
-        this.networkManager.requestRange(begin, end, {
-          onDone: this.onReceiveData.bind(this),
-          onProgress: this.onProgress.bind(this)
-        });
-      };
-    }
+    this.msgHandler = args.msgHandler;
 
     this.currRequestId = 0;
 
@@ -317,12 +298,9 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
     this.requestsByChunk = Object.create(null);
     this.promisesByRequest = Object.create(null);
     this.progressiveDataLength = 0;
+    this.aborted = false;
 
     this._loadedStreamCapability = createPromiseCapability();
-
-    if (args.initialData) {
-      this.onReceiveData({chunk: args.initialData});
-    }
   }
 
   ChunkedStreamManager.prototype = {
@@ -330,6 +308,44 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
       return this._loadedStreamCapability.promise;
     },
 
+    sendRequest: function ChunkedStreamManager_sendRequest(begin, end) {
+      var rangeReader = this.pdfNetworkStream.getRangeReader(begin, end);
+      if (!rangeReader.isStreamingSupported) {
+        rangeReader.onProgress = this.onProgress.bind(this);
+      }
+      var chunks = [], loaded = 0;
+      var manager = this;
+      var promise = new Promise(function (resolve, reject) {
+        var readChunk = function (chunk) {
+          try {
+            if (!chunk.done) {
+              var data = chunk.value;
+              chunks.push(data);
+              loaded += arrayByteLength(data);
+              if (rangeReader.isStreamingSupported) {
+                manager.onProgress({loaded: loaded});
+              }
+              rangeReader.read().then(readChunk, reject);
+              return;
+            }
+            var chunkData = arraysToBytes(chunks);
+            chunks = null;
+            resolve(chunkData);
+          } catch (e) {
+            reject(e);
+          }
+        };
+        rangeReader.read().then(readChunk, reject);
+      });
+      promise.then(function (data) {
+        if (this.aborted) {
+          return; // ignoring any data after abort
+        }
+        this.onReceiveData({chunk: data, begin: begin});
+      }.bind(this));
+      // TODO check errors
+    },
+
     // Get all the chunks that are not yet loaded and groups them into
     // contiguous ranges to load in as few requests as possible
     requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
@@ -549,8 +565,9 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
     },
 
     abort: function ChunkedStreamManager_abort() {
-      if (this.networkManager) {
-        this.networkManager.abortAllRequests();
+      this.aborted = true;
+      if (this.pdfNetworkStream) {
+        this.pdfNetworkStream.cancelAllRequests('abort');
       }
       for(var requestId in this.promisesByRequest) {
         var capability = this.promisesByRequest[requestId];
diff --git a/src/core/network.js b/src/core/network.js
index 221ebd7..f84026d 100644
--- a/src/core/network.js
+++ b/src/core/network.js
@@ -295,13 +295,347 @@ var NetworkManager = (function NetworkManagerClosure() {
 //#if !(FIREFOX || MOZCENTRAL)
 (function (root, factory) {
   if (typeof define === 'function' && define.amd) {
-    define('pdfjs/core/network', ['exports'], factory);
+    define('pdfjs/core/network', ['exports', 'pdfjs/shared/util',
+      'pdfjs/core/worker'], factory);
   } else if (typeof exports !== 'undefined') {
-    factory(exports);
+    factory(exports, require('../shared/util.js'), require('./worker.js'));
   } else {
-    factory((root.pdfjsCoreNetwork = {}));
+    factory((root.pdfjsCoreNetwork = {}), root.pdfjsSharedUtil,
+      root.pdfjsCoreWorker);
   }
-}(this, function (exports) {
+}(this, function (exports, sharedUtil, coreWorker) {
+
+  var assert = sharedUtil.assert;
+  var createPromiseCapability = sharedUtil.createPromiseCapability;
+  var isInt = sharedUtil.isInt;
+  var MissingPDFException = sharedUtil.MissingPDFException;
+  var UnexpectedResponseException = sharedUtil.UnexpectedResponseException;
+
+  /** @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;
+      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(chunk);
+      }
+      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();
+    }
+  };
+
+  coreWorker.setPDFNetworkStreamClass(PDFNetworkStream);
+
+  exports.PDFNetworkStream = PDFNetworkStream;
   exports.NetworkManager = NetworkManager;
 }));
 //#endif
diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js
index 564e7d2..8c6fd9b 100644
--- a/src/core/pdf_manager.js
+++ b/src/core/pdf_manager.js
@@ -157,21 +157,18 @@ var LocalPdfManager = (function LocalPdfManagerClosure() {
 })();
 
 var NetworkPdfManager = (function NetworkPdfManagerClosure() {
-  function NetworkPdfManager(docId, args, msgHandler) {
+  function NetworkPdfManager(docId, pdfNetworkStream, args) {
     this._docId = docId;
-    this.msgHandler = msgHandler;
+    this.msgHandler = args.msgHandler;
 
     var params = {
-      msgHandler: msgHandler,
-      httpHeaders: args.httpHeaders,
-      withCredentials: args.withCredentials,
-      chunkedViewerLoading: args.chunkedViewerLoading,
+      msgHandler: args.msgHandler,
+      url: args.url,
+      length: args.length,
       disableAutoFetch: args.disableAutoFetch,
-      initialData: args.initialData
+      rangeChunkSize: args.rangeChunkSize
     };
-    this.streamManager = new ChunkedStreamManager(args.length,
-                                                  args.rangeChunkSize,
-                                                  args.url, params);
+    this.streamManager = new ChunkedStreamManager(pdfNetworkStream, params);
     this.pdfDocument = new PDFDocument(this, this.streamManager.getStream(),
                                        args.password);
   }
diff --git a/src/core/worker.js b/src/core/worker.js
index f71e42b..c78b52e 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -41,10 +41,12 @@ var PasswordException = sharedUtil.PasswordException;
 var PasswordResponses = sharedUtil.PasswordResponses;
 var UnknownErrorException = sharedUtil.UnknownErrorException;
 var XRefParseException = sharedUtil.XRefParseException;
+var arrayByteLength = sharedUtil.arrayByteLength;
+var arraysToBytes = sharedUtil.arraysToBytes;
+var assert = sharedUtil.assert;
 var createPromiseCapability = sharedUtil.createPromiseCapability;
 var error = sharedUtil.error;
 var info = sharedUtil.info;
-var isInt = sharedUtil.isInt;
 var warn = sharedUtil.warn;
 var Ref = corePrimitives.Ref;
 var LocalPdfManager = corePdfManager.LocalPdfManager;
@@ -82,6 +84,350 @@ var WorkerTask = (function WorkerTaskClosure() {
   return WorkerTask;
 })();
 
+//#if !PRODUCTION
+/**
+ * Interface that represents PDF data transport. If possible, it allows
+ * progressively load entire or fragment of the PDF binary data.
+ *
+ * @interface
+ * */
+function IPDFStream() {}
+IPDFStream.prototype = {
+  /**
+   * Gets a reader for the entire PDF data.
+   * @returns {IPDFStreamReader}
+   */
+  getFullReader: function () { return null; },
+
+  /**
+   * Gets a reader for the range of the PDF data.
+   * @param {number} begin - the start offset of the data.
+   * @param {number} end - the end offset of the data.
+   * @returns {IPDFStreamRangeReader}
+   */
+  getRangeReader: function (begin, end) { return null; },
+
+  /**
+   * Cancels all opened reader and closes all their opened requests.
+   * @param {Object} reason - the reason for cancelling
+   */
+  cancelAllRequests: function (reason) {},
+};
+
+/**
+ * Interface for a PDF binary data reader.
+ *
+ * @interface
+ */
+function IPDFStreamReader() {}
+IPDFStreamReader.prototype = {
+  /**
+   * Gets a promise that is resolved when the headers and other metadata of
+   * the PDF data stream are available.
+   * @returns {Promise}
+   */
+  get headersReady() { return null; },
+
+  /**
+   * Gets PDF binary data length. It is defined after the headersReady promise
+   * is resolved.
+   * @returns {number} The data length (or 0 if unknown).
+   */
+  get contentLength() { return 0; },
+
+  /**
+   * Gets ability of the stream to handle range requests. It is defined after
+   * the headersReady promise is resolved. Rejected when the reader is cancelled
+   * or an error occurs.
+   * @returns {boolean}
+   */
+  get isRangeSupported() { return false; },
+
+  /**
+   * Gets ability of the stream to progressively load binary data. It is defined
+   * after the headersReady promise is resolved.
+   * @returns {boolean}
+   */
+  get isStreamingSupported() { return false; },
+
+  /**
+   * 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 set to true, then the stream has reached its end, otherwise the value
+   * contains binary data. Cancelled requests will be resolved with the done is
+   * set to true.
+   * @returns {Promise}
+   */
+  read: function () {},
+
+  /**
+   * Cancels all pending read requests and closes the stream.
+   * @param {Object} reason
+   */
+  cancel: function (reason) {},
+
+  /**
+   * Sets or gets the progress callback. The callback can be useful when the
+   * isStreamingSupported property of the object is defined as false.
+   * The callback is called with one parameter: an object with the loaded and
+   * total properties.
+   */
+  onProgress: null,
+};
+
+/**
+ * Interface for a PDF binary data fragment reader.
+ *
+ * @interface
+ */
+function IPDFStreamRangeReader() {}
+IPDFStreamRangeReader.prototype = {
+  /**
+   * Gets ability of the stream to progressively load binary data.
+   * @returns {boolean}
+   */
+  get isStreamingSupported() { return false; },
+
+  /**
+   * 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 set to true, then the stream has reached its end, otherwise the value
+   * contains binary data. Cancelled requests will be resolved with the done is
+   * set to true.
+   * @returns {Promise}
+   */
+  read: function () {},
+
+  /**
+   * Cancels all pending read requests and closes the stream.
+   * @param {Object} reason
+   */
+  cancel: function (reason) {},
+
+  /**
+   * Sets or gets the progress callback. The callback can be useful when the
+   * isStreamingSupported property of the object is defined as false.
+   * The callback is called with one parameter: an object with the loaded
+   * property.
+   */
+  onProgress: null,
+};
+//#endif
+
+/** @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);
+    }
+    this._msgHandler = msgHandler;
+
+    this._isRangeSupported = !(params.disableRange);
+    this._isStreamingSupported = !(params.disableStream);
+    this._contentLength = params.length;
+
+    this._fullRequestReader = null;
+    this._rangeReaders = [];
+
+    msgHandler.on('OnDataRange', this._onReceiveData.bind(this));
+    msgHandler.on('OnDataProgress', this._onProgress.bind(this));
+  }
+  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() {
+      assert(!this._fullRequestReader);
+      var queuedChunks = this._queuedChunks;
+      this._queuedChunks = null;
+      return new PDFWorkerStreamReader(this, queuedChunks);
+    },
+
+    getRangeReader: function PDFWorkerStream_getRangeReader(begin, end) {
+      var reader = new PDFWorkerStreamRangeReader(this, begin, end);
+      this._msgHandler.send('RequestDataRange', { begin: begin, end: end });
+      this._rangeReaders.push(reader);
+      return reader;
+    },
+
+    cancelAllRequests: function PDFWorkerStream_cancelAllRequests(reason) {
+      if (this._fullRequestReader) {
+        this._fullRequestReader.cancel(reason);
+      }
+      var readers = this._rangeReaders.slice(0);
+      readers.forEach(function (rangeReader) {
+        rangeReader.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;
+
+    this.onProgress = null; // not used
+  }
+  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 isStreamingSupported() {
+      return this._stream._isStreamingSupported;
+    },
+
+    get contentLength() {
+      return this._stream._contentLength;
+    },
+
+    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});
+      });
+      this._requests = [];
+    }
+  };
+
+  /** @implements {IPDFStreamRangeReader} */
+  function PDFWorkerStreamRangeReader(stream, begin, end) {
+    this._stream = stream;
+    this._begin = begin;
+    this._end = end;
+    this._queuedChunk = null;
+    this._requests = [];
+    this._done = false;
+
+    this.onProgress = null;
+  }
+  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;
+    },
+
+    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);
+    }
+  };
+
+  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 = PDFJS.WorkerMessageHandler = {
   setup: function wphSetup(handler, port) {
     var testMessageProcessed = false;
@@ -188,7 +534,6 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
       var pdfManager;
 
       var source = data.source;
-      var disableRange = data.disableRange;
       if (source.data) {
         try {
           pdfManager = new LocalPdfManager(docId, source.data, source.password);
@@ -196,144 +541,113 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
         } catch (ex) {
           pdfManagerCapability.reject(ex);
         }
-        return pdfManagerCapability.promise;
-      } else if (source.chunkedViewerLoading) {
-        try {
-          pdfManager = new NetworkPdfManager(docId, source, handler);
-          pdfManagerCapability.resolve(pdfManager);
-        } catch (ex) {
-          pdfManagerCapability.reject(ex);
+      }
+
+      var pdfStream;
+      try {
+        if (source.chunkedViewerLoading) {
+          pdfStream = new PDFWorkerStream(source, handler);
+        } else {
+          assert(PDFNetworkStream, 'pdfjs/core/network module is not loaded');
+          pdfStream = new PDFNetworkStream(data);
         }
+      } catch (ex) {
+        pdfManagerCapability.reject(ex);
         return pdfManagerCapability.promise;
       }
 
-      var networkManager = new NetworkManager(source.url, {
-        httpHeaders: source.httpHeaders,
-        withCredentials: source.withCredentials
-      });
-      var cachedChunks = [];
-      var fullRequestXhrId = networkManager.requestFull({
-        onHeadersReceived: function onHeadersReceived() {
-          if (disableRange) {
-            return;
-          }
-
-          var fullRequestXhr = networkManager.getRequestXhr(fullRequestXhrId);
-          if (fullRequestXhr.getResponseHeader('Accept-Ranges') !== 'bytes') {
-            return;
-          }
-
-          var contentEncoding =
-            fullRequestXhr.getResponseHeader('Content-Encoding') || 'identity';
-          if (contentEncoding !== 'identity') {
-            return;
-          }
+      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
+            });
+          };
+        }
 
-          var length = fullRequestXhr.getResponseHeader('Content-Length');
-          length = parseInt(length, 10);
-          if (!isInt(length)) {
-            return;
-          }
-          source.length = length;
-          if (length <= 2 * source.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;
-          }
+        if (!fullRequest.isRangeSupported) {
+          return;
+        }
 
-          if (networkManager.isStreamingRequest(fullRequestXhrId)) {
-            // We can continue fetching when progressive loading is enabled,
-            // and we don't need the autoFetch feature.
-            source.disableAutoFetch = true;
-          } 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);
-          }
+        // We don't need auto-fetch when streaming is enabled.
+        var disableAutoFetch = source.disableAutoFetch ||
+                               fullRequest.isStreamingSupported;
+        pdfManager = new NetworkPdfManager(docId, pdfStream, {
+          msgHandler: handler,
+          url: source.url,
+          password: source.password,
+          length: fullRequest.contentLength,
+          disableAutoFetch: disableAutoFetch,
+          rangeChunkSize: source.rangeChunkSize
+        });
+        pdfManagerCapability.resolve(pdfManager);
+        cancelXHRs = null;
+      }).catch(function (reason) {
+        pdfManagerCapability.reject(reason);
+        cancelXHRs = null;
+      });
 
+      var cachedChunks = [], loaded = 0;
+      var flushChunks = function () {
+        var pdfFile = arraysToBytes(cachedChunks);
+        if (source.length && pdfFile.length !== source.length) {
+          warn('reported HTTP length is different from actual');
+        }
+        // the data is array, instantiating directly from it
+        try {
+          pdfManager = new LocalPdfManager(docId, pdfFile, source.password);
+          pdfManagerCapability.resolve(pdfManager);
+        } catch (ex) {
+          pdfManagerCapability.reject(ex);
+        }
+        cachedChunks = [];
+      };
+      var readPromise = new Promise(function (resolve, reject) {
+        var readChunk = function (chunk) {
           try {
-            pdfManager = new NetworkPdfManager(docId, source, handler);
-            pdfManagerCapability.resolve(pdfManager);
-          } catch (ex) {
-            pdfManagerCapability.reject(ex);
-          }
-          cancelXHRs = null;
-        },
-
-        onProgressiveData: source.disableStream ? null :
-            function onProgressiveData(chunk) {
-          if (!pdfManager) {
-            cachedChunks.push(chunk);
-            return;
-          }
-          pdfManager.sendProgressiveData(chunk);
-        },
-
-        onDone: function onDone(args) {
-          if (pdfManager) {
-            return; // already processed
-          }
+            ensureNotTerminated();
+            if (chunk.done) {
+              if (!pdfManager) {
+                flushChunks();
+              }
+              cancelXHRs = null;
+              return;
+            }
 
-          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 data = chunk.value;
+            loaded += arrayByteLength(data);
+            if (!fullRequest.isStreamingSupported) {
+              handler.send('DocProgress', {
+                loaded: loaded,
+                total: Math.max(loaded, fullRequest.contentLength || 0)
+              });
             }
-            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
-          try {
-            pdfManager = new LocalPdfManager(docId, pdfFile, source.password);
-            pdfManagerCapability.resolve(pdfManager);
-          } catch (ex) {
-            pdfManagerCapability.reject(ex);
-          }
-          cancelXHRs = null;
-        },
+            if (pdfManager) {
+              pdfManager.sendProgressiveData(data);
+            } else {
+              cachedChunks.push(data);
+            }
 
-        onError: function onError(status) {
-          var exception;
-          if (status === 404 || status === 0 && /^file:/.test(source.url)) {
-            exception = new MissingPDFException('Missing PDF "' +
-                                                source.url + '".');
-            handler.send('MissingPDF', exception);
-          } else {
-            exception = new UnexpectedResponseException(
-              'Unexpected server response (' + status +
-              ') while retrieving PDF "' + source.url + '".', status);
-            handler.send('UnexpectedResponse', exception);
+            fullRequest.read().then(readChunk, reject);
+          } catch (e) {
+            reject(e);
           }
-          cancelXHRs = null;
-        },
-
-        onProgress: function onProgress(evt) {
-          handler.send('DocProgress', {
-            loaded: evt.loaded,
-            total: evt.lengthComputable ? evt.total : source.length
-          });
-        }
+        };
+        fullRequest.read().then(readChunk, reject);
+      });
+      readPromise.catch(function (e) {
+        pdfManagerCapability.reject(e);
+        cancelXHRs = null;
       });
 
       cancelXHRs = function () {
-        networkManager.abortRequest(fullRequestXhrId);
+        pdfStream.cancelAllRequests('abort');
       };
 
       return pdfManagerCapability.promise;
@@ -676,6 +990,7 @@ if (typeof window === 'undefined' &&
   initializeWorker();
 }
 
+exports.setPDFNetworkStreamClass = setPDFNetworkStreamClass;
 exports.WorkerTask = WorkerTask;
 exports.WorkerMessageHandler = WorkerMessageHandler;
 }));
diff --git a/src/shared/util.js b/src/shared/util.js
index 3a6b4f2..a13254e 100644
--- a/src/shared/util.js
+++ b/src/shared/util.js
@@ -567,6 +567,55 @@ function stringToBytes(str) {
   return bytes;
 }
 
+/**
+ * Gets length of the array (Array, Uint8Array, or string) in bytes.
+ * @param {Array|Uint8Array|string} arr
+ * @returns {number}
+ */
+function arrayByteLength(arr) {
+  if (arr.length !== undefined) {
+    return arr.length;
+  }
+  assert(arr.byteLength !== undefined);
+  return arr.byteLength;
+}
+
+/**
+ * Combines array items (arrays) into single Uint8Array object.
+ * @param {Array} arr - the array of the arrays (Array, Uint8Array, or string).
+ * @returns {Uint8Array}
+ */
+function arraysToBytes(arr) {
+  // Shortcut: if first and only item is Uint8Array, return it.
+  if (arr.length === 1 && (arr[0] instanceof Uint8Array)) {
+    return arr[0];
+  }
+  var resultLength = 0;
+  var i, ii = arr.length;
+  var item, itemLength ;
+  for (i = 0; i < ii; i++) {
+    item = arr[i];
+    itemLength = arrayByteLength(item);
+    resultLength += itemLength;
+  }
+  var pos = 0;
+  var data = new Uint8Array(resultLength);
+  for (i = 0; i < ii; i++) {
+    item = arr[i];
+    if (!(item instanceof Uint8Array)) {
+      if (typeof item === 'string') {
+        item = stringToBytes(item);
+      } else {
+        item = new Uint8Array(item);
+      }
+    }
+    itemLength = item.byteLength;
+    data.set(item, pos);
+    pos += itemLength;
+  }
+  return data;
+}
+
 function string32(value) {
   return String.fromCharCode((value >> 24) & 0xff, (value >> 16) & 0xff,
                              (value >> 8) & 0xff, value & 0xff);
@@ -2361,6 +2410,8 @@ exports.UnexpectedResponseException = UnexpectedResponseException;
 exports.UnknownErrorException = UnknownErrorException;
 exports.Util = Util;
 exports.XRefParseException = XRefParseException;
+exports.arrayByteLength = arrayByteLength;
+exports.arraysToBytes = arraysToBytes;
 exports.assert = assert;
 exports.bytesToString = bytesToString;
 exports.combineUrl = combineUrl;
diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js
index 94f8e27..a1ce751 100644
--- a/test/unit/api_spec.js
+++ b/test/unit/api_spec.js
@@ -8,6 +8,7 @@
 describe('api', function() {
   var basicApiUrl = combineUrl(window.location.href, '../pdfs/basicapi.pdf');
   var basicApiFileLength = 105779; // bytes
+  var TEST_TIMEOUT = 20000;
   function waitsForPromiseResolved(promise, successCallback) {
     var resolved = false;
     promise.then(function(val) {
@@ -20,7 +21,7 @@ describe('api', function() {
     });
     waitsFor(function() {
       return resolved;
-    }, 20000);
+    }, TEST_TIMEOUT);
   }
   function waitsForPromiseRejected(promise, failureCallback) {
     var rejected = false;
@@ -34,7 +35,13 @@ describe('api', function() {
     });
     waitsFor(function() {
       return rejected;
-    }, 20000);
+    }, TEST_TIMEOUT);
+  }
+  function waitSome(callback) {
+    var WAIT_TIMEOUT = 10;
+    setTimeout(function () {
+      callback();
+    }, WAIT_TIMEOUT);
   }
 
   describe('PDFJS', function() {
@@ -710,4 +717,86 @@ describe('api', function() {
       waitsForPromiseResolved(promiseDone, function() {});
     });
   });
+  describe('PDFDataRangeTransport', function () {
+    var pdfPath = combineUrl(window.location.href, '../pdfs/tracemonkey.pdf');
+    var loadPromise;
+    function getDocumentData() {
+      if (loadPromise) {
+        return loadPromise;
+      }
+      loadPromise = new Promise(function (resolve, reject) {
+        var xhr = new XMLHttpRequest(pdfPath);
+        xhr.open('GET', pdfPath);
+        xhr.responseType = 'arraybuffer';
+        xhr.onload = function () { resolve(new Uint8Array(xhr.response)); };
+        xhr.onerror = function () { reject(new Error('PDF is not loaded')); };
+        xhr.send();
+      });
+      return loadPromise;
+    }
+    it('should fetch document info and page using ranges', function () {
+      var transport;
+      var initialDataLength = 4000;
+      var fetches = 0;
+      var getDocumentPromise = getDocumentData().then(function (data) {
+        var initialData = data.subarray(0, initialDataLength);
+        transport = new PDFJS.PDFDataRangeTransport(data.length, initialData);
+        transport.requestDataRange = function (begin, end) {
+          fetches++;
+          waitSome(function () {
+            transport.onDataProgress(4000);
+            transport.onDataRange(begin, data.subarray(begin, end));
+          });
+        };
+        var loadingTask = PDFJS.getDocument(transport);
+        return loadingTask.promise;
+      });
+      var pdfDocument;
+      var getPagePromise = getDocumentPromise.then(function (pdfDocument_) {
+        pdfDocument = pdfDocument_;
+        var pagePromise = pdfDocument.getPage(10);
+        return pagePromise;
+      });
+
+      waitsForPromiseResolved(getPagePromise, function (page) {
+        expect(pdfDocument.numPages).toEqual(14);
+        expect(page.rotate).toEqual(0);
+        expect(fetches).toBeGreaterThan(2);
+      });
+    });
+    it('should fetch document info and page using range and streaming',
+        function () {
+      var transport;
+      var initialDataLength = 4000;
+      var fetches = 0;
+      var getDocumentPromise = getDocumentData().then(function (data) {
+        var initialData = data.subarray(0, initialDataLength);
+        transport = new PDFJS.PDFDataRangeTransport(data.length, initialData);
+        transport.requestDataRange = function (begin, end) {
+          fetches++;
+          if (fetches === 1) {
+            // send rest of the data on first range request.
+            transport.onDataProgressiveRead(data.subarray(initialDataLength));
+          }
+          waitSome(function () {
+            transport.onDataRange(begin, data.subarray(begin, end));
+          });
+        };
+        var loadingTask = PDFJS.getDocument(transport);
+        return loadingTask.promise;
+      });
+      var pdfDocument;
+      var getPagePromise = getDocumentPromise.then(function (pdfDocument_) {
+        pdfDocument = pdfDocument_;
+        var pagePromise = pdfDocument.getPage(10);
+        return pagePromise;
+      });
+
+      waitsForPromiseResolved(getPagePromise, function (page) {
+        expect(pdfDocument.numPages).toEqual(14);
+        expect(page.rotate).toEqual(0);
+        expect(fetches).toEqual(1);
+      });
+    });
+  });
 });
diff --git a/test/unit/network_spec.js b/test/unit/network_spec.js
new file mode 100644
index 0000000..16e5371
--- /dev/null
+++ b/test/unit/network_spec.js
@@ -0,0 +1,169 @@
+/* globals expect, it, describe, waitsFor, combineUrl, PDFNetworkStream */
+
+'use strict';
+
+describe('network', function() {
+  var pdf1 = combineUrl(window.location.href, '../pdfs/tracemonkey.pdf');
+  var pdf1Length = 1016315;
+  var pdf2 = combineUrl(window.location.href, '../pdfs/pdf.pdf');
+  var pdf2Length = 32472771;
+
+  function waitsForPromiseResolved(promise, successCallback) {
+    var TEST_TIMEOUT = 20000;
+    var resolved = false;
+    promise.then(function(val) {
+        resolved = true;
+        successCallback(val);
+      },
+      function(error) {
+        // Shouldn't get here.
+        expect(error).toEqual('the promise should not have been rejected');
+      });
+    waitsFor(function() {
+      return resolved;
+    }, TEST_TIMEOUT);
+  }
+
+  it('read without stream and range', function() {
+    var stream = new PDFNetworkStream({
+      source: {
+        url: pdf1,
+        rangeChunkSize: 65536,
+        disableStream: true,
+      },
+      disableRange: true
+    });
+
+    var fullReader = stream.getFullReader();
+
+    var isStreamingSupported, isRangeSupported;
+    var promise = fullReader.headersReady.then(function () {
+      isStreamingSupported = fullReader.isStreamingSupported;
+      isRangeSupported = fullReader.isRangeSupported;
+    });
+
+    var len = 0, count = 0;
+    var read = function () {
+      return fullReader.read().then(function (result) {
+        if (result.done) {
+          return;
+        }
+        count++;
+        len += result.value.byteLength;
+        return read();
+      });
+    };
+
+    var readPromise = read();
+
+    waitsForPromiseResolved(readPromise, function (page) {
+      expect(len).toEqual(pdf1Length);
+      expect(count).toEqual(1);
+      expect(isStreamingSupported).toEqual(false);
+      expect(isRangeSupported).toEqual(false);
+    });
+  });
+
+  it('read with streaming', function() {
+    var userAgent = window.navigator.userAgent;
+    // The test is valid for FF only: the XHR has support of the
+    // 'moz-chunked-array' response type.
+    // TODO enable for other browsers, e.g. when fetch/streams API is supported.
+    var m = /Mozilla\/5.0.*?rv:(\d+).*? Gecko/.exec(userAgent);
+    if (!m || m[1] < 9) {
+      return;
+    }
+
+    var stream = new PDFNetworkStream({
+      source: {
+        url: pdf2,
+        rangeChunkSize: 65536,
+        disableStream: false,
+      },
+      disableRange: false
+    });
+
+    var fullReader = stream.getFullReader();
+
+    var isStreamingSupported, isRangeSupported;
+    var promise = fullReader.headersReady.then(function () {
+      isStreamingSupported = fullReader.isStreamingSupported;
+      isRangeSupported = fullReader.isRangeSupported;
+    });
+
+    var len = 0, count = 0;
+    var read = function () {
+      return fullReader.read().then(function (result) {
+        if (result.done) {
+          return;
+        }
+        count++;
+        len += result.value.byteLength;
+        return read();
+      });
+    };
+
+    var readPromise = read();
+
+    waitsForPromiseResolved(readPromise, function (page) {
+      expect(len).toEqual(pdf2Length);
+      expect(count).toBeGreaterThan(1);
+      expect(isStreamingSupported).toEqual(true);
+    });
+  });
+
+  it('read custom ranges', function () {
+    // We don't test on browsers that don't support range request, so
+    // requiring this test to pass.
+    var rangeSize = 32768;
+    var stream = new PDFNetworkStream({
+      source: {
+        url: pdf1,
+        length: pdf1Length,
+        rangeChunkSize: rangeSize,
+        disableStream: true,
+      },
+      disableRange: false
+    });
+
+    var fullReader = stream.getFullReader();
+
+    var isStreamingSupported, isRangeSupported, fullReaderCancelled;
+    var promise = fullReader.headersReady.then(function () {
+      isStreamingSupported = fullReader.isStreamingSupported;
+      isRangeSupported = fullReader.isRangeSupported;
+      // we shall be able to close the full reader without issues
+      fullReader.cancel('Don\'t need full reader');
+      fullReaderCancelled = true;
+    });
+
+    // Skipping fullReader results, requesting something from the PDF end.
+    var tailSize = (pdf1Length % rangeSize) || rangeSize;
+
+    var range1Reader = stream.getRangeReader(pdf1Length - tailSize - rangeSize,
+                                             pdf1Length - tailSize);
+    var range2Reader = stream.getRangeReader(pdf1Length - tailSize, pdf1Length);
+
+    var result1 = {value: 0}, result2 = {value: 0};
+    var read = function (reader, lenResult) {
+      return reader.read().then(function (result) {
+        if (result.done) {
+          return;
+        }
+        lenResult.value += result.value.byteLength;
+        return read(reader, lenResult);
+      });
+    };
+
+    var readPromises = Promise.all([read(range1Reader, result1),
+                                    read(range2Reader, result2),
+                                    promise]);
+
+    waitsForPromiseResolved(readPromises, function (page) {
+      expect(result1.value).toEqual(rangeSize);
+      expect(result2.value).toEqual(tailSize);
+      expect(isRangeSupported).toEqual(true);
+      expect(fullReaderCancelled).toEqual(true);
+    });
+  });
+});
diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html
index 6ff77d3..3927ab7 100644
--- a/test/unit/unit_test.html
+++ b/test/unit/unit_test.html
@@ -36,6 +36,7 @@
   <script src="util_spec.js"></script>
   <script src="cmap_spec.js"></script>
   <script src="annotation_layer_spec.js"></script>
+  <script src="network_spec.js"></script>
 
   <script>
     'use strict';
@@ -48,11 +49,12 @@
           'pdfjs/core/annotation', 'pdfjs/core/crypto', 'pdfjs/core/stream',
           'pdfjs/core/fonts', 'pdfjs/core/ps_parser', 'pdfjs/core/function',
           'pdfjs/core/parser', 'pdfjs/core/evaluator', 'pdfjs/core/cmap',
-          'pdfjs/core/worker', 'pdfjs/display/api', 'pdfjs/display/metadata'],
+          'pdfjs/core/worker', 'pdfjs/core/network', 'pdfjs/display/api',
+          'pdfjs/display/metadata'],
           function (sharedUtil, sharedGlobal, corePrimitives, coreAnnotation,
                     coreCrypto, coreStream, coreFonts, corePsParser, coreFunction,
-                    coreParser, coreEvaluator, coreCMap, coreWorker, displayAPI,
-                    displayMetadata) {
+                    coreParser, coreEvaluator, coreCMap, coreWorker,
+                    coreNetwork, displayAPI, displayMetadata) {
 
         pdfjsLibs = {
           sharedUtil: sharedUtil,
@@ -68,6 +70,7 @@
           coreEvaluator: coreEvaluator,
           coreCMap: coreCMap,
           coreWorker: coreWorker,
+          coreNetwork: coreNetwork,
           displayAPI: displayAPI,
           displayMetadata: displayMetadata
         };

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/pdf.js.git



More information about the Pkg-javascript-commits mailing list