improvements and new example (#29)

Merge with #29. Using standard js code style (ugh...); rewritten documentation in markdown; Added example6
This commit is contained in:
anoy 2017-05-15 12:02:32 +02:00 committed by Federico Dossena
parent 0dd0eeaa86
commit 3f06ab1381
12 changed files with 1365 additions and 976 deletions

18
.editorconfig Normal file
View file

@ -0,0 +1,18 @@
# EditorConfig is awesome: http://EditorConfig.org
root = true
[*]
end_of_line = lf
trim_trailing_whitespace = true
insert_final_newline = true
indent_style = space
indent_size = 4
charset = utf-8
[*.js]
indent_size = 2
indent_style = space
[*.md]
trim_trailing_whitespace = false

239
doc.md Normal file
View file

@ -0,0 +1,239 @@
# HTML5 Speedtest
> by Federico Dossena
> Version 4.2, April 26 2017
> [https://github.com/adolfintel/speedtest/](https://github.com/adolfintel/speedtest/)
## Introduction
In this document, we will introduce an XHR based HTML5 Speedtest and see how to use it.
This test measures download speed, upload speed, ping and jitter.
First of all, the requirements to run this test:
* The browser have to support XHR Level 2 and Web Workers and Javascript must be enabled.
* Internet Explorer 11
* Microsoft Edge 12+
* Mozilla Firefox 12+
* Google Chrome / Chromium 31+
* Apple Safari 7.1+
* Opera 18+
* Client side, the test can use up to 500 megabytes of RAM
* Server side, you'll need a fast connection (at least 100 Mbps recommended), and the web server must accept large POST requests (up to 20 megabytes).
Apache2 and PHP are recommended, but not mandatory.
If this looks good, let's proceed and see how to use the test.
## Installation
To install the test on your server, upload the following files:
* `speedtest_worker.min.js`
* `garbage.php`
* `getIP.php`
* `empty.dat`
You may also want to upload one of the examples to test it.
Later we'll see how to use the test without PHP.
__Important:__ keep all the files together; all paths are relative to the js file
## Usage
To run the test, you need to do 3 things:
* Create the worker
* Write some code that handles the responses coming from the worker
* Start the test
### Creating the worker
```js
var w = new Worker("speedtest_worker.min.js")
```
__Important:__ use the minified version, it's smaller!
### Response handler
First, we set up a timer that fetches the status of the worker continuously:
```js
var timer = setInterval(function () {
w.postMessage('status')
}, 100)
```
Then we write a response handler that receives the status and updates the page. Later
we'll see the details of the format of the response.
```js
w.onmessage = function (event) {
var data = event.data.split(';')
var testState = data[0]
var dlStatus = data[1]
var ulStatus = data[2]
var pingStatus = data[3]
var jitterStatus = data[5]
var clientIp = data[4]
if (testState >= 4) {
clearInterval(timer) // test is finished or aborted
}
// .. update your page here ..
}
```
#### Response format
The response from the worker is composed of values separated by `;` (semicolon) in this
format:
`testState;dlStatus;ulStatus;pingStatus;clientIp;jitterStatus`
* __testState__ is an integer 0-5
* `0` = Test starting
* `1` = Download test in progress
* `2` = Ping + Jitter test in progress
* `3` = Upload test in progress
* `4` = Test finished
* `5` = Test aborted
* __dlStatus__ is either
* Empty string (not started or aborted)
* Download speed in Megabit/s as a number with 2 digits
* The string "Fail" (test failed)
* __ulStatus__ is either
* Empty string (not started or aborted)
* Upload speed in Megabit/s as a number with 2 digits
* The string "Fail" (test failed)
* __pingStatus__ is either
* Empty string (not started or aborted)
* Estimated ping in milliseconds as a number with 2 digits
* The string "Fail" (test failed)
* __clientIp__ is either
* Empty string (not fetched yet or failed)
* The client's IP address as a string
* __jitterStatus__ is either
* Empty string (not started or aborted)
* Estimated jitter in milliseconds as a number with 2 digits (lower = stable connection)
* The string "Fail" (test failed)
### Starting the test
To start the test, send the start command to the worker:
```js
w.postMessage('start')
```
This starts the test with the default settings, which is usually the best choice. If you want, you can change these settings and pass them to the worker as JSON with like this:
```js
w.postMessage('start {"param1": "value1", "param2": "value2", ...}')
```
#### Test parameters
* __time_dl__: How long the download test should be in seconds
* Default: `15`
* Recommended: `>=5`
* __time_ul__: How long the upload test should be in seconds
* Default: `15`
* Recommended: `>=10`
* __count_ping__: How many pings to perform in the ping test
* Default: `35`
* Recommended: `>=20`
* __url_dl__: path to garbage.php or a large file to use for the download test
* Default: `garbage.php`
* __Important:__ path is relative to js file
* __url_ul__: path to ab empty file or empty.dat to use for the upload test
* Default: `empty.dat`
* __Important:__ path is relative to js file
* __url_ping__: path to an empty file or empty.dat to use for the ping test
* Default: `empty.dat`
* __Important:__ path is relative to js file
* __url_getIp__: path to getIP.php or replacement
* Default: `getIP.php`
* __Important:__ path is relative to js file
* __enable_quirks__: enables browser-specific optimizations. These optimizations override some of the default settings below. They do not override settings that are explicitly set.
* Default: `true`
* __garbagePhp_chunkSize__: size of chunks sent by garbage.php in megabytes
* Default: `20`
* Recommended: `>=10`
* Default override: 5 on Safari if enable_quirks is true
* __xhr_dlMultistream__: how many streams should be opened for the download test
* Default: `10`
* Recommended: `>=3`
* Default override: 3 on Edge if enable_quirks is true
* Default override: 5 on Chromium-based if enable_quirks is true
* __xhr_ulMultistream__: how many streams should be opened for the upload test
* Default: `3`
* Recommended: `>=1`
* Default override: 1 on Firefox if enable_quirks is true
* Default override: 10 on Safari if enable_quirks is true
* __allow_fetchAPI__: allow the use of Fetch API for the download test instead of regular XHR. Experimental, not recommended.
* Default: `false`
* __force_fetchAPI__: forces the use of Fetch API on all browsers that support it
* Default: `false`
Fetch API are used if the following conditions are met:
* allow_fetchAPI is true
* Chromium-based browser with support for Fetch API and enable_quirks is true
OR force_fetchAPI is true and the browser supports Fetch API
### Aborting the test prematurely
The test can be aborted at any time by sending an abort command to the worker:
```js
w.postMessage('abort')
```
This will terminate all network activity and stop the worker.
__Important:__ do not simply kill the worker while it's running, as it will leave pending XHR requests!
## Using the test without PHP
If your server does not support PHP, or you're using something newer like Node.js, you can still use this test by replacing `garbage.php` and `getIP.php`.
### Replacements
#### Replacement for `garbage.php`
A replacement for `garbage.php` must generate incompressible garbage data.
A large file (10-100 Mbytes) is a possible replacement. You can get [one here](http://downloads.fdossena.com/geth.php?r=speedtest-bigfile).
If you're using Node.js or some other server, your replacement should accept the `ckSize` parameter (via GET) which tells it how many megabytes of garbage to generate.
It is important here to turn off compression, and generate incompressible data.
A symlink to `/dev/urandom` is also ok.
#### Replacement for `getIP.php`
Your replacement must simply respond with the client's IP as plaintext. Nothing fancy.
### JS
You need to start the test with your replacements like this:
```js
w.postMessage('start {"url_dl": "newGarbageURL", "url_getIp": "newIpURL"}')
```
## Known bugs and limitations
* __Chrome:__ high CPU usage from XHR requests with very fast connections (like gigabit).
For this reason, the test may report inaccurate results if your CPU is too slow. (Does not affect most computers)
* __IE11:__ the upload test is not precise on very fast connections
* __Safari:__ works, but needs more testing and tweaking for very fast connections
## Making changes
Since this is an open source project, you can modify it.
To make changes to the speedtest itself, edit `speedtest_worker.js`
To create the minified version, use UglifyJS like this:
```
uglifyjs -c --screw-ie8 speedtest_worker.js > speedtest_worker.min.js
```
Pull requests are much appreciated. If you don't use github (or git), simply contact me.
__Important:__ please add your name to modified versions to distinguish them from the main project.
## License
This software is under the GNU LGPL license, Version 3 or newer.
To put it short: you are free to use, study, modify, and redistribute this software and modified versions of it, for free or for money.
You can also use it in proprietary software but all changes to this software must remain under the same GNU LGPL license.

BIN
doc.pdf

Binary file not shown.

View file

@ -1,29 +1,35 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Speedtest</title> <title>Speedtest</title>
</head> </head>
<body> <body>
<h1>Speedtest</h1> <h1>Speedtest</h1>
<h4>IP Address</h4>
<div id="ip"></div> <h4>IP Address</h4>
<h4>Download</h4> <p id="ip"></p>
<div id="download"></div>
<h4>Upload</h4> <h4>Download</h4>
<div id="upload"></div> <p id="download"></p>
<h4>Latency</h4>
<div id="ping"></div> <h4>Upload</h4>
<script type="text/javascript"> <p id="upload"></p>
var w=new Worker("speedtest_worker.min.js"); //create new worker
setInterval(function(){w.postMessage("status");}.bind(this),100); //ask for status every 100ms <h4>Latency</h4>
w.onmessage=function(event){ //when status is received, split the string and put the values in the appropriate fields <p id="ping"></p>
var data=event.data.split(";"); //string format: status;download;upload;ping (speeds are in mbit/s) (status: 0=not started, 1=downloading, 2=uploading, 3=ping, 4=done, 5=aborted)
document.getElementById("download").innerHTML=data[1]+" Mbit/s"; <script type="text/javascript">
document.getElementById("upload").innerHTML=data[2]+" Mbit/s"; var w = new Worker('speedtest_worker.min.js') // create new worker
document.getElementById("ping").innerHTML=data[3]+" ms, "+data[5]+" ms jitter"; setInterval(function () { w.postMessage('status') }, 100) // ask for status every 100ms
document.getElementById("ip").innerHTML=data[4]; w.onmessage = function (event) { // when status is received, split the string and put the values in the appropriate fields
var data = event.data.split(';') // string format: status;download;upload;ping (speeds are in mbit/s) (status: 0=not started, 1=downloading, 2=uploading, 3=ping, 4=done, 5=aborted)
document.getElementById('download').textContent = data[1] + ' Mbit/s'
document.getElementById('upload').textContent = data[2] + ' Mbit/s'
document.getElementById('ping').textContent = data[3] + ' ms, ' + data[5] + ' ms jitter'
document.getElementById('ip').textContent = data[4]
} }
w.postMessage("start"); //start the speedtest (default params. keep garbage.php and empty.dat in the same directory as the js file) w.postMessage('start') // start the speedtest (default params. keep garbage.php and empty.dat in the same directory as the js file)
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,38 +1,45 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Speedtest</title> <title>Speedtest</title>
<style type="text/css"> <style type="text/css">
html,body{ html,
margin:0; body {
padding:0; margin: 0;
border:none; padding: 0;
text-align:center; border: none;
} text-align: center;
div.test{
display:inline-block;
margin:1em;
text-align:center;
}
div.testName,div.meterUnit{
font-size:3vw;
}
div.meter{
font-size:6vw;
line-height:1.5em;
height:1.5em !important;
}
.flash{
animation:flash 0.6s linear infinite;
}
@keyframes flash{
0%{opacity:0.6;}
50%{opacity:1;}
} }
div.test {
display: inline-block;
margin: 1em;
text-align: center;
}
div.testName,
div.meterUnit {
font-size: 3vw;
}
div.meter {
font-size: 6vw;
line-height: 1.5em;
height: 1.5em !important;
}
.flash {
animation: flash 0.6s linear infinite;
}
@keyframes flash {
0% { opacity: 0.6; }
50% { opacity: 1; }
}
</style> </style>
</head> </head>
<body>
<body>
<h1>Speedtest</h1> <h1>Speedtest</h1>
<div class="test"> <div class="test">
<div class="testName">Download</div> <div class="testName">Download</div>
@ -56,23 +63,29 @@
</div> </div>
<div id="ip"></div> <div id="ip"></div>
<script type="text/javascript"> <script type="text/javascript">
var w=new Worker("speedtest_worker.min.js"); var w = new Worker('speedtest_worker.min.js')
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100); var interval = setInterval(function () { w.postMessage('status') }, 100)
w.onmessage=function(event){ w.onmessage = function (event) {
var data=event.data.split(";"); var data = event.data.split(';')
var status=Number(data[0]); var status = Number(data[0])
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip"),jitter=document.getElementById("jitter"); var dl = document.getElementById('download')
dl.className=status==1?"flash":"";ping.className=status==2?"flash":"";jitter.className=ul.className=status==3?"flash":""; var ul = document.getElementById('upload')
if(status>=4){ var ping = document.getElementById('ping')
clearInterval(interval); var ip = document.getElementById('ip')
var jitter = document.getElementById('jitter')
dl.className = status === 1 ? 'flash' : ''
ping.className = status === 2 ? 'flash' : ''
jitter.className = ul.className = status === 3 ? 'flash' : ''
if (status >= 4) {
clearInterval(interval)
} }
dl.innerHTML=data[1]; dl.textContent = data[1]
ul.innerHTML=data[2]; ul.textContent = data[2]
ping.innerHTML=data[3]; ping.textContent = data[3]
jitter.innerHTML=data[5]; jitter.textContent = data[5]
ip.innerHTML="Your IP: "+data[4]; ip.textContent = 'Your IP: ' + data[4]
}.bind(this); }
w.postMessage('start {"url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","time_dl":"10","time_ul":"15","count_ping":"30"}'); //start with custom parameters. paths are relative to js file. you can omit parameters that you don't want to change w.postMessage('start {"url_dl": "garbage.php", "url_ul": "empty.dat", "url_ping": "empty.dat", "time_dl": "10", "time_ul": "15", "count_ping": "30"}') // start with custom parameters. paths are relative to js file. you can omit parameters that you don't want to change
</script> </script>
</body> </body>
</html> </html>

View file

@ -1,90 +1,107 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Speedtest</title> <title>Speedtest</title>
<style type="text/css"> <style type="text/css">
html,body{ html,
margin:0; body {
padding:0; margin: 0;
border:none; padding: 0;
text-align:center; border: none;
text-align: center;
} }
div.test{
display:inline-block; div.test {
margin:1em; display: inline-block;
font-size:2vw; margin: 1em;
min-width:20vw; font-size: 2vw;
text-align:center; min-width: 20vw;
text-align: center;
} }
div.testName,div.meterUnit{
font-size:1em; div.testName,
div.meterUnit {
font-size: 1em;
} }
div.meter{
font-size:1.5em; div.meter {
line-height:1.5em; font-size: 1.5em;
height:2em !important; line-height: 1.5em;
height: 2em !important;
} }
.flash{
animation:flash 0.6s linear infinite; .flash {
animation: flash 0.6s linear infinite;
} }
@keyframes flash{
0%{opacity:0.6;} @keyframes flash {
50%{opacity:1;} 0% { opacity: 0.6; }
50% { opacity: 1; }
} }
a{
display:inline-block; a {
border:0.15em solid #000000; display: inline-block;
padding:0.3em 0.5em; border: 0.15em solid #000000;
margin:0.6em; padding: 0.3em 0.5em;
color:#000000; margin: 0.6em;
text-decoration:none; color: #000000;
text-decoration: none;
} }
#ip{
margin:0.8em 0; #ip {
font-size:1.2em; margin: 0.8em 0;
font-size: 1.2em;
} }
@media all and (max-width: 50em){
div.test{ @media all and (max-width: 50em) {
font-size:2em; div.test {
font-size: 2em;
} }
} }
</style> </style>
<script type="text/javascript"> <script type="text/javascript">
var w=null; var w = null
function runTest(){ function runTest() {
document.getElementById("startBtn").style.display="none"; document.getElementById('startBtn').style.display = 'none'
document.getElementById("testArea").style.display=""; document.getElementById('testArea').style.display = ''
document.getElementById("abortBtn").style.display=""; document.getElementById('abortBtn').style.display = ''
w=new Worker("speedtest_worker.min.js"); w = new Worker('speedtest_worker.min.js')
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100); var interval = setInterval(function () { w.postMessage('status') }, 100)
w.onmessage=function(event){ w.onmessage = function (event) {
var data=event.data.split(";"); var data = event.data.split(';')
var status=Number(data[0]); var status = Number(data[0])
var dl=document.getElementById("download"),ul=document.getElementById("upload"),ping=document.getElementById("ping"),ip=document.getElementById("ip"),jitter=document.getElementById("jitter"); var dl = document.getElementById('download')
dl.className=status==1?"flash":"";ping.className=status==2?"flash":"";jitter.className=ul.className=status==3?"flash":""; var ul = document.getElementById('upload')
if(status>=4){ var ping = document.getElementById('ping')
clearInterval(interval); var ip = document.getElementById('ip')
document.getElementById("abortBtn").style.display="none"; var jitter = document.getElementById('jitter')
document.getElementById("startBtn").style.display=""; dl.className = status === 1 ? 'flash' : ''
w=null; ping.className = status === 2 ? 'flash' : ''
jitter.className = ul.className = status === 3 ? 'flash' : ''
if (status >= 4) {
clearInterval(interval)
document.getElementById('abortBtn').style.display = 'none'
document.getElementById('startBtn').style.display = ''
w = null
} }
if(status==5){ if (status === 5) {
document.getElementById("testArea").style.display="none"; document.getElementById('testArea').style.display = 'none'
} }
dl.innerHTML=data[1]; dl.textContent = data[1]
ul.innerHTML=data[2]; ul.textContent = data[2]
ping.innerHTML=data[3]; ping.textContent = data[3]
jitter.innerHTML=data[5]; jitter.textContent = data[5]
ip.innerHTML=data[4]; ip.textContent = data[4]
}.bind(this);
w.postMessage("start");
} }
function abortTest(){ w.postMessage('start')
if(w)w.postMessage("abort"); }
function abortTest() {
if (w) w.postMessage('abort')
} }
</script> </script>
</head> </head>
<body>
<body>
<h1>Speedtest</h1> <h1>Speedtest</h1>
<div id="testArea" style="display:none"> <div id="testArea" style="display:none">
<div id="ip">Please wait...</div> <div id="ip">Please wait...</div>
@ -112,5 +129,5 @@
<a href="javascript:abortTest()" id="abortBtn">Abort</a> <a href="javascript:abortTest()" id="abortBtn">Abort</a>
</div> </div>
<a href="javascript:runTest()" id="startBtn">Run speedtest</a> <a href="javascript:runTest()" id="startBtn">Run speedtest</a>
</body> </body>
</html> </html>

