Merge pull request #3904 from yurydelendik/mem-redux

Reduces amount of memory allocated during worker operations
This commit is contained in:
Brendan Dahl 2013-11-15 11:44:40 -08:00
commit 1f7bfc8cc7
7 changed files with 129 additions and 20 deletions

View File

@ -301,7 +301,8 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
PDFImage.buildImage(function(imageObj) { PDFImage.buildImage(function(imageObj) {
var imgData = imageObj.getImageData(); var imgData = imageObj.getImageData();
self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData]); self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData],
null, [imgData.data.buffer]);
}, self.handler, self.xref, resources, image, inline); }, self.handler, self.xref, resources, image, inline);
operatorList.addOp(OPS.paintImageXObject, args); operatorList.addOp(OPS.paintImageXObject, args);
@ -1457,14 +1458,30 @@ var PartialEvaluator = (function PartialEvaluatorClosure() {
} }
}; };
return PartialEvaluator; return PartialEvaluator;
})(); })();
var OperatorList = (function OperatorListClosure() { var OperatorList = (function OperatorListClosure() {
var CHUNK_SIZE = 100; var CHUNK_SIZE = 100;
function OperatorList(messageHandler, pageIndex) { function getTransfers(queue) {
var transfers = [];
var fnArray = queue.fnArray, argsArray = queue.argsArray;
for (var i = 0, ii = queue.length; i < ii; i++) {
switch (fnArray[i]) {
case OPS.paintInlineImageXObject:
case OPS.paintInlineImageXObjectGroup:
case OPS.paintImageMaskXObject:
var arg = argsArray[i][0]; // first param in imgData
transfers.push(arg.data.buffer);
break;
}
}
return transfers;
}
function OperatorList(messageHandler, pageIndex) {
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
// When there isn't a message handler the fn array needs to be able to grow // When there isn't a message handler the fn array needs to be able to grow
// since we can't flush the operators. // since we can't flush the operators.
@ -1529,6 +1546,7 @@ var OperatorList = (function OperatorListClosure() {
flush: function(lastChunk) { flush: function(lastChunk) {
PartialEvaluator.optimizeQueue(this); PartialEvaluator.optimizeQueue(this);
var transfers = getTransfers(this);
this.messageHandler.send('RenderPageChunk', { this.messageHandler.send('RenderPageChunk', {
operatorList: { operatorList: {
fnArray: this.fnArray, fnArray: this.fnArray,
@ -1537,7 +1555,7 @@ var OperatorList = (function OperatorListClosure() {
length: this.length length: this.length
}, },
pageIndex: this.pageIndex pageIndex: this.pageIndex
}); }, null, transfers);
this.dependencies = []; this.dependencies = [];
this.fnIndex = 0; this.fnIndex = 0;
this.argsArray = []; this.argsArray = [];
@ -1546,6 +1564,7 @@ var OperatorList = (function OperatorListClosure() {
return OperatorList; return OperatorList;
})(); })();
var TextState = (function TextStateClosure() { var TextState = (function TextStateClosure() {
function TextState() { function TextState() {
this.fontSize = 0; this.fontSize = 0;

View File

@ -15,7 +15,7 @@
* limitations under the License. * limitations under the License.
*/ */
/* globals bytesToString, ColorSpace, Dict, EOF, error, info, Jbig2Image, /* globals bytesToString, ColorSpace, Dict, EOF, error, info, Jbig2Image,
JpegImage, JpxImage, Lexer, Util */ JpegImage, JpxImage, Lexer, Util, PDFJS */
'use strict'; 'use strict';
@ -832,7 +832,7 @@ var JpegStream = (function JpegStreamClosure() {
} }
}; };
JpegStream.prototype.getIR = function JpegStream_getIR() { JpegStream.prototype.getIR = function JpegStream_getIR() {
return bytesToString(this.bytes); return PDFJS.createObjectURL(this.bytes, 'image/jpeg');
}; };
/** /**
* Checks if the image can be decoded and displayed by the browser without any * Checks if the image can be decoded and displayed by the browser without any

View File

@ -173,6 +173,9 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
handler.send('test', false); handler.send('test', false);
return; return;
} }
// making sure postMessage transfers are working
var supportTransfers = data[0] === 255;
handler.postMessageTransfers = supportTransfers;
// check if the response property is supported by xhr // check if the response property is supported by xhr
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var responseExists = 'response' in xhr; var responseExists = 'response' in xhr;
@ -186,7 +189,10 @@ var WorkerMessageHandler = PDFJS.WorkerMessageHandler = {
handler.send('test', false); handler.send('test', false);
return; return;
} }
handler.send('test', true); handler.send('test', {
supportTypedArray: true,
supportTransfers: supportTransfers
});
}); });
handler.on('GetDocRequest', function wphSetupDoc(data) { handler.on('GetDocRequest', function wphSetupDoc(data) {

View File

@ -85,6 +85,12 @@ PDFJS.disableAutoFetch = PDFJS.disableAutoFetch === undefined ?
*/ */
PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug; PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
/**
* Enables transfer usage in postMessage for ArrayBuffers.
* @var {boolean}
*/
PDFJS.postMessageTransfers = PDFJS.postMessageTransfers === undefined ?
true : PDFJS.postMessageTransfers;
/** /**
* This is the main entry point for loading a PDF and interacting with it. * This is the main entry point for loading a PDF and interacting with it.
* NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR) * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
@ -544,9 +550,13 @@ var WorkerTransport = (function WorkerTransportClosure() {
var messageHandler = new MessageHandler('main', worker); var messageHandler = new MessageHandler('main', worker);
this.messageHandler = messageHandler; this.messageHandler = messageHandler;
messageHandler.on('test', function transportTest(supportTypedArray) { messageHandler.on('test', function transportTest(data) {
var supportTypedArray = data && data.supportTypedArray;
if (supportTypedArray) { if (supportTypedArray) {
this.worker = worker; this.worker = worker;
if (!data.supportTransfers) {
PDFJS.postMessageTransfers = false;
}
this.setupMessageHandler(messageHandler); this.setupMessageHandler(messageHandler);
workerInitializedPromise.resolve(); workerInitializedPromise.resolve();
} else { } else {
@ -558,10 +568,16 @@ var WorkerTransport = (function WorkerTransportClosure() {
} }
}.bind(this)); }.bind(this));
var testObj = new Uint8Array(1); var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]);
// Some versions of Opera throw a DATA_CLONE_ERR on // Some versions of Opera throw a DATA_CLONE_ERR on serializing the
// serializing the typed array. // typed array. Also, checking if we can use transfers.
messageHandler.send('test', testObj); try {
messageHandler.send('test', testObj, null, [testObj.buffer]);
} catch (ex) {
info('Cannot use postMessage transfers');
testObj[0] = 0;
messageHandler.send('test', testObj);
}
return; return;
} catch (e) { } catch (e) {
info('The worker has been disabled.'); info('The worker has been disabled.');
@ -799,7 +815,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
}, this); }, this);
messageHandler.on('JpegDecode', function(data, promise) { messageHandler.on('JpegDecode', function(data, promise) {
var imageData = data[0]; var imageUrl = data[0];
var components = data[1]; var components = data[1];
if (components != 3 && components != 1) if (components != 3 && components != 1)
error('Only 3 component or 1 component can be returned'); error('Only 3 component or 1 component can be returned');
@ -829,8 +845,7 @@ var WorkerTransport = (function WorkerTransportClosure() {
} }
promise.resolve({ data: buf, width: width, height: height}); promise.resolve({ data: buf, width: width, height: height});
}).bind(this); }).bind(this);
var src = 'data:image/jpeg;base64,' + window.btoa(imageData); img.src = imageUrl;
img.src = src;
}); });
}, },

View File

@ -14,7 +14,7 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref */ /* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL */
'use strict'; 'use strict';
@ -1100,10 +1100,38 @@ PDFJS.createBlob = function createBlob(data, contentType) {
return bb.getBlob(contentType); return bb.getBlob(contentType);
}; };
PDFJS.createObjectURL = (function createObjectURLClosure() {
if (typeof URL !== 'undefined' && URL.createObjectURL) {
return function createObjectURL(data, contentType) {
var blob = PDFJS.createBlob(data, contentType);
return URL.createObjectURL(blob);
};
}
// Blob/createObjectURL is not available, falling back to data schema.
var digits =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
return function createObjectURL(data, contentType) {
var buffer = 'data:' + contentType + ';base64,';
for (var i = 0, ii = data.length; i < ii; i += 3) {
var b1 = data[i] & 0xFF;
var b2 = data[i + 1] & 0xFF;
var b3 = data[i + 2] & 0xFF;
var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
}
return buffer;
};
})();
function MessageHandler(name, comObj) { function MessageHandler(name, comObj) {
this.name = name; this.name = name;
this.comObj = comObj; this.comObj = comObj;
this.callbackIndex = 1; this.callbackIndex = 1;
this.postMessageTransfers = true;
var callbacks = this.callbacks = {}; var callbacks = this.callbacks = {};
var ah = this.actionHandler = {}; var ah = this.actionHandler = {};
@ -1170,8 +1198,9 @@ MessageHandler.prototype = {
* @param {String} actionName Action to call. * @param {String} actionName Action to call.
* @param {JSON} data JSON data to send. * @param {JSON} data JSON data to send.
* @param {function} [callback] Optional callback that will handle a reply. * @param {function} [callback] Optional callback that will handle a reply.
* @param {Array} [transfers] Optional list of transfers/ArrayBuffers
*/ */
send: function messageHandlerSend(actionName, data, callback) { send: function messageHandlerSend(actionName, data, callback, transfers) {
var message = { var message = {
action: actionName, action: actionName,
data: data data: data
@ -1181,14 +1210,18 @@ MessageHandler.prototype = {
this.callbacks[callbackId] = callback; this.callbacks[callbackId] = callback;
message.callbackId = callbackId; message.callbackId = callbackId;
} }
this.comObj.postMessage(message); if (transfers && this.postMessageTransfers) {
this.comObj.postMessage(message, transfers);
} else {
this.comObj.postMessage(message);
}
} }
}; };
function loadJpegStream(id, imageData, objs) { function loadJpegStream(id, imageUrl, objs) {
var img = new Image(); var img = new Image();
img.onload = (function loadJpegStream_onloadClosure() { img.onload = (function loadJpegStream_onloadClosure() {
objs.resolve(id, img); objs.resolve(id, img);
}); });
img.src = 'data:image/jpeg;base64,' + window.btoa(imageData); img.src = imageUrl;
} }

View File

@ -540,6 +540,39 @@ var tests = [
impact: 'Important', impact: 'Important',
area: 'Core' area: 'Core'
}, },
{
id: 'Worker-transfers',
name: 'Worker can use transfers for postMessage',
run: function () {
if (typeof Worker == 'undefined')
return { output: 'Skipped', emulated: '' };
try {
var worker = new Worker('worker-stub.js');
var promise = new Promise();
var timeout = setTimeout(function () {
promise.resolve({ output: 'Failed', emulated: '?' });
}, 5000);
worker.addEventListener('message', function (e) {
var data = e.data;
if (data.action == 'test-transfers' && data.result)
promise.resolve({ output: 'Success', emulated: '' });
else
promise.resolve({ output: 'Failed', emulated: 'Yes' });
}, false);
var testObj = new Uint8Array([255]);
worker.postMessage({action: 'test-transfers',
data: testObj}, [testObj.buffer]);
return promise;
} catch (e) {
return { output: 'Failed', emulated: 'Yes' };
}
},
impact: 'Normal',
area: 'Core'
},
{ {
id: 'Worker-xhr-response', id: 'Worker-xhr-response',
name: 'XMLHttpRequest supports the reponse property in web workers', name: 'XMLHttpRequest supports the reponse property in web workers',

View File

@ -21,6 +21,9 @@ onmessage = function (e) {
case 'test': case 'test':
postMessage({action: 'test', result: data.data instanceof Uint8Array}); postMessage({action: 'test', result: data.data instanceof Uint8Array});
break; break;
case 'test-transfers':
postMessage({action: 'test-transfers', result: data.data[0] === 255});
break;
case 'xhr': case 'xhr':
var xhr = new XMLHttpRequest(); var xhr = new XMLHttpRequest();
var responseExists = 'response' in xhr; var responseExists = 'response' in xhr;