From fc631af16ee08a93f6a15e6ec33b6a669eed016f Mon Sep 17 00:00:00 2001 From: Piotr Kosko Date: Mon, 16 Nov 2020 09:33:10 +0100 Subject: [PATCH] [Filetransfer] Fixed performance of strict Blob object passing [Bug] Because of need to pass strict Blob object to FormData since Chromium M76, there is a need to increase a performance to pass cordova tests - finish test in timeout. Because of that, xml request creation was moved before reading a file to make possible to trigger abort callback, even when the file is not fully loaded to memory yet. [Verification] Below code (based on failing test) produces times below 600ms. E.g. TEST FINISHED: Time taken: 555 /// , you need to have running http server accepting POST /// requests running on SERVER ip address var SERVER = "http://192.168.0.220:5000"; var persistentRoot; var GRACE_TIME_DELTA = 600; // in milliseconds var DEFAULT_FILESYSTEM_SIZE = 1024 * 50; //filesystem size in bytes var ABORT_DELAY = 100; // for abort() tests window.requestFileSystem(LocalFileSystem.PERSISTENT, DEFAULT_FILESYSTEM_SIZE, function (fileSystem) { persistentRoot = fileSystem.root; runTest(); }, function () { throw new Error('Failed to initialize persistent file system.'); } ); var unexpectedCallbacks = { httpFail: function () { }, httpWin: function () { }, fileSystemFail: function () { }, fileSystemWin: function () { }, fileOperationFail: function () { }, fileOperationWin: function () { }, }; var writeFile = function (fileSystem, name, content, success) { fileSystem.getFile(name, { create: true }, function (fileEntry) { fileEntry.createWriter(function (writer) { writer.onwrite = function () { success(fileEntry); }; writer.onabort = function (evt) { throw new Error('aborted creating test file \'' + name + '\': ' + evt); }; writer.error = function (evt) { throw new Error('aborted creating test file \'' + name + '\': ' + evt); }; if (cordova.platformId === 'browser') { // var builder = new BlobBuilder(); // builder.append(content + '\n'); var blob = new Blob([content + '\n'], { type: 'text/plain' }); writer.write(blob); } else { writer.write(content + "\n"); } }, unexpectedCallbacks.fileOperationFail); }, function () { throw new Error('could not create test file \'' + name + '\''); } ); }; var runTest = function () { transfer = new FileTransfer(); // assign onprogress handler var defaultOnProgressHandler = function (event) { if (event.lengthComputable) { expect(event.loaded).toBeGreaterThan(1); expect(event.total).toBeGreaterThan(0); expect(event.total).not.toBeLessThan(event.loaded); expect(event.lengthComputable).toBe(true, 'lengthComputable'); } else { // In IE, when lengthComputable === false, event.total somehow is equal to 2^64 if (isIE) { expect(event.total).toBe(Math.pow(2, 64)); } else { expect(event.total).toBe(0); } } }; transfer.onprogress = defaultOnProgressHandler; root = persistentRoot; fileName = 'testFile.txt'; localFilePath = root.toURL() + fileName; uploadParams = {}; uploadParams.value1 = "test"; uploadParams.value2 = "param"; uploadOptions = new FileUploadOptions(); uploadOptions.fileKey = "file"; uploadOptions.fileName = fileName; uploadOptions.mimeType = "text/plain"; uploadOptions.params = uploadParams; var fileURL = SERVER + ''; var startTime; var uploadFail = function (e) { console.log('uploadFail - ' + JSON.stringify(e)); console.log('TEST FINISHED: Time taken: ' + (new Date() - startTime)); }; var fileWin = function () { console.log('fileWin'); startTime = +new Date(); transfer.onprogress = (s) => { console.log('upload on progress: ' + JSON.stringify(s) + 'time taken: ' + (new Date() - startTime)) }; console.log('Call upload, time taken: ' + (new Date() - startTime)) transfer.upload(localFilePath, fileURL, (s) => { console.log('uploadWin!!! ' + JSON.stringify(s)); }, uploadFail, uploadOptions, true); console.log('After upload call, time taken: ' + (new Date() - startTime)) setTimeout(function () { console.log('Call abort, time taken: ' + (new Date() - startTime)) transfer.abort(); }, ABORT_DELAY); }; writeFile(root, fileName, new Array(100000).join('aborttest!'), fileWin); } Change-Id: I29717417009dc309456158529cebc85b904e31d3 (cherry picked from commit e6250f7b56a12370ca9b93576d14979fc16c118e) --- .../tizen/FileTransfer.js | 149 +++++++++++---------- 1 file changed, 79 insertions(+), 70 deletions(-) diff --git a/src/lib/plugins/cordova-plugin-file-transfer/tizen/FileTransfer.js b/src/lib/plugins/cordova-plugin-file-transfer/tizen/FileTransfer.js index 2042474..bcf20cf 100755 --- a/src/lib/plugins/cordova-plugin-file-transfer/tizen/FileTransfer.js +++ b/src/lib/plugins/cordova-plugin-file-transfer/tizen/FileTransfer.js @@ -98,86 +98,95 @@ exports = { function successCB(entry) { if (entry.isFile) { - var fullPath = entry.toURL(); - entry.file(function(file) { - function uploadFile(blobFile) { - var fd = new FormData(); + // initialize XMLHTTPRequest (without starting it) + var xhr = uploads[id] = new XMLHttpRequest(); - fd.append(fileKey, blobFile, fileName); + xhr.open(httpMethod, server); - for (var prop in params) { - if(params.hasOwnProperty(prop)) { - fd.append(prop, params[prop]); - } - } - var xhr = uploads[id] = new XMLHttpRequest(); + // Fill XHR headers + for (var header in headers) { + if (headers.hasOwnProperty(header)) { + xhr.setRequestHeader(header, headers[header]); + } + } + + xhr.onload = function(evt) { + + if (xhr.status === 200) { + uploads[id] && delete uploads[id]; + successCallback({ + bytesSent: file.size, + responseCode: xhr.status, + response: xhr.response + }); + } else if (xhr.status === 404) { + fail(FileTransferError.INVALID_URL_ERR, this.status, this.response); + } else { + fail(FileTransferError.CONNECTION_ERR, this.status, this.response); + } + }; - xhr.open(httpMethod, server); + xhr.ontimeout = function(evt) { + fail(FileTransferError.CONNECTION_ERR, this.status, this.response); + }; - // Fill XHR headers - for (var header in headers) { - if (headers.hasOwnProperty(header)) { - xhr.setRequestHeader(header, headers[header]); - } - } + xhr.onerror = function() { + fail(FileTransferError.CONNECTION_ERR, this.status, this.response); + }; - xhr.onload = function(evt) { - if (xhr.status === 200) { - uploads[id] && delete uploads[id]; - successCallback({ - bytesSent: file.size, - responseCode: xhr.status, - response: xhr.response - }); - } else if (xhr.status === 404) { - fail(FileTransferError.INVALID_URL_ERR, this.status, this.response); - } else { - fail(FileTransferError.CONNECTION_ERR, this.status, this.response); - } - }; - - xhr.ontimeout = function(evt) { - fail(FileTransferError.CONNECTION_ERR, this.status, this.response); - }; - - xhr.onerror = function() { - fail(FileTransferError.CONNECTION_ERR, this.status, this.response); - }; - - xhr.onabort = function () { - fail(FileTransferError.ABORT_ERR, this.status, this.response); - }; - - xhr.upload.onprogress = function (e) { - successCallback(e); - }; - - xhr.send(fd); - - // Special case when transfer already aborted, but XHR isn't sent. - // In this case XHR won't fire an abort event, so we need to check if transfers record - // isn't deleted by filetransfer.abort and if so, call XHR's abort method again - if (!uploads[id]) { - xhr.abort(); - } + xhr.onabort = function () { + fail(FileTransferError.ABORT_ERR, this.status, this.response); + }; + + xhr.upload.onprogress = function (e) { + successCallback(e); + }; + // end of XMLHTTPRequest initialization + + var fullPath = entry.toURL(); + var fileHandle; + + function closeHandle() { + if (fileHandle) { + fileHandle.close(); } + } - var fileHandle; - try { - fileHandle = tizen.filesystem.openFile(fullPath, 'r'); - var fileBlob = fileHandle.readBlob(); - uploadFile(fileBlob); - } catch (e) { - fail(FileTransferError.ABORT_ERR, 'Could not read file'); - } finally { - if (fileHandle) { - fileHandle.close(); + function uploadFile(blobFile) { + closeHandle(); + // create FormData + var fd = new FormData(); + fd.append(fileKey, blobFile, fileName); + for (var prop in params) { + if(params.hasOwnProperty(prop)) { + fd.append(prop, params[prop]); } } - }, function(error) { - fail(FileTransferError.CONNECTION_ERR); - }); + // sending already initialized request + xhr.send(fd); + + // Special case when transfer already aborted, but XHR isn't sent. + // In this case XHR won't fire an abort event, so we need to check if transfers record + // isn't deleted by filetransfer.abort and if so, call XHR's abort method again + if (!uploads[id]) { + xhr.abort(); + } + } + + try { + fileHandle = tizen.filesystem.openFile(fullPath, 'r'); + var fileBlob = fileHandle.readBlobNonBlocking( + uploadFile, + function(error) { + closeHandle(); + fail(FileTransferError.ABORT_ERR, 'Could not read file ' + e); + } + ); + } catch (e) { + closeHandle(); + fail(FileTransferError.ABORT_ERR, 'Could not read file ' + e); + } } } -- 2.7.4