diff --git a/extensions/firefox/content/PdfStreamConverter.jsm b/extensions/firefox/content/PdfStreamConverter.jsm index b331444ca..9e12f10f2 100644 --- a/extensions/firefox/content/PdfStreamConverter.jsm +++ b/extensions/firefox/content/PdfStreamConverter.jsm @@ -662,8 +662,8 @@ var RangedChromeActions = (function RangedChromeActionsClosure() { // If we are in range request mode, this means we manually issued xhr // requests, which we need to abort when we leave the page domWindow.addEventListener('unload', function unload(e) { - self.networkManager.abortAllRequests(); domWindow.removeEventListener(e.type, unload); + self.abortLoading(); }); } @@ -691,7 +691,7 @@ var RangedChromeActions = (function RangedChromeActionsClosure() { }, '*'); }; this.dataListener.oncomplete = function () { - delete self.dataListener; + self.dataListener = null; }; } @@ -735,6 +735,15 @@ var RangedChromeActions = (function RangedChromeActionsClosure() { }); }; + proto.abortLoading = function RangedChromeActions_abortLoading() { + this.networkManager.abortAllRequests(); + if (this.originalRequest) { + this.originalRequest.cancel(Cr.NS_BINDING_ABORTED); + this.originalRequest = null; + } + this.dataListener = null; + }; + return RangedChromeActions; })(); @@ -744,9 +753,10 @@ var StandardChromeActions = (function StandardChromeActionsClosure() { * This is for a single network stream */ function StandardChromeActions(domWindow, contentDispositionFilename, - dataListener) { + originalRequest, dataListener) { ChromeActions.call(this, domWindow, contentDispositionFilename); + this.originalRequest = originalRequest; this.dataListener = dataListener; } @@ -772,20 +782,29 @@ var StandardChromeActions = (function StandardChromeActionsClosure() { }, '*'); }; - this.dataListener.oncomplete = function ChromeActions_dataListenerComplete( - data, errorCode) { + this.dataListener.oncomplete = + function StandardChromeActions_dataListenerComplete(data, errorCode) { self.domWindow.postMessage({ pdfjsLoadAction: 'complete', data: data, errorCode: errorCode }, '*'); - delete self.dataListener; + self.dataListener = null; + self.originalRequest = null; }; return true; }; + proto.abortLoading = function StandardChromeActions_abortLoading() { + if (this.originalRequest) { + this.originalRequest.cancel(Cr.NS_BINDING_ABORTED); + this.originalRequest = null; + } + this.dataListener = null; + }; + return StandardChromeActions; })(); @@ -1029,7 +1048,7 @@ PdfStreamConverter.prototype = { rangeRequest, streamRequest, dataListener); } else { actions = new StandardChromeActions( - domWindow, contentDispositionFilename, dataListener); + domWindow, contentDispositionFilename, aRequest, dataListener); } var requestListener = new RequestListener(actions); domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { diff --git a/src/core/chunked_stream.js b/src/core/chunked_stream.js index b0a7b46fe..de20ba101 100644 --- a/src/core/chunked_stream.js +++ b/src/core/chunked_stream.js @@ -301,7 +301,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { this.chunksNeededByRequest = {}; this.requestsByChunk = {}; - this.callbacksByRequest = {}; + this.promisesByRequest = {}; this.progressiveDataLength = 0; this._loadedStreamCapability = createPromiseCapability(); @@ -320,12 +320,11 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { // contiguous ranges to load in as few requests as possible requestAllChunks: function ChunkedStreamManager_requestAllChunks() { var missingChunks = this.stream.getMissingChunks(); - this.requestChunks(missingChunks); + this._requestChunks(missingChunks); return this._loadedStreamCapability.promise; }, - requestChunks: function ChunkedStreamManager_requestChunks(chunks, - callback) { + _requestChunks: function ChunkedStreamManager_requestChunks(chunks) { var requestId = this.currRequestId++; var chunksNeeded; @@ -338,13 +337,11 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { } if (isEmptyObj(chunksNeeded)) { - if (callback) { - callback(); - } - return; + return Promise.resolve(); } - this.callbacksByRequest[requestId] = callback; + var capability = createPromiseCapability(); + this.promisesByRequest[requestId] = capability; var chunksToRequest = []; for (var chunk in chunksNeeded) { @@ -357,7 +354,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { } if (!chunksToRequest.length) { - return; + return capability.promise; } var groupedChunksToRequest = this.groupChunks(chunksToRequest); @@ -368,6 +365,8 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length); this.sendRequest(begin, end); } + + return capability.promise; }, getStream: function ChunkedStreamManager_getStream() { @@ -375,8 +374,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { }, // Loads any chunks in the requested range that are not yet loaded - requestRange: function ChunkedStreamManager_requestRange( - begin, end, callback) { + requestRange: function ChunkedStreamManager_requestRange(begin, end) { end = Math.min(end, this.length); @@ -388,11 +386,10 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { chunks.push(chunk); } - this.requestChunks(chunks, callback); + return this._requestChunks(chunks); }, - requestRanges: function ChunkedStreamManager_requestRanges(ranges, - callback) { + requestRanges: function ChunkedStreamManager_requestRanges(ranges) { ranges = ranges || []; var chunksToRequest = []; @@ -407,7 +404,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { } chunksToRequest.sort(function(a, b) { return a - b; }); - this.requestChunks(chunksToRequest, callback); + return this._requestChunks(chunksToRequest); }, // Groups a sorted array of chunks into as few contiguous larger @@ -506,17 +503,15 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { nextEmptyChunk = this.stream.nextEmptyChunk(endChunk); } if (isInt(nextEmptyChunk)) { - this.requestChunks([nextEmptyChunk]); + this._requestChunks([nextEmptyChunk]); } } for (i = 0; i < loadedRequests.length; ++i) { requestId = loadedRequests[i]; - var callback = this.callbacksByRequest[requestId]; - delete this.callbacksByRequest[requestId]; - if (callback) { - callback(); - } + var capability = this.promisesByRequest[requestId]; + delete this.promisesByRequest[requestId]; + capability.resolve(); } this.msgHandler.send('DocProgress', { @@ -537,6 +532,16 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() { getEndChunk: function ChunkedStreamManager_getEndChunk(end) { var chunk = Math.floor((end - 1) / this.chunkSize) + 1; return chunk; + }, + + abort: function ChunkedStreamManager_abort() { + if (this.networkManager) { + this.networkManager.abortAllRequests(); + } + for(var requestId in this.promisesByRequest) { + var capability = this.promisesByRequest[requestId]; + capability.reject(new Error('Request was aborted')); + } } }; diff --git a/src/core/obj.js b/src/core/obj.js index fe5135fc5..ef8766e46 100644 --- a/src/core/obj.js +++ b/src/core/obj.js @@ -1378,9 +1378,9 @@ var XRef = (function XRefClosure() { resolve(xref.fetch(ref, suppressEncryption)); } catch (e) { if (e instanceof MissingDataException) { - streamManager.requestRange(e.begin, e.end, function () { + streamManager.requestRange(e.begin, e.end).then(function () { tryFetch(resolve, reject); - }); + }, reject); return; } reject(e); @@ -1656,6 +1656,7 @@ var ObjectLoader = (function() { this.keys = keys; this.xref = xref; this.refSet = null; + this.capability = null; } ObjectLoader.prototype = { @@ -1676,11 +1677,11 @@ var ObjectLoader = (function() { nodesToVisit.push(this.obj[keys[i]]); } - this.walk(nodesToVisit); + this._walk(nodesToVisit); return this.capability.promise; }, - walk: function ObjectLoader_walk(nodesToVisit) { + _walk: function ObjectLoader_walk(nodesToVisit) { var nodesToRevisit = []; var pendingRequests = []; // DFS walk of the object graph. @@ -1727,7 +1728,7 @@ var ObjectLoader = (function() { } if (pendingRequests.length) { - this.xref.stream.manager.requestRanges(pendingRequests, + this.xref.stream.manager.requestRanges(pendingRequests).then( function pendingRequestCallback() { nodesToVisit = nodesToRevisit; for (var i = 0; i < nodesToRevisit.length; i++) { @@ -1738,8 +1739,8 @@ var ObjectLoader = (function() { this.refSet.remove(node); } } - this.walk(nodesToVisit); - }.bind(this)); + this._walk(nodesToVisit); + }.bind(this), this.capability.reject); return; } // Everything is loaded. diff --git a/src/core/pdf_manager.js b/src/core/pdf_manager.js index 3101e4edf..b264fdef9 100644 --- a/src/core/pdf_manager.js +++ b/src/core/pdf_manager.js @@ -183,7 +183,8 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { reject(e); return; } - pdfManager.streamManager.requestRange(e.begin, e.end, ensureHelper); + pdfManager.streamManager.requestRange(e.begin, e.end). + then(ensureHelper, reject); } } @@ -193,11 +194,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { NetworkPdfManager.prototype.requestRange = function NetworkPdfManager_requestRange(begin, end) { - return new Promise(function (resolve) { - this.streamManager.requestRange(begin, end, function() { - resolve(); - }); - }.bind(this)); + return this.streamManager.requestRange(begin, end); }; NetworkPdfManager.prototype.requestLoadedStream = @@ -217,7 +214,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() { NetworkPdfManager.prototype.terminate = function NetworkPdfManager_terminate() { - this.streamManager.networkManager.abortAllRequests(); + this.streamManager.abort(); }; return NetworkPdfManager; diff --git a/src/core/worker.js b/src/core/worker.js index 40d7d899e..9fc3ea440 100644 --- a/src/core/worker.js +++ b/src/core/worker.js @@ -25,6 +25,14 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { setup: function wphSetup(handler) { var pdfManager; + var terminated = false; + var cancelXHRs = null; + + function ensureNotTerminated() { + if (terminated) { + throw new Error('Worker was terminated'); + } + } function loadDocument(recoveryMode) { var loadDocumentCapability = createPromiseCapability(); @@ -61,13 +69,14 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { function getPdfManager(data) { var pdfManagerCapability = createPromiseCapability(); + var pdfManager; var source = data.source; var disableRange = data.disableRange; if (source.data) { try { pdfManager = new LocalPdfManager(source.data, source.password); - pdfManagerCapability.resolve(); + pdfManagerCapability.resolve(pdfManager); } catch (ex) { pdfManagerCapability.reject(ex); } @@ -75,7 +84,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { } else if (source.chunkedViewerLoading) { try { pdfManager = new NetworkPdfManager(source, handler); - pdfManagerCapability.resolve(); + pdfManagerCapability.resolve(pdfManager); } catch (ex) { pdfManagerCapability.reject(ex); } @@ -136,6 +145,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { } catch (ex) { pdfManagerCapability.reject(ex); } + cancelXHRs = null; }, onProgressiveData: source.disableStream ? null : @@ -176,10 +186,11 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { // the data is array, instantiating directly from it try { pdfManager = new LocalPdfManager(pdfFile, source.password); - pdfManagerCapability.resolve(); + pdfManagerCapability.resolve(pdfManager); } catch (ex) { pdfManagerCapability.reject(ex); } + cancelXHRs = null; }, onError: function onError(status) { @@ -194,6 +205,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { ') while retrieving PDF "' + source.url + '".', status); handler.send('UnexpectedResponse', exception); } + cancelXHRs = null; }, onProgress: function onProgress(evt) { @@ -204,6 +216,10 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { } }); + cancelXHRs = function () { + networkManager.abortRequest(fullRequestXhrId); + }; + return pdfManagerCapability.promise; } @@ -236,8 +252,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }); handler.on('GetDocRequest', function wphSetupDoc(data) { - var onSuccess = function(doc) { + ensureNotTerminated(); handler.send('GetDoc', { pdfInfo: doc }); }; @@ -260,6 +276,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { } }; + ensureNotTerminated(); + PDFJS.maxImageSize = data.maxImageSize === undefined ? -1 : data.maxImageSize; PDFJS.disableFontFace = data.disableFontFace; @@ -269,13 +287,26 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { null : data.cMapUrl; PDFJS.cMapPacked = data.cMapPacked === true; - getPdfManager(data).then(function () { + getPdfManager(data).then(function (newPdfManager) { + if (terminated) { + // We were in a process of setting up the manager, but it got + // terminated in the middle. + 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 }); }); }).then(function pdfManagerReady() { + ensureNotTerminated(); + loadDocument(false).then(onSuccess, function loadFailure(ex) { + ensureNotTerminated(); + // Try again with recoveryMode == true if (!(ex instanceof XRefParseException)) { if (ex instanceof PasswordException) { @@ -290,6 +321,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { pdfManager.requestLoadedStream(); pdfManager.onLoadedStream().then(function() { + ensureNotTerminated(); + loadDocument(true).then(onSuccess, onFailure); }); }, onFailure); @@ -441,7 +474,14 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { }); handler.on('Terminate', function wphTerminate(data) { - pdfManager.terminate(); + terminated = true; + if (pdfManager) { + pdfManager.terminate(); + pdfManager = null; + } + if (cancelXHRs) { + cancelXHRs(); + } }); } }; diff --git a/src/display/api.js b/src/display/api.js index c89c931a8..08b02caf1 100644 --- a/src/display/api.js +++ b/src/display/api.js @@ -265,6 +265,9 @@ PDFJS.getDocument = function getDocument(src, pdfDataRangeTransport = Object.create(pdfDataRangeTransport); pdfDataRangeTransport.length = src.length; pdfDataRangeTransport.initialData = src.initialData; + if (!pdfDataRangeTransport.abort) { + pdfDataRangeTransport.abort = function () {}; + } } src = Object.create(src); src.range = pdfDataRangeTransport; @@ -324,6 +327,7 @@ PDFJS.getDocument = function getDocument(src, workerInitializedCapability.promise.then(function transportInitialized() { transport.fetchDocument(task, params); }); + task._transport = transport; return task; }; @@ -336,6 +340,7 @@ var PDFDocumentLoadingTask = (function PDFDocumentLoadingTaskClosure() { /** @constructs PDFDocumentLoadingTask */ function PDFDocumentLoadingTask() { this._capability = createPromiseCapability(); + this._transport = null; /** * Callback to request a password if wrong or no password was provided. @@ -361,7 +366,14 @@ var PDFDocumentLoadingTask = (function PDFDocumentLoadingTaskClosure() { return this._capability.promise; }, - // TODO add cancel or abort method + /** + * Aborts all network requests and destroys worker. + * @return {Promise} A promise that is resolved after destruction activity + * is completed. + */ + destroy: function () { + return this._transport.destroy(); + }, /** * Registers callbacks to indicate the document loading completion. @@ -448,6 +460,9 @@ var PDFDataRangeTransport = (function pdfDataRangeTransportClosure() { requestDataRange: function PDFDataRangeTransport_requestDataRange(begin, end) { throw new Error('Abstract method PDFDataRangeTransport.requestDataRange'); + }, + + abort: function PDFDataRangeTransport_abort() { } }; return PDFDataRangeTransport; @@ -461,9 +476,10 @@ PDFJS.PDFDataRangeTransport = PDFDataRangeTransport; * @class */ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { - function PDFDocumentProxy(pdfInfo, transport) { + function PDFDocumentProxy(pdfInfo, transport, loadingTask) { this.pdfInfo = pdfInfo; this.transport = transport; + this.loadingTask = loadingTask; } PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ { /** @@ -586,7 +602,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() { * Destroys current document instance and terminates worker. */ destroy: function PDFDocumentProxy_destroy() { - this.transport.destroy(); + return this.transport.destroy(); } }; return PDFDocumentProxy; @@ -663,8 +679,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { this.commonObjs = transport.commonObjs; this.objs = new PDFObjects(); this.cleanupAfterRender = false; - this.pendingDestroy = false; + this.pendingCleanup = false; this.intentStates = {}; + this.destroyed = false; } PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ { /** @@ -728,7 +745,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { // If there was a pending destroy cancel it so no cleanup happens during // this call to render. - this.pendingDestroy = false; + this.pendingCleanup = false; var renderingIntent = (params.intent === 'print' ? 'print' : 'display'); @@ -775,7 +792,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { var self = this; intentState.displayReadyCapability.promise.then( function pageDisplayReadyPromise(transparency) { - if (self.pendingDestroy) { + if (self.pendingCleanup) { complete(); return; } @@ -795,9 +812,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() { } if (self.cleanupAfterRender) { - self.pendingDestroy = true; + self.pendingCleanup = true; } - self._tryDestroy(); + self._tryCleanup(); if (error) { internalRenderTask.capability.reject(error); @@ -858,20 +875,47 @@ var PDFPageProxy = (function PDFPageProxyClosure() { pageIndex: this.pageNumber - 1 }); }, + /** - * Destroys resources allocated by the page. + * Destroys page object. */ - destroy: function PDFPageProxy_destroy() { - this.pendingDestroy = true; - this._tryDestroy(); + _destroy: function PDFPageProxy_destroy() { + this.destroyed = true; + this.transport.pageCache[this.pageIndex] = null; + + Object.keys(this.intentStates).forEach(function(intent) { + var intentState = this.intentStates[intent]; + intentState.renderTasks.forEach(function(renderTask) { + renderTask.cancel(); + }); + }, this); + this.objs.clear(); + this.annotationsPromise = null; + this.pendingCleanup = false; + }, + + /** + * Cleans up resources allocated by the page. + * Deprecated, use cleanup() instead. + */ + destroy: function() { + this.cleanup(); + }, + + /** + * Cleans up resources allocated by the page. + */ + cleanup: function PDFPageProxy_cleanup() { + this.pendingCleanup = true; + this._tryCleanup(); }, /** * For internal use only. Attempts to clean up if rendering is in a state * where that's possible. * @ignore */ - _tryDestroy: function PDFPageProxy__destroy() { - if (!this.pendingDestroy || + _tryCleanup: function PDFPageProxy_tryCleanup() { + if (!this.pendingCleanup || Object.keys(this.intentStates).some(function(intent) { var intentState = this.intentStates[intent]; return (intentState.renderTasks.length !== 0 || @@ -885,7 +929,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { }, this); this.objs.clear(); this.annotationsPromise = null; - this.pendingDestroy = false; + this.pendingCleanup = false; }, /** * For internal use only. @@ -923,7 +967,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() { if (operatorListChunk.lastChunk) { intentState.receivingOperatorList = false; - this._tryDestroy(); + this._tryCleanup(); } } }; @@ -941,6 +985,8 @@ var WorkerTransport = (function WorkerTransportClosure() { this.commonObjs = new PDFObjects(); this.loadingTask = null; + this.destroyed = false; + this.destroyCapability = null; this.pageCache = []; this.pagePromises = []; @@ -1001,6 +1047,18 @@ var WorkerTransport = (function WorkerTransportClosure() { } WorkerTransport.prototype = { destroy: function WorkerTransport_destroy() { + if (this.destroyCapability) { + return this.destroyCapability.promise; + } + + this.destroyed = true; + this.destroyCapability = createPromiseCapability(); + + this.pageCache.forEach(function (page) { + if (page) { + page._destroy(); + } + }); this.pageCache = []; this.pagePromises = []; var self = this; @@ -1009,7 +1067,14 @@ var WorkerTransport = (function WorkerTransportClosure() { if (self.worker) { self.worker.terminate(); } - }); + if (self.pdfDataRangeTransport) { + self.pdfDataRangeTransport.abort(); + self.pdfDataRangeTransport = null; + } + self.messageHandler = null; + self.destroyCapability.resolve(); + }, this.destroyCapability.reject); + return this.destroyCapability.promise; }, setupFakeWorker: function WorkerTransport_setupFakeWorker() { @@ -1091,9 +1156,10 @@ var WorkerTransport = (function WorkerTransportClosure() { messageHandler.on('GetDoc', function transportDoc(data) { var pdfInfo = data.pdfInfo; this.numPages = data.pdfInfo.numPages; - var pdfDocument = new PDFDocumentProxy(pdfInfo, this); + var loadingTask = this.loadingTask; + var pdfDocument = new PDFDocumentProxy(pdfInfo, this, loadingTask); this.pdfDocument = pdfDocument; - this.loadingTask._capability.resolve(pdfDocument); + loadingTask._capability.resolve(pdfDocument); }, this); messageHandler.on('NeedPassword', @@ -1294,6 +1360,12 @@ var WorkerTransport = (function WorkerTransportClosure() { }, fetchDocument: function WorkerTransport_fetchDocument(loadingTask, source) { + if (this.destroyed) { + loadingTask._capability.reject(new Error('Loading aborted')); + this.destroyCapability.resolve(); + return; + } + this.loadingTask = loadingTask; source.disableAutoFetch = PDFJS.disableAutoFetch; @@ -1332,6 +1404,9 @@ var WorkerTransport = (function WorkerTransportClosure() { var promise = this.messageHandler.sendWithPromise('GetPage', { pageIndex: pageIndex }).then(function (pageInfo) { + if (this.destroyed) { + throw new Error('Transport destroyed'); + } var page = new PDFPageProxy(pageIndex, pageInfo, this); this.pageCache[pageIndex] = page; return page; @@ -1389,7 +1464,7 @@ var WorkerTransport = (function WorkerTransportClosure() { for (var i = 0, ii = this.pageCache.length; i < ii; i++) { var page = this.pageCache[i]; if (page) { - page.destroy(); + page.cleanup(); } } this.commonObjs.clear(); diff --git a/test/unit/api_spec.js b/test/unit/api_spec.js index b183b9793..36f97ab4e 100644 --- a/test/unit/api_spec.js +++ b/test/unit/api_spec.js @@ -11,31 +11,31 @@ describe('api', function() { var basicApiUrl = combineUrl(window.location.href, '../pdfs/basicapi.pdf'); var basicApiFileLength = 105779; // bytes function waitsForPromiseResolved(promise, successCallback) { - var data; + var resolved = false; promise.then(function(val) { - data = val; - successCallback(data); + resolved = true; + successCallback(val); }, function(error) { // Shouldn't get here. expect(false).toEqual(true); }); waitsFor(function() { - return data !== undefined; + return resolved; }, 20000); } function waitsForPromiseRejected(promise, failureCallback) { - var data; + var rejected = false; promise.then(function(val) { // Shouldn't get here. expect(false).toEqual(true); }, function(error) { - data = error; - failureCallback(data); + rejected = true; + failureCallback(error); }); waitsFor(function() { - return data !== undefined; + return rejected; }, 20000); } @@ -63,6 +63,28 @@ describe('api', function() { waitsForPromiseResolved(Promise.all(promises), function (data) { expect((data[0].loaded / data[0].total) > 0).toEqual(true); expect(data[1] instanceof PDFDocumentProxy).toEqual(true); + expect(loadingTask).toEqual(data[1].loadingTask); + }); + }); + it('creates pdf doc from URL and aborts before worker initialized', + function() { + var loadingTask = PDFJS.getDocument(basicApiUrl); + loadingTask.destroy(); + waitsForPromiseRejected(loadingTask.promise, function(reason) { + expect(true).toEqual(true); + }); + }); + it('creates pdf doc from URL and aborts loading after worker initialized', + function() { + var loadingTask = PDFJS.getDocument(basicApiUrl); + // This can be somewhat random -- we cannot guarantee perfect + // 'Terminate' message to the worker before/after setting up pdfManager. + var destroyed = loadingTask._transport.workerInitializedCapability. + promise.then(function () { + return loadingTask.destroy(); + }); + waitsForPromiseResolved(destroyed, function (data) { + expect(true).toEqual(true); }); }); it('creates pdf doc from typed array', function() { diff --git a/test/unit/unit_test.html b/test/unit/unit_test.html index 9193dbcd7..5fe1f254b 100644 --- a/test/unit/unit_test.html +++ b/test/unit/unit_test.html @@ -35,6 +35,7 @@ + diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js index bec6d1d95..d3f2d9ace 100644 --- a/web/pdf_page_view.js +++ b/web/pdf_page_view.js @@ -101,7 +101,7 @@ var PDFPageView = (function PDFPageViewClosure() { this.zoomLayer = null; this.reset(); if (this.pdfPage) { - this.pdfPage.destroy(); + this.pdfPage.cleanup(); } }, diff --git a/web/viewer.js b/web/viewer.js index 4b25ffe3c..bb53afa2e 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -410,6 +410,11 @@ var PDFViewerApplication = { function FirefoxComDataRangeTransport_requestDataRange(begin, end) { FirefoxCom.request('requestDataRange', { begin: begin, end: end }); }; + FirefoxComDataRangeTransport.prototype.abort = + function FirefoxComDataRangeTransport_abort() { + // Sync call to ensure abort is really started. + FirefoxCom.requestSync('abortLoading', null); + }; var pdfDataRangeTransport;