Browse Source

Adds destroy method to the document loading task.

Also renames PDFPageProxy.destroy method to cleanup.
Yury Delendik 10 years ago
parent
commit
59c13b32aa
  1. 33
      extensions/firefox/content/PdfStreamConverter.jsm
  2. 49
      src/core/chunked_stream.js
  3. 15
      src/core/obj.js
  4. 11
      src/core/pdf_manager.js
  5. 52
      src/core/worker.js
  6. 115
      src/display/api.js
  7. 38
      test/unit/api_spec.js
  8. 1
      test/unit/unit_test.html
  9. 2
      web/pdf_page_view.js
  10. 5
      web/viewer.js

33
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 // If we are in range request mode, this means we manually issued xhr
// requests, which we need to abort when we leave the page // requests, which we need to abort when we leave the page
domWindow.addEventListener('unload', function unload(e) { domWindow.addEventListener('unload', function unload(e) {
self.networkManager.abortAllRequests();
domWindow.removeEventListener(e.type, unload); domWindow.removeEventListener(e.type, unload);
self.abortLoading();
}); });
} }
@ -691,7 +691,7 @@ var RangedChromeActions = (function RangedChromeActionsClosure() {
}, '*'); }, '*');
}; };
this.dataListener.oncomplete = function () { 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; return RangedChromeActions;
})(); })();
@ -744,9 +753,10 @@ var StandardChromeActions = (function StandardChromeActionsClosure() {
* This is for a single network stream * This is for a single network stream
*/ */
function StandardChromeActions(domWindow, contentDispositionFilename, function StandardChromeActions(domWindow, contentDispositionFilename,
dataListener) { originalRequest, dataListener) {
ChromeActions.call(this, domWindow, contentDispositionFilename); ChromeActions.call(this, domWindow, contentDispositionFilename);
this.originalRequest = originalRequest;
this.dataListener = dataListener; this.dataListener = dataListener;
} }
@ -772,20 +782,29 @@ var StandardChromeActions = (function StandardChromeActionsClosure() {
}, '*'); }, '*');
}; };
this.dataListener.oncomplete = function ChromeActions_dataListenerComplete( this.dataListener.oncomplete =
data, errorCode) { function StandardChromeActions_dataListenerComplete(data, errorCode) {
self.domWindow.postMessage({ self.domWindow.postMessage({
pdfjsLoadAction: 'complete', pdfjsLoadAction: 'complete',
data: data, data: data,
errorCode: errorCode errorCode: errorCode
}, '*'); }, '*');
delete self.dataListener; self.dataListener = null;
self.originalRequest = null;
}; };
return true; 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; return StandardChromeActions;
})(); })();
@ -1029,7 +1048,7 @@ PdfStreamConverter.prototype = {
rangeRequest, streamRequest, dataListener); rangeRequest, streamRequest, dataListener);
} else { } else {
actions = new StandardChromeActions( actions = new StandardChromeActions(
domWindow, contentDispositionFilename, dataListener); domWindow, contentDispositionFilename, aRequest, dataListener);
} }
var requestListener = new RequestListener(actions); var requestListener = new RequestListener(actions);
domWindow.addEventListener(PDFJS_EVENT_ID, function(event) { domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {

49
src/core/chunked_stream.js

@ -301,7 +301,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
this.chunksNeededByRequest = {}; this.chunksNeededByRequest = {};
this.requestsByChunk = {}; this.requestsByChunk = {};
this.callbacksByRequest = {}; this.promisesByRequest = {};
this.progressiveDataLength = 0; this.progressiveDataLength = 0;
this._loadedStreamCapability = createPromiseCapability(); this._loadedStreamCapability = createPromiseCapability();
@ -320,12 +320,11 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
// contiguous ranges to load in as few requests as possible // contiguous ranges to load in as few requests as possible
requestAllChunks: function ChunkedStreamManager_requestAllChunks() { requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
var missingChunks = this.stream.getMissingChunks(); var missingChunks = this.stream.getMissingChunks();
this.requestChunks(missingChunks); this._requestChunks(missingChunks);
return this._loadedStreamCapability.promise; return this._loadedStreamCapability.promise;
}, },
requestChunks: function ChunkedStreamManager_requestChunks(chunks, _requestChunks: function ChunkedStreamManager_requestChunks(chunks) {
callback) {
var requestId = this.currRequestId++; var requestId = this.currRequestId++;
var chunksNeeded; var chunksNeeded;
@ -338,13 +337,11 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
} }
if (isEmptyObj(chunksNeeded)) { if (isEmptyObj(chunksNeeded)) {
if (callback) { return Promise.resolve();
callback();
}
return;
} }
this.callbacksByRequest[requestId] = callback; var capability = createPromiseCapability();
this.promisesByRequest[requestId] = capability;
var chunksToRequest = []; var chunksToRequest = [];
for (var chunk in chunksNeeded) { for (var chunk in chunksNeeded) {
@ -357,7 +354,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
} }
if (!chunksToRequest.length) { if (!chunksToRequest.length) {
return; return capability.promise;
} }
var groupedChunksToRequest = this.groupChunks(chunksToRequest); var groupedChunksToRequest = this.groupChunks(chunksToRequest);
@ -368,6 +365,8 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length); var end = Math.min(groupedChunk.endChunk * this.chunkSize, this.length);
this.sendRequest(begin, end); this.sendRequest(begin, end);
} }
return capability.promise;
}, },
getStream: function ChunkedStreamManager_getStream() { getStream: function ChunkedStreamManager_getStream() {
@ -375,8 +374,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
}, },
// Loads any chunks in the requested range that are not yet loaded // Loads any chunks in the requested range that are not yet loaded
requestRange: function ChunkedStreamManager_requestRange( requestRange: function ChunkedStreamManager_requestRange(begin, end) {
begin, end, callback) {
end = Math.min(end, this.length); end = Math.min(end, this.length);
@ -388,11 +386,10 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
chunks.push(chunk); chunks.push(chunk);
} }
this.requestChunks(chunks, callback); return this._requestChunks(chunks);
}, },
requestRanges: function ChunkedStreamManager_requestRanges(ranges, requestRanges: function ChunkedStreamManager_requestRanges(ranges) {
callback) {
ranges = ranges || []; ranges = ranges || [];
var chunksToRequest = []; var chunksToRequest = [];
@ -407,7 +404,7 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
} }
chunksToRequest.sort(function(a, b) { return a - b; }); 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 // Groups a sorted array of chunks into as few contiguous larger
@ -506,17 +503,15 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
nextEmptyChunk = this.stream.nextEmptyChunk(endChunk); nextEmptyChunk = this.stream.nextEmptyChunk(endChunk);
} }
if (isInt(nextEmptyChunk)) { if (isInt(nextEmptyChunk)) {
this.requestChunks([nextEmptyChunk]); this._requestChunks([nextEmptyChunk]);
} }
} }
for (i = 0; i < loadedRequests.length; ++i) { for (i = 0; i < loadedRequests.length; ++i) {
requestId = loadedRequests[i]; requestId = loadedRequests[i];
var callback = this.callbacksByRequest[requestId]; var capability = this.promisesByRequest[requestId];
delete this.callbacksByRequest[requestId]; delete this.promisesByRequest[requestId];
if (callback) { capability.resolve();
callback();
}
} }
this.msgHandler.send('DocProgress', { this.msgHandler.send('DocProgress', {
@ -537,6 +532,16 @@ var ChunkedStreamManager = (function ChunkedStreamManagerClosure() {
getEndChunk: function ChunkedStreamManager_getEndChunk(end) { getEndChunk: function ChunkedStreamManager_getEndChunk(end) {
var chunk = Math.floor((end - 1) / this.chunkSize) + 1; var chunk = Math.floor((end - 1) / this.chunkSize) + 1;
return chunk; 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'));
}
} }
}; };

15
src/core/obj.js

@ -1378,9 +1378,9 @@ var XRef = (function XRefClosure() {
resolve(xref.fetch(ref, suppressEncryption)); resolve(xref.fetch(ref, suppressEncryption));
} catch (e) { } catch (e) {
if (e instanceof MissingDataException) { if (e instanceof MissingDataException) {
streamManager.requestRange(e.begin, e.end, function () { streamManager.requestRange(e.begin, e.end).then(function () {
tryFetch(resolve, reject); tryFetch(resolve, reject);
}); }, reject);
return; return;
} }
reject(e); reject(e);
@ -1656,6 +1656,7 @@ var ObjectLoader = (function() {
this.keys = keys; this.keys = keys;
this.xref = xref; this.xref = xref;
this.refSet = null; this.refSet = null;
this.capability = null;
} }
ObjectLoader.prototype = { ObjectLoader.prototype = {
@ -1676,11 +1677,11 @@ var ObjectLoader = (function() {
nodesToVisit.push(this.obj[keys[i]]); nodesToVisit.push(this.obj[keys[i]]);
} }
this.walk(nodesToVisit); this._walk(nodesToVisit);
return this.capability.promise; return this.capability.promise;
}, },
walk: function ObjectLoader_walk(nodesToVisit) { _walk: function ObjectLoader_walk(nodesToVisit) {
var nodesToRevisit = []; var nodesToRevisit = [];
var pendingRequests = []; var pendingRequests = [];
// DFS walk of the object graph. // DFS walk of the object graph.
@ -1727,7 +1728,7 @@ var ObjectLoader = (function() {
} }
if (pendingRequests.length) { if (pendingRequests.length) {
this.xref.stream.manager.requestRanges(pendingRequests, this.xref.stream.manager.requestRanges(pendingRequests).then(
function pendingRequestCallback() { function pendingRequestCallback() {
nodesToVisit = nodesToRevisit; nodesToVisit = nodesToRevisit;
for (var i = 0; i < nodesToRevisit.length; i++) { for (var i = 0; i < nodesToRevisit.length; i++) {
@ -1738,8 +1739,8 @@ var ObjectLoader = (function() {
this.refSet.remove(node); this.refSet.remove(node);
} }
} }
this.walk(nodesToVisit); this._walk(nodesToVisit);
}.bind(this)); }.bind(this), this.capability.reject);
return; return;
} }
// Everything is loaded. // Everything is loaded.

11
src/core/pdf_manager.js

@ -183,7 +183,8 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() {
reject(e); reject(e);
return; 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 = NetworkPdfManager.prototype.requestRange =
function NetworkPdfManager_requestRange(begin, end) { function NetworkPdfManager_requestRange(begin, end) {
return new Promise(function (resolve) { return this.streamManager.requestRange(begin, end);
this.streamManager.requestRange(begin, end, function() {
resolve();
});
}.bind(this));
}; };
NetworkPdfManager.prototype.requestLoadedStream = NetworkPdfManager.prototype.requestLoadedStream =
@ -217,7 +214,7 @@ var NetworkPdfManager = (function NetworkPdfManagerClosure() {
NetworkPdfManager.prototype.terminate = NetworkPdfManager.prototype.terminate =
function NetworkPdfManager_terminate() { function NetworkPdfManager_terminate() {
this.streamManager.networkManager.abortAllRequests(); this.streamManager.abort();
}; };
return NetworkPdfManager; return NetworkPdfManager;

52
src/core/worker.js

@ -25,6 +25,14 @@
var WorkerMessageHandler = PDFJS.WorkerMessageHandler = { var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
setup: function wphSetup(handler) { setup: function wphSetup(handler) {
var pdfManager; var pdfManager;
var terminated = false;
var cancelXHRs = null;
function ensureNotTerminated() {
if (terminated) {
throw new Error('Worker was terminated');
}
}
function loadDocument(recoveryMode) { function loadDocument(recoveryMode) {
var loadDocumentCapability = createPromiseCapability(); var loadDocumentCapability = createPromiseCapability();
@ -61,13 +69,14 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
function getPdfManager(data) { function getPdfManager(data) {
var pdfManagerCapability = createPromiseCapability(); var pdfManagerCapability = createPromiseCapability();
var pdfManager;
var source = data.source; var source = data.source;
var disableRange = data.disableRange; var disableRange = data.disableRange;
if (source.data) { if (source.data) {
try { try {
pdfManager = new LocalPdfManager(source.data, source.password); pdfManager = new LocalPdfManager(source.data, source.password);
pdfManagerCapability.resolve(); pdfManagerCapability.resolve(pdfManager);
} catch (ex) { } catch (ex) {
pdfManagerCapability.reject(ex); pdfManagerCapability.reject(ex);
} }
@ -75,7 +84,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
} else if (source.chunkedViewerLoading) { } else if (source.chunkedViewerLoading) {
try { try {
pdfManager = new NetworkPdfManager(source, handler); pdfManager = new NetworkPdfManager(source, handler);
pdfManagerCapability.resolve(); pdfManagerCapability.resolve(pdfManager);
} catch (ex) { } catch (ex) {
pdfManagerCapability.reject(ex); pdfManagerCapability.reject(ex);
} }
@ -136,6 +145,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
} catch (ex) { } catch (ex) {
pdfManagerCapability.reject(ex); pdfManagerCapability.reject(ex);
} }
cancelXHRs = null;
}, },
onProgressiveData: source.disableStream ? null : onProgressiveData: source.disableStream ? null :
@ -176,10 +186,11 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
// the data is array, instantiating directly from it // the data is array, instantiating directly from it
try { try {
pdfManager = new LocalPdfManager(pdfFile, source.password); pdfManager = new LocalPdfManager(pdfFile, source.password);
pdfManagerCapability.resolve(); pdfManagerCapability.resolve(pdfManager);
} catch (ex) { } catch (ex) {
pdfManagerCapability.reject(ex); pdfManagerCapability.reject(ex);
} }
cancelXHRs = null;
}, },
onError: function onError(status) { onError: function onError(status) {
@ -194,6 +205,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
') while retrieving PDF "' + source.url + '".', status); ') while retrieving PDF "' + source.url + '".', status);
handler.send('UnexpectedResponse', exception); handler.send('UnexpectedResponse', exception);
} }
cancelXHRs = null;
}, },
onProgress: function onProgress(evt) { onProgress: function onProgress(evt) {
@ -204,6 +216,10 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
} }
}); });
cancelXHRs = function () {
networkManager.abortRequest(fullRequestXhrId);
};
return pdfManagerCapability.promise; return pdfManagerCapability.promise;
} }
@ -236,8 +252,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}); });
handler.on('GetDocRequest', function wphSetupDoc(data) { handler.on('GetDocRequest', function wphSetupDoc(data) {
var onSuccess = function(doc) { var onSuccess = function(doc) {
ensureNotTerminated();
handler.send('GetDoc', { pdfInfo: doc }); handler.send('GetDoc', { pdfInfo: doc });
}; };
@ -260,6 +276,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
} }
}; };
ensureNotTerminated();
PDFJS.maxImageSize = data.maxImageSize === undefined ? PDFJS.maxImageSize = data.maxImageSize === undefined ?
-1 : data.maxImageSize; -1 : data.maxImageSize;
PDFJS.disableFontFace = data.disableFontFace; PDFJS.disableFontFace = data.disableFontFace;
@ -269,13 +287,26 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
null : data.cMapUrl; null : data.cMapUrl;
PDFJS.cMapPacked = data.cMapPacked === true; 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); handler.send('PDFManagerReady', null);
pdfManager.onLoadedStream().then(function(stream) { pdfManager.onLoadedStream().then(function(stream) {
handler.send('DataLoaded', { length: stream.bytes.byteLength }); handler.send('DataLoaded', { length: stream.bytes.byteLength });
}); });
}).then(function pdfManagerReady() { }).then(function pdfManagerReady() {
ensureNotTerminated();
loadDocument(false).then(onSuccess, function loadFailure(ex) { loadDocument(false).then(onSuccess, function loadFailure(ex) {
ensureNotTerminated();
// Try again with recoveryMode == true // Try again with recoveryMode == true
if (!(ex instanceof XRefParseException)) { if (!(ex instanceof XRefParseException)) {
if (ex instanceof PasswordException) { if (ex instanceof PasswordException) {
@ -290,6 +321,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
pdfManager.requestLoadedStream(); pdfManager.requestLoadedStream();
pdfManager.onLoadedStream().then(function() { pdfManager.onLoadedStream().then(function() {
ensureNotTerminated();
loadDocument(true).then(onSuccess, onFailure); loadDocument(true).then(onSuccess, onFailure);
}); });
}, onFailure); }, onFailure);
@ -441,7 +474,14 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}); });
handler.on('Terminate', function wphTerminate(data) { handler.on('Terminate', function wphTerminate(data) {
pdfManager.terminate(); terminated = true;
if (pdfManager) {
pdfManager.terminate();
pdfManager = null;
}
if (cancelXHRs) {
cancelXHRs();
}
}); });
} }
}; };

