├── .gitattributes ├── .gitignore ├── README.md ├── bower.json ├── dist └── index.min.js ├── package.json ├── src └── index.js └── test └── test.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.log 3 | Thumbs.db 4 | node_modules 5 | npm-debug.log 6 | *.orig 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Fireball 2 | Breakpoints for performance. 3 | 4 | Fireball is a small script that runs when your web page is loaded. It generates a score based on the performance of the user's hardware. 5 | 6 | It hands off the work to a different thread so won't slow the rest of your site down while it's running. 7 | 8 | ## Installation 9 | 10 | ``` 11 | npm install fireball --save 12 | ``` 13 | 14 | ``` 15 | bower install fireball 16 | ``` 17 | 18 | ## Example usage 19 | 20 | ### Setup 21 | Fireball uses a [Worker](https://developer.mozilla.org/en-US/docs/Web/API/Worker) to calculate a score, which is loaded dynamically when fireball initialises. No need to host the worker script in another file. 22 | 23 | ### Running fireball, the simple way 24 | ```javascript 25 | var Fireball = require('fireball-js'); 26 | Fireball.run(); 27 | ``` 28 | 29 | or if adding the script directly or using bower Fireball will be already available: 30 | 31 | ```javascript 32 | Fireball.run(); 33 | ``` 34 | 35 | 36 | The resulting score will be available in your JavaScript as `Fireball.getScore()` after a few seconds. 37 | 38 | ```javascript 39 | if (Fireball.getScore() > 8000){ 40 | //Do something to delight the user 41 | } else { 42 | //Do something boring but easy on the CPU 43 | } 44 | ``` 45 | 46 | ### Running fireball with classes 47 | ```javascript 48 | Fireball.run({ 49 | speedRanges: [ 50 | {min: 0, className: 'speed-of-sloth'}, 51 | {min: 4000, className: 'speed-of-tortoise'}, 52 | {min: 8000, className: 'speed-of-puppy'}, 53 | {min: 16000, className: 'speed-of-cheetah'} 54 | ] 55 | }); 56 | ``` 57 | 58 | These breakpoints will be added as classes to the `` so you can target them in CSS. E.g. 59 | 60 | ```css 61 | body.speed-of-sloth .my-element { 62 | /* no box shadows, transitions, etc. */ 63 | } 64 | 65 | body.speed-of-cheetah .my-element { 66 | /* some hella fancy animation */ 67 | } 68 | ``` 69 | 70 | ### Running fireball with all the bells and whistles 71 | ```javascript 72 | Fireball.run({ 73 | debug: true, //shows an onscreen readout. Defaults to false 74 | runs: 7, //defaults to 7 75 | defaultScore: 5000, //defaults to 0 76 | classEl: 'body', //append a class indicating speed to this element. Defaults to 'body' 77 | speedRanges: [ //the speed breakpoints and classnames to use 78 | {min: 0, className: 'sloth'}, 79 | {min: 4000, className: 'tortoise'}, 80 | {min: 8000, className: 'puppy'}, 81 | {min: 16000, className: 'cheetah'} 82 | ], 83 | callback: function(score) { 84 | //do something now that the tests are done 85 | // or store the score in a global variable if you're that way inclined 86 | } 87 | }); 88 | ``` 89 | 90 | You can also register a callback like so. 91 | 92 | ``` 93 | Fireball.onSuccess(callback); 94 | ``` 95 | 96 | `callback` will be passed a single argument, the score. 97 | 98 | This is handy if you have a modular system and want to access the fireball score in a different module 99 | without using a global variable. If the score has already been calculated this will execute immediately. 100 | 101 | ## Browser support 102 | Chrome, Firefox, Safari, Android 4.4+. 103 | 104 | Won't work in the current IE (11) or Edge (25). 105 | 106 | ## Benchmark 107 | The Fireball score is roughly aligned with [the Octane benchmark](http://chromium.github.io/octane/) score; 108 | if a machine gets 15,000 on octane, the Fireball score will be within a few thousand of that. Probably. 109 | 110 | Check out the demo on [my site](http://www.dg707.com/fireball) to see what score your machine gets. 111 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fireball", 3 | "version": "1.1.2", 4 | "homepage": "https://github.com/davidgilbertson/fireball", 5 | "description": "Fireball is a small script that runs when your web page is loaded. It generates a score based on the performance of the user's hardware.", 6 | "main": "dist/index.min.js", 7 | "keywords": [ 8 | "fireball", 9 | "performance" 10 | ], 11 | "authors": [ 12 | "David Gilbertson (http://www.dg707.com)" 13 | ], 14 | "maintainers": [ 15 | "Fergal Hanley (http://fergalhanley.com)" 16 | ], 17 | "license": "ISC", 18 | "ignore": [ 19 | "**/.*", 20 | "node_modules" 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /dist/index.min.js: -------------------------------------------------------------------------------- 1 | !function(){"undefined"!=typeof window&&(window.exports={},window.module={})}(),function(){"use strict";function newEl(tag,opt){var el=document.createElement(tag);for(var prop in opt)if(opt.hasOwnProperty(prop))if("style"===prop)for(var styleProp in opt[prop])el[prop][styleProp]=opt[prop][styleProp];else el[prop]=opt[prop];return el}function runCallbacks(){callbacks.forEach(function(callback){callback(finalScore)})}function getMedianScore(){if(scores.length){var midPoint=Math.floor(scores.length/2),result=void 0;return scores.sort(),result=scores.length%2?scores[midPoint]:(scores[midPoint-1]+scores[midPoint])/2,Math.round(result)}}function log(str){if(!getEl("#logging-panel")){var logEl=newEl("div",{id:"logging-panel",style:{position:"fixed",width:"300px",height:"260px",bottom:"10px",left:"10px",padding:"10px",background:"#111","font-family":"monospace","font-size":"12px","line-height":"14px",color:"#eee",overflow:"auto","box-shadow":"2px 2px 20px rgba(0, 0, 0, 0.5)","z-index":"99"}}),logH1=newEl("h1",{textContent:"Fireball"});logEl.appendChild(logH1);var logMedian=newEl("p",{id:"log-median"});logEl.appendChild(logMedian);var logDiv=newEl("div",{id:"log"});logEl.appendChild(logDiv),getEl("body").appendChild(logEl)}var log=getEl("#log");"_finished_"===str?(getEl("#log-median").textContent="Score: "+getMedianScore().toLocaleString(),log.style.color="grey"):log.innerHTML+="

> "+str+"

"}function appendClasses(options){var i=void 0,className=options.speedRanges[0].className;for(i=1;i=options.speedRanges[i].min&&(className=options.speedRanges[i].className);if(options.speedRanges.forEach(function(speedRange){finalScore>=speedRange.min&&(className=speedRange.className)}),className){var classSelector=options.classEl||"body",classEl=getEl(classSelector);classEl&&classEl.classList.add(className)}}function run(){function logScore(rawScore){var thisScore=parseInt(6.1813*rawScore,10);scores.push(thisScore),finalScore=getMedianScore(),count++,debug&&log(finalScore.toLocaleString()),runs>count?setTimeout(function(){fbWorker.postMessage("run")},100):(hasFinished=!0,runCallbacks(),debug&&log("_finished_"),options.speedRanges&&appendClasses(options),options.callback&&options.callback())}var options=arguments.length<=0||void 0===arguments[0]?{}:arguments[0];if("undefined"!=typeof window){if(finalScore=options.defaultScore||0,!window.Worker)return void(options.callback&&options.callback());var debug=options.debug||!1,runs=options.runs||7,count=0,blobURL=URL.createObjectURL(new Blob(["("+fireballWorker.toString()+")()"],{type:"application/javascript"})),fbWorker=new Worker(blobURL);URL.revokeObjectURL(blobURL),fbWorker.addEventListener("message",function(e){logScore(e.data)},!1),fbWorker.postMessage("run")}}function getScore(){return finalScore}function onSuccess(callback){hasFinished?callback(finalScore):callbacks.push(callback)}Object.defineProperty(exports,"__esModule",{value:!0});var callbacks=[],finalScore=0,hasFinished=!1,scores=[],getEl=function(selector){return document.querySelector(selector)},fireballWorker=function(){function runTest(){for(var OPS=1e6,startTime=(new Date).valueOf(),i=0;OPS>i;i++)/=/.exec("111soqs57qo8o8480qo18sor2011r3n591q7s6s37r120904"),/=/.exec("SbeprqRkcvengvba=633669315660164980"),/=/.exec("FrffvbaQQS2=111soqs57qo8o8480qo18sor2011r3n591q7s6s37r120904");var endTime=(new Date).valueOf();return OPS/(endTime-startTime)}self.addEventListener("message",function(){self.postMessage(runTest())},!1)};exports.default={run:run,getScore:getScore,onSuccess:onSuccess},module.exports=exports.default}(),function(){"undefined"!=typeof window&&(window.Fireball=module.exports||exports.default||exports)}(); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "fireball-js", 3 | "version": "1.1.5", 4 | "description": "Breakpoints for performance", 5 | "main": "dist/index.min.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "build": "npm run babel && npm run bowrap && npm run uglify && npm run clean", 9 | "babel": "babel src/index.js --out-file src/index-es5-temp.js", 10 | "bowrap": "bowrap src/index-es5-temp.js -n Fireball -o src/index-bowrapped-temp.js", 11 | "uglify": "uglifyjs src/index-bowrapped-temp.js --output dist/index.min.js --screw-ie8 --compress", 12 | "clean": "del-cli src/*temp.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "git+https://github.com/davidgilbertson/fireball.git" 17 | }, 18 | "author": "David Gilbertson (http://www.dg707.com)", 19 | "contributors": [ 20 | { 21 | "name": "Fergal Hanley", 22 | "email": "fergalhanley@gmail.com", 23 | "url": "http://fergalhanley.com" 24 | } 25 | ], 26 | "license": "ISC", 27 | "bugs": { 28 | "url": "https://github.com/davidgilbertson/fireball/issues" 29 | }, 30 | "homepage": "https://github.com/davidgilbertson/fireball#readme", 31 | "devDependencies": { 32 | "babel": "^5.8.23", 33 | "bowrap": "1.0.5", 34 | "del-cli": "^0.2.0", 35 | "uglify-js": "^2.4.24" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | const callbacks = []; 2 | let finalScore = 0; 3 | let hasFinished = false; 4 | const scores = []; 5 | 6 | const getEl = selector => document.querySelector(selector); 7 | 8 | function newEl(tag, opt) { 9 | const el = document.createElement(tag); 10 | 11 | for (const prop in opt) { 12 | if (opt.hasOwnProperty(prop)) { 13 | if (prop === 'style') { 14 | for (const styleProp in opt[prop]) { 15 | el[prop][styleProp] = opt[prop][styleProp]; 16 | } 17 | } else { 18 | el[prop] = opt[prop]; 19 | } 20 | } 21 | } 22 | 23 | return el; 24 | } 25 | 26 | function runCallbacks() { 27 | callbacks.forEach(callback => { 28 | callback(finalScore); 29 | }); 30 | } 31 | 32 | function getMedianScore() { 33 | if (!scores.length) return; 34 | 35 | const midPoint = Math.floor(scores.length / 2); 36 | let result; 37 | 38 | scores.sort(); 39 | 40 | if (scores.length % 2) { 41 | result = scores[midPoint]; 42 | } else { 43 | result = (scores[midPoint - 1] + scores[midPoint]) / 2.0; 44 | } 45 | 46 | return Math.round(result); 47 | } 48 | 49 | function log(str) { 50 | if (!getEl('#logging-panel')) { 51 | const logEl = newEl('div', { 52 | id: 'logging-panel', 53 | style: { 54 | 'position': 'fixed', 55 | 'width': '300px', 56 | 'height': '260px', 57 | 'bottom': '10px', 58 | 'left': '10px', 59 | 'padding': '10px', 60 | 'background': '#111', 61 | 'font-family': 'monospace', 62 | 'font-size': '12px', 63 | 'line-height': '14px', 64 | 'color': '#eee', 65 | 'overflow': 'auto', 66 | 'box-shadow': '2px 2px 20px rgba(0, 0, 0, 0.5)', 67 | 'z-index': '99' 68 | } 69 | }); 70 | 71 | const logH1 = newEl('h1', {textContent: 'Fireball'}); 72 | logEl.appendChild(logH1); 73 | 74 | const logMedian = newEl('p', {id: 'log-median'}); 75 | logEl.appendChild(logMedian); 76 | 77 | const logDiv = newEl('div', {id: 'log'}); 78 | logEl.appendChild(logDiv); 79 | 80 | getEl('body').appendChild(logEl); 81 | } 82 | 83 | const log = getEl('#log'); 84 | 85 | if (str === '_finished_') { 86 | getEl('#log-median').textContent = `Score: ${getMedianScore().toLocaleString()}`; 87 | 88 | log.style.color = 'grey'; 89 | } else { 90 | log.innerHTML += `