View file

@ -1,39 +1,41 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Speedtest</title> <title>Speedtest</title>
<style type="text/css"> <style type="text/css">
html,
html,body{ body {
margin:0; margin: 0;
padding:0; padding: 0;
border:none; border: none;
text-align:center; text-align: center;
font-family: 'Open Sans'; font-family: 'Open Sans';
} }
h1,h2,h3,h4,h5,h6 h1,
{ h2,
h3,
h4,
h5,
h6 {
font-family: 'Roboto', sans-serif; font-family: 'Roboto', sans-serif;
font-weight: 700; font-weight: 700;
} }
div.meter{ div.meter {
display:inline-block; display: inline-block;
height:300px; height: 300px;
width:400px; width: 400px;
text-align: center; text-align: center;
font-size:6vw; font-size: 6vw;
} }
div#testArea div#testArea {
{
display: flex; display: flex;
justify-content: center; justify-content: center;
flex-flow: row wrap; flex-flow: row wrap;
} }
a { a {
text-decoration: none; text-decoration: none;
} }
@ -53,132 +55,131 @@
font-family: 'Roboto'; font-family: 'Roboto';
} }
#ip{ #ip {
margin:1em 0; margin: 1em 0;
font-size:1.2em; font-size: 1.2em;
} }
</style> </style>
<script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/raphael/2.1.4/raphael-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/justgage/1.2.2/justgage.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/justgage/1.2.2/justgage.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var w=null; var w = null
var ggdl,ggul,ggping; var ggdl, ggul, ggping
function runTest(){ function runTest() {
w=new Worker("speedtest_worker.min.js"); w = new Worker('speedtest_worker.min.js')
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100); var interval = setInterval(function () { w.postMessage('status') }, 100)
document.getElementById("abortBtn").style.display=""; document.getElementById('abortBtn').style.display = ''
document.getElementById("startBtn").style.display="none"; document.getElementById('startBtn').style.display = 'none'
w.onmessage=function(event){ w.onmessage = function (event) {
var data=event.data.split(";"); var data = event.data.split(';')
var status=Number(data[0]); var status = Number(data[0])
if(status>=4){ if (status >= 4) {
clearInterval(interval); clearInterval(interval)
document.getElementById("abortBtn").style.display="none"; document.getElementById('abortBtn').style.display = 'none'
document.getElementById("startBtn").style.display=""; document.getElementById('startBtn').style.display = ''
w=null; w = null
} }
updateGauge(ggdl, data[1]); updateGauge(ggdl, data[1])
updateGauge(ggul, data[2]); updateGauge(ggul, data[2])
updateGauge(ggping, data[3]); updateGauge(ggping, data[3])
document.getElementById("ip").innerHTML="Your IP: "+data[4]; document.getElementById('ip').textContent = 'Your IP: ' + data[4]
updateGauge(ggjitter, data[5]); updateGauge(ggjitter, data[5])
}.bind(this);
w.postMessage('start {"time_ul":"10", "time_dl":"10", "count_ping":"50", "url_dl":"garbage.php","url_ul":"empty.dat","url_ping":"empty.dat","url_getIp":"getIP.php"}');
} }
function abortTest(){ w.postMessage('start {"time_ul": "10", "time_dl": "10", "count_ping": "50", "url_dl": "garbage.php", "url_ul": "empty.dat", "url_ping": "empty.dat", "url_getIp": "getIP.php"}')
if(w)w.postMessage("abort"); }
function abortTest() {
if (w) w.postMessage('abort')
} }
document.addEventListener("DOMContentLoaded", function(event) { document.addEventListener('DOMContentLoaded', function (event) {
ggdl = new JustGage({ ggdl = new JustGage({
id: 'ggdl', id: 'ggdl',
title: "Download", title: 'Download',
label: "Mbit/s", label: 'Mbit/s',
titleFontFamily : "Open Sans", titleFontFamily: 'Open Sans',
valueFontFamily : "Open Sans", valueFontFamily: 'Open Sans',
refreshAnimationTime: 300, refreshAnimationTime: 300,
value: 0, value: 0,
min: 0, min: 0,
max: 10, max: 10,
decimals : 2, decimals: 2,
formatNumber: true, formatNumber: true,
humanFriendly : false, humanFriendly: false,
levelColors: [ levelColors: [
"#999999", '#999999',
"#339933" '#339933'
] ]
}); })
ggul = new JustGage({ ggul = new JustGage({
id: 'ggul', id: 'ggul',
title: "Upload", title: 'Upload',
label: "Mbit/s", label: 'Mbit/s',
titleFontFamily : "Open Sans", titleFontFamily: 'Open Sans',
valueFontFamily : "Open Sans", valueFontFamily: 'Open Sans',
refreshAnimationTime: 300, refreshAnimationTime: 300,
value: 0, value: 0,
min: 0, min: 0,
max: 10, max: 10,
decimals : 2, decimals: 2,
formatNumber: true, formatNumber: true,
humanFriendly : false, humanFriendly: false,
levelColors: [ levelColors: [
"#999999", '#999999',
"#333399" '#333399'
] ]
}); })
ggping = new JustGage({ ggping = new JustGage({
id: 'ggping', id: 'ggping',
title: "Ping", title: 'Ping',
label: "ms", label: 'ms',
titleFontFamily : "Open Sans", titleFontFamily: 'Open Sans',
valueFontFamily : "Open Sans", valueFontFamily: 'Open Sans',
refreshAnimationTime: 300, refreshAnimationTime: 300,
value: 0, value: 0,
min: 0, min: 0,
max: 100, max: 100,
decimals : 2, decimals: 2,
formatNumber: true, formatNumber: true,
humanFriendly : false, humanFriendly: false,
levelColors: [ levelColors: [
"#999999", '#999999',
"#993333" '#993333'
] ]
}); })
ggjitter = new JustGage({ ggjitter = new JustGage({
id: 'ggjitter', id: 'ggjitter',
title: "Jitter", title: 'Jitter',
label: "ms", label: 'ms',
titleFontFamily : "Open Sans", titleFontFamily: 'Open Sans',
valueFontFamily : "Open Sans", valueFontFamily: 'Open Sans',
refreshAnimationTime: 300, refreshAnimationTime: 300,
value: 0, value: 0,
min: 0, min: 0,
max: 100, max: 100,
decimals : 2, decimals: 2,
formatNumber: true, formatNumber: true,
humanFriendly : false, humanFriendly: false,
levelColors: [ levelColors: [
"#999999", '#999999',
"#993333" '#993333'
] ]
}); })
}); })
function updateGauge(gauge, value) function updateGauge(gauge, value) {
{
// Alway use next power of 2 as maximum // Alway use next power of 2 as maximum
var max = Math.max(Math.pow(2, Math.ceil(Math.log2(value))), gauge.config.max); var max = Math.max(Math.pow(2, Math.ceil(Math.log2(value))), gauge.config.max)
// Refresh the gauge // Refresh the gauge
gauge.refresh(value, max); gauge.refresh(value, max)
} }
</script> </script>
<link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700|Roboto:400,500,700" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,500,700|Roboto:400,500,700" rel="stylesheet">
</head> </head>
<body>
<body>
<h1>Speed Test</h1> <h1>Speed Test</h1>
<div id="testArea"> <div id="testArea">
<div class="meter" id="ggdl"></div> <div class="meter" id="ggdl"></div>
@ -191,6 +192,6 @@
<a href="javascript:runTest()" id="startBtn" class="button">Start</a> <a href="javascript:runTest()" id="startBtn" class="button">Start</a>
<a href="javascript:abortTest()" id="abortBtn" class="button" style="display:none;">Abort</a> <a href="javascript:abortTest()" id="abortBtn" class="button" style="display:none;">Abort</a>
</div> </div>
<p>Fonts: <a href="https://fonts.google.com/">Google Gonts</a> | Gauges: <a href="http://justgage.com/">justgage.com</a></p> <p>Fonts: <a href="https://fonts.google.com/">Google Fonts</a> | Gauges: <a href="http://justgage.com/">justgage.com</a></p>
</body> </body>
</html> </html>

