From ceec0fce2acb7caba028d86becceb8b86cef6bee Mon Sep 17 00:00:00 2001 From: Igor Zhukov Date: Mon, 27 Oct 2014 20:18:45 +0300 Subject: [PATCH] Performance improvements Supported SSL. Closes #71 Added setZeroTimeout. Now downloading files when tab in background and improved performance Multiple file parts download in parallel TLDeserialization.fetchBytes returns typed array --- app/js/lib/bin_utils.js | 3 +- app/js/lib/mtproto.js | 54 ++++++++++++++++++++++++----------- app/js/lib/mtproto_wrapper.js | 2 +- app/js/lib/ng_utils.js | 12 ++++---- app/js/lib/polyfill.js | 35 ++++++++++++++++++++++- app/js/lib/tl_utils.js | 6 ++-- 6 files changed, 81 insertions(+), 31 deletions(-) diff --git a/app/js/lib/bin_utils.js b/app/js/lib/bin_utils.js index e3b82e86..709c0e53 100644 --- a/app/js/lib/bin_utils.js +++ b/app/js/lib/bin_utils.js @@ -161,7 +161,8 @@ function convertToArrayBuffer(bytes) { if (bytes instanceof ArrayBuffer) { return bytes; } - if (bytes.buffer !== undefined) { + if (bytes.buffer !== undefined && + bytes.buffer.byteLength == bytes.length * bytes.BYTES_PER_ELEMENT) { return bytes.buffer; } return bytesToArrayBuffer(bytes); diff --git a/app/js/lib/mtproto.js b/app/js/lib/mtproto.js index 10c3fce6..35815532 100644 --- a/app/js/lib/mtproto.js +++ b/app/js/lib/mtproto.js @@ -10,17 +10,27 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) .factory('MtpDcConfigurator', function () { var dcOptions = Config.Modes.test ? [ - {id: 1, host: '173.240.5.253', port: 80}, - {id: 2, host: '149.154.167.40', port: 80}, - {id: 3, host: '174.140.142.5', port: 80} + {id: 1, url: 'http://173.240.5.253'}, + {id: 2, url: 'http://149.154.167.40'}, + {id: 3, url: 'http://174.140.142.5'} + ] + : (location.protocol == 'https:' + ? [ + {id: 1, url: 'https://pluto.web.telegram.org'}, + {id: 2, url: 'https://venus.web.telegram.org'}, + {id: 3, url: 'https://aurora.web.telegram.org'}, + {id: 4, url: 'https://vesta.web.telegram.org'}, + {id: 5, url: 'https://flora.web.telegram.org'} ] : [ - {id: 1, host: '173.240.5.1', port: 80}, - {id: 2, host: '149.154.167.51', port: 80}, - {id: 3, host: '174.140.142.6', port: 80}, - {id: 4, host: '149.154.167.91', port: 80}, - {id: 5, host: '149.154.171.5', port: 80} - ]; + {id: 1, url: 'http://173.240.5.1'}, + {id: 2, url: 'http://149.154.167.51'}, + {id: 3, url: 'http://174.140.142.6'}, + {id: 4, url: 'http://149.154.167.91'}, + {id: 5, url: 'http://149.154.171.5'} + ]); + + var sslSubdomains = 'pluto,venus,aurora,vesta,flora'; var chosenServers = {}; @@ -31,7 +41,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) for (i = 0; i < dcOptions.length; i++) { dcOption = dcOptions[i]; if (dcOption.id == dcID) { - chosenServer = dcOption.host + ':' + dcOption.port; + chosenServer = dcOption.url; } } chosenServers[dcID] = chosenServer; @@ -197,7 +207,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) var requestData = xhrSendBuffer ? resultBuffer : resultArray, requestPromise; try { - requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(dcID) + '/apiw1', requestData, { + requestPromise = $http.post(MtpDcConfigurator.chooseServer(dcID) + '/apiw1', requestData, { responseType: 'arraybuffer', transformRequest: null }); @@ -791,7 +801,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) longPoll: true }).then(function () { delete self.longPollPending; - $timeout(self.checkLongPoll.bind(self), 0); + setZeroTimeout(self.checkLongPoll.bind(self)); }, function () { console.log('Long-poll failed'); }); @@ -948,7 +958,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) MtpNetworker.prototype.performSheduledRequest = function() { - // console.trace('sheduled', this.dcID, this.iii); + // console.log(dT(), 'sheduled', this.dcID, this.iii); if (this.offline) { console.log(dT(), 'Cancel sheduled'); return false; @@ -1126,7 +1136,9 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) }; MtpNetworker.prototype.getDecryptedMessage = function (msgKey, encryptedData) { + // console.log(dT(), 'get decrypted start'); return this.getMsgKeyIv(msgKey, false).then(function (keyIv) { + // console.log(dT(), 'after msg key iv'); return CryptoWorker.aesDecrypt(encryptedData, keyIv[0], keyIv[1]); }); }; @@ -1162,7 +1174,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) responseType: 'arraybuffer', transformRequest: null }); - requestPromise = $http.post('http://' + MtpDcConfigurator.chooseServer(self.dcID) + '/apiw1', requestData, options); + requestPromise = $http.post(MtpDcConfigurator.chooseServer(self.dcID) + '/apiw1', requestData, options); } catch (e) { requestPromise = $q.reject(e); } @@ -1206,6 +1218,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) encryptedData = deserializer.fetchRawBytes(responseBuffer.byteLength - deserializer.getOffset(), true, 'encrypted_data'); return this.getDecryptedMessage(msgKey, encryptedData).then(function (dataWithPadding) { + // console.log(dT(), 'after decrypt'); var deserializer = new TLDeserialization(dataWithPadding, {mtproto: true}); var salt = deserializer.fetchIntBytes(64, false, 'salt'); @@ -1216,6 +1229,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) var messageBody = deserializer.fetchRawBytes(false, true, 'message_data'); + // console.log(dT(), 'before hash'); var hashData = convertToUint8Array(dataWithPadding).subarray(0, deserializer.getOffset()); return CryptoWorker.sha1Hash(hashData).then(function (dataHash) { @@ -1223,6 +1237,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) console.warn(msgKey, bytesFromArrayBuffer(dataHash)); throw new Error('server msgKey mismatch'); } + // console.log(dT(), 'after hash check'); var buffer = bytesToArrayBuffer(messageBody); var deserializerOptions = { @@ -1243,7 +1258,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) } if (this.offset != offset + result.bytes) { console.warn(dT(), 'set offset', this.offset, offset, result.bytes); - console.log(dT(), result); + // console.log(dT(), result); this.offset = offset + result.bytes; } // console.log(dT(), 'override message', result); @@ -1262,6 +1277,7 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) var deserializer = new TLDeserialization(buffer, deserializerOptions); var response = deserializer.fetchObject('', 'INPUT'); + // console.log(dT(), 'after fetch'); return { response: response, @@ -1294,12 +1310,16 @@ angular.module('izhukov.mtproto', ['izhukov.utils']) return false; } - // console.log('shedule req', delay); + // console.log(dT(), 'shedule req', delay); // console.trace(); $timeout.cancel(this.nextReqPromise); + if (delay > 0) { + this.nextReqPromise = $timeout(this.performSheduledRequest.bind(this), delay || 0); + } else { + setZeroTimeout(this.performSheduledRequest.bind(this)) + } - this.nextReqPromise = $timeout(this.performSheduledRequest.bind(this), delay || 0); this.nextReq = nextReq; }; diff --git a/app/js/lib/mtproto_wrapper.js b/app/js/lib/mtproto_wrapper.js index 104e9f1e..bf6ca356 100644 --- a/app/js/lib/mtproto_wrapper.js +++ b/app/js/lib/mtproto_wrapper.js @@ -464,7 +464,7 @@ angular.module('izhukov.mtproto.wrapper', ['izhukov.utils', 'izhukov.mtproto']) fileDownload: true, createNetworker: true }); - }, 6).then(function (result) { + }, 2).then(function (result) { writeFilePromise.then(function () { if (canceled) { return $q.when(); diff --git a/app/js/lib/ng_utils.js b/app/js/lib/ng_utils.js index 8bbd2d80..5e7c81a9 100644 --- a/app/js/lib/ng_utils.js +++ b/app/js/lib/ng_utils.js @@ -33,7 +33,7 @@ angular.module('izhukov.utils', []) }) -.service('FileManager', function ($window, $timeout, $q) { +.service('FileManager', function ($window, $q) { $window.URL = $window.URL || $window.webkitURL; $window.BlobBuilder = $window.BlobBuilder || $window.WebKitBlobBuilder || $window.MozBlobBuilder; @@ -98,10 +98,10 @@ angular.module('izhukov.utils', []) else { try { var blob = blobConstruct([bytesToArrayBuffer(bytes)]); + fileWriter.write(blob); } catch (e) { deferred.reject(e); } - fileWriter.write(blob); } return deferred.promise; @@ -150,7 +150,7 @@ angular.module('izhukov.utils', []) return false; } blobParts.push(blob); - $timeout(function () { + setZeroTimeout(function () { if (fakeFileWriter.onwriteend) { fakeFileWriter.onwriteend(); } @@ -493,7 +493,7 @@ angular.module('izhukov.utils', []) finalizeTask = function (taskID, result) { var deferred = awaiting[taskID]; if (deferred !== undefined) { - console.log(dT(), 'CW done'); + // console.log(dT(), 'CW done'); deferred.resolve(result); delete awaiting[taskID]; } @@ -524,7 +524,7 @@ angular.module('izhukov.utils', []) } function performTaskWorker (task, params, embed) { - console.log(dT(), 'CW start', task); + // console.log(dT(), 'CW start', task); var deferred = $q.defer(); awaiting[taskID] = deferred; @@ -562,7 +562,6 @@ angular.module('izhukov.utils', []) }, aesEncrypt: function (bytes, keyBytes, ivBytes) { if (aesNaClEmbed) { - // aesEncryptSync(bytes, keyBytes, ivBytes); return performTaskWorker('aes-encrypt', { bytes: addPadding(convertToArrayBuffer(bytes)), keyBytes: convertToArrayBuffer(keyBytes), @@ -575,7 +574,6 @@ angular.module('izhukov.utils', []) }, aesDecrypt: function (encryptedBytes, keyBytes, ivBytes) { if (aesNaClEmbed) { - // aesDecryptSync(encryptedBytes, keyBytes, ivBytes); return performTaskWorker('aes-decrypt', { encryptedBytes: addPadding(convertToArrayBuffer(encryptedBytes)), keyBytes: convertToArrayBuffer(keyBytes), diff --git a/app/js/lib/polyfill.js b/app/js/lib/polyfill.js index fcb51ab9..30e9e8ab 100644 --- a/app/js/lib/polyfill.js +++ b/app/js/lib/polyfill.js @@ -95,4 +95,37 @@ if (!Function.prototype.bind) { return fBound; }; -} \ No newline at end of file +} + +/* setZeroTimeout polyfill, from http://dbaron.org/log/20100309-faster-timeouts */ +(function(global) { + var timeouts = []; + var messageName = 'zero-timeout-message'; + + function setZeroTimeout(fn) { + timeouts.push(fn); + global.postMessage(messageName, '*'); + } + + function handleMessage(event) { + if (event.source == global && event.data == messageName) { + event.stopPropagation(); + if (timeouts.length > 0) { + var fn = timeouts.shift(); + fn(); + } + } + } + + global.addEventListener('message', handleMessage, true); + + var originalSetTimeout = global.setTimeout; + global.setTimeout = function (callback, delay) { + if (!delay || delay <= 5) { + return setZeroTimeout(callback); + } + return originalSetTimeout(callback, delay); + }; + + global.setZeroTimeout = setZeroTimeout; +})(this); \ No newline at end of file diff --git a/app/js/lib/tl_utils.js b/app/js/lib/tl_utils.js index b9feb8ed..3c92adb0 100644 --- a/app/js/lib/tl_utils.js +++ b/app/js/lib/tl_utils.js @@ -402,10 +402,8 @@ TLDeserialization.prototype.fetchBytes = function (field) { (this.byteView[this.offset++] << 16); } - var bytes = []; - for (var i = 0; i < len; i++) { - bytes.push(this.byteView[this.offset++]); - } + var bytes = this.byteView.subarray(this.offset, this.offset + len); + this.offset += len; // Padding while (this.offset % 4) {