For fun, I decided to inspect the obfuscated source code on TypingTest and figure out how to write an auto typer for it. I came up with the following UserScript.
A couple of notes:
- The site's code simply checks for focus on the
.test-edit-areaelement, and handles eachkeydownandkeypressevent that occurs on the element, starting the countdown timer on the first encounteredkeypressevent. - The site's
keydownhandler useskeyCodeexclusively, and itskeypresshandler useskeyCodeas a fallback afterwhichandcharCode, so I thought it would be easier to just assign that single getter forkeyCode. - I used a mutation observer to watch for the
.test-text-areaand.test-edit-areabecause they're dynamically loaded after the document idles and the script was occasionally failing due to race condition. - I intentionally worked to avoid scoped variable references in the generator function and the
keyCodegetter, because they improved iteration speed significantly (from ~1900 wpm to ~2450 wpm) - I used
detailto store thetext, as per the convention forCustomEvent. - I used non-lexical scoping for
ibecause it slightly improves iteration speed (~2550 wpm instead of ~2450 wpm running on macOS Sierra Chrome 54) - Changing
intervalto0seemed to make no discernible difference, so I left it at1to emphasize the asynchrony of the script.
// ==UserScript==
// @name Auto Typer
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Automatically takes typing test in an infinite loop
// @author Patrick Roberts
// @match http://www.typingtest.com/test.html*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// select the target node
const container = document.querySelector('#test-container');
// configure observer to look for added children
const config = { childList: true };
// create an observer instance
const observer = new MutationObserver(verifyDependencies);
// pass in the target node, as well as the observer options
observer.observe(container, config);
// check for existence of elements independently of mutations
// in case mutations occurred before observer was initialized
verifyDependencies();
function verifyDependencies() {
// get references to needed elements to check for existence after each mutation
const textarea = container.querySelector('.test-text-area');
const editor = container.querySelector('.test-edit-area');
// wait to initialize when needed elements are in existence
if (textarea !== null && editor !== null) {
// pass references to textarea and editor
initializeAutoTyper(textarea, editor);
// disconnect observer to end callbacks
observer.disconnect();
}
}
function initializeAutoTyper(textarea, editor) {
// formatted string from text area
const text = textarea.textContent.replace(/\n| +(?= )/g, '').trim().replace(/\u21a9|$/g, '\r');
// initialize iterator
const iterator = typer(text, editor);
// wait 0 ms between keystrokes
const interval = 1;
// typer generator function
function* typer(text, editor) {
// want non-lexical scoping to speed up iteration
var i;
// initialize properties
const descriptor = {
// use `detail` to avoid a scoped reference in the `keyCode` getter
detail: {
value: text
},
keyCode: {
get: function getter() {
return this.detail.charCodeAt(i);
}
}
};
// initialize re-useable events
const down = new KeyboardEvent('keydown');
const press = new KeyboardEvent('keypress');
// use descriptor because simply assigning `keyCode` does not "stick"
Object.defineProperties(down, descriptor);
Object.defineProperties(press, descriptor);
// loop the test infinitely
while (true) {
// iterate through test string
for (i = 0; i < text.length; i++) {
// keydown and keypress events automatically update necessary getters by assigning `i`
editor.dispatchEvent(down);
editor.dispatchEvent(press);
// pause between events to allow for page reflow
yield;
}
}
}
// focus then type
editor.focus();
setInterval(iterator.next.bind(iterator), interval);
}
})();
I am aware that generator functions are not optimized by the v8 runtime compiler, so I'm sure there are performance improvements to make, but this approach seemed to be the "cleanest" method in terms of readability to me.
One specific issue (not a bug, because the script executes as intended) is that the iteration speed steadily drops the farther along the text it goes, and then picks back up when the inner for loop is finished and restarts at the beginning of the string again. I don't know why this is happening.
Any suggestions to improve readability, performance, and code standardization are very welcome.