View file

@ -1,250 +1,256 @@
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
<head> <head>
<title>Speedtest</title> <title>Speedtest</title>
<style type="text/css"> <style type="text/css">
html,body{ html,
margin:0; body {
padding:0; margin: 0;
border:none; padding: 0;
text-align:center; border: none;
} text-align: center;
#startBtn{
display:inline-block;
border:0.15em solid #000000;
padding:0.3em 0.5em;
margin:0.6em;
color:#000000;
text-decoration:none;
}
#ip{
margin:0.8em 0;
font-size:1.2em;
} }
#chart1Area, #chart2Area{ #startBtn {
width:100%; display: inline-block;
max-width:30em; border: 0.15em solid #000000;
height:10em; padding: 0.3em 0.5em;
display:block; margin: 0.6em;
margin:0 auto; color: #000000;
text-decoration: none;
}
#ip {
margin: 0.8em 0;
font-size: 1.2em;
}
#chart1Area,
#chart2Area {
width: 100%;
max-width: 30em;
height: 10em;
display: block;
margin: 0 auto;
} }
</style> </style>
<script src="example5_data/Chart.bundle.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.5.0/Chart.bundle.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var w=null; var w = null
function runTest(){ function runTest() {
var chart1ctx=document.getElementById("chart1Area").getContext('2d'), var chart1ctx = document.getElementById('chart1Area').getContext('2d')
chart2ctx=document.getElementById("chart2Area").getContext('2d'); var chart2ctx = document.getElementById('chart2Area').getContext('2d')
var dlDataset={ var dlDataset = {
label: "Download", label: 'Download',
fill: false, fill: false,
lineTension: 0.1, lineTension: 0.1,
backgroundColor: "rgba(75,192,192,0.4)", backgroundColor: 'rgba(75,192,192,0.4)',
borderColor: "rgba(75,192,192,1)", borderColor: 'rgba(75,192,192,1)',
borderCapStyle: 'butt', borderCapStyle: 'butt',
borderDash: [], borderDash: [],
borderDashOffset: 0.0, borderDashOffset: 0.0,
borderJoinStyle: 'miter', borderJoinStyle: 'miter',
pointBorderColor: "rgba(75,192,192,1)", pointBorderColor: 'rgba(75,192,192,1)',
pointBackgroundColor: "#fff", pointBackgroundColor: '#fff',
pointBorderWidth: 1, pointBorderWidth: 1,
pointHoverRadius: 5, pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,192,192,1)", pointHoverBackgroundColor: 'rgba(75,192,192,1)',
pointHoverBorderColor: "rgba(220,220,220,1)", pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2, pointHoverBorderWidth: 2,
pointRadius: 1, pointRadius: 1,
pointHitRadius: 10, pointHitRadius: 10,
data: [0], data: [0],
spanGaps: false, spanGaps: false
},
ulDataset={
label: "Upload",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(192,192,75,0.4)",
borderColor: "rgba(192,192,75,1)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(192,192,75,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(192,192,75,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [0],
spanGaps: false,
},
pingDataset={
label: "Ping",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(75,220,75,0.4)",
borderColor: "rgba(75,220,75,1)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(75,220,75,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(75,220,75,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false,
},
jitterDataset={
label: "Jitter",
fill: false,
lineTension: 0.1,
backgroundColor: "rgba(220,75,75,0.4)",
borderColor: "rgba(220,75,75,1)",
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: "rgba(220,75,75,1)",
pointBackgroundColor: "#fff",
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: "rgba(220,75,75,1)",
pointHoverBorderColor: "rgba(220,220,220,1)",
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false,
} }
; var ulDataset = {
var chart1Options={ label: 'Upload',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(192,192,75,0.4)',
borderColor: 'rgba(192,192,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(192,192,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(192,192,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [0],
spanGaps: false
}
var pingDataset = {
label: 'Ping',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(75,220,75,0.4)',
borderColor: 'rgba(75,220,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(75,220,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(75,220,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false
}
var jitterDataset = {
label: 'Jitter',
fill: false,
lineTension: 0.1,
backgroundColor: 'rgba(220,75,75,0.4)',
borderColor: 'rgba(220,75,75,1)',
borderCapStyle: 'butt',
borderDash: [],
borderDashOffset: 0.0,
borderJoinStyle: 'miter',
pointBorderColor: 'rgba(220,75,75,1)',
pointBackgroundColor: '#fff',
pointBorderWidth: 1,
pointHoverRadius: 5,
pointHoverBackgroundColor: 'rgba(220,75,75,1)',
pointHoverBorderColor: 'rgba(220,220,220,1)',
pointHoverBorderWidth: 2,
pointRadius: 1,
pointHitRadius: 10,
data: [],
spanGaps: false
}
var chart1Options = {
type: 'line', type: 'line',
data: { data: {
datasets: [dlDataset,ulDataset] datasets: [dlDataset, ulDataset]
}, },
options:{ options: {
responsive: true, responsive: true,
legend: { legend: {
position: 'bottom', position: 'bottom'
}, },
scales: { scales: {
xAxes: [{ xAxes: [{
display: true, display: true,
scaleLabel: { scaleLabel: {
display: false, display: false
}, },
ticks:{ ticks: {
beginAtZero: true beginAtZero: true
} }
}], }],
yAxes: [{ yAxes: [{
display: true, display: true,
scaleLabel: "Speed", scaleLabel: 'Speed',
ticks:{ ticks: {
beginAtZero: true beginAtZero: true
} }
}] }]
} }
} }
}, }
chart2Options={ var chart2Options = {
type: 'line', type: 'line',
data: { data: {
datasets: [pingDataset,jitterDataset] datasets: [pingDataset, jitterDataset]
}, },
options:{ options: {
responsive: true, responsive: true,
legend: { legend: {
position: 'bottom', position: 'bottom'
}, },
scales: { scales: {
xAxes: [{ xAxes: [{
display: true, display: true,
scaleLabel: { scaleLabel: {
display: false, display: false
}, },
ticks:{ ticks: {
beginAtZero: true beginAtZero: true
} }
}], }],
yAxes: [{ yAxes: [{
display: true, display: true,
scaleLabel: "Latency", scaleLabel: 'Latency',
ticks:{ ticks: {
beginAtZero: true beginAtZero: true
} }
}] }]
} }
} }
}; }
var chart1 = new Chart(chart1ctx, chart1Options), var chart1 = new Chart(chart1ctx, chart1Options)
chart2 = new Chart(chart2ctx, chart2Options); var chart2 = new Chart(chart2ctx, chart2Options)
document.getElementById("startBtn").style.display="none"; document.getElementById('startBtn').style.display = 'none'
document.getElementById("testArea").style.display=""; document.getElementById('testArea').style.display = ''
document.getElementById("abortBtn").style.display=""; document.getElementById('abortBtn').style.display = ''
w=new Worker("speedtest_worker.min.js"); w = new Worker('speedtest_worker.min.js')
var interval=setInterval(function(){w.postMessage("status");}.bind(this),100); var interval = setInterval(function () { w.postMessage('status') }, 100)
w.onmessage=function(event){ w.onmessage = function (event) {
var data=event.data.split(";"); var data = event.data.split(';')
var status=Number(data[0]); var status = Number(data[0])
if(status>=4){ if (status >= 4) {
clearInterval(interval); clearInterval(interval)
document.getElementById("abortBtn").style.display="none"; document.getElementById('abortBtn').style.display = 'none'
document.getElementById("startBtn").style.display=""; document.getElementById('startBtn').style.display = ''
w=null; w = null
} }
if(status==5){ if (status === 5) {
document.getElementById("testArea").style.display="none"; document.getElementById('testArea').style.display = 'none'
} }
if(status==1&&Number(data[1])>0){ if (status === 1 && Number(data[1]) > 0) {
chart1.data.datasets[0].data.push(Number(data[1])); chart1.data.datasets[0].data.push(Number(data[1]))
chart1.data.labels[chart1.data.datasets[0].data.length-1]=""; chart1.data.labels[chart1.data.datasets[0].data.length - 1] = ''
chart1.update(); chart1.update()
} }
if(status==3&&Number(data[2])>0){ if (status === 3 && Number(data[2]) > 0) {
chart1.data.datasets[1].data.push(Number(data[2])); chart1.data.datasets[1].data.push(Number(data[2]))
chart1.data.labels[chart1.data.datasets[1].data.length-1]=""; chart1.data.labels[chart1.data.datasets[1].data.length - 1] = ''
chart1.update(); chart1.update()
} }
if(status==2&&Number(data[3])>0){ if (status === 2 && Number(data[3]) > 0) {
chart2.data.datasets[0].data.push(Number(data[3])); chart2.data.datasets[0].data.push(Number(data[3]))
chart2.data.datasets[1].data.push(Number(data[5])); chart2.data.datasets[1].data.push(Number(data[5]))
chart2.data.labels[chart2.data.datasets[0].data.length-1]=""; chart2.data.labels[chart2.data.datasets[0].data.length - 1] = ''
chart2.data.labels[chart2.data.datasets[1].data.length-1]=""; chart2.data.labels[chart2.data.datasets[1].data.length - 1] = ''
chart2.update(); chart2.update()
} }
ip.innerHTML=data[4]; ip.textContent = data[4]
}.bind(this);
w.postMessage("start");
} }
function abortTest(){ w.postMessage('start')
if(w)w.postMessage("abort"); }
function abortTest() {
if (w) w.postMessage('abort')
} }
</script> </script>
</head> </head>
<body>
<body>
<h1>Speedtest</h1> <h1>Speedtest</h1>
<div id="testArea" style="display:none"> <div id="testArea" style="display:none">
<div id="ip">Please wait...</div> <p id="ip">Please wait...</p>
<h2>Speed</h2> <h2>Speed</h2>
<canvas id="chart1Area"></canvas> <canvas id="chart1Area"></canvas>
<h2>Latency</h2> <h2>Latency</h2>
<canvas id="chart2Area"></canvas> <canvas id="chart2Area"></canvas>
<br/> <br/>
<a href="javascript:abortTest()" id="abortBtn">Abort</a> <a href="javascript:abortTest()" id="abortBtn">Abort</a>
</div> </div>
<a href="javascript:runTest()" id="startBtn">Run speedtest</a> <a href="javascript:runTest()" id="startBtn">Run speedtest</a>
<br/><br/> <br/><br/> Charts by <a href="http://www.chartjs.org/">Chart.js</a>
Charts by <a href="http://www.chartjs.org/">Chart.js</a> </body>
</body>
</html> </html>

