Adds thread abort capabilities.

This commit is contained in:
Yury Delendik 2015-10-20 20:50:32 -05:00
parent 59c13b32aa
commit 58c3ea0820
6 changed files with 186 additions and 53 deletions

View File

@ -281,7 +281,7 @@ var Annotation = (function AnnotationClosure() {
}.bind(this)); }.bind(this));
}, },
getOperatorList: function Annotation_getOperatorList(evaluator) { getOperatorList: function Annotation_getOperatorList(evaluator, task) {
if (!this.appearance) { if (!this.appearance) {
return Promise.resolve(new OperatorList()); return Promise.resolve(new OperatorList());
@ -308,7 +308,8 @@ var Annotation = (function AnnotationClosure() {
return resourcesPromise.then(function(resources) { return resourcesPromise.then(function(resources) {
var opList = new OperatorList(); var opList = new OperatorList();
opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]); 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 () { then(function () {
opList.addOp(OPS.endAnnotation, []); opList.addOp(OPS.endAnnotation, []);
self.appearance.reset(); self.appearance.reset();
@ -319,7 +320,7 @@ var Annotation = (function AnnotationClosure() {
}; };
Annotation.appendToOperatorList = function Annotation_appendToOperatorList( Annotation.appendToOperatorList = function Annotation_appendToOperatorList(
annotations, opList, pdfManager, partialEvaluator, intent) { annotations, opList, pdfManager, partialEvaluator, task, intent) {
function reject(e) { function reject(e) {
annotationsReadyCapability.reject(e); annotationsReadyCapability.reject(e);
@ -332,7 +333,7 @@ var Annotation = (function AnnotationClosure() {
if (intent === 'display' && annotations[i].isViewable() || if (intent === 'display' && annotations[i].isViewable() ||
intent === 'print' && annotations[i].isPrintable()) { intent === 'print' && annotations[i].isPrintable()) {
annotationPromises.push( annotationPromises.push(
annotations[i].getOperatorList(partialEvaluator)); annotations[i].getOperatorList(partialEvaluator, task));
} }
} }
Promise.all(annotationPromises).then(function(datas) { Promise.all(annotationPromises).then(function(datas) {
@ -564,9 +565,10 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
} }
Util.inherit(TextWidgetAnnotation, WidgetAnnotation, { Util.inherit(TextWidgetAnnotation, WidgetAnnotation, {
getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator) { getOperatorList: function TextWidgetAnnotation_getOperatorList(evaluator,
task) {
if (this.appearance) { if (this.appearance) {
return Annotation.prototype.getOperatorList.call(this, evaluator); return Annotation.prototype.getOperatorList.call(this, evaluator, task);
} }
var opList = new OperatorList(); var opList = new OperatorList();
@ -579,7 +581,8 @@ var TextWidgetAnnotation = (function TextWidgetAnnotationClosure() {
} }
var stream = new Stream(stringToBytes(data.defaultAppearance)); var stream = new Stream(stringToBytes(data.defaultAppearance));
return evaluator.getOperatorList(stream, this.fieldResources, opList). return evaluator.getOperatorList(stream, task,
this.fieldResources, opList).
then(function () { then(function () {
return opList; return opList;
}); });

View File

@ -161,7 +161,7 @@ var Page = (function PageClosure() {
}.bind(this)); }.bind(this));
}, },
getOperatorList: function Page_getOperatorList(handler, intent) { getOperatorList: function Page_getOperatorList(handler, task, intent) {
var self = this; var self = this;
var pdfManager = this.pdfManager; var pdfManager = this.pdfManager;
@ -194,8 +194,8 @@ var Page = (function PageClosure() {
pageIndex: self.pageIndex, pageIndex: self.pageIndex,
intent: intent intent: intent
}); });
return partialEvaluator.getOperatorList(contentStream, self.resources, return partialEvaluator.getOperatorList(contentStream, task,
opList).then(function () { self.resources, opList).then(function () {
return opList; return opList;
}); });
}); });
@ -212,7 +212,7 @@ var Page = (function PageClosure() {
} }
var annotationsReadyPromise = Annotation.appendToOperatorList( var annotationsReadyPromise = Annotation.appendToOperatorList(
annotations, pageOpList, pdfManager, partialEvaluator, intent); annotations, pageOpList, pdfManager, partialEvaluator, task, intent);
return annotationsReadyPromise.then(function () { return annotationsReadyPromise.then(function () {
pageOpList.flush(true); pageOpList.flush(true);
return pageOpList; return pageOpList;
@ -220,7 +220,7 @@ var Page = (function PageClosure() {
}); });
}, },
extractTextContent: function Page_extractTextContent() { extractTextContent: function Page_extractTextContent(task) {
var handler = { var handler = {
on: function nullHandlerOn() {}, on: function nullHandlerOn() {},
send: function nullHandlerSend() {} send: function nullHandlerSend() {}
@ -249,6 +249,7 @@ var Page = (function PageClosure() {
self.fontCache); self.fontCache);
return partialEvaluator.getTextContent(contentStream, return partialEvaluator.getTextContent(contentStream,
task,
self.resources); self.resources);
}); });
}, },

View File

@ -125,6 +125,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
buildFormXObject: function PartialEvaluator_buildFormXObject(resources, buildFormXObject: function PartialEvaluator_buildFormXObject(resources,
xobj, smask, xobj, smask,
operatorList, operatorList,
task,
initialState) { initialState) {
var matrix = xobj.dict.getArray('Matrix'); var matrix = xobj.dict.getArray('Matrix');
var bbox = xobj.dict.getArray('BBox'); var bbox = xobj.dict.getArray('BBox');
@ -157,7 +158,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]); operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
return this.getOperatorList(xobj, return this.getOperatorList(xobj, task,
(xobj.dict.get('Resources') || resources), operatorList, initialState). (xobj.dict.get('Resources') || resources), operatorList, initialState).
then(function () { then(function () {
operatorList.addOp(OPS.paintFormXObjectEnd, []); operatorList.addOp(OPS.paintFormXObjectEnd, []);
@ -269,7 +270,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
handleSMask: function PartialEvaluator_handleSmask(smask, resources, handleSMask: function PartialEvaluator_handleSmask(smask, resources,
operatorList, operatorList, task,
stateManager) { stateManager) {
var smaskContent = smask.get('G'); var smaskContent = smask.get('G');
var smaskOptions = { var smaskOptions = {
@ -277,13 +278,13 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
backdrop: smask.get('BC') backdrop: smask.get('BC')
}; };
return this.buildFormXObject(resources, smaskContent, smaskOptions, return this.buildFormXObject(resources, smaskContent, smaskOptions,
operatorList, stateManager.state.clone()); operatorList, task, stateManager.state.clone());
}, },
handleTilingType: handleTilingType:
function PartialEvaluator_handleTilingType(fn, args, resources, function PartialEvaluator_handleTilingType(fn, args, resources,
pattern, patternDict, pattern, patternDict,
operatorList) { operatorList, task) {
// Create an IR of the pattern code. // Create an IR of the pattern code.
var tilingOpList = new OperatorList(); var tilingOpList = new OperatorList();
// Merge the available resources, to prevent issues when the patternDict // 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 resourcesArray = [patternDict.get('Resources'), resources];
var patternResources = Dict.merge(this.xref, resourcesArray); var patternResources = Dict.merge(this.xref, resourcesArray);
return this.getOperatorList(pattern, patternResources, tilingOpList).then( return this.getOperatorList(pattern, task, patternResources,
function () { tilingOpList).then(function () {
// Add the dependencies to the parent operator list so they are // Add the dependencies to the parent operator list so they are
// resolved before sub operator list is executed synchronously. // resolved before sub operator list is executed synchronously.
operatorList.addDependencies(tilingOpList.dependencies); operatorList.addDependencies(tilingOpList.dependencies);
@ -305,7 +306,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
handleSetFont: handleSetFont:
function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef, function PartialEvaluator_handleSetFont(resources, fontArgs, fontRef,
operatorList, state) { operatorList, task, state) {
// TODO(mack): Not needed? // TODO(mack): Not needed?
var fontName; var fontName;
if (fontArgs) { if (fontArgs) {
@ -319,8 +320,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (!translated.font.isType3Font) { if (!translated.font.isType3Font) {
return translated; return translated;
} }
return translated.loadType3Data(self, resources, operatorList).then( return translated.loadType3Data(self, resources, operatorList, task).
function () { then(function () {
return translated; return translated;
}); });
}).then(function (translated) { }).then(function (translated) {
@ -367,8 +368,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
setGState: function PartialEvaluator_setGState(resources, gState, setGState: function PartialEvaluator_setGState(resources, gState,
operatorList, xref, operatorList, task,
stateManager) { xref, stateManager) {
// This array holds the converted/processed state data. // This array holds the converted/processed state data.
var gStateObj = []; var gStateObj = [];
var gStateMap = gState.map; var gStateMap = gState.map;
@ -392,8 +393,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
break; break;
case 'Font': case 'Font':
promise = promise.then(function () { promise = promise.then(function () {
return self.handleSetFont(resources, null, value[0], return self.handleSetFont(resources, null, value[0], operatorList,
operatorList, stateManager.state). task, stateManager.state).
then(function (loadedName) { then(function (loadedName) {
operatorList.addDependency(loadedName); operatorList.addDependency(loadedName);
gStateObj.push([key, [loadedName, value[1]]]); gStateObj.push([key, [loadedName, value[1]]]);
@ -412,7 +413,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (isDict(dict)) { if (isDict(dict)) {
promise = promise.then(function () { promise = promise.then(function () {
return self.handleSMask(dict, resources, operatorList, return self.handleSMask(dict, resources, operatorList,
stateManager); task, stateManager);
}); });
gStateObj.push([key, true]); gStateObj.push([key, true]);
} else { } else {
@ -593,7 +594,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args, handleColorN: function PartialEvaluator_handleColorN(operatorList, fn, args,
cs, patterns, resources, xref) { cs, patterns, resources, task, xref) {
// compile tiling patterns // compile tiling patterns
var patternName = args[args.length - 1]; var patternName = args[args.length - 1];
// SCN/scn applies patterns along with normal colors // SCN/scn applies patterns along with normal colors
@ -606,7 +607,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (typeNum === TILING_PATTERN) { if (typeNum === TILING_PATTERN) {
var color = cs.base ? cs.base.getRgb(args, 0) : null; var color = cs.base ? cs.base.getRgb(args, 0) : null;
return this.handleTilingType(fn, color, resources, pattern, return this.handleTilingType(fn, color, resources, pattern,
dict, operatorList); dict, operatorList, task);
} else if (typeNum === SHADING_PATTERN) { } else if (typeNum === SHADING_PATTERN) {
var shading = dict.get('Shading'); var shading = dict.get('Shading');
var matrix = dict.get('Matrix'); var matrix = dict.get('Matrix');
@ -623,6 +624,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
}, },
getOperatorList: function PartialEvaluator_getOperatorList(stream, getOperatorList: function PartialEvaluator_getOperatorList(stream,
task,
resources, resources,
operatorList, operatorList,
initialState) { initialState) {
@ -641,6 +643,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var timeSlotManager = new TimeSlotManager(); var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) { return new Promise(function next(resolve, reject) {
task.ensureNotTerminated();
timeSlotManager.reset(); timeSlotManager.reset();
var stop, operation = {}, i, ii, cs; var stop, operation = {}, i, ii, cs;
while (!(stop = timeSlotManager.check())) { while (!(stop = timeSlotManager.check())) {
@ -683,7 +686,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (type.name === 'Form') { if (type.name === 'Form') {
stateManager.save(); stateManager.save();
return self.buildFormXObject(resources, xobj, null, return self.buildFormXObject(resources, xobj, null,
operatorList, operatorList, task,
stateManager.state.clone()). stateManager.state.clone()).
then(function () { then(function () {
stateManager.restore(); stateManager.restore();
@ -707,8 +710,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
case OPS.setFont: case OPS.setFont:
var fontSize = args[1]; var fontSize = args[1];
// eagerly collect all fonts // eagerly collect all fonts
return self.handleSetFont(resources, args, null, return self.handleSetFont(resources, args, null, operatorList,
operatorList, stateManager.state). task, stateManager.state).
then(function (loadedName) { then(function (loadedName) {
operatorList.addDependency(loadedName); operatorList.addDependency(loadedName);
operatorList.addOp(OPS.setFont, [loadedName, fontSize]); operatorList.addOp(OPS.setFont, [loadedName, fontSize]);
@ -814,7 +817,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs = stateManager.state.fillColorSpace; cs = stateManager.state.fillColorSpace;
if (cs.name === 'Pattern') { if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setFillColorN, return self.handleColorN(operatorList, OPS.setFillColorN,
args, cs, patterns, resources, xref).then(function() { args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject); next(resolve, reject);
}, reject); }, reject);
} }
@ -825,7 +828,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
cs = stateManager.state.strokeColorSpace; cs = stateManager.state.strokeColorSpace;
if (cs.name === 'Pattern') { if (cs.name === 'Pattern') {
return self.handleColorN(operatorList, OPS.setStrokeColorN, return self.handleColorN(operatorList, OPS.setStrokeColorN,
args, cs, patterns, resources, xref).then(function() { args, cs, patterns, resources, task, xref).then(function() {
next(resolve, reject); next(resolve, reject);
}, reject); }, reject);
} }
@ -859,8 +862,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} }
var gState = extGState.get(dictName.name); var gState = extGState.get(dictName.name);
return self.setGState(resources, gState, operatorList, xref, return self.setGState(resources, gState, operatorList, task,
stateManager).then(function() { xref, stateManager).then(function() {
next(resolve, reject); next(resolve, reject);
}, reject); }, reject);
case OPS.moveTo: case OPS.moveTo:
@ -898,7 +901,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (stop) { if (stop) {
deferred.then(function () { deferred.then(function () {
next(resolve, reject); next(resolve, reject);
}); }, reject);
return; return;
} }
// Some PDFs don't close all restores inside object/form. // 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 = (stateManager || new StateManager(new TextState())); stateManager = (stateManager || new StateManager(new TextState()));
@ -1088,6 +1092,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
var timeSlotManager = new TimeSlotManager(); var timeSlotManager = new TimeSlotManager();
return new Promise(function next(resolve, reject) { return new Promise(function next(resolve, reject) {
task.ensureNotTerminated();
timeSlotManager.reset(); timeSlotManager.reset();
var stop, operation = {}, args = []; var stop, operation = {}, args = [];
while (!(stop = timeSlotManager.check())) { while (!(stop = timeSlotManager.check())) {
@ -1243,7 +1248,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
stateManager.transform(matrix); stateManager.transform(matrix);
} }
return self.getTextContent(xobj, return self.getTextContent(xobj, task,
xobj.dict.get('Resources') || resources, stateManager). xobj.dict.get('Resources') || resources, stateManager).
then(function (formTextContent) { then(function (formTextContent) {
Util.appendToArray(bidiTexts, formTextContent.items); Util.appendToArray(bidiTexts, formTextContent.items);
@ -1283,7 +1288,7 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
if (stop) { if (stop) {
deferred.then(function () { deferred.then(function () {
next(resolve, reject); next(resolve, reject);
}); }, reject);
return; return;
} }
resolve(textContent); resolve(textContent);
@ -1848,7 +1853,7 @@ var TranslatedFont = (function TranslatedFontClosure() {
]); ]);
this.sent = true; this.sent = true;
}, },
loadType3Data: function (evaluator, resources, parentOperatorList) { loadType3Data: function (evaluator, resources, parentOperatorList, task) {
assert(this.font.isType3Font); assert(this.font.isType3Font);
if (this.type3Loaded) { if (this.type3Loaded) {
@ -1865,7 +1870,7 @@ var TranslatedFont = (function TranslatedFontClosure() {
loadCharProcsPromise = loadCharProcsPromise.then(function (key) { loadCharProcsPromise = loadCharProcsPromise.then(function (key) {
var glyphStream = charProcs[key]; var glyphStream = charProcs[key];
var operatorList = new OperatorList(); var operatorList = new OperatorList();
return evaluator.getOperatorList(glyphStream, fontResources, return evaluator.getOperatorList(glyphStream, task, fontResources,
operatorList).then(function () { operatorList).then(function () {
charProcOperatorList[key] = operatorList.getIR(); charProcOperatorList[key] = operatorList.getIR();

View File

@ -22,11 +22,42 @@
'use strict'; '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 = { var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
setup: function wphSetup(handler) { setup: function wphSetup(handler) {
var pdfManager; var pdfManager;
var terminated = false; var terminated = false;
var cancelXHRs = null; var cancelXHRs = null;
var WorkerTasks = [];
function ensureNotTerminated() { function ensureNotTerminated() {
if (terminated) { if (terminated) {
@ -34,6 +65,16 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
} }
} }
function startWorkerTask(task) {
WorkerTasks.push(task);
}
function finishWorkerTask(task) {
task.finish();
var i = WorkerTasks.indexOf(task);
WorkerTasks.splice(i, 1);
}
function loadDocument(recoveryMode) { function loadDocument(recoveryMode) {
var loadDocumentCapability = createPromiseCapability(); var loadDocumentCapability = createPromiseCapability();
@ -413,17 +454,25 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}); });
handler.on('RenderPageRequest', function wphSetupRenderPage(data) { 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(); var start = Date.now();
// Pre compile the pdf page and fetch the fonts/images. // 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=' + info('page=' + pageNum + ' - getOperatorList: time=' +
(Date.now() - start) + 'ms, len=' + operatorList.fnArray.length); (Date.now() - start) + 'ms, len=' + operatorList.fnArray.length);
}, function(e) { }, function(e) {
finishWorkerTask(task);
if (task.terminated) {
return; // ignoring errors from the terminated thread
}
var minimumStackMessage = var minimumStackMessage =
'worker.js: while trying to getPage() and getOperatorList()'; 'worker.js: while trying to getPage() and getOperatorList()';
@ -458,13 +507,23 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
}, this); }, this);
handler.on('GetTextContent', function wphExtractText(data) { handler.on('GetTextContent', function wphExtractText(data) {
return pdfManager.getPage(data.pageIndex).then(function(page) { var pageIndex = data.pageIndex;
var pageNum = data.pageIndex + 1; return pdfManager.getPage(pageIndex).then(function(page) {
var task = new WorkerTask('GetTextContent: page ' + pageIndex);
startWorkerTask(task);
var pageNum = pageIndex + 1;
var start = Date.now(); 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=' + info('text indexing: page=' + pageNum + ' - time=' +
(Date.now() - start) + 'ms'); (Date.now() - start) + 'ms');
return textContent; return textContent;
}, function (reason) {
finishWorkerTask(task);
if (task.terminated) {
return; // ignoring errors from the terminated thread
}
throw reason;
}); });
}); });
}); });
@ -482,6 +541,14 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
if (cancelXHRs) { if (cancelXHRs) {
cancelXHRs(); cancelXHRs();
} }
var waitOn = [];
WorkerTasks.forEach(function (task) {
waitOn.push(task.finished);
task.terminate();
});
return Promise.all(waitOn).then(function () {});
}); });
} }
}; };

View File

@ -883,15 +883,20 @@ var PDFPageProxy = (function PDFPageProxyClosure() {
this.destroyed = true; this.destroyed = true;
this.transport.pageCache[this.pageIndex] = null; this.transport.pageCache[this.pageIndex] = null;
var waitOn = [];
Object.keys(this.intentStates).forEach(function(intent) { Object.keys(this.intentStates).forEach(function(intent) {
var intentState = this.intentStates[intent]; var intentState = this.intentStates[intent];
intentState.renderTasks.forEach(function(renderTask) { intentState.renderTasks.forEach(function(renderTask) {
var renderCompleted = renderTask.capability.promise.
catch(function () {}); // ignoring failures
waitOn.push(renderCompleted);
renderTask.cancel(); renderTask.cancel();
}); });
}, this); }, this);
this.objs.clear(); this.objs.clear();
this.annotationsPromise = null; this.annotationsPromise = null;
this.pendingCleanup = false; this.pendingCleanup = false;
return Promise.all(waitOn);
}, },
/** /**
@ -1054,15 +1059,21 @@ var WorkerTransport = (function WorkerTransportClosure() {
this.destroyed = true; this.destroyed = true;
this.destroyCapability = createPromiseCapability(); 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) { this.pageCache.forEach(function (page) {
if (page) { if (page) {
page._destroy(); waitOn.push(page._destroy());
} }
}); });
this.pageCache = []; this.pageCache = [];
this.pagePromises = []; this.pagePromises = [];
var self = this; 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(); FontLoader.clear();
if (self.worker) { if (self.worker) {
self.worker.terminate(); self.worker.terminate();

View File

@ -1,13 +1,13 @@
/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */ /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
/* globals expect, it, describe, PartialEvaluator, StringStream, OPS, /* globals expect, it, describe, PartialEvaluator, StringStream, OPS,
OperatorList, waitsFor, runs, Dict, Name, Stream */ OperatorList, waitsFor, runs, Dict, Name, Stream, WorkerTask */
'use strict'; 'use strict';
describe('evaluator', function() { describe('evaluator', function() {
function XrefMock(queue) { function XrefMock(queue) {
this.queue = queue; this.queue = queue || [];
} }
XrefMock.prototype = { XrefMock.prototype = {
fetchIfRef: function() { fetchIfRef: function() {
@ -35,7 +35,9 @@ describe('evaluator', function() {
var done = false; var done = false;
runs(function () { runs(function () {
var result = new OperatorList(); 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); check(result);
done = true; 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;
});
});
});
}); });