diff --git a/doc.md b/doc.md index 7c553429e666ee144ce1d518b53de7947bc49fe4..8afae693e3815ce9c18642eb57f793b7209e63eb 100644 --- a/doc.md +++ b/doc.md @@ -162,6 +162,7 @@ w.postMessage('start {"param1": "value1", "param2": "value2", ...}') * `P`: ping + jitter test * `_`: delay 1 second * Default test order: `ID_U_P` + * Default override: `ID_P_U` on Firefox if enable_quirks is true because Firefox does not stop upload XHRs right away, it takes 2-3 seconds. * __Important:__ Tests can only be run once * __enable_quirks__: enables browser-specific optimizations. These optimizations override some of the default settings. They do not override settings that are explicitly set. * Default: `true` diff --git a/speedtest_worker.js b/speedtest_worker.js index a17201b65b96945df2e43d228133303dc926e24e..5a0169c8880867e6e33f4608790371e009e6df68 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -72,45 +72,44 @@ this.addEventListener('message', function (e) { var ss = e.data.substring(5) if (ss) s = JSON.parse(ss) }catch(e){ twarn('Error parsing custom settings JSON. Please check your syntax') } - if (typeof s.test_order !== 'undefined') settings.test_order = s.test_order.toUpperCase() // test order - if (typeof s.url_dl !== 'undefined') settings.url_dl = s.url_dl // download url - if (typeof s.url_ul !== 'undefined') settings.url_ul = s.url_ul // upload url - if (typeof s.url_ping !== 'undefined') settings.url_ping = s.url_ping // ping url - if (typeof s.url_getIp !== 'undefined') settings.url_getIp = s.url_getIp // url to getIP.php - if (typeof s.time_dl !== 'undefined') settings.time_dl = s.time_dl // duration of download test - if (typeof s.time_ul !== 'undefined') settings.time_ul = s.time_ul // duration of upload test - if (typeof s.enable_quirks !== 'undefined') settings.enable_quirks = s.enable_quirks // enable quirks or not - // quirks for specific browsers. more may be added in future releases - if (settings.enable_quirks) { + //copy custom settings + for(var key in s){ + if(typeof settings[key] !== 'undefined') settings[key]=s[key]; else twarn("Unknown setting ignored: "+key); + } + // quirks for specific browsers. apply only if not overridden. more may be added in future releases + if (settings.enable_quirks||(typeof s.enable_quirks !== 'undefined' && s.enable_quirks)) { var ua = navigator.userAgent if (/Firefox.(\d+\.\d+)/i.test(ua)) { - // ff more precise with 1 upload stream - settings.xhr_ulMultistream = 1 + if(typeof s.xhr_ulMultistream === 'undefined'){ + // ff more precise with 1 upload stream + settings.xhr_ulMultistream = 1 + } + if(typeof s.test_order === 'undefined'){ + // ff more precise if upload test is performed after ping because upload XHRs are not interrupted immediately + settings.test_order= 'ID_P_U' + } } if (/Edge.(\d+\.\d+)/i.test(ua)) { - // edge more precise with 3 download streams - settings.xhr_dlMultistream = 3 + if(typeof s.xhr_dlMultistream === 'undefined'){ + // edge more precise with 3 download streams + settings.xhr_dlMultistream = 3 + } if (/Edge\/15.(\d+)/i.test(ua) || /Edge\/16.(\d+)/i.test(ua)) { //Edge 15 introduced a bug that causes onprogress events to not get fired, so for Edge 15, we have to use the "small chunks" workaround that reduces accuracy settings.forceIE11Workaround = true } } if (/Chrome.(\d+)/i.test(ua) && (!!self.fetch)) { - // chrome more precise with 5 streams - settings.xhr_dlMultistream = 5 + if(typeof s.xhr_dlMultistream === 'undefined'){ + // chrome more precise with 5 streams + settings.xhr_dlMultistream = 5 + } } } - if (typeof s.count_ping !== 'undefined') settings.count_ping = s.count_ping // number of pings for ping test - if (typeof s.xhr_dlMultistream !== 'undefined') settings.xhr_dlMultistream = s.xhr_dlMultistream // number of download streams - if (typeof s.xhr_ulMultistream !== 'undefined') settings.xhr_ulMultistream = s.xhr_ulMultistream // number of upload streams - if (typeof s.xhr_ignoreErrors !== 'undefined') settings.xhr_ignoreErrors = s.xhr_ignoreErrors // what to do in case of errors during the test - if (typeof s.xhr_dlUseBlob !== 'undefined') settings.xhr_dlUseBlob = s.xhr_dlUseBlob // use blob for download test - if (typeof s.garbagePhp_chunkSize !== 'undefined') settings.garbagePhp_chunkSize = s.garbagePhp_chunkSize // size of garbage.php chunks - if (typeof s.time_dlGraceTime !== 'undefined') settings.time_dlGraceTime = s.time_dlGraceTime // dl test grace time before measuring - if (typeof s.time_ulGraceTime !== 'undefined') settings.time_ulGraceTime = s.time_ulGraceTime // ul test grace time before measuring - if (typeof s.overheadCompensationFactor !== 'undefined') settings.overheadCompensationFactor = s.overheadCompensationFactor //custom overhead compensation factor (default assumes HTTP+TCP+IP+ETH with typical MTUs) - if (typeof s.telemetry_level !== 'undefined') settings.telemetry_level = s.telemetry_level === 'basic' ? 1 : s.telemetry_level === 'full' ? 2 : 0; // telemetry level - if (typeof s.url_telemetry !== 'undefined') settings.url_telemetry = s.url_telemetry // url to telemetry.php + //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 + settings.test_order=settings.test_order.toUpperCase(); } catch (e) { twarn('Possible error in custom test settings. Some settings may not be applied. Exception: '+e) } // run the tests tlog(JSON.stringify(settings)) diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js index 3dd6734a82f429a3839534b10ca5045800d86d83..a7673f6c512595b5fac88b053c78ab6a18c08424 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;i<xhr.length;i++){try{xhr[i].onprogress=null,xhr[i].onload=null,xhr[i].onerror=null}catch(e){}try{xhr[i].upload.onprogress=null,xhr[i].upload.onload=null,xhr[i].upload.onerror=null}catch(e){}try{xhr[i].abort()}catch(e){}try{delete xhr[i]}catch(e){}}xhr=null}}function getIp(done){tlog("getIp"),ipCalled||(ipCalled=!0,xhr=new XMLHttpRequest,xhr.onload=function(){tlog("IP: "+xhr.responseText),clientIp=xhr.responseText,done()},xhr.onerror=function(){tlog("getIp failed"),done()},xhr.open("GET",settings.url_getIp+url_sep(settings.url_getIp)+"r="+Math.random(),!0),xhr.send())}function dlTest(done){if(tlog("dlTest"),!dlCalled){dlCalled=!0;var totLoaded=0,startT=(new Date).getTime(),graceTimeDone=!1,failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(1===testStatus){tlog("dl test stream started "+i+" "+delay);var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x,xhr[i].onprogress=function(event){if(tlog("dl stream progress event "+i+" "+event.loaded),1!==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].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;i<settings.xhr_dlMultistream;i++)testStream(i,100*i);interval=setInterval(function(){tlog("DL: "+dlStatus+(graceTimeDone?"":" (in grace time)"));var t=(new Date).getTime()-startT;if(!(200>t))if(graceTimeDone){var speed=totLoaded/(t/1e3);dlStatus=(8*speed*settings.overheadCompensationFactor/1048576).toFixed(2),(t/1e3>settings.time_dl&&dlStatus>0||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),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;i<settings.xhr_ulMultistream;i++)testStream(i,100*i);interval=setInterval(function(){tlog("UL: "+ulStatus+(graceTimeDone?"":" (in grace time)"));var t=(new Date).getTime()-startT;if(!(200>t))if(graceTimeDone){var speed=totLoaded/(t/1e3);ulStatus=(8*speed*settings.overheadCompensationFactor/1048576).toFixed(2),(t/1e3>settings.time_ul&&ulStatus>0||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),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"),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,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),i<settings.count_ping?doPing():done()}.bind(this),xhr[0].onerror=function(){tlog("ping failed"),0===settings.xhr_ignoreErrors&&(pingStatus="Fail",jitterStatus="Fail",clearRequests(),done()),1===settings.xhr_ignoreErrors&&doPing(),2===settings.xhr_ignoreErrors&&(i++,i<settings.count_ping?doPing():done())}.bind(this),xhr[0].open("GET",settings.url_ping+url_sep(settings.url_ping)+"r="+Math.random(),!0),xhr[0].send()}.bind(this);doPing()}}function sendTelemetry(){if(!(settings.telemetry_level<1)){xhr=new XMLHttpRequest,xhr.onload=function(){console.log("TELEMETRY OL "+xhr.responseText)},xhr.onerror=function(){console.log("TELEMETRY ERROR "+xhr)},xhr.open("POST",settings.url_telemetry+"?r="+Math.random(),!0);try{var fd=new FormData;fd.append("dl",dlStatus),fd.append("ul",ulStatus),fd.append("ping",pingStatus),fd.append("jitter",jitterStatus),fd.append("log",settings.telemetry_level>1?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="",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,overheadCompensationFactor:1048576/925e3,telemetry_level:0,url_telemetry:"telemetry.php"},xhr=null,interval=null,delayTimer=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),"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")}if("undefined"!=typeof s.test_order&&(settings.test_order=s.test_order.toUpperCase()),"undefined"!=typeof s.url_dl&&(settings.url_dl=s.url_dl),"undefined"!=typeof s.url_ul&&(settings.url_ul=s.url_ul),"undefined"!=typeof s.url_ping&&(settings.url_ping=s.url_ping),"undefined"!=typeof s.url_getIp&&(settings.url_getIp=s.url_getIp),"undefined"!=typeof s.time_dl&&(settings.time_dl=s.time_dl),"undefined"!=typeof s.time_ul&&(settings.time_ul=s.time_ul),"undefined"!=typeof s.enable_quirks&&(settings.enable_quirks=s.enable_quirks),settings.enable_quirks){var ua=navigator.userAgent;/Firefox.(\d+\.\d+)/i.test(ua)&&(settings.xhr_ulMultistream=1),/Edge.(\d+\.\d+)/i.test(ua)&&(settings.xhr_dlMultistream=3,(/Edge\/15.(\d+)/i.test(ua)||/Edge\/16.(\d+)/i.test(ua))&&(settings.forceIE11Workaround=!0)),/Chrome.(\d+)/i.test(ua)&&self.fetch&&(settings.xhr_dlMultistream=5)}"undefined"!=typeof s.count_ping&&(settings.count_ping=s.count_ping),"undefined"!=typeof s.xhr_dlMultistream&&(settings.xhr_dlMultistream=s.xhr_dlMultistream),"undefined"!=typeof s.xhr_ulMultistream&&(settings.xhr_ulMultistream=s.xhr_ulMultistream),"undefined"!=typeof s.xhr_ignoreErrors&&(settings.xhr_ignoreErrors=s.xhr_ignoreErrors),"undefined"!=typeof s.xhr_dlUseBlob&&(settings.xhr_dlUseBlob=s.xhr_dlUseBlob),"undefined"!=typeof s.garbagePhp_chunkSize&&(settings.garbagePhp_chunkSize=s.garbagePhp_chunkSize),"undefined"!=typeof s.time_dlGraceTime&&(settings.time_dlGraceTime=s.time_dlGraceTime),"undefined"!=typeof s.time_ulGraceTime&&(settings.time_ulGraceTime=s.time_ulGraceTime),"undefined"!=typeof s.overheadCompensationFactor&&(settings.overheadCompensationFactor=s.overheadCompensationFactor),"undefined"!=typeof s.telemetry_level&&(settings.telemetry_level="basic"===s.telemetry_level?1:"full"===s.telemetry_level?2:0),"undefined"!=typeof s.url_telemetry&&(settings.url_telemetry=s.url_telemetry)}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 runNextTest=function(){if(test_pointer>=settings.test_order.length)return testStatus=4,void sendTelemetry();switch(settings.test_order.charAt(test_pointer)){case"I":test_pointer++,getIp(runNextTest);break;case"D":test_pointer++,testStatus=1,dlTest(runNextTest);break;case"U":test_pointer++,testStatus=3,ulTest(runNextTest);break;case"P":test_pointer++,testStatus=2,pingTest(runNextTest);break;case"_":test_pointer++,delayTimer=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;i<r.length;i++)r[i]=Math.random()}catch(e){}for(var req=[],reqsmall=[],i=0;20>i;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=!1,ptCalled=!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;i<xhr.length;i++){try{xhr[i].onprogress=null,xhr[i].onload=null,xhr[i].onerror=null}catch(e){}try{xhr[i].upload.onprogress=null,xhr[i].upload.onload=null,xhr[i].upload.onerror=null}catch(e){}try{xhr[i].abort()}catch(e){}try{delete xhr[i]}catch(e){}}xhr=null}}function getIp(done){tlog("getIp"),ipCalled||(ipCalled=!0,xhr=new XMLHttpRequest,xhr.onload=function(){tlog("IP: "+xhr.responseText),clientIp=xhr.responseText,done()},xhr.onerror=function(){tlog("getIp failed"),done()},xhr.open("GET",settings.url_getIp+url_sep(settings.url_getIp)+"r="+Math.random(),!0),xhr.send())}function dlTest(done){if(tlog("dlTest"),!dlCalled){dlCalled=!0;var totLoaded=0,startT=(new Date).getTime(),graceTimeDone=!1,failed=!1;xhr=[];for(var testStream=function(i,delay){setTimeout(function(){if(1===testStatus){tlog("dl test stream started "+i+" "+delay);var prevLoaded=0,x=new XMLHttpRequest;xhr[i]=x,xhr[i].onprogress=function(event){if(tlog("dl stream progress event "+i+" "+event.loaded),1!==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].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;i<settings.xhr_dlMultistream;i++)testStream(i,100*i);interval=setInterval(function(){tlog("DL: "+dlStatus+(graceTimeDone?"":" (in grace time)"));var t=(new Date).getTime()-startT;if(!(200>t))if(graceTimeDone){var speed=totLoaded/(t/1e3);dlStatus=(8*speed*settings.overheadCompensationFactor/1048576).toFixed(2),(t/1e3>settings.time_dl&&dlStatus>0||failed)&&((failed||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),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;i<settings.xhr_ulMultistream;i++)testStream(i,100*i);interval=setInterval(function(){tlog("UL: "+ulStatus+(graceTimeDone?"":" (in grace time)"));var t=(new Date).getTime()-startT;if(!(200>t))if(graceTimeDone){var speed=totLoaded/(t/1e3);ulStatus=(8*speed*settings.overheadCompensationFactor/1048576).toFixed(2),(t/1e3>settings.time_ul&&ulStatus>0||failed)&&((failed||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),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"),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,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),i<settings.count_ping?doPing():done()}.bind(this),xhr[0].onerror=function(){tlog("ping failed"),0===settings.xhr_ignoreErrors&&(pingStatus="Fail",jitterStatus="Fail",clearRequests(),done()),1===settings.xhr_ignoreErrors&&doPing(),2===settings.xhr_ignoreErrors&&(i++,i<settings.count_ping?doPing():done())}.bind(this),xhr[0].open("GET",settings.url_ping+url_sep(settings.url_ping)+"r="+Math.random(),!0),xhr[0].send()}.bind(this);doPing()}}function sendTelemetry(){if(!(settings.telemetry_level<1)){xhr=new XMLHttpRequest,xhr.onload=function(){console.log("TELEMETRY OL "+xhr.responseText)},xhr.onerror=function(){console.log("TELEMETRY ERROR "+xhr)},xhr.open("POST",settings.url_telemetry+"?r="+Math.random(),!0);try{var fd=new FormData;fd.append("dl",dlStatus),fd.append("ul",ulStatus),fd.append("ping",pingStatus),fd.append("jitter",jitterStatus),fd.append("log",settings.telemetry_level>1?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="",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,overheadCompensationFactor:1048576/925e3,telemetry_level:0,url_telemetry:"telemetry.php"},xhr=null,interval=null,delayTimer=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),"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),(/Edge\/15.(\d+)/i.test(ua)||/Edge\/16.(\d+)/i.test(ua))&&(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 runNextTest=function(){if(test_pointer>=settings.test_order.length)return testStatus=4,void sendTelemetry();switch(settings.test_order.charAt(test_pointer)){case"I":test_pointer++,getIp(runNextTest);break;case"D":test_pointer++,testStatus=1,dlTest(runNextTest);break;case"U":test_pointer++,testStatus=3,ulTest(runNextTest);break;case"P":test_pointer++,testStatus=2,pingTest(runNextTest);break;case"_":test_pointer++,delayTimer=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;i<r.length;i++)r[i]=Math.random()}catch(e){}for(var req=[],reqsmall=[],i=0;20>i;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=!1,ptCalled=!1;