> ${str}

`; 91 | } 92 | } 93 | 94 | function appendClasses(options) { 95 | let i; 96 | let className = options.speedRanges[0].className; 97 | 98 | for (i = 1; i < options.speedRanges.length; i++) { 99 | if (finalScore >= options.speedRanges[i].min) { 100 | className = options.speedRanges[i].className; 101 | } 102 | } 103 | 104 | options.speedRanges.forEach(speedRange => { 105 | if (finalScore >= speedRange.min) { 106 | className = speedRange.className; 107 | } 108 | }); 109 | 110 | if (className) { 111 | const classSelector = options.classEl || 'body'; 112 | const classEl = getEl(classSelector); 113 | 114 | if (classEl) { 115 | classEl.classList.add(className); // If window.Worker exists, classList almost certainly does 116 | } 117 | } 118 | } 119 | 120 | const fireballWorker = () => { 121 | 'use strict'; 122 | 123 | self.addEventListener('message', () => { 124 | self.postMessage(runTest()); 125 | }, false); 126 | 127 | function runTest() { 128 | const OPS = 1000000; 129 | var startTime = new Date().valueOf(); 130 | 131 | for (let i = 0; i < OPS; i++) { 132 | /=/.exec('111soqs57qo8o8480qo18sor2011r3n591q7s6s37r120904'); 133 | /=/.exec('SbeprqRkcvengvba=633669315660164980'); 134 | /=/.exec('FrffvbaQQS2=111soqs57qo8o8480qo18sor2011r3n591q7s6s37r120904'); 135 | } 136 | 137 | const endTime = new Date().valueOf(); 138 | return OPS / (endTime - startTime); 139 | } 140 | }; 141 | 142 | function run(options = {}) { 143 | if (typeof window === 'undefined') return; 144 | 145 | finalScore = options.defaultScore || 0; 146 | 147 | if (!window.Worker) { 148 | options.callback && options.callback(); 149 | return; 150 | } 151 | 152 | const debug = options.debug || false; 153 | const runs = options.runs || 7; 154 | let count = 0; 155 | const blobURL = URL.createObjectURL(new Blob( 156 | [`(${fireballWorker.toString()})()`], 157 | {type: 'application/javascript'} 158 | )); 159 | 160 | const fbWorker = new Worker(blobURL); 161 | 162 | URL.revokeObjectURL(blobURL); 163 | 164 | fbWorker.addEventListener('message', e => { 165 | logScore(e.data); 166 | }, false); 167 | 168 | function logScore(rawScore) { 169 | const thisScore = parseInt(rawScore * 6.1813, 10); //align it roughly with Octane 170 | 171 | scores.push(thisScore); 172 | finalScore = getMedianScore(); 173 | 174 | count++; 175 | 176 | if (debug) log(finalScore.toLocaleString()); 177 | 178 | if (count < runs) { 179 | setTimeout(() => { 180 | fbWorker.postMessage('run'); 181 | }, 100); 182 | } else { 183 | hasFinished = true; 184 | 185 | runCallbacks(); 186 | 187 | if (debug) log('_finished_'); 188 | 189 | if (options.speedRanges) appendClasses(options); 190 | 191 | options.callback && options.callback(); 192 | } 193 | } 194 | 195 | fbWorker.postMessage('run'); 196 | } 197 | 198 | function getScore() { 199 | return finalScore; 200 | } 201 | 202 | function onSuccess(callback) { 203 | if (hasFinished) { 204 | callback(finalScore); 205 | } else { 206 | callbacks.push(callback); 207 | } 208 | } 209 | 210 | export default { 211 | run, 212 | getScore, 213 | onSuccess, 214 | }; 215 | -------------------------------------------------------------------------------- /test/test.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Fireball test 6 | 7 | 8 | 9 | 23 | 24 | --------------------------------------------------------------------------------