From 3c5c8d2a0b0ea88f8c4d67d202c48a0bd59b5ff8 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:29:37 +0200
Subject: [PATCH 01/12] Remove the typed array check when calling
 `LoopbackPort` in `PDFWorker._setupFakeWorker`

With version 2.0, native support for typed arrays is now a requirement for using the PDF.js library; see PR 9094 where the old polyfills were removed.
Hence the `isTypedArraysPresent` check, when setting up fake workers, no longer serves any purpose here and can thus be removed.
---
 src/display/api.js | 7 +------
 1 file changed, 1 insertion(+), 6 deletions(-)

diff --git a/src/display/api.js b/src/display/api.js
index b121661f8..92b842eba 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -1553,12 +1553,7 @@ var PDFWorker = (function PDFWorkerClosure() {
           this._readyCapability.reject(new Error('Worker was destroyed'));
           return;
         }
-
-        // We cannot turn on proper fake port simulation (this includes
-        // structured cloning) when typed arrays are not supported. Relying
-        // on a chance that messages will be sent in proper order.
-        var isTypedArraysPresent = Uint8Array !== Float32Array;
-        var port = new LoopbackPort(isTypedArraysPresent);
+        let port = new LoopbackPort();
         this._port = port;
 
         // All fake workers use the same port, making id unique.

From 47a9d38280fc54d156c77ad89e7ac1b2b4bcc4ec Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:29:42 +0200
Subject: [PATCH 02/12] Add more validation in `PDFWorker.fromPort`

The signature of the `PDFWorker.fromPort` method, in addition to the `PDFWorker` constructor, was changed in PR 9480.
Hence it's probably a good idea to add a bit more validation to `PDFWorker.fromPort`, to ensure that it won't fail silently for an API consumer that updates to version 2.0 of the PDF.js library.
---
 src/display/api.js | 3 +++
 1 file changed, 3 insertions(+)

diff --git a/src/display/api.js b/src/display/api.js
index 92b842eba..cce2704b0 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -1593,6 +1593,9 @@ var PDFWorker = (function PDFWorkerClosure() {
    * @param {PDFWorkerParameters} params - The worker initialization parameters.
    */
   PDFWorker.fromPort = function(params) {
+    if (!params || !params.port) {
+      throw new Error('PDFWorker.fromPort - invalid method signature.');
+    }
     if (pdfWorkerPorts.has(params.port)) {
       return pdfWorkerPorts.get(params.port);
     }

From dc6e1b4176700e8796225afba524bebecab7dfa3 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:29:47 +0200
Subject: [PATCH 03/12] Use `Uint8ClampedArray` for the image data returned by
 `JpegDecode`, in src/display/api.js

Since all the built-in PDF.js image decoders now return their data as `Uint8ClampedArray`, for consistency `JpegDecode` on the main-thread should be doing the same thing; follow-up to PR 8778.
---
 src/display/api.js | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/src/display/api.js b/src/display/api.js
index cce2704b0..ce3713afa 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -2028,7 +2028,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
             var height = img.height;
             var size = width * height;
             var rgbaLength = size * 4;
-            var buf = new Uint8Array(size * components);
+            var buf = new Uint8ClampedArray(size * components);
             var tmpCanvas = document.createElement('canvas');
             tmpCanvas.width = width;
             tmpCanvas.height = height;

From eef53347fef37838f6ed451c8b85a357dd32a263 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:29:52 +0200
Subject: [PATCH 04/12] Ensure that the correct data is sent, with the `test`
 message, from the worker if typed arrays aren't properly supported

With native typed array support now being mandatory in PDF.js, since version 2.0, this probably isn't a huge problem even though the current code seems wrong (it was changed in PR 6571).

Note how in the `!(data instanceof Uint8Array)` case we're currently attempting to send `handler.send('test', 'main', false);` to the main-thread, which doesn't really make any sense since the signature of the method reads `send(actionName, data, transfers) {`.
Hence the data that's *actually* being sent here is `'main'`, with `false` as the transferList, which just seems weird. On the main-thread, this means that we're in this case checking `data && data.supportTypedArray`, where `data` contains the string `'main'` rather than being falsy. Since a string doesn't have a `supportTypedArray` property, that check still fails as expected but it doesn't seem great nonetheless.
---
 src/core/worker.js | 2 +-
 src/display/api.js | 3 +--
 2 files changed, 2 insertions(+), 3 deletions(-)

diff --git a/src/core/worker.js b/src/core/worker.js
index 45c8e33cc..567343864 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -335,7 +335,7 @@ var WorkerMessageHandler = {
 
       // check if Uint8Array can be sent to worker
       if (!(data instanceof Uint8Array)) {
-        handler.send('test', 'main', false);
+        handler.send('test', false);
         return;
       }
       // making sure postMessage transfers are working
diff --git a/src/display/api.js b/src/display/api.js
index ce3713afa..3bd0a60ee 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -1480,8 +1480,7 @@ var PDFWorker = (function PDFWorkerClosure() {
               terminateEarly();
               return; // worker was destroyed
             }
-            var supportTypedArray = data && data.supportTypedArray;
-            if (supportTypedArray) {
+            if (data && data.supportTypedArray) {
               this._messageHandler = messageHandler;
               this._port = worker;
               this._webWorker = worker;

From e89afa589929e31575de83f21f2d4810cdc94291 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:29:56 +0200
Subject: [PATCH 05/12] Stop sending the `PDFManagerReady` message from the
 Worker, since it's unused in the API

After PR 8617 the `PDFManagerReady` message handler function, in `src/display/api.js`, is now a no-op. Hence it seems completely unnecessary to keep sending this message from `src/core/worker.js`.
---
 src/core/worker.js | 3 +--
 src/display/api.js | 3 ---
 2 files changed, 1 insertion(+), 5 deletions(-)

diff --git a/src/core/worker.js b/src/core/worker.js
index 567343864..106d31bcb 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -629,9 +629,8 @@ var WorkerMessageHandler = {
           newPdfManager.terminate();
           throw new Error('Worker was terminated');
         }
-
         pdfManager = newPdfManager;
-        handler.send('PDFManagerReady', null);
+
         pdfManager.onLoadedStream().then(function(stream) {
           handler.send('DataLoaded', { length: stream.bytes.byteLength, });
         });
diff --git a/src/display/api.js b/src/display/api.js
index 3bd0a60ee..a9415b071 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -1834,9 +1834,6 @@ var WorkerTransport = (function WorkerTransportClosure() {
         this.downloadInfoCapability.resolve(data);
       }, this);
 
-      messageHandler.on('PDFManagerReady', function transportPage(data) {
-      }, this);
-
       messageHandler.on('StartRenderPage', function transportRender(data) {
         if (this.destroyed) {
           return; // Ignore any pending requests if the worker was terminated.

From 4f4b50e01eb95f470bf58ed67be7196b0fedf720 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:30:00 +0200
Subject: [PATCH 06/12] Rename `PDFDocumentProxy.pdfInfo` to
 `PDFDocumentProxy._pdfInfo` to indicate that the property should be
 considered "private"

Since `PDFDocumentProxy` already provide getters for all the data returned by `GetDoc` (in the Worker), there isn't any compelling reason for accessing the `pdfInfo` directly on `PDFDocumentProxy`.
---
 src/display/api.js | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/src/display/api.js b/src/display/api.js
index a9415b071..cfda93dd8 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -585,7 +585,7 @@ var PDFDataRangeTransport = (function pdfDataRangeTransportClosure() {
  */
 var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
   function PDFDocumentProxy(pdfInfo, transport, loadingTask) {
-    this.pdfInfo = pdfInfo;
+    this._pdfInfo = pdfInfo;
     this.transport = transport;
     this.loadingTask = loadingTask;
   }
@@ -594,14 +594,14 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
      * @return {number} Total number of pages the PDF contains.
      */
     get numPages() {
-      return this.pdfInfo.numPages;
+      return this._pdfInfo.numPages;
     },
     /**
      * @return {string} A unique ID to identify a PDF. Not guaranteed to be
      * unique.
      */
     get fingerprint() {
-      return this.pdfInfo.fingerprint;
+      return this._pdfInfo.fingerprint;
     },
     /**
      * @param {number} pageNumber The page number to get. The first page is 1.

From b263b702e8f9230484e835d88c206feda2ecb743 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:30:06 +0200
Subject: [PATCH 07/12] Rename `PDFPageProxy.pageInfo` to
 `PDFPageProxy._pageInfo` to indicate that the property should be considered
 "private"

Since `PDFPageProxy` already provide getters for all the data returned by `GetPage` (in the Worker), there isn't any compelling reason for accessing the `pageInfo` directly on `PDFPageProxy`.

The patch also changes the `GetPage` handler, in `src/core/worker.js`, to use modern JavaScript features.
---
 src/core/worker.js | 20 +++++++++-----------
 src/display/api.js | 10 +++++-----
 2 files changed, 14 insertions(+), 16 deletions(-)

diff --git a/src/core/worker.js b/src/core/worker.js
index 106d31bcb..5b5020dfd 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -639,19 +639,17 @@ var WorkerMessageHandler = {
 
     handler.on('GetPage', function wphSetupGetPage(data) {
       return pdfManager.getPage(data.pageIndex).then(function(page) {
-        var rotatePromise = pdfManager.ensure(page, 'rotate');
-        var refPromise = pdfManager.ensure(page, 'ref');
-        var userUnitPromise = pdfManager.ensure(page, 'userUnit');
-        var viewPromise = pdfManager.ensure(page, 'view');
-
         return Promise.all([
-          rotatePromise, refPromise, userUnitPromise, viewPromise
-        ]).then(function(results) {
+          pdfManager.ensure(page, 'rotate'),
+          pdfManager.ensure(page, 'ref'),
+          pdfManager.ensure(page, 'userUnit'),
+          pdfManager.ensure(page, 'view'),
+        ]).then(function([rotate, ref, userUnit, view]) {
           return {
-            rotate: results[0],
-            ref: results[1],
-            userUnit: results[2],
-            view: results[3],
+            rotate,
+            ref,
+            userUnit,
+            view,
           };
         });
       });
diff --git a/src/display/api.js b/src/display/api.js
index cfda93dd8..b5fa9e8d2 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -831,7 +831,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
 var PDFPageProxy = (function PDFPageProxyClosure() {
   function PDFPageProxy(pageIndex, pageInfo, transport, pdfBug = false) {
     this.pageIndex = pageIndex;
-    this.pageInfo = pageInfo;
+    this._pageInfo = pageInfo;
     this.transport = transport;
     this._stats = (pdfBug ? new StatTimer() : DummyStatTimer);
     this._pdfBug = pdfBug;
@@ -853,27 +853,27 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
      * @return {number} The number of degrees the page is rotated clockwise.
      */
     get rotate() {
-      return this.pageInfo.rotate;
+      return this._pageInfo.rotate;
     },
     /**
      * @return {Object} The reference that points to this page. It has 'num' and
      * 'gen' properties.
      */
     get ref() {
-      return this.pageInfo.ref;
+      return this._pageInfo.ref;
     },
     /**
      * @return {number} The default size of units in 1/72nds of an inch.
      */
     get userUnit() {
-      return this.pageInfo.userUnit;
+      return this._pageInfo.userUnit;
     },
     /**
      * @return {Array} An array of the visible portion of the PDF page in the
      * user space units - [x1, y1, x2, y2].
      */
     get view() {
-      return this.pageInfo.view;
+      return this._pageInfo.view;
     },
 
     /**

From c8e2163bbca459b5050e3c772e21a5bbbdec6cf8 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:30:11 +0200
Subject: [PATCH 08/12] Remove incorrect/unnecessary validation of the
 `verbosity` parameter in the `PDFWorker` constructor (PR 9480 follow-up)

---
 src/display/api.js | 7 ++++---
 1 file changed, 4 insertions(+), 3 deletions(-)

diff --git a/src/display/api.js b/src/display/api.js
index b5fa9e8d2..65ee503a1 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -16,7 +16,7 @@
 
 import {
   assert, createPromiseCapability, getVerbosityLevel, info, InvalidPDFException,
-  isArrayBuffer, isNum, isSameOrigin, MissingPDFException, NativeImageDecoding,
+  isArrayBuffer, isSameOrigin, MissingPDFException, NativeImageDecoding,
   PasswordException, setVerbosityLevel, shadow, stringToBytes,
   UnexpectedResponseException, UnknownErrorException, unreachable, Util, warn
 } from '../shared/util';
@@ -1382,7 +1382,8 @@ var PDFWorker = (function PDFWorkerClosure() {
    * @param {PDFWorkerParameters} params - The worker initialization parameters.
    */
   function PDFWorker({ name = null, port = null,
-                       postMessageTransfers = true, verbosity = null, } = {}) {
+                       postMessageTransfers = true,
+                       verbosity = getVerbosityLevel(), } = {}) {
     if (port && pdfWorkerPorts.has(port)) {
       throw new Error('Cannot use more than one PDFWorker per port');
     }
@@ -1390,7 +1391,7 @@ var PDFWorker = (function PDFWorkerClosure() {
     this.name = name;
     this.destroyed = false;
     this.postMessageTransfers = postMessageTransfers !== false;
-    this.verbosity = (isNum(verbosity) ? verbosity : getVerbosityLevel());
+    this.verbosity = verbosity;
 
     this._readyCapability = createPromiseCapability();
     this._port = null;

From 871bf5c68b1d6ad0365baac4e1056cb14faf635c Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:30:14 +0200
Subject: [PATCH 09/12] Remove the, now obsolete, handling of the
 `CMapReaderFactory` parameter in `getDocument`

This special handling was added in PR 8567, but was made redundant in PR 8721 which stopped sending everything but the kitchen sink to the Worker side.
---
 src/display/api.js | 14 +++++---------
 1 file changed, 5 insertions(+), 9 deletions(-)

diff --git a/src/display/api.js b/src/display/api.js
index 65ee503a1..acd8fc746 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -231,7 +231,6 @@ function getDocument(src) {
   let params = Object.create(null);
   var rangeTransport = null;
   let worker = null;
-  let CMapReaderFactory = DOMCMapReaderFactory;
 
   for (var key in source) {
     if (key === 'url' && typeof window !== 'undefined') {
@@ -260,14 +259,12 @@ function getDocument(src) {
                         'data property.');
       }
       continue;
-    } else if (key === 'CMapReaderFactory') {
-      CMapReaderFactory = source[key];
-      continue;
     }
     params[key] = source[key];
   }
 
   params.rangeChunkSize = params.rangeChunkSize || DEFAULT_RANGE_CHUNK_SIZE;
+  params.CMapReaderFactory = params.CMapReaderFactory || DOMCMapReaderFactory;
   params.ignoreErrors = params.stopAtErrors !== true;
   params.pdfBug = params.pdfBug === true;
 
@@ -355,7 +352,7 @@ function getDocument(src) {
       var messageHandler = new MessageHandler(docId, workerId, worker.port);
       messageHandler.postMessageTransfers = worker.postMessageTransfers;
       var transport = new WorkerTransport(messageHandler, task, networkStream,
-                                          params, CMapReaderFactory);
+                                          params);
       task._transport = transport;
       messageHandler.send('Ready', null);
     });
@@ -387,7 +384,7 @@ function _fetchDocument(worker, source, pdfDataRangeTransport, docId) {
     docId,
     apiVersion: (typeof PDFJSDev !== 'undefined' ?
                  PDFJSDev.eval('BUNDLE_VERSION') : null),
-    source: {
+    source: { // Only send the required properties, and *not* the entire object.
       data: source.data,
       url: source.url,
       password: source.password,
@@ -1614,14 +1611,13 @@ var PDFWorker = (function PDFWorkerClosure() {
  * @ignore
  */
 var WorkerTransport = (function WorkerTransportClosure() {
-  function WorkerTransport(messageHandler, loadingTask, networkStream,
-                           params, CMapReaderFactory) {
+  function WorkerTransport(messageHandler, loadingTask, networkStream, params) {
     this.messageHandler = messageHandler;
     this.loadingTask = loadingTask;
     this.commonObjs = new PDFObjects();
     this.fontLoader = new FontLoader(loadingTask.docId);
     this._params = params;
-    this.CMapReaderFactory = new CMapReaderFactory({
+    this.CMapReaderFactory = new params.CMapReaderFactory({
       baseUrl: params.cMapUrl,
       isCompressed: params.cMapPacked,
     });

From 2fdaa3d54c7bcadbdfc9cec0f9c8282af40d5c04 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:30:23 +0200
Subject: [PATCH 10/12] Update the `postMessageTransfers` comment in
 `createDocumentHandler` in the `src/core/worker.js` file

Since the old comment mentions a now unsupported browser, let's update it such that someone won't accidentally conclude that the code in question can be removed.
---
 src/core/worker.js | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/src/core/worker.js b/src/core/worker.js
index 5b5020dfd..6cfa09950 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -390,8 +390,8 @@ var WorkerMessageHandler = {
     var workerHandlerName = docParams.docId + '_worker';
     var handler = new MessageHandler(workerHandlerName, docId, port);
 
-    // Ensure that postMessage transfers are correctly enabled/disabled,
-    // to prevent "DataCloneError" in older versions of IE (see issue 6957).
+    // Ensure that postMessage transfers are always correctly enabled/disabled,
+    // to prevent "DataCloneError" in browsers without transfers support.
     handler.postMessageTransfers = docParams.postMessageTransfers;
 
     function ensureNotTerminated() {

From 547f119be628f58e12237addd528ea5239b80e6c Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Tue, 5 Jun 2018 20:30:26 +0200
Subject: [PATCH 11/12] Simplify the error handling slightly in the
 `src/display/node_stream.js` file

The various classes have `this._errored` and `this._reason` properties, where the first one is a boolean indicating if an error was encountered and the second one contains the actual `Error` (or `null` initially).

In practice this means that errors are basically tracked *twice*, rather than just once. This kind of double-bookkeeping is generally a bad idea, since it's quite easy for the properties to (accidentally) get into an inconsistent state whenever the relevant code is modified.

Rather than using a separate boolean, we can just as well check the "error" property directly (since `null` is falsy).

---

Somewhat unrelated to this patch, but `src/display/node_stream.js` is currently *not* handling errors in a consistent or even correct way; compared with `src/display/network.js` and `src/display/fetch_stream.js`.
Obviously using the `createResponseStatusError` utility function, from `src/display/network_utils.js`, might not make much sense in a Node.js environment. However at the *very* least it seems that `MissingPDFException`, or `UnknownErrorException` when one cannot tell that the PDF file is "missing", should be manually thrown.

As is, the API (i.e. `getDocument`) is not returning the *expected* errors when loading fails in Node.js environments (as evident from the `pending` API unit-test).
---
 src/display/node_stream.js | 37 +++++++++++++++----------------------
 test/unit/api_spec.js      |  4 +++-
 2 files changed, 18 insertions(+), 23 deletions(-)

diff --git a/src/display/node_stream.js b/src/display/node_stream.js
index 3332d46d9..84ab37a8b 100644
--- a/src/display/node_stream.js
+++ b/src/display/node_stream.js
@@ -90,8 +90,7 @@ class BaseFullReader {
   constructor(stream) {
     this._url = stream.url;
     this._done = false;
-    this._errored = false;
-    this._reason = null;
+    this._storedError = null;
     this.onProgress = null;
     let source = stream.source;
     this._contentLength = source.length; // optional
@@ -137,8 +136,8 @@ class BaseFullReader {
       if (this._done) {
         return Promise.resolve({ value: undefined, done: true, });
       }
-      if (this._errored) {
-        return Promise.reject(this._reason);
+      if (this._storedError) {
+        return Promise.reject(this._storedError);
       }
 
       let chunk = this._readableStream.read();
@@ -170,8 +169,7 @@ class BaseFullReader {
   }
 
   _error(reason) {
-    this._errored = true;
-    this._reason = reason;
+    this._storedError = reason;
     this._readCapability.resolve();
   }
 
@@ -199,8 +197,8 @@ class BaseFullReader {
     }
 
     // Destroy ReadableStream if already in errored state.
-    if (this._errored) {
-      this._readableStream.destroy(this._reason);
+    if (this._storedError) {
+      this._readableStream.destroy(this._storedError);
     }
   }
 }
@@ -209,8 +207,7 @@ class BaseRangeReader {
   constructor(stream) {
     this._url = stream.url;
     this._done = false;
-    this._errored = false;
-    this._reason = null;
+    this._storedError = null;
     this.onProgress = null;
     this._loaded = 0;
     this._readableStream = null;
@@ -228,8 +225,8 @@ class BaseRangeReader {
       if (this._done) {
         return Promise.resolve({ value: undefined, done: true, });
       }
-      if (this._errored) {
-        return Promise.reject(this._reason);
+      if (this._storedError) {
+        return Promise.reject(this._storedError);
       }
 
       let chunk = this._readableStream.read();
@@ -258,8 +255,7 @@ class BaseRangeReader {
   }
 
   _error(reason) {
-    this._errored = true;
-    this._reason = reason;
+    this._storedError = reason;
     this._readCapability.resolve();
   }
 
@@ -281,8 +277,8 @@ class BaseRangeReader {
     });
 
     // Destroy readableStream if already in errored state.
-    if (this._errored) {
-      this._readableStream.destroy(this._reason);
+    if (this._storedError) {
+      this._readableStream.destroy(this._storedError);
     }
   }
 }
@@ -339,8 +335,7 @@ class PDFNodeStreamFullReader extends BaseFullReader {
     }
 
     this._request.on('error', (reason) => {
-      this._errored = true;
-      this._reason = reason;
+      this._storedError = reason;
       this._headersCapability.reject(reason);
     });
     // Note: `request.end(data)` is used to write `data` to request body
@@ -378,8 +373,7 @@ class PDFNodeStreamRangeReader extends BaseRangeReader {
     }
 
     this._request.on('error', (reason) => {
-      this._errored = true;
-      this._reason = reason;
+      this._storedError = reason;
     });
     this._request.end();
   }
@@ -398,8 +392,7 @@ class PDFNodeStreamFsFullReader extends BaseFullReader {
 
     fs.lstat(path, (error, stat) => {
       if (error) {
-        this._errored = true;
-        this._reason = error;
+        this._storedError = error;
         this._headersCapability.reject(error);
         return;
       }
diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js
index 3b0cdf23f..1d3264649 100644
--- a/test/unit/api_spec.js
+++ b/test/unit/api_spec.js
@@ -164,7 +164,9 @@ describe('api', function() {
     });
     it('creates pdf doc from non-existent URL', function(done) {
       if (isNodeJS()) {
-        pending('XMLHttpRequest is not supported in Node.js.');
+        pending('Fix `src/display/node_stream.js` to actually throw ' +
+                'a `MissingPDFException` in all cases where a PDF file ' +
+                'cannot be found, such that this test-case can be enabled.');
       }
       var loadingTask = getDocument(
         buildGetDocumentParams('non-existent.pdf'));

From 07d610615c2832b9dc3ca1f5cdd976dc64510ea7 Mon Sep 17 00:00:00 2001
From: Jonas Jenwald <jonas.jenwald@gmail.com>
Date: Thu, 7 Jun 2018 13:52:40 +0200
Subject: [PATCH 12/12] Move, and modernize, `Util.loadScript` from
 `src/shared/util.js` to `src/display/dom_utils.js`

Not only is the `Util.loadScript` helper function unused on the Worker side, even trying to use it there would throw an Error (since `document` isn't defined/available in Workers).
Hence this helper function is moved, and its code modernized slightly by having it return a Promise rather than needing a callback function.

Finally, to reduced code duplication, the "new" loadScript function is exported and used in the viewer.
---
 src/display/api.js       |  6 ++---
 src/display/dom_utils.js | 14 +++++++++++
 src/pdf.js               |  1 +
 src/shared/util.js       | 15 ------------
 web/app.js               | 51 +++++++++++++---------------------------
 5 files changed, 34 insertions(+), 53 deletions(-)

diff --git a/src/display/api.js b/src/display/api.js
index acd8fc746..c9c8d4164 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -21,8 +21,8 @@ import {
   UnexpectedResponseException, UnknownErrorException, unreachable, Util, warn
 } from '../shared/util';
 import {
-  DOMCanvasFactory, DOMCMapReaderFactory, DummyStatTimer, PageViewport,
-  RenderingCancelledException, StatTimer
+  DOMCanvasFactory, DOMCMapReaderFactory, DummyStatTimer, loadScript,
+  PageViewport, RenderingCancelledException, StatTimer
 } from './dom_utils';
 import { FontFaceObject, FontLoader } from './font_loader';
 import { apiCompatibilityParams } from './api_compatibility';
@@ -1356,7 +1356,7 @@ var PDFWorker = (function PDFWorkerClosure() {
       }
     } else {
       let loader = fakeWorkerFilesLoader || function(callback) {
-        Util.loadScript(getWorkerSrc(), function() {
+        loadScript(getWorkerSrc()).then(function() {
           callback(window.pdfjsWorker.WorkerMessageHandler);
         });
       };
diff --git a/src/display/dom_utils.js b/src/display/dom_utils.js
index 94e3b10f2..06f7a7b8e 100644
--- a/src/display/dom_utils.js
+++ b/src/display/dom_utils.js
@@ -428,6 +428,19 @@ class DummyStatTimer {
   }
 }
 
+function loadScript(src) {
+  return new Promise((resolve, reject) => {
+    let script = document.createElement('script');
+    script.src = src;
+
+    script.onload = resolve;
+    script.onerror = function() {
+      reject(new Error(`Cannot load script at: ${script.src}`));
+    };
+    (document.head || document.documentElement).appendChild(script);
+  });
+}
+
 export {
   PageViewport,
   RenderingCancelledException,
@@ -440,4 +453,5 @@ export {
   DOMSVGFactory,
   StatTimer,
   DummyStatTimer,
+  loadScript,
 };
diff --git a/src/pdf.js b/src/pdf.js
index 1de68d330..cc4c2c5e6 100644
--- a/src/pdf.js
+++ b/src/pdf.js
@@ -108,6 +108,7 @@ exports.RenderingCancelledException =
 exports.getFilenameFromUrl = pdfjsDisplayDOMUtils.getFilenameFromUrl;
 exports.LinkTarget = pdfjsDisplayDOMUtils.LinkTarget;
 exports.addLinkAttributes = pdfjsDisplayDOMUtils.addLinkAttributes;
+exports.loadScript = pdfjsDisplayDOMUtils.loadScript;
 exports.GlobalWorkerOptions = pdfjsDisplayWorkerOptions.GlobalWorkerOptions;
 exports.apiCompatibilityParams =
   pdfjsDisplayAPICompatibility.apiCompatibilityParams;
diff --git a/src/shared/util.js b/src/shared/util.js
index 6ffbedf60..a52df0c42 100644
--- a/src/shared/util.js
+++ b/src/shared/util.js
@@ -908,21 +908,6 @@ var Util = (function UtilClosure() {
     }
   };
 
-  Util.loadScript = function Util_loadScript(src, callback) {
-    var script = document.createElement('script');
-    var loaded = false;
-    script.setAttribute('src', src);
-    if (callback) {
-      script.onload = function() {
-        if (!loaded) {
-          callback();
-        }
-        loaded = true;
-      };
-    }
-    document.getElementsByTagName('head')[0].appendChild(script);
-  };
-
   return Util;
 })();
 
diff --git a/web/app.js b/web/app.js
index de4f8c8ec..b9f3f1d62 100644
--- a/web/app.js
+++ b/web/app.js
@@ -22,8 +22,8 @@ import {
 } from './ui_utils';
 import {
   build, createBlob, getDocument, getFilenameFromUrl, GlobalWorkerOptions,
-  InvalidPDFException, LinkTarget, MissingPDFException, OPS, PDFWorker, shadow,
-  UnexpectedResponseException, UNSUPPORTED_FEATURES, version
+  InvalidPDFException, LinkTarget, loadScript, MissingPDFException, OPS,
+  PDFWorker, shadow, UnexpectedResponseException, UNSUPPORTED_FEATURES, version
 } from 'pdfjs-lib';
 import { CursorTool, PDFCursorTools } from './pdf_cursor_tools';
 import { PDFRenderingQueue, RenderingStates } from './pdf_rendering_queue';
@@ -1551,11 +1551,11 @@ if (typeof PDFJSDev === 'undefined' || PDFJSDev.test('GENERIC')) {
 }
 
 function loadFakeWorker() {
-  return new Promise(function(resolve, reject) {
-    if (!GlobalWorkerOptions.workerSrc) {
-      GlobalWorkerOptions.workerSrc = AppOptions.get('workerSrc');
-    }
-    if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION')) {
+  if (!GlobalWorkerOptions.workerSrc) {
+    GlobalWorkerOptions.workerSrc = AppOptions.get('workerSrc');
+  }
+  if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('PRODUCTION')) {
+    return new Promise(function(resolve, reject) {
       if (typeof SystemJS === 'object') {
         SystemJS.import('pdfjs/core/worker').then((worker) => {
           window.pdfjsWorker = worker;
@@ -1568,37 +1568,18 @@ function loadFakeWorker() {
         reject(new Error(
           'SystemJS or CommonJS must be used to load fake worker.'));
       }
-    } else {
-      let script = document.createElement('script');
-      script.src = PDFWorker.getWorkerSrc();
-      script.onload = function() {
-        resolve();
-      };
-      script.onerror = function() {
-        reject(new Error(`Cannot load fake worker at: ${script.src}`));
-      };
-      (document.head || document.documentElement).appendChild(script);
-    }
-  });
+    });
+  }
+  return loadScript(PDFWorker.getWorkerSrc());
 }
 
 function loadAndEnablePDFBug(enabledTabs) {
-  return new Promise(function (resolve, reject) {
-    let appConfig = PDFViewerApplication.appConfig;
-    let script = document.createElement('script');
-    script.src = appConfig.debuggerScriptPath;
-    script.onload = function () {
-      PDFBug.enable(enabledTabs);
-      PDFBug.init({
-        OPS,
-      }, appConfig.mainContainer);
-      resolve();
-    };
-    script.onerror = function () {
-      reject(new Error('Cannot load debugger at ' + script.src));
-    };
-    (document.getElementsByTagName('head')[0] || document.body).
-      appendChild(script);
+  let appConfig = PDFViewerApplication.appConfig;
+  return loadScript(appConfig.debuggerScriptPath).then(function() {
+    PDFBug.enable(enabledTabs);
+    PDFBug.init({
+      OPS,
+    }, appConfig.mainContainer);
   });
 }