From 24f14d44cb7ebda981bcbb3e1cb83feab801bd24 Mon Sep 17 00:00:00 2001
From: Yury Delendik <ydelendik@mozilla.com>
Date: Mon, 12 Jun 2017 16:04:35 -0500
Subject: [PATCH] Preventing from using the same canvas for multiple render()

---
 src/display/api.js    | 20 +++++++++++++++++++-
 test/unit/api_spec.js | 26 ++++++++++++++++++++++++++
 2 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/src/display/api.js b/src/display/api.js
index fcde21d49..729b62764 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -901,7 +901,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
         stats.time('Rendering');
         internalRenderTask.initializeGraphics(transparency);
         internalRenderTask.operatorListChanged();
-      }, complete);
+      }).catch(complete);
 
       return renderTask;
     },
@@ -2103,6 +2103,7 @@ var RenderTask = (function RenderTaskClosure() {
  * @ignore
  */
 var InternalRenderTask = (function InternalRenderTaskClosure() {
+  let canvasInRendering = new WeakMap();
 
   function InternalRenderTask(callback, params, objs, commonObjs, operatorList,
                               pageNumber, canvasFactory) {
@@ -2125,6 +2126,7 @@ var InternalRenderTask = (function InternalRenderTaskClosure() {
     this._continueBound = this._continue.bind(this);
     this._scheduleNextBound = this._scheduleNext.bind(this);
     this._nextBound = this._next.bind(this);
+    this._canvas = params.canvasContext.canvas;
   }
 
   InternalRenderTask.prototype = {
@@ -2132,6 +2134,16 @@ var InternalRenderTask = (function InternalRenderTaskClosure() {
     initializeGraphics:
         function InternalRenderTask_initializeGraphics(transparency) {
 
+      if (this._canvas) {
+        if (canvasInRendering.has(this._canvas)) {
+          throw new Error(
+            'Cannot use the same canvas during multiple render() operations. ' +
+            'Use different canvas or ensure previous operations were ' +
+            'cancelled or completed.');
+        }
+        canvasInRendering.set(this._canvas, this);
+      }
+
       if (this.cancelled) {
         return;
       }
@@ -2163,6 +2175,9 @@ var InternalRenderTask = (function InternalRenderTaskClosure() {
     cancel: function InternalRenderTask_cancel() {
       this.running = false;
       this.cancelled = true;
+      if (this._canvas) {
+        canvasInRendering.delete(this._canvas);
+      }
 
       if ((typeof PDFJSDev !== 'undefined' && PDFJSDev.test('PDFJS_NEXT')) ||
           getDefaultSetting('pdfjsNext')) {
@@ -2223,6 +2238,9 @@ var InternalRenderTask = (function InternalRenderTaskClosure() {
         this.running = false;
         if (this.operatorList.lastChunk) {
           this.gfx.endDrawing();
+          if (this._canvas) {
+            canvasInRendering.delete(this._canvas);
+          }
           this.callback();
         }
       }
diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js
index f22988e76..d6d3d690c 100644
--- a/test/unit/api_spec.js
+++ b/test/unit/api_spec.js
@@ -1041,6 +1041,32 @@ describe('api', function() {
         done();
       });
     });
+    it('multiple render() on the same canvas', function(done) {
+      if (isNodeJS()) {
+        pending('TODO: Support Canvas testing in Node.js.');
+      }
+      var viewport = page.getViewport(1);
+      var canvasAndCtx = CanvasFactory.create(viewport.width, viewport.height);
+
+      var renderTask1 = page.render({
+        canvasContext: canvasAndCtx.context,
+        viewport,
+      });
+      var renderTask2 = page.render({
+        canvasContext: canvasAndCtx.context,
+        viewport,
+      });
+
+      Promise.all([
+        renderTask1.promise,
+        renderTask2.promise.then(() => {
+          done.fail('shall fail rendering');
+        }, (reason) => {
+          /* it fails because we already using this canvas */
+          expect(/multiple render\(\)/.test(reason.message)).toEqual(true);
+        })
+      ]).then(done);
+    });
   });
   describe('Multiple PDFJS instances', function() {
     if (isNodeJS()) {