File diff suppressed because one or more lines are too long

104
example6.html Normal file
View file

@ -0,0 +1,104 @@
<!doctype html>
<html>
<head>
<meta charset="utf-8" />
<meta name="referrer" content="no-referrer" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no, user-scalable=no" />
<title>Speedtest</title>
<link href="https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet" />
<style type="text/css">
.st-block {
text-align: center;
}
.st-btn {
margin-top: -0.5rem;
margin-left: 1.5rem;
}
.st-value>span:empty::before {
content: "0.00";
color: #636c72;
}
#st-ip:empty::before {
content: "___.___.___.___";
color: #636c72;
}
</style>
</head>
<body class="my-4">
<div class="container">
<div class="row">
<div class="col-sm-12 mb-3">
<p class="h1">
Speedtest
<button id="st-start" class="btn btn-outline-primary st-btn" onclick="startTest()">Start</button>
<button id="st-stop" class="btn btn-danger st-btn" onclick="stopTest()" hidden="true">Stop</button>
</p>
<p class="lead">
Your IP: <span id="st-ip"></span>
</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Download</h3>
<p class="display-4 st-value"><span id="st-download"></span></p>
<p class="lead">Mbit/s</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Upload</h3>
<p class="display-4 st-value"><span id="st-upload"></span></p>
<p class="lead">Mbit/s</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Ping</h3>
<p class="display-4 st-value"><span id="st-ping"></span></p>
<p class="lead">ms</p>
</div>
<div class="col-lg-3 col-md-6 mb-3 st-block">
<h3>Jitter</h3>
<p class="display-4 st-value"><span id="st-jitter"></span></p>
<p class="lead">ms</p>
</div>
</div>
</div>
<script type="text/javascript">
var worker = null
function startTest() {
document.getElementById('st-start').hidden = true
document.getElementById('st-stop').hidden = false
worker = new Worker('speedtest_worker.min.js')
var interval = setInterval(function () { worker.postMessage('status') }, 100)
worker.onmessage = function (event) {
var download = document.getElementById('st-download')
var upload = document.getElementById('st-upload')
var ping = document.getElementById('st-ping')
var jitter = document.getElementById('st-jitter')
var ip = document.getElementById('st-ip')
var data = event.data.split(';')
var status = Number(data[0])
if (status >= 4) {
clearInterval(interval)
document.getElementById('st-start').hidden = false
document.getElementById('st-stop').hidden = true
w = null
}
if (status === 5) {
// speedtest cancelled, clear output data
data = []
}
download.textContent = data[1]
upload.textContent = data[2]
ping.textContent = data[3]
ip.textContent = data[4]
jitter.textContent = data[5]
}
worker.postMessage('start')
}
function stopTest() {
if (worker) worker.postMessage('abort')
}
</script>
</body>
</html>

