diff --git a/doc.md b/doc.md index 5b67e68..1b08aec 100644 --- a/doc.md +++ b/doc.md @@ -1,7 +1,7 @@ # HTML5 Speedtest > by Federico Dossena -> Version 4.5.3, February 23, 2018 +> Version 4.5.4, March 21, 2018 > [https://github.com/adolfintel/speedtest/](https://github.com/adolfintel/speedtest/) @@ -237,6 +237,10 @@ w.postMessage('start '+JSON.stringify(params)) * Default: `3` * Recommended: `>=1` * Default override: 1 on Firefox if enable_quirks is true +* __xhr_ul_blob_megabytes__: size in megabytes of the blobs sent during the upload test + * Default: `20` + * Default override: 4 on Chromium-based mobile browsers (limitation introduced around version 65). This will be forced + * Default override: IE11 and Edge currently use a different method for the upload test. This parameter is ignored * __xhr_multistreamDelay__: how long should the multiple streams be delayed (in ms) * Default: `300` * Recommended: `>=100`, `<=700` diff --git a/speedtest_worker.js b/speedtest_worker.js index 8ef3063..451000c 100644 --- a/speedtest_worker.js +++ b/speedtest_worker.js @@ -1,5 +1,5 @@ /* - HTML5 Speedtest v4.5.3 + HTML5 Speedtest v4.5.4 by Federico Dossena https://github.com/adolfintel/speedtest/ GNU LGPLv3 License @@ -39,6 +39,7 @@ var settings = { 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) + xhr_ul_blob_megabytes: 20, //size in megabytes of the upload blobs sent in the upload test (forced to 4 on chrome mobile) garbagePhp_chunkSize: 20, // size of chunks sent by garbage.php (can be different if enable_quirks is active) enable_quirks: true, // enable quirks for specific browsers. currently it overrides settings to optimize for specific browsers, unless they are already being overridden with the start command ping_allowPerformanceApi: true, // if enabled, the ping test will attempt to calculate the ping more precisely using the Performance API. Currently works perfectly in Chrome, badly in Edge, and not at all in Firefox. If Performance API is not supported or the result is obviously wrong, a fallback is provided. @@ -109,6 +110,10 @@ this.addEventListener('message', function (e) { //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 } + if (/Chrome.(\d+)/i.test(ua)&&/Android|iPhone|iPad|iPod|Windows Phone/i.test(ua)){ //cheap af + //Chrome mobile introduced a limitation somewhere around version 65, we have to limit XHR upload size to 4 megabytes + settings.xhr_ul_blob_megabytes=4; + } //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 @@ -252,22 +257,22 @@ function dlTest (done) { }.bind(this), 200) } // upload test, calls done function whent it's over -// garbage data for upload test -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 = [] -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') if (ulCalled) return; else ulCalled = true // ulTest already called? +// garbage data for upload test + 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 = [] + for (var i = 0; i < settings.xhr_ul_blob_megabytes; 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 totLoaded = 0.0, // total number of transmitted bytes startT = new Date().getTime(), // timestamp when test was started graceTimeDone = false, //set to true after the grace time is past diff --git a/speedtest_worker.min.js b/speedtest_worker.min.js index d4e3668..ba2b216 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,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].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:"IP_D_U",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",getIp_ispInfo:!0,getIp_ispInfo_distance:"km",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),/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(5!=testStatus){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=settings.test_order.length)return testStatus=4,void sendTelemetry();switch(settings.test_order.charAt(test_pointer)){case"I":if(test_pointer++,l)return void g();l=!0,getIp(g);break;case"D":if(test_pointer++,a)return void g();a=!0,testStatus=1,dlTest(g);break;case"U":if(test_pointer++,o)return void g();o=!0,testStatus=3,ulTest(g);break;case"P":if(test_pointer++,u)return void g();u=!0,testStatus=2,pingTest(g);break;case"_":test_pointer++,setTimeout(g,1e3);break;default:test_pointer++}}};g()}"abort"===e[0]&&(tlog("manually aborted"),clearRequests(),g=null,interval&&clearInterval(interval),settings.telemetry_level>1&&sendTelemetry(),testStatus=5,dlStatus="",ulStatus="",pingStatus="",jitterStatus="")});var ipCalled=!1;function getIp(t){tlog("getIp"),ipCalled||(ipCalled=!0,(xhr=new XMLHttpRequest).onload=function(){tlog("IP: "+xhr.responseText),clientIp=xhr.responseText,t()},xhr.onerror=function(){tlog("getIp failed"),t()},xhr.open("GET",settings.url_getIp+url_sep(settings.url_getIp)+(settings.getIp_ispInfo?"isp=true"+(settings.getIp_ispInfo_distance?"&distance="+settings.getIp_ispInfo_distance+"&":"&"):"&")+"r="+Math.random(),!0),xhr.send())}var dlCalled=!1;function dlTest(t){if(tlog("dlTest"),!dlCalled){dlCalled=!0;var e=0,r=(new Date).getTime(),s=!1,n=!1;xhr=[];for(var i=function(t,r){setTimeout(function(){if(1===testStatus){tlog("dl test stream started "+t+" "+r);var s=0,l=new XMLHttpRequest;xhr[t]=l,xhr[t].onprogress=function(r){if(tlog("dl stream progress event "+t+" "+r.loaded),1!==testStatus)try{l.abort()}catch(t){}var n=r.loaded<=0?0:r.loaded-s;isNaN(n)||!isFinite(n)||n<0||(e+=n,s=r.loaded)}.bind(this),xhr[t].onload=function(){tlog("dl stream finished "+t);try{xhr[t].abort()}catch(t){}i(t,0)}.bind(this),xhr[t].onerror=function(){tlog("dl stream failed "+t),0===settings.xhr_ignoreErrors&&(n=!0);try{xhr[t].abort()}catch(t){}delete xhr[t],1===settings.xhr_ignoreErrors&&i(t,0)}.bind(this);try{settings.xhr_dlUseBlob?xhr[t].responseType="blob":xhr[t].responseType="arraybuffer"}catch(t){}xhr[t].open("GET",settings.url_dl+url_sep(settings.url_dl)+"r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,!0),xhr[t].send()}}.bind(this),1+r)}.bind(this),l=0;lsettings.time_dl&&dlStatus>0||n)&&((n||isNaN(dlStatus))&&(dlStatus="Fail"),clearRequests(),clearInterval(interval),dlProgress=1,tlog("dlTest finished "+dlStatus),t())):i>1e3*settings.time_dlGraceTime&&(e>0&&(r=(new Date).getTime(),e=0),s=!0))}.bind(this),200)}}var ulCalled=!1;function ulTest(t){if(tlog("ulTest"),!ulCalled){ulCalled=!0;var e=new ArrayBuffer(1048576);try{e=new Float32Array(e);for(var r=0;rsettings.time_ul&&ulStatus>0||o)&&((o||isNaN(ulStatus))&&(ulStatus="Fail"),clearRequests(),clearInterval(interval),ulProgress=1,tlog("ulTest finished "+ulStatus),t())):e>1e3*settings.time_ulGraceTime&&(i>0&&(l=(new Date).getTime(),i=0),a=!0))}.bind(this),200)}}var ptCalled=!1;function pingTest(t){if(tlog("pingTest"),!ptCalled){ptCalled=!0;var e=null,r=0,s=0,n=0,i=0;xhr=[];var l=function(){tlog("ping"),pingProgress=n/settings.count_ping,e=(new Date).getTime(),xhr[0]=new XMLHttpRequest,xhr[0].onload=function(){if(tlog("pong"),0===n)e=(new Date).getTime();else{var a=(new Date).getTime()-e;if(settings.ping_allowPerformanceApi)try{var o=performance.getEntries(),u=(o=o[o.length-1]).responseStart-o.requestStart;u<=0&&(u=o.duration),u>0&&us?.2*s+.8*g:.9*s+.1*g),i=a}pingStatus=r.toFixed(2),jitterStatus=s.toFixed(2),n++,tlog("PING: "+pingStatus+" JITTER: "+jitterStatus),n1?log:""),xhr.send(t)}catch(t){var e="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(e)}}} \ No newline at end of file