diff --git a/doc.md b/doc.md index 80d6033..e293e56 100644 --- a/doc.md +++ b/doc.md @@ -1,7 +1,7 @@ # HTML5 Speedtest > by Federico Dossena -> Version 4.5, November 1 2017 +> Version 4.5, November 1, 2017 > [https://github.com/adolfintel/speedtest/](https://github.com/adolfintel/speedtest/) @@ -229,6 +229,9 @@ w.postMessage('start '+JSON.stringify(params)) * Default: `3` * Recommended: `>=1` * Default override: 1 on Firefox if enable_quirks is true +* __xhr_multistreamDelay__: how long should the multiple streams be delayed (in ms) + * Default: `300` + * Recommended: `>=100`, `<=700` * __xhr_ignoreErrors__: how to react to errors in download/upload streams and the ping test * `0`: Fail test on error (behaviour of previous versions of this test) * `1`: Restart a stream/ping when it fails diff --git a/speedtest_worker.js b/speedtest_worker.js index 347754d..7e37e15 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -6,7 +6,7 @@ */ // data reported to main thread -var testStatus = 0 // 0=not started, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort/error +var testStatus = -1 // -1=not started, 0=starting, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort/error var dlStatus = '' // download speed in megabit/s with 2 decimal digits var ulStatus = '' // upload speed in megabit/s with 2 decimal digits var pingStatus = '' // ping in milliseconds with 2 decimal digits @@ -34,6 +34,7 @@ var settings = { url_getIp: 'getIP.php', // path to getIP.php relative to this js file, or a similar thing that outputs the client's ip xhr_dlMultistream: 10, // number of download streams to use (can be different if enable_quirks is active) xhr_ulMultistream: 3, // number of upload streams to use (can be different if enable_quirks is active) + xhr_multistreamDelay: 300, //how much concurrent requests should be delayed xhr_ignoreErrors: 1, // 0=fail on errors, 1=attempt to restart a stream if it fails, 2=ignore all errors xhr_dlUseBlob: false, // if set to true, it reduces ram usage but uses the hard drive (useful with large garbagePhp_chunkSize and/or high xhr_dlMultistream) garbagePhp_chunkSize: 20, // size of chunks sent by garbage.php (can be different if enable_quirks is active) @@ -67,8 +68,8 @@ this.addEventListener('message', function (e) { if (params[0] === 'status') { // return status postMessage(testStatus + ';' + dlStatus + ';' + ulStatus + ';' + pingStatus + ';' + clientIp + ';' + jitterStatus + ';' + dlProgress + ';' + ulProgress + ';' + pingProgress) } - if (params[0] === 'start' && testStatus === 0) { // start new test - testStatus = 1 + if (params[0] === 'start' && testStatus === -1) { // start new test + testStatus = 0 try { // parse settings, if present var s = {} @@ -98,9 +99,6 @@ this.addEventListener('message', function (e) { // edge more precise with 3 download streams settings.xhr_dlMultistream = 3 } - //Edge 15 introduced a bug that causes onprogress events to not get fired, we have to use the "small chunks" workaround that reduces accuracy - //TODO: MOVE OUT OF QUIRKS! - settings.forceIE11Workaround = true } if (/Chrome.(\d+)/i.test(ua) && (!!self.fetch)) { if(typeof s.xhr_dlMultistream === 'undefined'){ @@ -109,6 +107,10 @@ this.addEventListener('message', function (e) { } } } + if (/Edge.(\d+\.\d+)/i.test(ua)) { + //Edge 15 introduced a bug that causes onprogress events to not get fired, we have to use the "small chunks" workaround that reduces accuracy + settings.forceIE11Workaround = true + } //telemetry_level has to be parsed and not just copied if(typeof s.telemetry_level !== 'undefined') settings.telemetry_level = s.telemetry_level === 'basic' ? 1 : s.telemetry_level === 'full' ? 2 : 0; // telemetry level //transform test_order to uppercase, just in case @@ -210,7 +212,7 @@ function dlTest (done) { if (settings.xhr_ignoreErrors === 0) failed=true //abort try { xhr[i].abort() } catch (e) { } delete (xhr[i]) - if (settings.xhr_ignoreErrors === 1) testStream(i, 100) //restart stream after 100ms + if (settings.xhr_ignoreErrors === 1) testStream(i, 0) //restart stream }.bind(this) // send xhr try { if (settings.xhr_dlUseBlob) xhr[i].responseType = 'blob'; else xhr[i].responseType = 'arraybuffer' } catch (e) { } @@ -220,7 +222,7 @@ function dlTest (done) { }.bind(this) // open streams for (var i = 0; i < settings.xhr_dlMultistream; i++) { - testStream(i, 100 * i) + testStream(i, settings.xhr_multistreamDelay * i) } // every 200ms, update dlStatus interval = setInterval(function () { @@ -256,12 +258,14 @@ var r = new ArrayBuffer(1048576) try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { } var req = [] var reqsmall = [] +var reqmedium = [] for (var i = 0; i < 20; i++) req.push(r) req = new Blob(req) r = new ArrayBuffer(262144) try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { } reqsmall.push(r) reqsmall = new Blob(reqsmall) + var ulCalled = false // used to prevent multiple accidental calls to ulTest function ulTest (done) { tlog('ulTest') @@ -292,7 +296,7 @@ function ulTest (done) { // IE11 workarond: xhr.upload does not work properly, therefore we send a bunch of small 256k requests and use the onload event as progress. This is not precise, especially on fast connections xhr[i].onload = function () { tlog('ul stream progress event (ie11wa)') - totLoaded += 262144 + totLoaded += reqsmall.size; testStream(i, 0) } xhr[i].onerror = function () { @@ -301,11 +305,12 @@ function ulTest (done) { if (settings.xhr_ignoreErrors === 0) failed = true //abort try { xhr[i].abort() } catch (e) { } delete (xhr[i]) - if (settings.xhr_ignoreErrors === 1) testStream(i,100); //restart stream after 100ms + if (settings.xhr_ignoreErrors === 1) testStream(i,0); //restart stream } xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching xhr[i].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway) xhr[i].send(reqsmall) + xhr[i].sendT=Date.now() } else { // REGULAR version, no workaround xhr[i].upload.onprogress = function (event) { @@ -327,7 +332,7 @@ function ulTest (done) { if (settings.xhr_ignoreErrors === 0) failed=true //abort try { xhr[i].abort() } catch (e) { } delete (xhr[i]) - if (settings.xhr_ignoreErrors === 1) testStream(i, 100) //restart stream after 100ms + if (settings.xhr_ignoreErrors === 1) testStream(i, 0) //restart stream }.bind(this) // send xhr xhr[i].open('POST', settings.url_ul + url_sep(settings.url_ul) + 'r=' + Math.random(), true) // random string to prevent caching @@ -338,7 +343,7 @@ function ulTest (done) { }.bind(this) // open streams for (var i = 0; i < settings.xhr_ulMultistream; i++) { - testStream(i, 100 * i) + testStream(i, settings.xhr_multistreamDelay * i) } // every 200ms, update ulStatus interval = setInterval(function () { diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js index bede3f5..633341d 100644 --- a/speedtest_worker.min.js +++ b/speedtest_worker.min.js @@ -1 +1 @@ -function tlog(s){log+=Date.now()+": "+s+"\n"}function twarn(s){log+=Date.now()+" WARN: "+s+"\n",console.warn(s)}function url_sep(url){return url.match(/\?/)?"&":"?"}function clearRequests(){if(tlog("stopping pending XHRs"),xhr){for(var i=0;iloadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].onload=function(){tlog("dl stream finished "+i);try{xhr[i].abort()}catch(e){}testStream(i,0)}.bind(this),xhr[i].onerror=function(){tlog("dl stream failed "+i),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this);try{settings.xhr_dlUseBlob?xhr[i].responseType="blob":xhr[i].responseType="arraybuffer"}catch(e){}xhr[i].open("GET",settings.url_dl+url_sep(settings.url_dl)+"r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,!0),xhr[i].send()}}.bind(this),1+delay)}.bind(this),i=0;it))if(graceTimeDone){var speed=totLoaded/(t/1e3);dlStatus=(8*speed*settings.overheadCompensationFactor/(settings.useMebibits?1048576:1e6)).toFixed(2),(t/1e3>settings.time_dl&&dlStatus>0||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest finished "+dlStatus),done())}else t>1e3*settings.time_dlGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0)}.bind(this),200)}}function ulTest(done){if(tlog("ulTest"),!ulCalled){ulCalled=!0;var totLoaded=0,startT=(new Date).getTime(),graceTimeDone=!1,failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(3===testStatus){tlog("ul test stream started "+i+" "+delay);var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x;var ie11workaround;if(settings.forceIE11Workaround)ie11workaround=!0;else try{xhr[i].upload.onprogress,ie11workaround=!1}catch(e){ie11workaround=!0}ie11workaround?(xhr[i].onload=function(){tlog("ul stream progress event (ie11wa)"),totLoaded+=262144,testStream(i,0)},xhr[i].onerror=function(){tlog("ul stream failed (ie11wa)"),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)},xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(reqsmall)):(xhr[i].upload.onprogress=function(event){if(tlog("ul stream progress event "+i+" "+event.loaded),3!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||0>loadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].upload.onload=function(){tlog("ul stream finished "+i),testStream(i,0)}.bind(this),xhr[i].upload.onerror=function(){tlog("ul stream failed "+i),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,100)}.bind(this),xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(req))}}.bind(this),1)}.bind(this),i=0;it))if(graceTimeDone){var speed=totLoaded/(t/1e3);ulStatus=(8*speed*settings.overheadCompensationFactor/(settings.useMebibits?1048576:1e6)).toFixed(2),(t/1e3>settings.time_ul&&ulStatus>0||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest finished "+ulStatus),done())}else t>1e3*settings.time_ulGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0)}.bind(this),200)}}function pingTest(done){if(tlog("pingTest"),!ptCalled){ptCalled=!0;var prevT=null,ping=0,jitter=0,i=0,prevInstspd=0;xhr=[];var doPing=function(){tlog("ping"),pingProgress=i/settings.count_ping,prevT=(new Date).getTime(),xhr[0]=new XMLHttpRequest,xhr[0].onload=function(){if(tlog("pong"),0===i)prevT=(new Date).getTime();else{var instspd=(new Date).getTime()-prevT;if(settings.ping_allowPerformanceApi)try{var p=performance.getEntries();p=p[p.length-1];var d=p.responseStart-p.requestStart;0>=d&&(d=p.duration),d>0&&instspd>d&&(instspd=d)}catch(e){tlog("Performance API not supported, using estimate")}var instjitter=Math.abs(instspd-prevInstspd);1===i?ping=instspd:(ping=.9*ping+.1*instspd,jitter=instjitter>jitter?.2*jitter+.8*instjitter:.9*jitter+.1*instjitter),prevInstspd=instspd}pingStatus=ping.toFixed(2),jitterStatus=jitter.toFixed(2),i++,tlog("PING: "+pingStatus+" JITTER: "+jitterStatus),i1?log:""),xhr.send(fd)}catch(ex){var postData="dl="+encodeURIComponent(dlStatus)+"&ul="+encodeURIComponent(ulStatus)+"&ping="+encodeURIComponent(pingStatus)+"&jitter="+encodeURIComponent(jitterStatus)+"&log="+encodeURIComponent(settings.telemetry_level>1?log:"");xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),xhr.send(postData)}}}var testStatus=0,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",dlProgress=0,ulProgress=0,pingProgress=0,log="",settings={test_order:"ID_U_P",time_ul:15,time_dl:15,time_ulGraceTime:3,time_dlGraceTime:1.5,count_ping:35,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,garbagePhp_chunkSize:20,enable_quirks:!0,ping_allowPerformanceApi:!0,overheadCompensationFactor:1.06,useMebibits:!1,telemetry_level:0,url_telemetry:"telemetry.php"},xhr=null,interval=null,test_pointer=0;this.addEventListener("message",function(e){var params=e.data.split(" ");if("status"===params[0]&&postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus+";"+dlProgress+";"+ulProgress+";"+pingProgress),"start"===params[0]&&0===testStatus){testStatus=1;try{var s={};try{var ss=e.data.substring(5);ss&&(s=JSON.parse(ss))}catch(e){twarn("Error parsing custom settings JSON. Please check your syntax")}for(var key in s)"undefined"!=typeof settings[key]?settings[key]=s[key]:twarn("Unknown setting ignored: "+key);if(settings.enable_quirks||"undefined"!=typeof s.enable_quirks&&s.enable_quirks){var ua=navigator.userAgent;/Firefox.(\d+\.\d+)/i.test(ua)&&("undefined"==typeof s.xhr_ulMultistream&&(settings.xhr_ulMultistream=1),"undefined"==typeof s.test_order&&(settings.test_order="ID_P_U")),/Edge.(\d+\.\d+)/i.test(ua)&&("undefined"==typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=3),settings.forceIE11Workaround=!0),/Chrome.(\d+)/i.test(ua)&&self.fetch&&"undefined"==typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=5)}"undefined"!=typeof s.telemetry_level&&(settings.telemetry_level="basic"===s.telemetry_level?1:"full"===s.telemetry_level?2:0),settings.test_order=settings.test_order.toUpperCase()}catch(e){twarn("Possible error in custom test settings. Some settings may not be applied. Exception: "+e)}tlog(JSON.stringify(settings)),test_pointer=0;var iRun=!1,dRun=!1,uRun=!1,pRun=!1,runNextTest=function(){if(test_pointer>=settings.test_order.length)return testStatus=4,void sendTelemetry();switch(settings.test_order.charAt(test_pointer)){case"I":if(test_pointer++,iRun)return void runNextTest();iRun=!0,getIp(runNextTest);break;case"D":if(test_pointer++,dRun)return void runNextTest();dRun=!0,testStatus=1,dlTest(runNextTest);break;case"U":if(test_pointer++,uRun)return void runNextTest();uRun=!0,testStatus=3,ulTest(runNextTest);break;case"P":if(test_pointer++,pRun)return void runNextTest();pRun=!0,testStatus=2,pingTest(runNextTest);break;case"_":test_pointer++,setTimeout(runNextTest,1e3);break;default:test_pointer++}};runNextTest()}"abort"===params[0]&&(tlog("manually aborted"),clearRequests(),runNextTest=null,interval&&clearInterval(interval),settings.telemetry_level>1&&sendTelemetry(),testStatus=5,dlStatus="",ulStatus="",pingStatus="",jitterStatus="")});var ipCalled=!1,dlCalled=!1,r=new ArrayBuffer(1048576);try{r=new Float32Array(r);for(var i=0;ii;i++)req.push(r);req=new Blob(req),r=new ArrayBuffer(262144);try{r=new Float32Array(r);for(var i=0;iloadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].onload=function(){tlog("dl stream finished "+i);try{xhr[i].abort()}catch(e){}testStream(i,0)}.bind(this),xhr[i].onerror=function(){tlog("dl stream failed "+i),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,0)}.bind(this);try{settings.xhr_dlUseBlob?xhr[i].responseType="blob":xhr[i].responseType="arraybuffer"}catch(e){}xhr[i].open("GET",settings.url_dl+url_sep(settings.url_dl)+"r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,!0),xhr[i].send()}}.bind(this),1+delay)}.bind(this),i=0;it))if(graceTimeDone){var speed=totLoaded/(t/1e3);dlStatus=(8*speed*settings.overheadCompensationFactor/(settings.useMebibits?1048576:1e6)).toFixed(2),(t/1e3>settings.time_dl&&dlStatus>0||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest finished "+dlStatus),done())}else t>1e3*settings.time_dlGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0)}.bind(this),200)}}function ulTest(done){if(tlog("ulTest"),!ulCalled){ulCalled=!0;var totLoaded=0,startT=(new Date).getTime(),graceTimeDone=!1,failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(3===testStatus){tlog("ul test stream started "+i+" "+delay);var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x;var ie11workaround;if(settings.forceIE11Workaround)ie11workaround=!0;else try{xhr[i].upload.onprogress,ie11workaround=!1}catch(e){ie11workaround=!0}ie11workaround?(xhr[i].onload=function(){tlog("ul stream progress event (ie11wa)"),totLoaded+=reqsmall.size,testStream(i,0)},xhr[i].onerror=function(){tlog("ul stream failed (ie11wa)"),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,0)},xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(reqsmall),xhr[i].sendT=Date.now()):(xhr[i].upload.onprogress=function(event){if(tlog("ul stream progress event "+i+" "+event.loaded),3!==testStatus)try{x.abort()}catch(e){}var loadDiff=event.loaded<=0?0:event.loaded-prevLoaded;isNaN(loadDiff)||!isFinite(loadDiff)||0>loadDiff||(totLoaded+=loadDiff,prevLoaded=event.loaded)}.bind(this),xhr[i].upload.onload=function(){tlog("ul stream finished "+i),testStream(i,0)}.bind(this),xhr[i].upload.onerror=function(){tlog("ul stream failed "+i),0===settings.xhr_ignoreErrors&&(failed=!0);try{xhr[i].abort()}catch(e){}delete xhr[i],1===settings.xhr_ignoreErrors&&testStream(i,0)}.bind(this),xhr[i].open("POST",settings.url_ul+url_sep(settings.url_ul)+"r="+Math.random(),!0),xhr[i].setRequestHeader("Content-Encoding","identity"),xhr[i].send(req))}}.bind(this),1)}.bind(this),i=0;it))if(graceTimeDone){var speed=totLoaded/(t/1e3);ulStatus=(8*speed*settings.overheadCompensationFactor/(settings.useMebibits?1048576:1e6)).toFixed(2),(t/1e3>settings.time_ul&&ulStatus>0||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest finished "+ulStatus),done())}else t>1e3*settings.time_ulGraceTime&&(totLoaded>0&&(startT=(new Date).getTime(),totLoaded=0),graceTimeDone=!0)}.bind(this),200)}}function pingTest(done){if(tlog("pingTest"),!ptCalled){ptCalled=!0;var prevT=null,ping=0,jitter=0,i=0,prevInstspd=0;xhr=[];var doPing=function(){tlog("ping"),pingProgress=i/settings.count_ping,prevT=(new Date).getTime(),xhr[0]=new XMLHttpRequest,xhr[0].onload=function(){if(tlog("pong"),0===i)prevT=(new Date).getTime();else{var instspd=(new Date).getTime()-prevT;if(settings.ping_allowPerformanceApi)try{var p=performance.getEntries();p=p[p.length-1];var d=p.responseStart-p.requestStart;0>=d&&(d=p.duration),d>0&&instspd>d&&(instspd=d)}catch(e){tlog("Performance API not supported, using estimate")}var instjitter=Math.abs(instspd-prevInstspd);1===i?ping=instspd:(ping=.9*ping+.1*instspd,jitter=instjitter>jitter?.2*jitter+.8*instjitter:.9*jitter+.1*instjitter),prevInstspd=instspd}pingStatus=ping.toFixed(2),jitterStatus=jitter.toFixed(2),i++,tlog("PING: "+pingStatus+" JITTER: "+jitterStatus),i1?log:""),xhr.send(fd)}catch(ex){var postData="dl="+encodeURIComponent(dlStatus)+"&ul="+encodeURIComponent(ulStatus)+"&ping="+encodeURIComponent(pingStatus)+"&jitter="+encodeURIComponent(jitterStatus)+"&log="+encodeURIComponent(settings.telemetry_level>1?log:"");xhr.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),xhr.send(postData)}}}var testStatus=-1,dlStatus="",ulStatus="",pingStatus="",jitterStatus="",clientIp="",dlProgress=0,ulProgress=0,pingProgress=0,log="",settings={test_order:"ID_U_P",time_ul:15,time_dl:15,time_ulGraceTime:3,time_dlGraceTime:1.5,count_ping:35,url_dl:"garbage.php",url_ul:"empty.php",url_ping:"empty.php",url_getIp:"getIP.php",xhr_dlMultistream:10,xhr_ulMultistream:3,xhr_multistreamDelay:300,xhr_ignoreErrors:1,xhr_dlUseBlob:!1,garbagePhp_chunkSize:20,enable_quirks:!0,ping_allowPerformanceApi:!0,overheadCompensationFactor:1.06,useMebibits:!1,telemetry_level:0,url_telemetry:"telemetry.php"},xhr=null,interval=null,test_pointer=0;this.addEventListener("message",function(e){var params=e.data.split(" ");if("status"===params[0]&&postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus+";"+dlProgress+";"+ulProgress+";"+pingProgress),"start"===params[0]&&-1===testStatus){testStatus=0;try{var s={};try{var ss=e.data.substring(5);ss&&(s=JSON.parse(ss))}catch(e){twarn("Error parsing custom settings JSON. Please check your syntax")}for(var key in s)"undefined"!=typeof settings[key]?settings[key]=s[key]:twarn("Unknown setting ignored: "+key);if(settings.enable_quirks||"undefined"!=typeof s.enable_quirks&&s.enable_quirks){var ua=navigator.userAgent;/Firefox.(\d+\.\d+)/i.test(ua)&&("undefined"==typeof s.xhr_ulMultistream&&(settings.xhr_ulMultistream=1),"undefined"==typeof s.test_order&&(settings.test_order="ID_P_U")),/Edge.(\d+\.\d+)/i.test(ua)&&"undefined"==typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=3),/Chrome.(\d+)/i.test(ua)&&self.fetch&&"undefined"==typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=5)}/Edge.(\d+\.\d+)/i.test(ua)&&(settings.forceIE11Workaround=!0),"undefined"!=typeof s.telemetry_level&&(settings.telemetry_level="basic"===s.telemetry_level?1:"full"===s.telemetry_level?2:0),settings.test_order=settings.test_order.toUpperCase()}catch(e){twarn("Possible error in custom test settings. Some settings may not be applied. Exception: "+e)}tlog(JSON.stringify(settings)),test_pointer=0;var iRun=!1,dRun=!1,uRun=!1,pRun=!1,runNextTest=function(){if(test_pointer>=settings.test_order.length)return testStatus=4,void sendTelemetry();switch(settings.test_order.charAt(test_pointer)){case"I":if(test_pointer++,iRun)return void runNextTest();iRun=!0,getIp(runNextTest);break;case"D":if(test_pointer++,dRun)return void runNextTest();dRun=!0,testStatus=1,dlTest(runNextTest);break;case"U":if(test_pointer++,uRun)return void runNextTest();uRun=!0,testStatus=3,ulTest(runNextTest);break;case"P":if(test_pointer++,pRun)return void runNextTest();pRun=!0,testStatus=2,pingTest(runNextTest);break;case"_":test_pointer++,setTimeout(runNextTest,1e3);break;default:test_pointer++}};runNextTest()}"abort"===params[0]&&(tlog("manually aborted"),clearRequests(),runNextTest=null,interval&&clearInterval(interval),settings.telemetry_level>1&&sendTelemetry(),testStatus=5,dlStatus="",ulStatus="",pingStatus="",jitterStatus="")});var ipCalled=!1,dlCalled=!1,r=new ArrayBuffer(1048576);try{r=new Float32Array(r);for(var i=0;ii;i++)req.push(r);req=new Blob(req),r=new ArrayBuffer(262144);try{r=new Float32Array(r);for(var i=0;i