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/annotation.js b/src/core/annotation.js
index 5844f2e4d..f08d22d3b 100644
--- a/src/core/annotation.js
+++ b/src/core/annotation.js
@@ -281,7 +281,7 @@ var Annotation = (function AnnotationClosure() {
}.bind(this));
},
- getOperatorList: function Annotation_getOperatorList(evaluator) {
+ getOperatorList: function Annotation_getOperatorList(evaluator, task) {
if (!this.appearance) {
return Promise.resolve(new OperatorList());
@@ -308,7 +308,8 @@ var Annotation = (function AnnotationClosure() {
return resourcesPromise.then(function(resources) {
var opList = new OperatorList();
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
- return evaluator.getOperatorList(self.appearance, resources, opList).
+ return evaluator.getOperatorList(self.appearance, task,
+ resources, opList).
then(function () {
opList.addOp(OPS.endAnnotation, []);
self.appearance.reset();
@@ -319,7 +320,7 @@ var Annotation = (function AnnotationClosure() {
};
Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
- annotations, opList, pdfManager, partialEvaluator, intent) {
+ annotations, opList, pdfManager, partialEvaluator, task, intent) {
function reject(e) {
annotationsReadyCapability.reject(e);
@@ -332,7 +333,7 @@ var Annotation = (function AnnotationClosure() {
if (intent === 'display' && annotations[i].isViewable() ||
intent === 'print' && annotations[i].isPrintable()) {
annotationPromises.push(
- annotations[i].getOperatorList(partialEvaluator));
+ annotations[i].getOperatorList(partialEvaluator, task));
}
}
Promise.all(annotationPromises).then(function(datas) {
@@ -564,9 +565,10 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
}
Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
- getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) {
+ getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator,
+ task) {
if (this.appearance) {
- return Annotation.prototype.getOperatorList.call(this, evaluator);
+ return Annotation.prototype.getOperatorList.call(this, evaluator, task);
}
var opList = new OperatorList();
@@ -579,7 +581,8 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
}
var stream = new Stream(stringToBytes(data.defaultAppearance));
- return evaluator.getOperatorList(stream, this.fieldResources, opList).
+ return evaluator.getOperatorList(stream, task,
+ this.fieldResources, opList).
then(function () {
return opList;
});
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/core.js b/src/core/core.js
index e18824877..2342e3199 100644
--- a/src/core/core.js
+++ b/src/core/core.js
@@ -161,7 +161,7 @@ var Page = (function PageClosure() {
}.bind(this));
},
- getOperatorList: function Page_getOperatorList(handler, intent) {
+ getOperatorList: function Page_getOperatorList(handler, task, intent) {
var self = this;
var pdfManager = this.pdfManager;
@@ -194,8 +194,8 @@ var Page = (function PageClosure() {
pageIndex: self.pageIndex,
intent: intent
});
- return partialEvaluator.getOperatorList(contentStream, self.resources,
- opList).then(function () {
+ return partialEvaluator.getOperatorList(contentStream, task,
+ self.resources, opList).then(function () {
return opList;
});
});
@@ -212,7 +212,7 @@ var Page = (function PageClosure() {
}
var annotationsReadyPromise = Annotation.appendToOperatorList(
- annotations, pageOpList, pdfManager, partialEvaluator, intent);
+ annotations, pageOpList, pdfManager, partialEvaluator, task, intent);
return annotationsReadyPromise.then(function () {
pageOpList.flush(true);
return pageOpList;
@@ -220,7 +220,7 @@ var Page = (function PageClosure() {
});
},
- extractTextContent: function Page_extractTextContent() {
+ extractTextContent: function Page_extractTextContent(task) {
var handler = {
on: function nullHandlerOn() {},
send: function nullHandlerSend() {}
@@ -249,6 +249,7 @@ var Page = (function PageClosure() {
self.fontCache);
return partialEvaluator.getTextContent(contentStream,
+ task,
self.resources);
});
},
diff --git a/src/core/evaluator.js b/src/core/evaluator.js
index 973f93eea..41f6571a7 100644
--- a/src/core/evaluator.js
+++ b/src/core/evaluator.js
@@ -125,6 +125,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
xobj, smask,
operatorList,
+ task,
initialState) {
var matrix = xobj.dict.getArray('Matrix');
var bbox = xobj.dict.getArray('BBox');
@@ -157,7 +158,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
- return this.getOperatorList(xobj,
+ return this.getOperatorList(xobj, task,
(xobj.dict.get('Resources') || resources), operatorList, initialState).
then(function () {
operatorList.addOp(OPS.paintFormXObjectEnd, []);
@@ -269,7 +270,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
handleSMask: function PartialEvaluator_handleSmask(smask, resources,
- operatorList,
+ operatorList, task,
stateManager) {
var smaskContent = smask.get('G');
var smaskOptions = {
@@ -277,13 +278,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
backdrop: smask.get('BC')
};
return this.buildFormXObject(resources, smaskContent, smaskOptions,
- operatorList, stateManager.state.clone());
+ operatorList, task, stateManager.state.clone());
},
handleTilingType:
function PartialEvaluator_handleTilingType(fn, args, resources,
pattern, patternDict,
- operatorList) {
+ operatorList, task) {
// Create an IR of the pattern code.
var tilingOpList = new OperatorList();
// Merge the available resources, to prevent issues when the patternDict
@@ -291,8 +292,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var resourcesArray = [patternDict.get('Resources'), resources];
var patternResources = Dict.merge(this.xref, resourcesArray);
- return this.getOperatorList(pattern, patternResources, tilingOpList).then(
- function () {
+ return this.getOperatorList(pattern, task, patternResources,
+ tilingOpList).then(function () {
// Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously.
operatorList.addDependencies(tilingOpList.dependencies);
@@ -305,7 +306,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
handleSetFont:
function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef,
- operatorList, state) {
+ operatorList, task, state) {
// TODO(mack): Not needed?
var fontName;
if (fontArgs) {
@@ -319,8 +320,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (!translated.font.isType3Font) {
return translated;
}
- return translated.loadType3Data(self, resources, operatorList).then(
- function () {
+ return translated.loadType3Data(self, resources, operatorList, task).
+ then(function () {
return translated;
});
}).then(function (translated) {
@@ -367,8 +368,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
setGState: function PartialEvaluator_setGState(resources, gState,
- operatorList, xref,
- stateManager) {
+ operatorList, task,
+ xref, stateManager) {
// This array holds the converted/processed state data.
var gStateObj = [];
var gStateMap = gState.map;
@@ -392,8 +393,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
break;
case 'Font':
promise = promise.then(function () {
- return self.handleSetFont(resources, null, value[0],
- operatorList, stateManager.state).
+ return self.handleSetFont(resources, null, value[0], operatorList,
+ task, stateManager.state).
then(function (loadedName) {
operatorList.addDependency(loadedName);
gStateObj.push([key, [loadedName, value[1]]]);
@@ -412,7 +413,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (isDict(dict)) {
promise = promise.then(function () {
return self.handleSMask(dict, resources, operatorList,
- stateManager);
+ task, stateManager);
});
gStateObj.push([key, true]);
} else {
@@ -593,7 +594,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args,
- cs, patterns, resources, xref) {
+ cs, patterns, resources, task, xref) {
// compile tiling patterns
var patternName = args[args.length - 1];
// SCN/scn applies patterns along with normal colors
@@ -606,7 +607,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (typeNum === TILING_PATTERN) {
var color = cs.base ? cs.base.getRgb(args, 0) : null;
return this.handleTilingType(fn, color, resources, pattern,
- dict, operatorList);
+ dict, operatorList, task);
} else if (typeNum === SHADING_PATTERN) {
var shading = dict.get('Shading');
var matrix = dict.get('Matrix');
@@ -623,6 +624,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
},
getOperatorList: function PartialEvaluator_getOperatorList(stream,
+ task,
resources,
operatorList,
initialState) {
@@ -641,6 +643,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) {
+ task.ensureNotTerminated();
timeSlotManager.reset();
var stop, operation = {}, i, ii, cs;
while (!(stop = timeSlotManager.check())) {
@@ -683,7 +686,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (type.name === 'Form') {
stateManager.save();
return self.buildFormXObject(resources, xobj, null,
- operatorList,
+ operatorList, task,
stateManager.state.clone()).
then(function () {
stateManager.restore();
@@ -707,8 +710,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
case OPS.setFont:
var fontSize = args[1];
// eagerly collect all fonts
- return self.handleSetFont(resources, args, null,
- operatorList, stateManager.state).
+ return self.handleSetFont(resources, args, null, operatorList,
+ task, stateManager.state).
then(function (loadedName) {
operatorList.addDependency(loadedName);
operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
@@ -814,7 +817,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs = stateManager.state.fillColorSpace;
if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setFillColorN,
- args, cs, patterns, resources, xref).then(function() {
+ args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject);
}, reject);
}
@@ -825,7 +828,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs = stateManager.state.strokeColorSpace;
if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setStrokeColorN,
- args, cs, patterns, resources, xref).then(function() {
+ args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject);
}, reject);
}
@@ -859,8 +862,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}
var gState = extGState.get(dictName.name);
- return self.setGState(resources, gState, operatorList, xref,
- stateManager).then(function() {
+ return self.setGState(resources, gState, operatorList, task,
+ xref, stateManager).then(function() {
next(resolve, reject);
}, reject);
case OPS.moveTo:
@@ -898,7 +901,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (stop) {
deferred.then(function () {
next(resolve, reject);
- });
+ }, reject);
return;
}
// Some PDFs don't close all restores inside object/form.
@@ -910,7 +913,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
});
},
- getTextContent: function PartialEvaluator_getTextContent(stream, resources,
+ getTextContent: function PartialEvaluator_getTextContent(stream, task,
+ resources,
stateManager) {
stateManager = (stateManager || new StateManager(new TextState()));
@@ -1088,6 +1092,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) {
+ task.ensureNotTerminated();
timeSlotManager.reset();
var stop, operation = {}, args = [];
while (!(stop = timeSlotManager.check())) {
@@ -1243,7 +1248,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
stateManager.transform(matrix);
}
- return self.getTextContent(xobj,
+ return self.getTextContent(xobj, task,
xobj.dict.get('Resources') || resources, stateManager).
then(function (formTextContent) {
Util.appendToArray(bidiTexts, formTextContent.items);
@@ -1283,7 +1288,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (stop) {
deferred.then(function () {
next(resolve, reject);
- });
+ }, reject);
return;
}
resolve(textContent);
@@ -1848,7 +1853,7 @@ var TranslatedFont = (function TranslatedFontClosure() {
]);
this.sent = true;
},
- loadType3Data: function (evaluator, resources, parentOperatorList) {
+ loadType3Data: function (evaluator, resources, parentOperatorList, task) {
assert(this.font.isType3Font);
if (this.type3Loaded) {
@@ -1865,7 +1870,7 @@ var TranslatedFont = (function TranslatedFontClosure() {
loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
var glyphStream = charProcs[key];
var operatorList = new OperatorList();
- return evaluator.getOperatorList(glyphStream, fontResources,
+ return evaluator.getOperatorList(glyphStream, task, fontResources,
operatorList).then(function () {
charProcOperatorList[key] = operatorList.getIR();
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..7440534e7 100644
--- a/src/core/worker.js
+++ b/src/core/worker.js
@@ -22,9 +22,58 @@
'use strict';
+var WorkerTask = (function WorkerTaskClosure() {
+ function WorkerTask(name) {
+ this.name = name;
+ this.terminated = false;
+ this._capability = createPromiseCapability();
+ }
+
+ WorkerTask.prototype = {
+ get finished() {
+ return this._capability.promise;
+ },
+
+ finish: function () {
+ this._capability.resolve();
+ },
+
+ terminate: function () {
+ this.terminated = true;
+ },
+
+ ensureNotTerminated: function () {
+ if (this.terminated) {
+ throw new Error('Worker task was terminated');
+ }
+ }
+ };
+
+ return WorkerTask;
+})();
+
var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
setup: function wphSetup(handler) {
var pdfManager;
+ var terminated = false;
+ var cancelXHRs = null;
+ var WorkerTasks = [];
+
+ function ensureNotTerminated() {
+ if (terminated) {
+ throw new Error('Worker was terminated');
+ }
+ }
+
+ function startWorkerTask(task) {
+ WorkerTasks.push(task);
+ }
+
+ function finishWorkerTask(task) {
+ task.finish();
+ var i = WorkerTasks.indexOf(task);
+ WorkerTasks.splice(i, 1);
+ }
function loadDocument(recoveryMode) {
var loadDocumentCapability = createPromiseCapability();
@@ -61,13 +110,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 +125,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 +186,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
} catch (ex) {
pdfManagerCapability.reject(ex);
}
+ cancelXHRs = null;
},
onProgressiveData: source.disableStream ? null :
@@ -176,10 +227,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 +246,7 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
') while retrieving PDF "' + source.url + '".', status);
handler.send('UnexpectedResponse', exception);
}
+ cancelXHRs = null;
},
onProgress: function onProgress(evt) {
@@ -204,6 +257,10 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}
});
+ cancelXHRs = function () {
+ networkManager.abortRequest(fullRequestXhrId);
+ };
+
return pdfManagerCapability.promise;
}
@@ -236,8 +293,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
});
handler.on('GetDocRequest', function wphSetupDoc(data) {
-
var onSuccess = function(doc) {
+ ensureNotTerminated();
handler.send('GetDoc', { pdfInfo: doc });
};
@@ -260,6 +317,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}
};
+ ensureNotTerminated();
+
PDFJS.maxImageSize = data.maxImageSize === undefined ?
-1 : data.maxImageSize;
PDFJS.disableFontFace = data.disableFontFace;
@@ -269,13 +328,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 +362,8 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
pdfManager.requestLoadedStream();
pdfManager.onLoadedStream().then(function() {
+ ensureNotTerminated();
+
loadDocument(true).then(onSuccess, onFailure);
});
}, onFailure);
@@ -380,17 +454,25 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
});
handler.on('RenderPageRequest', function wphSetupRenderPage(data) {
- pdfManager.getPage(data.pageIndex).then(function(page) {
+ var pageIndex = data.pageIndex;
+ pdfManager.getPage(pageIndex).then(function(page) {
+ var task = new WorkerTask('RenderPageRequest: page ' + pageIndex);
+ startWorkerTask(task);
- var pageNum = data.pageIndex + 1;
+ var pageNum = pageIndex + 1;
var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images.
- page.getOperatorList(handler, data.intent).then(function(operatorList) {
+ page.getOperatorList(handler, task, data.intent).then(
+ function(operatorList) {
+ finishWorkerTask(task);
info('page=' + pageNum + ' - getOperatorList: time=' +
(Date.now() - start) + 'ms, len=' + operatorList.fnArray.length);
-
}, function(e) {
+ finishWorkerTask(task);
+ if (task.terminated) {
+ return; // ignoring errors from the terminated thread
+ }
var minimumStackMessage =
'worker.js: while trying to getPage() and getOperatorList()';
@@ -425,13 +507,23 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}, this);
handler.on('GetTextContent', function wphExtractText(data) {
- return pdfManager.getPage(data.pageIndex).then(function(page) {
- var pageNum = data.pageIndex + 1;
+ var pageIndex = data.pageIndex;
+ return pdfManager.getPage(pageIndex).then(function(page) {
+ var task = new WorkerTask('GetTextContent: page ' + pageIndex);
+ startWorkerTask(task);
+ var pageNum = pageIndex + 1;
var start = Date.now();
- return page.extractTextContent().then(function(textContent) {
+ return page.extractTextContent(task).then(function(textContent) {
+ finishWorkerTask(task);
info('text indexing: page=' + pageNum + ' - time=' +
(Date.now() - start) + 'ms');
return textContent;
+ }, function (reason) {
+ finishWorkerTask(task);
+ if (task.terminated) {
+ return; // ignoring errors from the terminated thread
+ }
+ throw reason;
});
});
});
@@ -441,7 +533,22 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
});
handler.on('Terminate', function wphTerminate(data) {
- pdfManager.terminate();
+ terminated = true;
+ if (pdfManager) {
+ pdfManager.terminate();
+ pdfManager = null;
+ }
+ if (cancelXHRs) {
+ cancelXHRs();
+ }
+
+ var waitOn = [];
+ WorkerTasks.forEach(function (task) {
+ waitOn.push(task.finished);
+ task.terminate();
+ });
+
+ return Promise.all(waitOn).then(function () {});
});
}
};
diff --git a/src/display/api.js b/src/display/api.js
index c89c931a8..fb47469e0 100644
--- a/src/display/api.js
+++ b/src/display/api.js
@@ -19,7 +19,7 @@
Promise, PasswordResponses, PasswordException, InvalidPDFException,
MissingPDFException, UnknownErrorException, FontFaceObject,
loadJpegStream, createScratchCanvas, CanvasGraphics, stringToBytes,
- UnexpectedResponseException */
+ UnexpectedResponseException, deprecated */
'use strict';
@@ -259,12 +259,19 @@ PDFJS.getDocument = function getDocument(src,
var task = new PDFDocumentLoadingTask();
// Support of the obsolete arguments (for compatibility with API v1.0)
+ if (arguments.length > 1) {
+ deprecated('getDocument is called with pdfDataRangeTransport, ' +
+ 'passwordCallback or progressCallback argument');
+ }
if (pdfDataRangeTransport) {
if (!(pdfDataRangeTransport instanceof PDFDataRangeTransport)) {
// Not a PDFDataRangeTransport instance, trying to add missing properties.
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 +331,7 @@ PDFJS.getDocument = function getDocument(src,
workerInitializedCapability.promise.then(function transportInitialized() {
transport.fetchDocument(task, params);
});
+ task._transport = transport;
return task;
};
@@ -336,6 +344,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 +370,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 +464,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 +480,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 +606,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 +683,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 +749,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');
@@ -769,13 +790,14 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
// Obsolete parameter support
if (params.continueCallback) {
+ deprecated('render is used with continueCallback parameter');
renderTask.onContinue = params.continueCallback;
}
var self = this;
intentState.displayReadyCapability.promise.then(
function pageDisplayReadyPromise(transparency) {
- if (self.pendingDestroy) {
+ if (self.pendingCleanup) {
complete();
return;
}
@@ -795,9 +817,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 +880,52 @@ 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;
+
+ var waitOn = [];
+ Object.keys(this.intentStates).forEach(function(intent) {
+ var intentState = this.intentStates[intent];
+ intentState.renderTasks.forEach(function(renderTask) {
+ var renderCompleted = renderTask.capability.promise.
+ catch(function () {}); // ignoring failures
+ waitOn.push(renderCompleted);
+ renderTask.cancel();
+ });
+ }, this);
+ this.objs.clear();
+ this.annotationsPromise = null;
+ this.pendingCleanup = false;
+ return Promise.all(waitOn);
+ },
+
+ /**
+ * Cleans up resources allocated by the page. (deprecated)
+ */
+ destroy: function() {
+ deprecated('page destroy method, use cleanup() instead');
+ 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 +939,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
}, this);
this.objs.clear();
this.annotationsPromise = null;
- this.pendingDestroy = false;
+ this.pendingCleanup = false;
},
/**
* For internal use only.
@@ -923,7 +977,7 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
if (operatorListChunk.lastChunk) {
intentState.receivingOperatorList = false;
- this._tryDestroy();
+ this._tryCleanup();
}
}
};
@@ -941,6 +995,8 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.commonObjs = new PDFObjects();
this.loadingTask = null;
+ this.destroyed = false;
+ this.destroyCapability = null;
this.pageCache = [];
this.pagePromises = [];
@@ -1001,15 +1057,40 @@ var WorkerTransport = (function WorkerTransportClosure() {
}
WorkerTransport.prototype = {
destroy: function WorkerTransport_destroy() {
+ if (this.destroyCapability) {
+ return this.destroyCapability.promise;
+ }
+
+ this.destroyed = true;
+ this.destroyCapability = createPromiseCapability();
+
+ var waitOn = [];
+ // We need to wait for all renderings to be completed, e.g.
+ // timeout/rAF can take a long time.
+ this.pageCache.forEach(function (page) {
+ if (page) {
+ waitOn.push(page._destroy());
+ }
+ });
this.pageCache = [];
this.pagePromises = [];
var self = this;
- this.messageHandler.sendWithPromise('Terminate', null).then(function () {
+ // We also need to wait for the worker to finish its long running tasks.
+ var terminated = this.messageHandler.sendWithPromise('Terminate', null);
+ waitOn.push(terminated);
+ Promise.all(waitOn).then(function () {
FontLoader.clear();
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 +1172,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 +1376,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 +1420,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 +1480,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/src/shared/util.js b/src/shared/util.js
index f4821bcc9..1adeb82c4 100644
--- a/src/shared/util.js
+++ b/src/shared/util.js
@@ -213,6 +213,11 @@ function warn(msg) {
}
}
+// Deprecated API function -- treated as warnings.
+function deprecated(details) {
+ warn('Deprecated API usage: ' + details);
+}
+
// Fatal errors that should trigger the fallback UI and halt execution by
// throwing an exception.
function error(msg) {
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/evaluator_spec.js b/test/unit/evaluator_spec.js
index 5bf007d0b..5e21122c5 100644
--- a/test/unit/evaluator_spec.js
+++ b/test/unit/evaluator_spec.js
@@ -1,13 +1,13 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* globals expect, it, describe, PartialEvaluator, StringStream, OPS,
- OperatorList, waitsFor, runs, Dict, Name, Stream */
+ OperatorList, waitsFor, runs, Dict, Name, Stream, WorkerTask */
'use strict';
describe('evaluator', function() {
function XrefMock(queue) {
- this.queue = queue;
+ this.queue = queue || [];
}
XrefMock.prototype = {
fetchIfRef: function() {
@@ -35,7 +35,9 @@ describe('evaluator', function() {
var done = false;
runs(function () {
var result = new OperatorList();
- evaluator.getOperatorList(stream, resources, result).then(function () {
+ var task = new WorkerTask('OperatorListCheck');
+ evaluator.getOperatorList(stream, task, resources, result).then(
+ function () {
check(result);
done = true;
});
@@ -259,4 +261,48 @@ describe('evaluator', function() {
});
});
});
+
+ describe('thread control', function() {
+ it('should abort operator list parsing', function () {
+ var evaluator = new PartialEvaluator(new PdfManagerMock(),
+ new XrefMock(), new HandlerMock(),
+ 'prefix');
+ var stream = new StringStream('qqQQ');
+ var resources = new ResourcesMock();
+ var done = false;
+ runs(function () {
+ var result = new OperatorList();
+ var task = new WorkerTask('OperatorListAbort');
+ task.terminate();
+ evaluator.getOperatorList(stream, task, resources, result).catch(
+ function () {
+ done = true;
+ expect(!!result.fnArray && !!result.argsArray).toEqual(true);
+ expect(result.fnArray.length).toEqual(0);
+ });
+ });
+ waitsFor(function () {
+ return done;
+ });
+ });
+ it('should abort text parsing parsing', function () {
+ var resources = new ResourcesMock();
+ var evaluator = new PartialEvaluator(new PdfManagerMock(),
+ new XrefMock(), new HandlerMock(),
+ 'prefix');
+ var stream = new StringStream('qqQQ');
+ var done = false;
+ runs(function () {
+ var task = new WorkerTask('TextContentAbort');
+ task.terminate();
+ evaluator.getTextContent(stream, task, resources).catch(
+ function () {
+ done = true;
+ });
+ });
+ waitsFor(function () {
+ return done;
+ });
+ });
+ });
});
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/chromecom.js b/web/chromecom.js
index 6823dd3a1..134dd6f00 100644
--- a/web/chromecom.js
+++ b/web/chromecom.js
@@ -64,7 +64,7 @@ var ChromeCom = (function ChromeComClosure() {
var streamUrl = response.streamUrl;
if (streamUrl) {
console.log('Found data stream for ' + file);
- PDFViewerApplication.open(streamUrl, 0, undefined, undefined, {
+ PDFViewerApplication.open(streamUrl, {
length: response.contentLength
});
PDFViewerApplication.setTitleUsingUrl(file);
@@ -91,7 +91,7 @@ var ChromeCom = (function ChromeComClosure() {
resolveLocalFileSystemURL(file, function onResolvedFSURL(fileEntry) {
fileEntry.file(function(fileObject) {
var blobUrl = URL.createObjectURL(fileObject);
- PDFViewerApplication.open(blobUrl, 0, undefined, undefined, {
+ PDFViewerApplication.open(blobUrl, {
length: fileObject.size
});
});
@@ -100,7 +100,7 @@ var ChromeCom = (function ChromeComClosure() {
// usual way of getting the File's data (via the Web worker).
console.warn('Cannot resolve file ' + file + ', ' + error.name + ' ' +
error.message);
- PDFViewerApplication.open(file, 0);
+ PDFViewerApplication.open(file);
});
return;
}
@@ -109,7 +109,7 @@ var ChromeCom = (function ChromeComClosure() {
// There is no UI to input a different URL, so this assumption will hold
// for now.
setReferer(file, function() {
- PDFViewerApplication.open(file, 0);
+ PDFViewerApplication.open(file);
});
return;
}
@@ -122,14 +122,14 @@ var ChromeCom = (function ChromeComClosure() {
}
isAllowedFileSchemeAccess(function(isAllowedAccess) {
if (isAllowedAccess) {
- PDFViewerApplication.open(file, 0);
+ PDFViewerApplication.open(file);
} else {
requestAccessToLocalFile(file);
}
});
return;
}
- PDFViewerApplication.open(file, 0);
+ PDFViewerApplication.open(file);
});
};
diff --git a/web/pdf_page_view.js b/web/pdf_page_view.js
index bec6d1d95..34345a3af 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();
}
},
@@ -457,9 +457,9 @@ var PDFPageView = (function PDFPageViewClosure() {
canvasContext: ctx,
viewport: this.viewport,
// intent: 'default', // === 'display'
- continueCallback: renderContinueCallback
};
var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+ renderTask.onContinue = renderContinueCallback;
this.renderTask.promise.then(
function pdfPageRenderCallback() {
diff --git a/web/pdf_thumbnail_view.js b/web/pdf_thumbnail_view.js
index 88c92c738..24e4cf5f3 100644
--- a/web/pdf_thumbnail_view.js
+++ b/web/pdf_thumbnail_view.js
@@ -285,10 +285,10 @@ var PDFThumbnailView = (function PDFThumbnailViewClosure() {
var renderContext = {
canvasContext: ctx,
- viewport: drawViewport,
- continueCallback: renderContinueCallback
+ viewport: drawViewport
};
var renderTask = this.renderTask = this.pdfPage.render(renderContext);
+ renderTask.onContinue = renderContinueCallback;
renderTask.promise.then(
function pdfPageRenderCallback() {
diff --git a/web/viewer.js b/web/viewer.js
index 4b25ffe3c..947b42241 100644
--- a/web/viewer.js
+++ b/web/viewer.js
@@ -92,6 +92,7 @@ var PDFViewerApplication = {
initialized: false,
fellback: false,
pdfDocument: null,
+ pdfLoadingTask: null,
sidebarOpen: false,
printing: false,
/** @type {PDFViewer} */
@@ -410,6 +411,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;
@@ -429,8 +435,8 @@ var PDFViewerApplication = {
pdfDataRangeTransport =
new FirefoxComDataRangeTransport(args.length, args.data);
- PDFViewerApplication.open(args.pdfUrl, 0, undefined,
- pdfDataRangeTransport);
+ PDFViewerApplication.open(args.pdfUrl,
+ {range: pdfDataRangeTransport});
if (args.length) {
PDFViewerApplication.pdfDocumentProperties
@@ -455,7 +461,7 @@ var PDFViewerApplication = {
'An error occurred while loading the PDF.'), e);
break;
}
- PDFViewerApplication.open(args.data, 0);
+ PDFViewerApplication.open(args.data);
break;
}
});
@@ -482,36 +488,76 @@ var PDFViewerApplication = {
document.title = title;
},
+ /**
+ * Closes opened PDF document.
+ * @returns {Promise} - Returns the promise, which is resolved when all
+ * destruction is completed.
+ */
close: function pdfViewClose() {
var errorWrapper = document.getElementById('errorWrapper');
errorWrapper.setAttribute('hidden', 'true');
- if (!this.pdfDocument) {
- return;
+ if (!this.pdfLoadingTask) {
+ return Promise.resolve();
}
- this.pdfDocument.destroy();
- this.pdfDocument = null;
+ var promise = this.pdfLoadingTask.destroy();
+ this.pdfLoadingTask = null;
- this.pdfThumbnailViewer.setDocument(null);
- this.pdfViewer.setDocument(null);
- this.pdfLinkService.setDocument(null, null);
+ if (this.pdfDocument) {
+ this.pdfDocument = null;
+
+ this.pdfThumbnailViewer.setDocument(null);
+ this.pdfViewer.setDocument(null);
+ this.pdfLinkService.setDocument(null, null);
+ }
if (typeof PDFBug !== 'undefined') {
PDFBug.cleanup();
}
+ return promise;
},
- // TODO(mack): This function signature should really be pdfViewOpen(url, args)
- open: function pdfViewOpen(file, scale, password,
- pdfDataRangeTransport, args) {
- if (this.pdfDocument) {
- // Reload the preferences if a document was previously opened.
- Preferences.reload();
+ /**
+ * Opens PDF document specified by URL or array with additional arguments.
+ * @param {string|TypedArray|ArrayBuffer} file - PDF location or binary data.
+ * @param {Object} args - (optional) Additional arguments for the getDocument
+ * call, e.g. HTTP headers ('httpHeaders') or
+ * alternative data transport ('range').
+ * @returns {Promise} - Returns the promise, which is resolved when document
+ * is opened.
+ */
+ open: function pdfViewOpen(file, args) {
+ var scale = 0;
+ if (arguments.length > 2 || typeof args === 'number') {
+ console.warn('Call of open() with obsolete signature.');
+ if (typeof args === 'number') {
+ scale = args; // scale argument was found
+ }
+ args = arguments[4] || null;
+ if (arguments[3] && typeof arguments[3] === 'object') {
+ // The pdfDataRangeTransport argument is present.
+ args = Object.create(args);
+ args.range = arguments[3];
+ }
+ if (typeof arguments[2] === 'string') {
+ // The password argument is present.
+ args = Object.create(args);
+ args.password = arguments[2];
+ }
}
- this.close();
- var parameters = {password: password};
+ if (this.pdfLoadingTask) {
+ // We need to destroy already opened document.
+ return this.close().then(function () {
+ // Reload the preferences if a document was previously opened.
+ Preferences.reload();
+ // ... and repeat the open() call.
+ return this.open(file, args);
+ }.bind(this));
+ }
+
+ var parameters = Object.create(null);
if (typeof file === 'string') { // URL
this.setTitleUsingUrl(file);
parameters.url = file;
@@ -530,18 +576,20 @@ var PDFViewerApplication = {
var self = this;
self.downloadComplete = false;
- var passwordNeeded = function passwordNeeded(updatePassword, reason) {
+ var loadingTask = PDFJS.getDocument(parameters);
+ this.pdfLoadingTask = loadingTask;
+
+ loadingTask.onPassword = function passwordNeeded(updatePassword, reason) {
PasswordPrompt.updatePassword = updatePassword;
PasswordPrompt.reason = reason;
PasswordPrompt.open();
};
- function getDocumentProgress(progressData) {
+ loadingTask.onProgress = function getDocumentProgress(progressData) {
self.progress(progressData.loaded / progressData.total);
- }
+ };
- PDFJS.getDocument(parameters, pdfDataRangeTransport, passwordNeeded,
- getDocumentProgress).then(
+ var result = loadingTask.promise.then(
function getDocumentCallback(pdfDocument) {
self.load(pdfDocument, scale);
},
@@ -567,12 +615,15 @@ var PDFViewerApplication = {
message: message
};
self.error(loadingErrorMessage, moreInfo);
+
+ throw new Error(loadingErrorMessage);
}
);
if (args && args.length) {
PDFViewerApplication.pdfDocumentProperties.setFileSize(args.length);
}
+ return result;
},
download: function pdfViewDownload() {
@@ -999,6 +1050,9 @@ var PDFViewerApplication = {
},
cleanup: function pdfViewCleanup() {
+ if (!this.pdfDocument) {
+ return; // run cleanup when document is loaded
+ }
this.pdfViewer.cleanup();
this.pdfThumbnailViewer.cleanup();
this.pdfDocument.cleanup();
@@ -1493,7 +1547,7 @@ function webViewerInitialized() {
PDFViewerApplication.setTitleUsingUrl(file);
var xhr = new XMLHttpRequest();
xhr.onload = function() {
- PDFViewerApplication.open(new Uint8Array(xhr.response), 0);
+ PDFViewerApplication.open(new Uint8Array(xhr.response));
};
try {
xhr.open('GET', file);
@@ -1507,7 +1561,7 @@ function webViewerInitialized() {
}
if (file) {
- PDFViewerApplication.open(file, 0);
+ PDFViewerApplication.open(file);
}
//#endif
//#if CHROME
@@ -1722,14 +1776,14 @@ window.addEventListener('change', function webViewerChange(evt) {
if (!PDFJS.disableCreateObjectURL &&
typeof URL !== 'undefined' && URL.createObjectURL) {
- PDFViewerApplication.open(URL.createObjectURL(file), 0);
+ PDFViewerApplication.open(URL.createObjectURL(file));
} else {
// Read the local file into a Uint8Array.
var fileReader = new FileReader();
fileReader.onload = function webViewerChangeFileReaderOnload(evt) {
var buffer = evt.target.result;
var uint8Array = new Uint8Array(buffer);
- PDFViewerApplication.open(uint8Array, 0);
+ PDFViewerApplication.open(uint8Array);
};
fileReader.readAsArrayBuffer(file);
}