diff --git a/.eslintrc b/.eslintrc index 603ead6d9..94cc07932 100644 --- a/.eslintrc +++ b/.eslintrc @@ -69,6 +69,7 @@ "no-redeclare": "error", "no-self-assign": "error", "no-self-compare": "error", + "no-throw-literal": "error", "no-unused-expressions": "error", "no-unused-labels": "error", "no-useless-call": "error", diff --git a/gulpfile.js b/gulpfile.js index 1b57ae82a..43420e128 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -67,7 +67,8 @@ var DEFINES = { CHROME: false, MINIFIED: false, SINGLE_FILE: false, - COMPONENTS: false + COMPONENTS: false, + PDFJS_NEXT: false, }; function createStringSource(filename, content) { @@ -675,7 +676,7 @@ gulp.task('minified', ['minified-pre'], function (done) { gulp.task('firefox-pre', ['buildnumber', 'locale'], function () { console.log(); console.log('### Building Firefox extension'); - var defines = builder.merge(DEFINES, {FIREFOX: true}); + var defines = builder.merge(DEFINES, { FIREFOX: true, PDFJS_NEXT: true, }); var FIREFOX_BUILD_CONTENT_DIR = FIREFOX_BUILD_DIR + '/content/', FIREFOX_EXTENSION_DIR = 'extensions/firefox/', @@ -786,7 +787,7 @@ gulp.task('firefox', ['firefox-pre'], function (done) { gulp.task('mozcentral-pre', ['buildnumber', 'locale'], function () { console.log(); console.log('### Building mozilla-central extension'); - var defines = builder.merge(DEFINES, {MOZCENTRAL: true}); + var defines = builder.merge(DEFINES, { MOZCENTRAL: true, PDFJS_NEXT: true, }); var MOZCENTRAL_DIR = BUILD_DIR + 'mozcentral/', MOZCENTRAL_EXTENSION_DIR = MOZCENTRAL_DIR + 'browser/extensions/pdfjs/', @@ -859,7 +860,7 @@ gulp.task('mozcentral', ['mozcentral-pre']); gulp.task('chromium-pre', ['buildnumber', 'locale'], function () { console.log(); console.log('### Building Chromium extension'); - var defines = builder.merge(DEFINES, {CHROME: true}); + var defines = builder.merge(DEFINES, { CHROME: true, PDFJS_NEXT: true, }); var CHROME_BUILD_DIR = BUILD_DIR + '/chromium/', CHROME_BUILD_CONTENT_DIR = CHROME_BUILD_DIR + '/content/'; diff --git a/src/display/api.js b/src/display/api.js index a2567c5d6..984b27b72 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -60,6 +60,7 @@ var FontFaceObject = displayFontLoader.FontFaceObject; var FontLoader = displayFontLoader.FontLoader; var CanvasGraphics = displayCanvas.CanvasGraphics; var Metadata = displayMetadata.Metadata; +var RenderingCancelledException = displayDOMUtils.RenderingCancelledException; var getDefaultSetting = displayDOMUtils.getDefaultSetting; var DOMCanvasFactory = displayDOMUtils.DOMCanvasFactory; var DOMCMapReaderFactory = displayDOMUtils.DOMCMapReaderFactory; @@ -2065,7 +2066,7 @@ var RenderTask = (function RenderTaskClosure() { /** * Cancels the rendering task. If the task is currently rendering it will * not be cancelled until graphics pauses with a timeout. The promise that - * this object extends will resolved when cancelled. + * this object extends will be rejected when cancelled. */ cancel: function RenderTask_cancel() { this._internalRenderTask.cancel(); @@ -2147,7 +2148,14 @@ var InternalRenderTask = (function InternalRenderTaskClosure() { cancel: function InternalRenderTask_cancel() { this.running = false; this.cancelled = true; - this.callback('cancelled'); + + if ((typeof PDFJSDev !== 'undefined' && PDFJSDev.test('PDFJS_NEXT')) || + getDefaultSetting('pdfjsNext')) { + this.callback(new RenderingCancelledException( + 'Rendering cancelled, page ' + this.pageNumber, 'canvas')); + } else { + this.callback('cancelled'); + } }, operatorListChanged: function InternalRenderTask_operatorListChanged() { diff --git a/src/display/dom_utils.js b/src/display/dom_utils.js index 558bce22e..8812e6f19 100644 --- a/src/display/dom_utils.js +++ b/src/display/dom_utils.js @@ -176,6 +176,19 @@ var CustomStyle = (function CustomStyleClosure() { return CustomStyle; })(); +var RenderingCancelledException = (function RenderingCancelledException() { + function RenderingCancelledException(msg, type) { + this.message = msg; + this.type = type; + } + + RenderingCancelledException.prototype = new Error(); + RenderingCancelledException.prototype.name = 'RenderingCancelledException'; + RenderingCancelledException.constructor = RenderingCancelledException; + + return RenderingCancelledException; +})(); + var hasCanvasTypedArrays; if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('FIREFOX || MOZCENTRAL || CHROME')) { @@ -308,6 +321,8 @@ function getDefaultSetting(id) { return globalSettings ? globalSettings.externalLinkRel : DEFAULT_LINK_REL; case 'enableStats': return !!(globalSettings && globalSettings.enableStats); + case 'pdfjsNext': + return !!(globalSettings && globalSettings.pdfjsNext); default: throw new Error('Unknown default setting: ' + id); } @@ -338,6 +353,7 @@ exports.isExternalLinkTargetSet = isExternalLinkTargetSet; exports.isValidUrl = isValidUrl; exports.getFilenameFromUrl = getFilenameFromUrl; exports.LinkTarget = LinkTarget; +exports.RenderingCancelledException = RenderingCancelledException; exports.hasCanvasTypedArrays = hasCanvasTypedArrays; exports.getDefaultSetting = getDefaultSetting; exports.DEFAULT_LINK_REL = DEFAULT_LINK_REL; diff --git a/src/display/global.js b/src/display/global.js index b06abb5ff..c4e5f653e 100644 --- a/src/display/global.js +++ b/src/display/global.js @@ -251,6 +251,13 @@ PDFJS.isEvalSupported = (PDFJS.isEvalSupported === undefined ? true : PDFJS.isEvalSupported); + /** + * Opt-in to backwards incompatible API changes. NOTE: + * If the `PDFJS_NEXT` build flag is set, it will override this setting. + * @var {boolean} + */ + PDFJS.pdfjsNext = (PDFJS.pdfjsNext === undefined) ? false : PDFJS.pdfjsNext; + if (typeof PDFJSDev === 'undefined' || !PDFJSDev.test('MOZCENTRAL')) { var savedOpenExternalLinksInNewWindow = PDFJS.openExternalLinksInNewWindow; delete PDFJS.openExternalLinksInNewWindow; diff --git a/src/main_loader.js b/src/main_loader.js index 1de29358f..0d3c24ba1 100644 --- a/src/main_loader.js +++ b/src/main_loader.js @@ -62,6 +62,8 @@ exports.removeNullCharacters = sharedUtil.removeNullCharacters; exports.shadow = sharedUtil.shadow; exports.createBlob = sharedUtil.createBlob; + exports.RenderingCancelledException = + displayDOMUtils.RenderingCancelledException; exports.getFilenameFromUrl = displayDOMUtils.getFilenameFromUrl; exports.addLinkAttributes = displayDOMUtils.addLinkAttributes; })); diff --git a/src/pdf.js b/src/pdf.js index 1dc23daa0..62d60a003 100644 --- a/src/pdf.js +++ b/src/pdf.js @@ -52,5 +52,7 @@ exports.createObjectURL = pdfjsSharedUtil.createObjectURL; exports.removeNullCharacters = pdfjsSharedUtil.removeNullCharacters; exports.shadow = pdfjsSharedUtil.shadow; exports.createBlob = pdfjsSharedUtil.createBlob; +exports.RenderingCancelledException = + pdfjsDisplayDOMUtils.RenderingCancelledException; exports.getFilenameFromUrl = pdfjsDisplayDOMUtils.getFilenameFromUrl; exports.addLinkAttributes = pdfjsDisplayDOMUtils.addLinkAttributes; diff --git a/test/driver.js b/test/driver.js index 5dda360ae..7aba45fa4 100644 --- a/test/driver.js +++ b/test/driver.js @@ -263,6 +263,8 @@ var Driver = (function DriverClosure() { // eslint-disable-line no-unused-vars PDFJS.cMapUrl = '../external/bcmaps/'; PDFJS.enableStats = true; PDFJS.imageResourcesPath = '/web/images/'; + // Opt-in to using the latest API. + PDFJS.pdfjsNext = true; // Set the passed options this.inflight = options.inflight; diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index dec19928f..9c013f052 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -17,19 +17,24 @@ (function (root, factory) { if (typeof define === 'function' && define.amd) { define('pdfjs-test/unit/api_spec', ['exports', 'pdfjs/shared/util', - 'pdfjs/display/global', 'pdfjs/display/api'], factory); + 'pdfjs/display/dom_utils', 'pdfjs/display/global', 'pdfjs/display/api'], + factory); } else if (typeof exports !== 'undefined') { factory(exports, require('../../src/shared/util.js'), - require('../../src/display/global.js'), - require('../../src/display/api.js')); + require('../../src/display/dom_utils.js'), + require('../../src/display/global.js'), + require('../../src/display/api.js')); } else { factory((root.pdfjsTestUnitApiSpec = {}), root.pdfjsSharedUtil, - root.pdfjsDisplayGlobal, root.pdfjsDisplayApi); + root.pdfjsDisplayDOMUtils, root.pdfjsDisplayGlobal, root.pdfjsDisplayApi); } -}(this, function (exports, sharedUtil, displayGlobal, displayApi) { +}(this, function (exports, sharedUtil, displayDOMUtils, displayGlobal, + displayApi) { var PDFJS = displayGlobal.PDFJS; var createPromiseCapability = sharedUtil.createPromiseCapability; +var DOMCanvasFactory = displayDOMUtils.DOMCanvasFactory; +var RenderingCancelledException = displayDOMUtils.RenderingCancelledException; var PDFDocumentProxy = displayApi.PDFDocumentProxy; var InvalidPDFException = sharedUtil.InvalidPDFException; var MissingPDFException = sharedUtil.MissingPDFException; @@ -42,6 +47,16 @@ var FontType = sharedUtil.FontType; describe('api', function() { var basicApiUrl = new URL('../pdfs/basicapi.pdf', window.location).href; var basicApiFileLength = 105779; // bytes + var CanvasFactory; + + beforeAll(function(done) { + CanvasFactory = new DOMCanvasFactory(); + done(); + }); + + afterAll(function () { + CanvasFactory = null; + }); function waitSome(callback) { var WAIT_TIMEOUT = 10; @@ -1000,6 +1015,26 @@ describe('api', function() { done.fail(reason); }); }); + + it('cancels rendering of page', function(done) { + var viewport = page.getViewport(1); + var canvasAndCtx = CanvasFactory.create(viewport.width, viewport.height); + + var renderTask = page.render({ + canvasContext: canvasAndCtx.context, + viewport: viewport, + }); + renderTask.cancel(); + + renderTask.promise.then(function() { + done.fail('shall cancel rendering'); + }).catch(function (error) { + expect(error instanceof RenderingCancelledException).toEqual(true); + expect(error.type).toEqual('canvas'); + CanvasFactory.destroy(canvasAndCtx); + done(); + }); + }); }); describe('Multiple PDFJS instances', function() { // Regression test for https://github.com/mozilla/pdf.js/issues/6205 @@ -1022,15 +1057,16 @@ describe('api', function() { pdfDocuments.push(pdf); return pdf.getPage(1); }).then(function(page) { - var c = document.createElement('canvas'); - var v = page.getViewport(1.2); - c.width = v.width; - c.height = v.height; + var viewport = page.getViewport(1.2); + var canvasAndCtx = CanvasFactory.create(viewport.width, + viewport.height); return page.render({ - canvasContext: c.getContext('2d'), - viewport: v, + canvasContext: canvasAndCtx.context, + viewport: viewport, }).then(function() { - return c.toDataURL(); + var data = canvasAndCtx.canvas.toDataURL(); + CanvasFactory.destroy(canvasAndCtx); + return data; }); }); } diff --git a/test/unit/jasmine-boot.js b/test/unit/jasmine-boot.js index 0ca0b9923..4bc5fc885 100644 --- a/test/unit/jasmine-boot.js +++ b/test/unit/jasmine-boot.js @@ -60,6 +60,8 @@ function initializePDFJS(callback) { // Configure the worker. displayGlobal.PDFJS.workerSrc = '../../src/worker_loader.js'; + // Opt-in to using the latest API. + displayGlobal.PDFJS.pdfjsNext = true; callback(); }); diff --git a/web/app.js b/web/app.js index 3922750f3..e33807222 100644 --- a/web/app.js +++ b/web/app.js @@ -117,6 +117,7 @@ function configure(PDFJS) { PDFJS.cMapUrl = '../external/bcmaps/'; PDFJS.cMapPacked = true; PDFJS.workerSrc = '../src/worker_loader.js'; + PDFJS.pdfjsNext = true; } else { PDFJS.cMapUrl = '../web/cmaps/'; PDFJS.cMapPacked = true; diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index 3ffe250ec..48289d2d2 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -441,7 +441,9 @@ var PDFPageView = (function PDFPageViewClosure() { self.paintTask = null; } - if (error === 'cancelled') { + if (((typeof PDFJSDev === 'undefined' || + !PDFJSDev.test('PDFJS_NEXT')) && error === 'cancelled') || + error instanceof pdfjsLib.RenderingCancelledException) { self.error = null; return Promise.resolve(undefined); } @@ -632,7 +634,13 @@ var PDFPageView = (function PDFPageViewClosure() { var cancelled = false; var ensureNotCancelled = function () { if (cancelled) { - throw 'cancelled'; + if ((typeof PDFJSDev !== 'undefined' && + PDFJSDev.test('PDFJS_NEXT')) || pdfjsLib.PDFJS.pdfjsNext) { + throw new pdfjsLib.RenderingCancelledException( + 'Rendering cancelled, page ' + self.id, 'svg'); + } else { + throw 'cancelled'; // eslint-disable-line no-throw-literal + } } };