115
src/display/api.js

@ -265,6 +265,9 @@ PDFJS.getDocument = function getDocument(src,
pdfDataRangeTransport = Object.create(pdfDataRangeTransport); pdfDataRangeTransport = Object.create(pdfDataRangeTransport);
pdfDataRangeTransport.length = src.length; pdfDataRangeTransport.length = src.length;
pdfDataRangeTransport.initialData = src.initialData; pdfDataRangeTransport.initialData = src.initialData;
if (!pdfDataRangeTransport.abort) {
pdfDataRangeTransport.abort = function () {};
}
} }
src = Object.create(src); src = Object.create(src);
src.range = pdfDataRangeTransport; src.range = pdfDataRangeTransport;
@ -324,6 +327,7 @@ PDFJS.getDocument = function getDocument(src,
workerInitializedCapability.promise.then(function transportInitialized() { workerInitializedCapability.promise.then(function transportInitialized() {
transport.fetchDocument(task, params); transport.fetchDocument(task, params);
}); });
task._transport = transport;
return task; return task;
}; };
@ -336,6 +340,7 @@ var PDFDocumentLoadingTask = (function PDFDocumentLoadingTaskClosure() {
/** @constructs PDFDocumentLoadingTask */ /** @constructs PDFDocumentLoadingTask */
function PDFDocumentLoadingTask() { function PDFDocumentLoadingTask() {
this._capability = createPromiseCapability(); this._capability = createPromiseCapability();
this._transport = null;
/** /**
* Callback to request a password if wrong or no password was provided. * Callback to request a password if wrong or no password was provided.
@ -361,7 +366,14 @@ var PDFDocumentLoadingTask = (function PDFDocumentLoadingTaskClosure() {
return this._capability.promise; 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. * Registers callbacks to indicate the document loading completion.
@ -448,6 +460,9 @@ var PDFDataRangeTransport = (function pdfDataRangeTransportClosure() {
requestDataRange: requestDataRange:
function PDFDataRangeTransport_requestDataRange(begin, end) { function PDFDataRangeTransport_requestDataRange(begin, end) {
throw new Error('Abstract method PDFDataRangeTransport.requestDataRange'); throw new Error('Abstract method PDFDataRangeTransport.requestDataRange');
},
abort: function PDFDataRangeTransport_abort() {
} }
}; };
return PDFDataRangeTransport; return PDFDataRangeTransport;
@ -461,9 +476,10 @@ PDFJS.PDFDataRangeTransport = PDFDataRangeTransport;
* @class * @class
*/ */
var PDFDocumentProxy = (function PDFDocumentProxyClosure() { var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
function PDFDocumentProxy(pdfInfo, transport) { function PDFDocumentProxy(pdfInfo, transport, loadingTask) {
this.pdfInfo = pdfInfo; this.pdfInfo = pdfInfo;
this.transport = transport; this.transport = transport;
this.loadingTask = loadingTask;
} }
PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ { PDFDocumentProxy.prototype = /** @lends PDFDocumentProxy.prototype */ {
/** /**
@ -586,7 +602,7 @@ var PDFDocumentProxy = (function PDFDocumentProxyClosure() {
* Destroys current document instance and terminates worker. * Destroys current document instance and terminates worker.
*/ */
destroy: function PDFDocumentProxy_destroy() { destroy: function PDFDocumentProxy_destroy() {
this.transport.destroy(); return this.transport.destroy();
} }
}; };
return PDFDocumentProxy; return PDFDocumentProxy;
@ -663,8 +679,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.commonObjs = transport.commonObjs; this.commonObjs = transport.commonObjs;
this.objs = new PDFObjects(); this.objs = new PDFObjects();
this.cleanupAfterRender = false; this.cleanupAfterRender = false;
this.pendingDestroy = false; this.pendingCleanup = false;
this.intentStates = {}; this.intentStates = {};
this.destroyed = false;
} }
PDFPageProxy.prototype = /** @lends PDFPageProxy.prototype */ { 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 // If there was a pending destroy cancel it so no cleanup happens during
// this call to render. // this call to render.
this.pendingDestroy = false; this.pendingCleanup = false;
var renderingIntent = (params.intent === 'print' ? 'print' : 'display'); var renderingIntent = (params.intent === 'print' ? 'print' : 'display');
@ -775,7 +792,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
var self = this; var self = this;
intentState.displayReadyCapability.promise.then( intentState.displayReadyCapability.promise.then(
function pageDisplayReadyPromise(transparency) { function pageDisplayReadyPromise(transparency) {
if (self.pendingDestroy) { if (self.pendingCleanup) {
complete(); complete();
return; return;
} }
@ -795,9 +812,9 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
} }
if (self.cleanupAfterRender) { if (self.cleanupAfterRender) {
self.pendingDestroy = true; self.pendingCleanup = true;
} }
self._tryDestroy(); self._tryCleanup();
if (error) { if (error) {
internalRenderTask.capability.reject(error); internalRenderTask.capability.reject(error);
@ -858,20 +875,47 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
pageIndex: this.pageNumber - 1 pageIndex: this.pageNumber - 1
}); });
}, },
/** /**
* Destroys resources allocated by the page. * Destroys page object.
*/ */
destroy: function PDFPageProxy_destroy() { _destroy: function PDFPageProxy_destroy() {
this.pendingDestroy = true; this.destroyed = true;
this._tryDestroy(); 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 * For internal use only. Attempts to clean up if rendering is in a state
* where that's possible. * where that's possible.
* @ignore * @ignore
*/ */
_tryDestroy: function PDFPageProxy__destroy() { _tryCleanup: function PDFPageProxy_tryCleanup() {
if (!this.pendingDestroy || if (!this.pendingCleanup ||
Object.keys(this.intentStates).some(function(intent) { Object.keys(this.intentStates).some(function(intent) {
var intentState = this.intentStates[intent]; var intentState = this.intentStates[intent];
return (intentState.renderTasks.length !== 0 || return (intentState.renderTasks.length !== 0 ||
@ -885,7 +929,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
}, this); }, this);
this.objs.clear(); this.objs.clear();
this.annotationsPromise = null; this.annotationsPromise = null;
this.pendingDestroy = false; this.pendingCleanup = false;
}, },
/** /**
* For internal use only. * For internal use only.
@ -923,7 +967,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
if (operatorListChunk.lastChunk) { if (operatorListChunk.lastChunk) {
intentState.receivingOperatorList = false; intentState.receivingOperatorList = false;
this._tryDestroy(); this._tryCleanup();
} }
} }
}; };
@ -941,6 +985,8 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.commonObjs = new PDFObjects(); this.commonObjs = new PDFObjects();
this.loadingTask = null; this.loadingTask = null;
this.destroyed = false;
this.destroyCapability = null;
this.pageCache = []; this.pageCache = [];
this.pagePromises = []; this.pagePromises = [];
@ -1001,6 +1047,18 @@ var WorkerTransport = (function WorkerTransportClosure() {
} }
WorkerTransport.prototype = { WorkerTransport.prototype = {
destroy: function WorkerTransport_destroy() { 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.pageCache = [];
this.pagePromises = []; this.pagePromises = [];
var self = this; var self = this;
@ -1009,7 +1067,14 @@ var WorkerTransport = (function WorkerTransportClosure() {
if (self.worker) { if (self.worker) {
self.worker.terminate(); 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() { setupFakeWorker: function WorkerTransport_setupFakeWorker() {
@ -1091,9 +1156,10 @@ var WorkerTransport = (function WorkerTransportClosure() {
messageHandler.on('GetDoc', function transportDoc(data) { messageHandler.on('GetDoc', function transportDoc(data) {
var pdfInfo = data.pdfInfo; var pdfInfo = data.pdfInfo;
this.numPages = data.pdfInfo.numPages; 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.pdfDocument = pdfDocument;
this.loadingTask._capability.resolve(pdfDocument); loadingTask._capability.resolve(pdfDocument);
}, this); }, this);
messageHandler.on('NeedPassword', messageHandler.on('NeedPassword',
@ -1294,6 +1360,12 @@ var WorkerTransport = (function WorkerTransportClosure() {
}, },
fetchDocument: function WorkerTransport_fetchDocument(loadingTask, source) { fetchDocument: function WorkerTransport_fetchDocument(loadingTask, source) {
if (this.destroyed) {
loadingTask._capability.reject(new Error('Loading aborted'));
this.destroyCapability.resolve();
return;
}
this.loadingTask = loadingTask; this.loadingTask = loadingTask;
source.disableAutoFetch = PDFJS.disableAutoFetch; source.disableAutoFetch = PDFJS.disableAutoFetch;
@ -1332,6 +1404,9 @@ var WorkerTransport = (function WorkerTransportClosure() {
var promise = this.messageHandler.sendWithPromise('GetPage', { var promise = this.messageHandler.sendWithPromise('GetPage', {
pageIndex: pageIndex pageIndex: pageIndex
}).then(function (pageInfo) { }).then(function (pageInfo) {
if (this.destroyed) {
throw new Error('Transport destroyed');
}
var page = new PDFPageProxy(pageIndex, pageInfo, this); var page = new PDFPageProxy(pageIndex, pageInfo, this);
this.pageCache[pageIndex] = page; this.pageCache[pageIndex] = page;
return page; return page;
@ -1389,7 +1464,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
for (var i = 0, ii = this.pageCache.length; i < ii; i++) { for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
var page = this.pageCache[i]; var page = this.pageCache[i];
if (page) { if (page) {
page.destroy(); page.cleanup();
} }
} }
this.commonObjs.clear(); this.commonObjs.clear();

38
test/unit/api_spec.js

@ -11,31 +11,31 @@ describe('api', function() {
var basicApiUrl = combineUrl(window.location.href, '../pdfs/basicapi.pdf'); var basicApiUrl = combineUrl(window.location.href, '../pdfs/basicapi.pdf');
var basicApiFileLength = 105779; // bytes var basicApiFileLength = 105779; // bytes
function waitsForPromiseResolved(promise, successCallback) { function waitsForPromiseResolved(promise, successCallback) {
var data; var resolved = false;
promise.then(function(val) { promise.then(function(val) {
data = val; resolved = true;
successCallback(data); successCallback(val);
}, },
function(error) { function(error) {
// Shouldn't get here. // Shouldn't get here.
expect(false).toEqual(true); expect(false).toEqual(true);
}); });
waitsFor(function() { waitsFor(function() {
return data !== undefined; return resolved;
}, 20000); }, 20000);
} }
function waitsForPromiseRejected(promise, failureCallback) { function waitsForPromiseRejected(promise, failureCallback) {
var data; var rejected = false;
promise.then(function(val) { promise.then(function(val) {
// Shouldn't get here. // Shouldn't get here.
expect(false).toEqual(true); expect(false).toEqual(true);
}, },
function(error) { function(error) {
data = error; rejected = true;
failureCallback(data); failureCallback(error);
}); });
waitsFor(function() { waitsFor(function() {
return data !== undefined; return rejected;
}, 20000); }, 20000);
} }
@ -63,6 +63,28 @@ describe('api', function() {
waitsForPromiseResolved(Promise.all(promises), function (data) { waitsForPromiseResolved(Promise.all(promises), function (data) {
expect((data[0].loaded / data[0].total) > 0).toEqual(true); expect((data[0].loaded / data[0].total) > 0).toEqual(true);
expect(data[1] instanceof PDFDocumentProxy).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() { it('creates pdf doc from typed array', function() {

1
test/unit/unit_test.html

@ -35,6 +35,7 @@
<script src="../../src/core/parser.js"></script> <script src="../../src/core/parser.js"></script>
<script src="../../src/core/ps_parser.js"></script> <script src="../../src/core/ps_parser.js"></script>
<script src="../../src/display/pattern_helper.js"></script> <script src="../../src/display/pattern_helper.js"></script>
<script src="../../src/display/font_loader.js"></script>
<script src="../../src/display/annotation_helper.js"></script> <script src="../../src/display/annotation_helper.js"></script>
<script src="../../src/core/stream.js"></script> <script src="../../src/core/stream.js"></script>
<script src="../../src/core/worker.js"></script> <script src="../../src/core/worker.js"></script>

2
web/pdf_page_view.js

@ -101,7 +101,7 @@ var PDFPageView = (function PDFPageViewClosure() {
this.zoomLayer = null; this.zoomLayer = null;
this.reset(); this.reset();
if (this.pdfPage) { if (this.pdfPage) {
this.pdfPage.destroy(); this.pdfPage.cleanup();
} }
}, },

5
web/viewer.js

@ -410,6 +410,11 @@ var PDFViewerApplication = {
function FirefoxComDataRangeTransport_requestDataRange(begin, end) { function FirefoxComDataRangeTransport_requestDataRange(begin, end) {
FirefoxCom.request('requestDataRange', { begin: begin, end: 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; var pdfDataRangeTransport;

Loading…
Cancel
Save