ScriptTest

Javascript Performance Tests

  1. // ==UserScript==
  2. // @name ScriptTest
  3. // @version 1.5
  4. // @description Javascript Performance Tests
  5. // @author Danny Hinshaw
  6. // @match https://www.google.com/scripttest
  7. // @namespace http://nulleffort.com/
  8. // ==/UserScript==
  9.  
  10. (function() {
  11. 'use strict';
  12. /*jshint esnext: true */
  13.  
  14. // JS Test UI Object
  15. const TEST_UI = {
  16. runFlag : true,
  17. errorFlag: false,
  18. templates: {
  19. favicon : function() {
  20. const link = document.querySelector("link[rel*='icon']") || document.createElement('link');
  21. link.type = 'image/x-icon';
  22. link.rel = 'shortcut icon';
  23. link.href = 'http://nulleffort.com/wp-content/uploads/2016/11/cropped-favicon-32x32.png';
  24. return document.querySelector('head').appendChild(link);
  25. },
  26. bootStrap : function() {
  27. const bootLink = document.createElement('link');
  28. bootLink.rel = 'stylesheet';
  29. bootLink.href = 'https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css';
  30. const fontLink = document.createElement('link');
  31. fontLink.rel = 'stylesheet';
  32. fontLink.href = 'https://fonts.googleapis.com/css?family=Ubuntu';
  33. const links = [bootLink, fontLink];
  34.  
  35. return links.forEach(link => document.querySelector('head').appendChild(link));
  36. },
  37. jQueryLib: function() {
  38. const jQ1 = `<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>`;
  39. const jQ2 = `<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.2.4/jquery.min.js"></script>`;
  40. const jQ3 = `<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.12.4/jquery.min.js"></script>`;
  41. const lib = document.getElementById('library').value;
  42. switch (lib) { case '1': return jQ1; case '2': return jQ2; case '3': return jQ3; default: return ''; }
  43. },
  44. body : `
  45. <header>
  46. <h1>< ScriptTest /></h1>
  47. <p>We're going the distance... we're going for speed.</p>
  48. </header>
  49. <main>
  50. <div id="intro">
  51. <div id="instructions">
  52. <p>
  53. To use ScriptTest, enter any html you may need for your test scripts in the html box (not required). Do not worry about headers etc, the html is already set up, you are essentially just entering elements into a body.
  54. </p>
  55. <p>
  56. Add as many test boxes as the Javascript snippets you'd like to test. Each editor will be treated as a separate test case. You do not need to add any loops as ScriptTest will take care of that for you (just select from the settings how many iterations you want for testing).
  57. </p>
  58. </div>
  59. <div id="settings" class="col-xs-12">
  60. <h3 class="row-xs-12">Settings</h3>
  61. <div class="col-xs-12">
  62. <div class="setting_cont row-xs-6">
  63. <span> Iterations:</span>
  64. <select id="iterations" class="form-control">
  65. <option value="101">100</option>
  66. <option value="1001">1,000</option>
  67. <option value="10001">10,000</option>
  68. <option value="100001">100,000</option>
  69. </select>
  70. </div>
  71. <div class="setting_cont row-xs-6">
  72. <span> Library:</span>
  73. <select id="library" class="form-control">
  74. <option value="0">None</option>
  75. <option value="3">jQuery 3.2.1</option>
  76. <option value="2">jQuery 2.2.4</option>
  77. <option value="1">jQuery 1.12.4</option>
  78. </select>
  79. </div>
  80. </div>
  81. <div class="col-xs-12">
  82. <div class="setting_cont row-xs-6">
  83. <span>Click to add more tests:</span>
  84. <button id="add_btn" class="btn btn-outline-primary">Add Test</button>
  85. </div>
  86. <div class="setting_cont row-xs-6">
  87. <span>Click to remove all tests:</span>
  88. <button id="remove_all_btn" class="btn btn-outline-primary">Remove Tests</button>
  89. </div>
  90. <br>
  91. </div>
  92. </div>
  93. </div>
  94. <div id="html_div">
  95. <div class="html_editor">
  96. <br>
  97. <label for="html">HTML</label>
  98. <textarea id="html" placeholder="Enter HTML" cols="70" rows="15"></textarea>
  99. </div>
  100. </div>
  101. <div id="test_1_div" class="js_div">
  102. <div class="js_editor">
  103. <br>
  104. <label for="test_1">Javascript Test 1</label>
  105. <textarea id="test_1" placeholder="Enter Javascript" cols="70" rows="15"></textarea>
  106. </div>
  107. </div>
  108. <div id="test_commands">
  109. <br>
  110. <span>Click to run tests</span>
  111. <button id="start_btn" class="btn btn-outline-primary">Run</button>
  112. </div>
  113. <div id="res-text-cont">
  114. <p id="results_label">Results</p>
  115. <p id="results_instruct">*The first test is used as a base to compare the rest.</p>
  116. </div>
  117. <div id="results_table">
  118. <div class="results_body">
  119. </div>
  120. </div>
  121. </main>
  122. <footer>
  123. <span>This script is currently in beta. There are no gaurantees it will work with your browser... or work at all.</span>
  124. </footer>
  125. <div id="blackout">
  126. <div class="circle1"></div>
  127. <div class="circle2"></div>
  128. <div class="circle3"></div>
  129. </div>
  130. `,
  131. css : `
  132. <style>`+
  133.  
  134. /***** Main CSS Styles ****/
  135.  
  136. `body { color: white; overflow-x: hidden; }
  137. header { align: left; background-color: #1a1b1c; box-shadow: 0px 2px 5px #201717; margin: -10px -10px 1px; padding: 25px; }
  138. header > h1 { color: #72f766; font-family: 'Ubuntu', sans-serif; font-size: 4rem; letter-spacing: .15rem; margin-left: 25px; text-align: center; -webkit-font-smoothing: antialiased }
  139. header > p { font-family: 'Ubuntu', sans-serif; font-style: italic; margin-left: 25px; text-align: center; -webkit-font-smoothing: antialiased }
  140. body { background-color: #3c434f}
  141. main { padding: 25px; font-family: 'Ubuntu', sans-serif; font-size: 1.5rem; letter-spacing: .05em; }
  142. #instructions { padding-bottom: 2rem; }
  143. #instructions > *, #settings > * , #test_commands, #res-text-cont { margin-left: 1.5em; }
  144. #html_div > *, div.js_div > * { margin-left: 3em }
  145. p { color: #E7DFDD }
  146. #settings { display: inline-block; padding-bottom: 1.75em; }
  147. h3 { color: #72f766; }
  148. #html_div { border-top: ridge; }
  149. div.setting_cont { display: inline-block; padding: .5em; width: auto; }
  150. .form-control { display: inline-flex; width: 10rem; }
  151. .btn { background-color: rgb(23, 39, 67); }
  152. .btn.focus, .btn:focus, .btn:hover { color: rgb(183, 202, 184); text-decoration: underline; }
  153. label { color: #72f766; }
  154. label, textarea { display: block; margin: 10px 0; }
  155. textarea { background-color: #1d1f20; color: #85d17e; font-family: monospace; font-size: 15px; }
  156. .js_div { border-top: ridge }
  157. footer { padding: 2rem; }
  158. footer > span { font-family: Andale Mono, monospace; font-style: italic; }
  159. iframe { display: none }
  160. #errDIV { background-color: #800000; border-style: inset; margin-top: 15px; padding: 1px 10px 10px; }
  161. #res-text-cont { display: none; }
  162. #results_label { color: #72f766; font-size: 1.5em; padding-top: 3rem; }
  163. #results_table { display: table; visibility: hidden; width: 100%; }
  164. .results_row { display: table-row }
  165. .test_rank > span::after { content: attr(data-rel); margin-left: 5px; }
  166. .cell { background-color: #161F2F; border: 1px solid #999999; display: table-cell; padding: 3px 10px; }
  167. .results_body { display: table-row-group }`+
  168.  
  169. /**** Loader CSS ***/
  170. `
  171. #blackout {
  172. height: 100%;
  173. width: 100%;
  174. left:0;
  175. bottom:0;
  176. background: rgba(61, 59, 59, 0.69);
  177. display: none;
  178. overflow: hidden;
  179. position: absolute;
  180. z-index: 10;
  181. }
  182. .circle1 {
  183. display: inline-flex;
  184. height: 10rem;
  185. width: 10rem;
  186. border-radius: 50%;
  187. border: .75rem solid rgba(206, 157, 227, 0.66);
  188. margin: 40% auto;
  189. margin-left: 40%;
  190. animation: load 1.5s 0.1s infinite;
  191. opacity: 0.8;
  192. }
  193. .circle2 {
  194. display: inline-flex;
  195. height: 10rem;
  196. width: 10rem;
  197. border-radius: 50%;
  198. border: .7rem solid rgba(132, 138, 235, 0.64);
  199. margin: 0 auto;
  200. margin-left: -45px;
  201. animation: load 1.5s 0.2s infinite;
  202. opacity: 0.8;
  203. }
  204. .circle3 {
  205. display: inline-flex;
  206. height: 10rem;
  207. width: 10rem;
  208. border-radius: 50%;
  209. border: .6rem solid rgba(90, 177, 171, 0.64);
  210. margin: 0 auto;
  211. margin-left: -45px;
  212. animation: load 1.5s 0.3s infinite;
  213. opacity: 0.8;
  214. }
  215. @keyframes load {
  216. from {
  217. transform: rotate3d(1,2,3, 0deg);
  218. }
  219. 50% {
  220. transform: rotate3d(1,2,3, 360deg);
  221. }
  222. to {
  223. transform: rotate3d(1,2,3, 0deg);
  224. }
  225. } </style>`,
  226. mainScript: function(j) {
  227. return `
  228. <html lang="en">
  229. <head>
  230. <meta http-equiv="cache-control" content="max-age=0" />
  231. <meta http-equiv="cache-control" content="no-cache" />
  232. <meta http-equiv="expires" content="0" />
  233. <meta http-equiv="expires" content="Tue, 01 Jan 1980 1:00:00 GMT" />
  234. <meta http-equiv="pragma" content="no-cache" />
  235. ${j}
  236. <script>
  237. window.addEventListener('load', () => {
  238. window.parent.dispatchEvent(new Event('perf:load'));
  239. });
  240. </script>
  241. <script id="_script_">` +
  242. /************************ Iframe Performance Function ******************************/
  243. `window.addEventListener('perf:exec', e => {
  244. const {fn} = e.detail;
  245. let t0, t1;
  246. try {
  247. let script = new Function(fn);
  248. document.body.innerHTML = window.parent.document.body.querySelector('iframe').dataset.body;
  249. t0 = performance.now();
  250. script();
  251. t1 = performance.now();
  252. script = null;
  253. } catch(err) {
  254. const event = new CustomEvent('perf:error', {detail: {error: err}});
  255. window.parent.dispatchEvent(event);
  256. return console.error(err);
  257. }
  258. const event = new CustomEvent('perf:done', {detail: {time: t1-t0}});
  259. e.ownerObject.dispatch(event);
  260. });`+
  261. '</script></head><body></body></html>';
  262. },
  263. id : () => {
  264. return document.querySelectorAll('textarea').length;
  265. },
  266. editor : function() {
  267. // Editor template
  268. let editor = document.createElement('div');
  269. editor.id = 'test_' + TEST_UI.templates.id() + '_div';
  270. editor.className = "js_div";
  271. editor.innerHTML = `
  272. <div class="js_editor">
  273. <label for="test_${TEST_UI.templates.id()}">Javascript Test ${TEST_UI.templates.id()}</label>
  274. <button class="btn btn-outline-primary remove_btn">Remove Test ${TEST_UI.templates.id()}</button>
  275. <textarea id="test_${TEST_UI.templates.id()}" class="js_editor" placeholder="Enter Javascript" cols="70" rows="15"></textarea>
  276. </div>`;
  277.  
  278. // Set button styles for focus/blur
  279. document.querySelectorAll('button').forEach(button => button.addEventListener("mouseup", e => e.target.blur()));
  280. // Dynamic Editor Removal
  281. document.querySelector('main').insertBefore(editor, document.getElementById('test_commands'));
  282. [].forEach.call(document.querySelectorAll('button.remove_btn'), b => b.onclick = () => {
  283. b.parentNode.parentNode.remove();
  284. rename();
  285. });
  286.  
  287. function rename() {
  288. let i = 1;
  289. [].forEach.call(document.querySelectorAll('div.js_div ~ div.js_div'), e => {
  290. i++;
  291. e.querySelector('button.remove_btn').textContent = 'Remove Test '+i;
  292. e.querySelector('div>label').textContent = 'Javascript Test '+i;
  293. e.querySelector('div>textarea').id = 'test_'+i;
  294. e.id = 'test_'+i+'_div';
  295. });
  296. }
  297. },
  298. },
  299.  
  300. iframe : {
  301. create: function(html) {
  302. const iframe = document.createElement('iframe');
  303. iframe.id = '__iframe';
  304. iframe.srcdoc = TEST_UI.templates.mainScript(TEST_UI.templates.jQueryLib());
  305. iframe.dataset.body = html;
  306.  
  307. document.body.append(iframe);
  308. TEST_UI.frameDOM = { iframe: iframe, dom: iframe.contentDocument, window: iframe.contentWindow };
  309. return TEST_UI.frameDOM;
  310. },
  311. },
  312. perfFunc : function() {
  313.  
  314. console.log('Testing in progress...');
  315.  
  316. TEST_UI.runFlag = false;
  317. TEST_UI.iterations = Number(document.querySelector('#iterations').value);
  318. const textarea = document.querySelectorAll('textarea:not(#html)'),
  319. resultTable = document.getElementById('results_label'),
  320. resultBody = document.querySelector('.results_body');
  321.  
  322. // Check for values in textareas before running further
  323. for (let i = 0; i < textarea.length; i++) {
  324. const val = textarea[i].value;
  325.  
  326. // Handling for UI errors (empty textareas)
  327. if (!val) {
  328. TEST_UI.onError(textarea[i]);
  329. return console.log('Script in', textarea[i].id, 'has no value');
  330. } else if (document.getElementById('errDIV')) {
  331. TEST_UI.errorFlag = false;
  332. document.getElementById('errDIV').remove();
  333. [].forEach.call(document.querySelectorAll('textarea:not(#html)'), t => {
  334. if (t.style.backgroundColor === 'red')
  335. t.style.backgroundColor = "#1d1f20";
  336. });
  337. }
  338. }
  339. function genTable(num, id, mean) {
  340.  
  341. TEST_UI.commands.loaded();
  342. // Check if table has yet been displayed
  343. if (resultTable.offsetParent === null && !TEST_UI.errorFlag) resultTable.style.display = 'block';
  344.  
  345. resultBody.innerHTML += `
  346. <div class="results_row">
  347. <div class="cell"><p class="test_number">${id.replace('test_', 'Test ')}</p></div>
  348. <div class="cell"><p class="test_time">${Math.round(mean*1000000)/1000000 + ' milliseconds'}</p></div>
  349. <div class="cell"><p class="test_rank"><span></span></p></div>
  350. </div>`;
  351.  
  352. const tableTimes = Array.from(resultBody.querySelectorAll('.test_time'));
  353.  
  354. if (tableTimes.length === textarea.length) {
  355. const ranks = resultBody.querySelectorAll('.test_rank>span');
  356. tableTimes.map(({ textContent:ms }) => ms.split(' ')[0])
  357. .map((time, _, a) => 100 * (a[0] / time - 1))
  358. .forEach((v, i) => {
  359. ranks[i].dataset.rel = v ? (v > 0 ? 'faster' : 'slower') : '';
  360. ranks[i].textContent = Math.abs(v || 100).toFixed(2) + '%';
  361. });
  362. }
  363. }
  364.  
  365. function end(num, id, times) {
  366. times.shift();
  367. const mean = times.reduce((a, b) => a + b) / TEST_UI.iterations;
  368. genTable(num, id, mean);
  369. TEST_UI.templates.favicon();
  370. }
  371.  
  372. for (let i = 0; i < textarea.length; i++) {
  373. const val = textarea[i].value;
  374.  
  375. TEST_UI.runners = [];
  376. let j = TEST_UI.iterations;
  377.  
  378. while (j--) {
  379. if (TEST_UI.runFlag === false) {
  380. const promise = new Promise(resolve => {
  381. const pf = new PerfRunner(TEST_UI.frameDOM);
  382. pf.listen('perf:done', e => resolve(e.detail.time));
  383. pf.run(val);
  384. });
  385. TEST_UI.runners.push(promise);
  386. }
  387. }
  388. Promise.all(TEST_UI.runners).then(end.bind(null, i, textarea[i].id));
  389. }//);
  390. TEST_UI.runFlag = true;
  391. document.getElementById('__iframe').remove();
  392. },
  393. onError : function(err) {
  394.  
  395. TEST_UI.commands.loaded();
  396. if (document.getElementById('results_label').offsetParent !== null)
  397. document.getElementById('results_label').style.display = 'none';
  398.  
  399. if (document.getElementById('errDIV'))
  400. document.getElementById('errDIV').remove();
  401.  
  402. const errDiv = document.createElement('div');
  403. errDiv.id = 'errDIV';
  404. TEST_UI.errorFlag = true;
  405. TEST_UI.runFlag = true;
  406. document.getElementById('results_label').parentNode.append(errDiv);
  407.  
  408. if (err.detail) {
  409. errDiv.innerHTML = `<h2>Error:</h2><p>${err.detail.error.stack}</p>`;
  410. } else {
  411. errDiv.innerHTML = '<h2>Error: One or more JS editors is empty</h2>';
  412. err.style.backgroundColor = 'red';
  413. }
  414.  
  415. },
  416. commands : {
  417. loading : function() {
  418. [].forEach.call(document.querySelectorAll('div.results_row'), r => r.remove());
  419.  
  420. window.scrollTo(0, 0);
  421. document.body.style.overflowY = 'hidden';
  422. document.getElementById('blackout').style.display = 'block';
  423. document.getElementById('res-text-cont').style.display = 'none';
  424. },
  425. loaded : function() {
  426. document.body.style.overflowY = 'auto';
  427. document.getElementById('blackout').style.display = '';
  428. document.getElementById('res-text-cont').style.display = 'block';
  429. window.scrollTo(0,document.body.scrollHeight);
  430. },
  431. removeAll : function() {
  432. [].forEach.call(document.querySelectorAll('div.js_div ~ div.js_div'), e => e.remove());
  433. document.getElementById('test_1').value = "";
  434. },
  435. removeTest: function(b) {
  436. b.parentNode.remove();
  437. },
  438. run : function() {
  439. if (TEST_UI.runFlag) {
  440. TEST_UI.commands.loading();
  441. TEST_UI.iframe.create(document.querySelector('#html').value);
  442. document.getElementById('results_table').style.visibility = 'visible';
  443. TEST_UI.runFlag = true;
  444. }
  445. },
  446. },
  447. init : function() {
  448.  
  449. console.log('ScriptTest started');
  450.  
  451. document.querySelector('style').remove();
  452.  
  453. // Setup UI
  454. // Set favicon & BootStrap
  455. TEST_UI.templates.favicon();
  456. TEST_UI.templates.bootStrap();
  457. document.head.innerHTML += TEST_UI.templates.css;
  458. document.body.innerHTML = TEST_UI.templates.body;
  459. document.title = 'ScriptTest';
  460.  
  461. // Set button actions
  462. document.getElementById('add_btn').onclick = () => TEST_UI.templates.editor();
  463. document.getElementById('remove_all_btn').onclick = () => TEST_UI.commands.removeAll();
  464. document.getElementById('start_btn').onclick = () => TEST_UI.commands.run();
  465. // Set button styles for focus/blur
  466. document.querySelectorAll('button').forEach(button => button.addEventListener("mouseup", e => e.target.blur()));
  467. // Custom window event listener
  468. window.addEventListener('perf:load', TEST_UI.perfFunc);
  469. window.addEventListener('perf:error', TEST_UI.onError);
  470. }
  471. };
  472.  
  473. //Class to handle async promises in perfFunc
  474. class PerfRunner {
  475. constructor(iframe) {
  476. this.iframe = iframe;
  477. this.listeners = {};
  478. }
  479.  
  480. run(fn) {
  481. const event = new CustomEvent('perf:exec', { detail: { fn: fn } });
  482. event.ownerObject = this;
  483. this.iframe.window.dispatchEvent(event);
  484. }
  485.  
  486. listen(type, callback) {
  487. if (!this.listeners[type])
  488. this.listeners[type] = [];
  489. this.listeners[type].push(callback);
  490. }
  491.  
  492. dispatch(event) {
  493. if (!this.listeners[event.type]) return;
  494. this.listeners[event.type].forEach(fn => fn.call(this, event));
  495. }
  496. }
  497.  
  498. // Initialize
  499. if (!window.location.href.includes('scripttest')) {
  500. // Add link for ScriptTest to google.com
  501. const parentDiv = document.querySelector('div.gb_zf.gb_R.gb_Pf.gb_Hf'),
  502. firstBtn = document.querySelectorAll('div.gb_Q.gb_R')[1],
  503. startBtn = document.createElement('div');
  504.  
  505. startBtn.className = 'gb_Q gb_R';
  506. startBtn.innerHTML = '<a class="gb_P" href="https://www.google.com/scripttest">ScriptTest</a>';
  507. parentDiv.insertBefore(startBtn, firstBtn);
  508. } else {
  509. // Load UI
  510. TEST_UI.init();
  511. }
  512.  
  513. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址