View file

@ -5,34 +5,34 @@
GNU LGPLv3 License GNU LGPLv3 License
*/ */
//data reported to main thread // 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 = 0 // 0=not started, 1=download test, 2=ping+jitter test, 3=upload test, 4=finished, 5=abort/error
dlStatus="", //download speed in megabit/s with 2 decimal digits var dlStatus = '' // download speed in megabit/s with 2 decimal digits
ulStatus="", //upload speed in megabit/s with 2 decimal digits var ulStatus = '' // upload speed in megabit/s with 2 decimal digits
pingStatus="", //ping in milliseconds with 2 decimal digits var pingStatus = '' // ping in milliseconds with 2 decimal digits
jitterStatus="", //jitter in milliseconds with 2 decimal digits var jitterStatus = '' // jitter in milliseconds with 2 decimal digits
clientIp=""; //client's IP address as reported by getIP.php var clientIp = '' // client's IP address as reported by getIP.php
//test settings. can be overridden by sending specific values with the start command // test settings. can be overridden by sending specific values with the start command
var settings={ var settings = {
time_ul:15, //duration of upload test in seconds time_ul: 15, // duration of upload test in seconds
time_dl:15, //duration of download test in seconds time_dl: 15, // duration of download test in seconds
count_ping:35, //number of pings to perform in ping test count_ping: 35, // number of pings to perform in ping test
url_dl:"garbage.php", //path to a large file or garbage.php, used for download test. must be relative to this js file url_dl: 'garbage.php', // path to a large file or garbage.php, used for download test. must be relative to this js file
url_ul:"empty.dat", //path to an empty file, used for upload test. must be relative to this js file url_ul: 'empty.dat', // path to an empty file, used for upload test. must be relative to this js file
url_ping:"empty.dat", //path to an empty file, used for ping test. must be relative to this js file url_ping: 'empty.dat', // path to an empty file, used for ping test. must be relative to this js file
url_getIp:"getIP.php", //path to getIP.php relative to this js file, or a similar thing that outputs the client's ip 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_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_ulMultistream: 3, // number of upload streams to use (can be different if enable_quirks is active)
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_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) 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 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
allow_fetchAPI:false, //enables Fetch API. currently disabled because it leaks memory like no tomorrow allow_fetchAPI: false, // enables Fetch API. currently disabled because it leaks memory like no tomorrow
force_fetchAPI:false //when Fetch API is enabled, it will force usage on every browser that supports it force_fetchAPI: false // when Fetch API is enabled, it will force usage on every browser that supports it
}; }
var xhr=null, //array of currently active xhr requests var xhr = null // array of currently active xhr requests
interval=null; //timer used in tests var interval = null // timer used in tests
/* /*
when set to true (automatically) the download test will use the fetch api instead of xhr. when set to true (automatically) the download test will use the fetch api instead of xhr.
@ -40,7 +40,7 @@ var xhr=null, //array of currently active xhr requests
-allow_fetchAPI is true AND -allow_fetchAPI is true AND
-(we're on chrome that supports fetch api AND enable_quirks is true) OR (we're on any browser that supports fetch api AND force_fetchAPI is true) -(we're on chrome that supports fetch api AND enable_quirks is true) OR (we're on any browser that supports fetch api AND force_fetchAPI is true)
*/ */
var useFetchAPI=false; var useFetchAPI = false
/* /*
listener for commands from main thread to this worker. listener for commands from main thread to this worker.
@ -50,300 +50,301 @@ var useFetchAPI=false;
-start: starts the test. optionally, settings can be passed as JSON. -start: starts the test. optionally, settings can be passed as JSON.
example: start {"time_ul":"10", "time_dl":"10", "count_ping":"50"} example: start {"time_ul":"10", "time_dl":"10", "count_ping":"50"}
*/ */
this.addEventListener('message', function(e){ this.addEventListener('message', function (e) {
var params=e.data.split(" "); var params = e.data.split(' ')
if(params[0]=="status"){ //return status if (params[0] === 'status') { // return status
postMessage(testStatus+";"+dlStatus+";"+ulStatus+";"+pingStatus+";"+clientIp+";"+jitterStatus); postMessage(testStatus + ';' + dlStatus + ';' + ulStatus + ';' + pingStatus + ';' + clientIp + ';' + jitterStatus)
} }
if(params[0]=="start"&&testStatus==0){ //start new test if (params[0] === 'start' && testStatus === 0) { // start new test
testStatus=1; testStatus = 1
try{ try {
//parse settings, if present // parse settings, if present
var s=JSON.parse(e.data.substring(5)); var s = JSON.parse(e.data.substring(5))
if(typeof s.url_dl != "undefined") settings.url_dl=s.url_dl; //download url 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_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_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.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_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.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 if (typeof s.enable_quirks !== 'undefined') settings.enable_quirks = s.enable_quirks // enable quirks or not
if(typeof s.allow_fetchAPI != "undefined") settings.allow_fetchAPI=s.allow_fetchAPI; //allows fetch api to be used if supported if (typeof s.allow_fetchAPI !== 'undefined') settings.allow_fetchAPI = s.allow_fetchAPI // allows fetch api to be used if supported
//quirks for specific browsers. more may be added in future releases // quirks for specific browsers. more may be added in future releases
if(settings.enable_quirks){ if (settings.enable_quirks) {
var ua=navigator.userAgent; var ua = navigator.userAgent
if(/Firefox.(\d+\.\d+)/i.test(ua)){ if (/Firefox.(\d+\.\d+)/i.test(ua)) {
//ff more precise with 1 upload stream // ff more precise with 1 upload stream
settings.xhr_ulMultistream=1; settings.xhr_ulMultistream = 1
} }
if(/Edge.(\d+\.\d+)/i.test(ua)){ if (/Edge.(\d+\.\d+)/i.test(ua)) {
//edge more precise with 3 download streams // edge more precise with 3 download streams
settings.xhr_dlMultistream=3; settings.xhr_dlMultistream = 3
} }
if((/Safari.(\d+)/i.test(ua))&&!(/Chrome.(\d+)/i.test(ua))){ if ((/Safari.(\d+)/i.test(ua)) && !(/Chrome.(\d+)/i.test(ua))) {
//safari more precise with 10 upload streams and 5mb chunks for download test // safari more precise with 10 upload streams and 5mb chunks for download test
settings.xhr_ulMultistream=10; settings.xhr_ulMultistream = 10
settings.garbagePhp_chunkSize=5; settings.garbagePhp_chunkSize = 5
} }
if(/Chrome.(\d+)/i.test(ua)&&(!!self.fetch)){ if (/Chrome.(\d+)/i.test(ua) && (!!self.fetch)) {
//chrome can't handle large xhr very well, use fetch api if available and allowed // chrome can't handle large xhr very well, use fetch api if available and allowed
if(settings.allow_fetchAPI) useFetchAPI=true; if (settings.allow_fetchAPI) useFetchAPI = true
//chrome more precise with 5 streams // chrome more precise with 5 streams
settings.xhr_dlMultistream=5; 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.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_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_ulMultistream !== 'undefined') settings.xhr_ulMultistream = s.xhr_ulMultistream // number of upload streams
if(typeof s.xhr_dlUseBlob != "undefined") settings.xhr_dlUseBlob=s.xhr_dlUseBlob; //use blob for download 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.garbagePhp_chunkSize !== 'undefined') settings.garbagePhp_chunkSize = s.garbagePhp_chunkSize // size of garbage.php chunks
if(typeof s.force_fetchAPI != "undefined") settings.force_fetchAPI=s.force_fetchAPI; //use fetch api on all browsers that support it if enabled if (typeof s.force_fetchAPI !== 'undefined') settings.force_fetchAPI = s.force_fetchAPI // use fetch api on all browsers that support it if enabled
if(settings.allow_fetchAPI&&settings.force_fetchAPI&&(!!self.fetch)) useFetchAPI=true; if (settings.allow_fetchAPI && settings.force_fetchAPI && (!!self.fetch)) useFetchAPI = true
}catch(e){} } catch (e) { }
//run the tests // run the tests
console.log(settings); console.log(settings)
console.log("Fetch API: "+useFetchAPI); console.log('Fetch API: ' + useFetchAPI)
getIp(function(){dlTest(function(){testStatus=2;pingTest(function(){testStatus=3;ulTest(function(){testStatus=4;});});})}); getIp(function () { dlTest(function () { testStatus = 2; pingTest(function () { testStatus = 3; ulTest(function () { testStatus = 4 }) }) }) })
} }
if(params[0]=="abort"){ //abort command if (params[0] === 'abort') { // abort command
clearRequests(); //stop all xhr activity clearRequests() // stop all xhr activity
if(interval)clearInterval(interval); //clear timer if present if (interval) clearInterval(interval) // clear timer if present
testStatus=5;dlStatus="";ulStatus="";pingStatus="";jitterStatus=""; //set test as aborted testStatus = 5; dlStatus = ''; ulStatus = ''; pingStatus = ''; jitterStatus = '' // set test as aborted
} }
}); })
//stops all XHR activity, aggressively // stops all XHR activity, aggressively
function clearRequests(){ function clearRequests () {
if(xhr){ if (xhr) {
for(var i=0;i<xhr.length;i++){ for (var i = 0; i < xhr.length; i++) {
if(useFetchAPI)try{xhr[i].cancelRequested=true;}catch(e){} if (useFetchAPI) try { xhr[i].cancelRequested = true } catch (e) { }
try{xhr[i].onprogress=null; xhr[i].onload=null; xhr[i].onerror=null;}catch(e){} 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].upload.onprogress = null; xhr[i].upload.onload = null; xhr[i].upload.onerror = null } catch (e) { }
try{xhr[i].abort();}catch(e){} try { xhr[i].abort() } catch (e) { }
try{delete(xhr[i]);}catch(e){} try { delete (xhr[i]) } catch (e) { }
} }
xhr=null; xhr = null
} }
} }
//gets client's IP using url_getIp, then calls the done function // gets client's IP using url_getIp, then calls the done function
function getIp(done){ function getIp (done) {
xhr=new XMLHttpRequest(); xhr = new XMLHttpRequest()
xhr.onload=function(){ xhr.onload = function () {
clientIp=xhr.responseText; clientIp = xhr.responseText
done(); done()
} }
xhr.onerror=function(){ xhr.onerror = function () {
done(); done()
} }
xhr.open("GET",settings.url_getIp+"?r="+Math.random(),true); xhr.open('GET', settings.url_getIp + '?r=' + Math.random(), true)
xhr.send(); xhr.send()
} }
//download test, calls done function when it's over // download test, calls done function when it's over
var dlCalled=false; //used to prevent multiple accidental calls to dlTest var dlCalled = false // used to prevent multiple accidental calls to dlTest
function dlTest(done){ function dlTest (done) {
if(dlCalled) return; else dlCalled=true; //dlTest already called? if (dlCalled) return; else dlCalled = true // dlTest already called?
var totLoaded=0.0, //total number of loaded bytes var totLoaded = 0.0, // total number of loaded bytes
startT=new Date().getTime(), //timestamp when test was started startT = new Date().getTime(), // timestamp when test was started
failed=false; //set to true if a stream fails failed = false // set to true if a stream fails
xhr=[]; xhr = []
//function to create a download stream. streams are slightly delayed so that they will not end at the same time // function to create a download stream. streams are slightly delayed so that they will not end at the same time
var testStream=function(i,delay){ var testStream = function (i, delay) {
setTimeout(function(){ setTimeout(function () {
if(testStatus!=1) return; //delayed stream ended up starting after the end of the download test if (testStatus !== 1) return // delayed stream ended up starting after the end of the download test
if(useFetchAPI){ if (useFetchAPI) {
xhr[i]=fetch(settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize).then(function(response) { xhr[i] = fetch(settings.url_dl + '?r=' + Math.random() + '&ckSize=' + settings.garbagePhp_chunkSize).then(function (response) {
var reader = response.body.getReader(); var reader = response.body.getReader()
var consume=function() { var consume = function () {
return reader.read().then(function(result){ return reader.read().then(function (result) {
if(result.done) testStream(i); else{ if (result.done) testStream(i); else {
totLoaded+=result.value.length; totLoaded += result.value.length
if(xhr[i].cancelRequested) reader.cancel(); if (xhr[i].cancelRequested) reader.cancel()
} }
return consume(); return consume()
}.bind(this)); }.bind(this))
}.bind(this); }.bind(this)
return consume(); return consume()
}.bind(this)); }.bind(this))
}else{ } else {
var prevLoaded=0; //number of bytes loaded last time onprogress was called var prevLoaded = 0 // number of bytes loaded last time onprogress was called
var x=new XMLHttpRequest(); var x = new XMLHttpRequest()
xhr[i]=x; xhr[i] = x
xhr[i].onprogress=function(event){ xhr[i].onprogress = function (event) {
if(testStatus!=1){try{x.abort();}catch(e){}} //just in case this XHR is still running after the download test if (testStatus !== 1) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the download test
//progress event, add number of new loaded bytes to totLoaded // progress event, add number of new loaded bytes to totLoaded
var loadDiff=event.loaded<=0?0:(event.loaded-prevLoaded); var loadDiff = event.loaded <= 0 ? 0 : (event.loaded - prevLoaded)
if(isNaN(loadDiff)||!isFinite(loadDiff)||loadDiff<0) return; //just in case if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return // just in case
totLoaded+=loadDiff; totLoaded += loadDiff
prevLoaded=event.loaded; prevLoaded = event.loaded
}.bind(this); }.bind(this)
xhr[i].onload=function(){ xhr[i].onload = function () {
//the large file has been loaded entirely, start again // the large file has been loaded entirely, start again
try{xhr[i].abort();}catch(e){} //reset the stream data to empty ram try { xhr[i].abort() } catch (e) { } // reset the stream data to empty ram
testStream(i,0); testStream(i, 0)
}.bind(this); }.bind(this)
xhr[i].onerror=function(){ xhr[i].onerror = function () {
//error, abort // error, abort
failed=true; failed = true
try{xhr[i].abort();}catch(e){} try { xhr[i].abort() } catch (e) { }
delete(xhr[i]); delete (xhr[i])
}.bind(this); }.bind(this)
//send xhr // send xhr
try{if(settings.xhr_dlUseBlob) xhr[i].responseType='blob'; else xhr[i].responseType='arraybuffer';}catch(e){} try { if (settings.xhr_dlUseBlob) xhr[i].responseType = 'blob'; else xhr[i].responseType = 'arraybuffer' } catch (e) { }
xhr[i].open("GET",settings.url_dl+"?r="+Math.random()+"&ckSize="+settings.garbagePhp_chunkSize,true); //random string to prevent caching xhr[i].open('GET', settings.url_dl + '?r=' + Math.random() + '&ckSize=' + settings.garbagePhp_chunkSize, true) // random string to prevent caching
xhr[i].send(); xhr[i].send()
} }
}.bind(this),1+delay); }.bind(this), 1 + delay)
}.bind(this); }.bind(this)
//open streams // open streams
for(var i=0;i<settings.xhr_dlMultistream;i++){ for (var i = 0; i < settings.xhr_dlMultistream; i++) {
testStream(i,100*i); testStream(i, 100 * i)
} }
//every 200ms, update dlStatus // every 200ms, update dlStatus
interval=setInterval(function(){ interval = setInterval(function () {
var t=new Date().getTime()-startT; var t = new Date().getTime() - startT
if(t<200) return; if (t < 200) return
var speed=totLoaded/(t/1000.0); var speed = totLoaded / (t / 1000.0)
dlStatus=((speed*8)/925000.0).toFixed(2); //925000 instead of 1048576 to account for overhead dlStatus = ((speed * 8) / 925000.0).toFixed(2) // 925000 instead of 1048576 to account for overhead
if((t/1000.0)>settings.time_dl||failed){ //test is over, stop streams and timer if ((t / 1000.0) > settings.time_dl || failed) { // test is over, stop streams and timer
if(failed||isNaN(dlStatus)) dlStatus="Fail"; if (failed || isNaN(dlStatus)) dlStatus = 'Fail'
clearRequests(); clearRequests()
clearInterval(interval); clearInterval(interval)
done(); done()
} }
}.bind(this),200); }.bind(this), 200)
} }
//upload test, calls done function whent it's over // upload test, calls done function whent it's over
//garbage data for upload test // garbage data for upload test
var r=new ArrayBuffer(1048576); var r = new ArrayBuffer(1048576)
try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random();}catch(e){} try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { }
var req=[],reqsmall=[]; var req = []
for(var i=0;i<20;i++) req.push(r); var reqsmall = []
req=new Blob(req); for (var i = 0; i < 20; i++) req.push(r)
r=new ArrayBuffer(262144); req = new Blob(req)
try{r=new Float32Array(r);for(var i=0;i<r.length;i++)r[i]=Math.random();}catch(e){} r = new ArrayBuffer(262144)
reqsmall.push(r); try { r = new Float32Array(r); for (var i = 0; i < r.length; i++)r[i] = Math.random() } catch (e) { }
reqsmall=new Blob(reqsmall); reqsmall.push(r)
var ulCalled=false; //used to prevent multiple accidental calls to ulTest reqsmall = new Blob(reqsmall)
function ulTest(done){ var ulCalled = false // used to prevent multiple accidental calls to ulTest
if(ulCalled) return; else ulCalled=true; //ulTest already called? function ulTest (done) {
var totLoaded=0.0, //total number of transmitted bytes if (ulCalled) return; else ulCalled = true // ulTest already called?
startT=new Date().getTime(), //timestamp when test was started var totLoaded = 0.0 // total number of transmitted bytes
failed=false; //set to true if a stream fails var startT = new Date().getTime() // timestamp when test was started
xhr=[]; var failed = false // set to true if a stream fails
//function to create an upload stream. streams are slightly delayed so that they will not end at the same time xhr = []
var testStream=function(i,delay){ // function to create an upload stream. streams are slightly delayed so that they will not end at the same time
setTimeout(function(){ var testStream = function (i, delay) {
if(testStatus!=3) return; //delayed stream ended up starting after the end of the upload test setTimeout(function () {
var prevLoaded=0; //number of bytes transmitted last time onprogress was called if (testStatus !== 3) return // delayed stream ended up starting after the end of the upload test
var x=new XMLHttpRequest(); var prevLoaded = 0 // number of bytes transmitted last time onprogress was called
xhr[i]=x; var x = new XMLHttpRequest()
var ie11workaround; xhr[i] = x
try{ var ie11workaround
xhr[i].upload.onprogress; try {
ie11workaround=false; xhr[i].upload.onprogress
}catch(e){ ie11workaround = false
ie11workaround=true; } catch (e) {
ie11workaround = true
} }
if(ie11workaround){ if (ie11workaround) {
//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 // 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(){ xhr[i].onload = function () {
totLoaded+=262144; totLoaded += 262144
testStream(i,0); testStream(i, 0)
} }
xhr[i].onerror=function(){ xhr[i].onerror = function () {
//error, abort // error, abort
failed=true; failed = true
try{xhr[i].abort();}catch(e){} try { xhr[i].abort() } catch (e) { }
delete(xhr[i]); delete (xhr[i])
} }
xhr[i].open("POST",settings.url_ul+"?r="+Math.random(),true); //random string to prevent caching xhr[i].open('POST', 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].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
xhr[i].send(reqsmall); xhr[i].send(reqsmall)
}else{ } else {
//REGULAR version, no workaround // REGULAR version, no workaround
xhr[i].upload.onprogress=function(event){ xhr[i].upload.onprogress = function (event) {
if(testStatus!=3){try{x.abort();}catch(e){}} //just in case this XHR is still running after the upload test if (testStatus !== 3) { try { x.abort() } catch (e) { } } // just in case this XHR is still running after the upload test
//progress event, add number of new loaded bytes to totLoaded // progress event, add number of new loaded bytes to totLoaded
var loadDiff=event.loaded<=0?0:(event.loaded-prevLoaded); var loadDiff = event.loaded <= 0 ? 0 : (event.loaded - prevLoaded)
if(isNaN(loadDiff)||!isFinite(loadDiff)||loadDiff<0) return; //just in case if (isNaN(loadDiff) || !isFinite(loadDiff) || loadDiff < 0) return // just in case
totLoaded+=loadDiff; totLoaded += loadDiff
prevLoaded=event.loaded; prevLoaded = event.loaded
}.bind(this); }.bind(this)
xhr[i].upload.onload=function(){ xhr[i].upload.onload = function () {
//this stream sent all the garbage data, start again // this stream sent all the garbage data, start again
testStream(i,0); testStream(i, 0)
}.bind(this); }.bind(this)
xhr[i].upload.onerror=function(){ xhr[i].upload.onerror = function () {
//error, abort // error, abort
failed=true; failed = true
try{xhr[i].abort();}catch(e){} try { xhr[i].abort() } catch (e) { }
delete(xhr[i]); delete (xhr[i])
}.bind(this); }.bind(this)
//send xhr // send xhr
xhr[i].open("POST",settings.url_ul+"?r="+Math.random(),true); //random string to prevent caching xhr[i].open('POST', 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].setRequestHeader('Content-Encoding', 'identity') // disable compression (some browsers may refuse it, but data is incompressible anyway)
xhr[i].send(req); xhr[i].send(req)
} }
}.bind(this),1); }.bind(this), 1)
}.bind(this); }.bind(this)
//open streams // open streams
for(var i=0;i<settings.xhr_ulMultistream;i++){ for (var i = 0; i < settings.xhr_ulMultistream; i++) {
testStream(i,100*i); testStream(i, 100 * i)
} }
//every 200ms, update ulStatus // every 200ms, update ulStatus
interval=setInterval(function(){ interval = setInterval(function () {
var t=new Date().getTime()-startT; var t = new Date().getTime() - startT
if(t<200) return; if (t < 200) return
var speed=totLoaded/(t/1000.0); var speed = totLoaded / (t / 1000.0)
ulStatus=((speed*8)/925000.0).toFixed(2); //925000 instead of 1048576 to account for overhead ulStatus = ((speed * 8) / 925000.0).toFixed(2) // 925000 instead of 1048576 to account for overhead
if((t/1000.0)>settings.time_ul||failed){ //test is over, stop streams and timer if ((t / 1000.0) > settings.time_ul || failed) { // test is over, stop streams and timer
if(failed||isNaN(ulStatus)) ulStatus="Fail"; if (failed || isNaN(ulStatus)) ulStatus = 'Fail'
clearRequests(); clearRequests()
clearInterval(interval); clearInterval(interval)
done(); done()
} }
}.bind(this),200); }.bind(this), 200)
} }
//ping+jitter test, function done is called when it's over // ping+jitter test, function done is called when it's over
var ptCalled=false; //used to prevent multiple accidental calls to pingTest var ptCalled = false // used to prevent multiple accidental calls to pingTest
function pingTest(done){ function pingTest (done) {
if(ptCalled) return; else ptCalled=true; //pingTest already called? if (ptCalled) return; else ptCalled = true // pingTest already called?
var prevT=null, //last time a pong was received var prevT = null // last time a pong was received
ping=0.0, //current ping value var ping = 0.0 // current ping value
jitter=0.0, //current jitter value var jitter = 0.0 // current jitter value
i=0, //counter of pongs received var i = 0 // counter of pongs received
prevInstspd=0; //last ping time, used for jitter calculation var prevInstspd = 0 // last ping time, used for jitter calculation
xhr=[]; xhr = []
//ping function // ping function
var doPing=function(){ var doPing = function () {
prevT=new Date().getTime(); prevT = new Date().getTime()
xhr[0]=new XMLHttpRequest(); xhr[0] = new XMLHttpRequest()
xhr[0].onload=function(){ xhr[0].onload = function () {
//pong // pong
if(i==0){ if (i === 0) {
prevT=new Date().getTime(); //first pong prevT = new Date().getTime() // first pong
}else{ } else {
var instspd=(new Date().getTime()-prevT)/2; var instspd = (new Date().getTime() - prevT) / 2
var instjitter=Math.abs(instspd-prevInstspd); var instjitter = Math.abs(instspd - prevInstspd)
if(i==1)ping=instspd; /*first ping, can't tell jiutter yet*/ else{ if (i === 1) ping = instspd; /* first ping, can't tell jiutter yet*/ else {
ping=ping*0.9+instspd*0.1; //ping, weighted average ping = ping * 0.9 + instspd * 0.1 // ping, weighted average
jitter=instjitter>jitter?(jitter*0.2+instjitter*0.8):(jitter*0.9+instjitter*0.1); //update jitter, weighted average. spikes in ping values are given more weight. jitter = instjitter > jitter ? (jitter * 0.2 + instjitter * 0.8) : (jitter * 0.9 + instjitter * 0.1) // update jitter, weighted average. spikes in ping values are given more weight.
} }
prevInstspd=instspd; prevInstspd = instspd
} }
pingStatus=ping.toFixed(2); pingStatus = ping.toFixed(2)
jitterStatus=jitter.toFixed(2); jitterStatus = jitter.toFixed(2)
i++; i++
if(i<settings.count_ping) doPing(); else done(); //more pings to do? if (i < settings.count_ping) doPing(); else done() // more pings to do?
}.bind(this); }.bind(this)
xhr[0].onerror=function(){ xhr[0].onerror = function () {
//a ping failed, cancel test // a ping failed, cancel test
pingStatus="Fail"; pingStatus = 'Fail'
jitterStatus="Fail"; jitterStatus = 'Fail'
clearRequests(); clearRequests()
done(); done()
}.bind(this); }.bind(this)
//sent xhr // sent xhr
xhr[0].open("GET",settings.url_ping+"?r="+Math.random(),true); //random string to prevent caching xhr[0].open('GET', settings.url_ping + '?r=' + Math.random(), true) // random string to prevent caching
xhr[0].send(); xhr[0].send()
}.bind(this); }.bind(this)
doPing(); //start first ping doPing() // start first ping
} }

File diff suppressed because one or more lines are too long