Tim Etler

Building Favitimer

November 21, 2015

Somewhat recently Google came out with a cool new search feature that lets you start a timer via a search. I really liked this workflow since it let me really quickly set up a timer from the browser by using the chrome “tab to search” functionality. I’d just open a new tab, type in “google.com”, hit tab, type “x minute timer”, hit enter, and boom, instant timer. I could get a timer set up in seconds. But there was an annoyance. I had to switch to the tab to see how much time was left! How could I live like this? So I came up with a solution; what if the favicon updated to show how much time was left? I mostly set timers while I’m cooking, so this would allow me to pull up a recipe on one tab, and be able to instantly glance at how much time is left in the other tab. By using the favicon I could also pin the tab to use less space.

I did a thorough search and couldn’t find anything that did this, and favitimer.com was available, so I bought the domain and took a weekend to go make it. While building it, I ended up using a few interesting techniques, so I thought it’d be nice to do a write up on them. If you’d like to see the code, everything is up on GitHub;

##Updating the Favicon

The crux of this mini app is setting a dynamic image as the tab favicon. Not all browsers support this, so straight off, IE and Safari are out. IE doesn’t support updating the favicon at all before IE11, and Safari doesn’t show favicons at all. Favicons can only be updating via the meta tag’s href attribute, which only leaves us with one option for updating it dynamically, which is with a data URI. Data URIs are a great new browser feature that enable a whole host of new tricks, and I end up using it in a variety of different ways later on. It’s a way of encoding any kind of data, including binary data as an inline string. For binary data, you can encode it in base 64. You also include a MIME type to let the browser know what kind of data you’re encoding.

Now that we have a way of updating the favicon, we need a way to generate the image data. Currently the best way to generate bitmap images is through the canvas API, and even better, the canvas API already provides a toDataURL method for generating a data URI so updating the favicon is very easy. All you have to do is render something on your canvas and set the favicon link href attribute to the data URI result.

To show how much time is left on the timer, I opted for a circle outline that slowly fills up radially until it forms a complete circle. For example, a timer that’s 25% done will show a quarter circle. The canvas API also provides an arc drawing method that takes a start and end radian that makes it easy to create a semi-circle.

Here’s the full drawing code:

function draw(canvas, percent, ms, color) {
  var context = canvas.getContext('2d'),
      diameter;
  diameter = Math.min(canvas.height, canvas.width);
  context.lineWidth = diameter / 12;
  if (percent >= 1){
    context.strokeStyle = '#00cc00';
  } else {
    context.strokeStyle = '#dd0000';
  }
  context.strokeStyle = color || context.strokeStyle;
  context.font = 'bold 7.5px monospace';
  context.textAlign = 'center';
  context.fillStyle = '#333333';
  context.clearRect(0, 0, canvas.width, canvas.height);
  context.beginPath();
  context.arc(diameter / 2, diameter / 2, diameter * 0.375, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * percent, false);
  context.stroke();
  if (ms > 0) {
    ms /= 1000;
    ms = Math.ceil(ms);
    if (ms > 60) { ms /= 60; }
    if (ms > 60) { ms /= 60; }
    ms = Math.floor(ms);
    context.fillText(ms, 8, 11, 10);
  }
}

function drawFavicon(canvas, percent, ms, color) {
  draw(canvas, percent, ms, color)
  link.href = canvas.toDataURL('image/png');
}

I reuse the image in the body of the page, so I made the method generic. Color is used to indicate the state of the timer, and the time left is represented in milliseconds, showing hours, minutes, or seconds left depending on how much time is left (the unit is left out because the number needs to fit into the small space of a favicon).

##Smoother Animation

Because the timer is displayed in the favicon, it’s intended to reside in an inactive tab so you can just glance at it while doing something else. But while building the app, I discovered a limitation to requestAnimationFrame, and setInterval, which is that they gets throttled to a few fps when the tab is inactive. The browser doesn’t want inactive tabs sucking up resources when nobody is looking at them. While that’s fine most of the time, it wasn’t OK for this app since you want to see the timer updating accurately when off the page!

After some research I found that you can workaround this issue by using a web worker. Web workers are designed to run in parallel, so they’re not throttled when a tab is inactive. Since web workers don’t have access to the original calling context, I wasn’t able to use requestAnimationFrame as that relies on the original window context, so that left my best option as setInterval. I created an interval worker that simply ran its own setInterval and passed a message back whenever it fired, and that fixed the animation smoothness problem.

The worker script is super simple:

var interval = null;
this.onmessage = function(event) {
  if (event.data.start) {
    interval = setInterval(function () {
      this.postMessage('');
    }, event.data.ms);
  }
  if (event.data.stop) {
    clearInterval(interval);
  }
};

To start or stop a worker you just send a message with either {start: true}, or {stop: true}. I didn’t want to break support with browsers that don’t have web workers enabled, so I also wrapped the interval methods with a fallback to the regular setInterval method along with its caveat:

function startInterval (callback, delay) {
  var interval;
  try {
    interval = new Worker(workerURL);
    interval.onmessage = callback;
    interval.postMessage({start: true, ms: delay});
  } catch (error) {
    interval = setInterval(callback, delay);
  } finally {
    return interval;
  }
}

function stopInterval (interval) {
  if (typeof interval === 'number') {
    clearInterval(interval);
  } else if (interval) {
    interval.postMessage({stop:true});
    interval.terminate();
  }
}

As a side note, never assume that setTimeout or setInterval will actually execute in the time you tell it to, there are tons of factors that prevent them from being accurate, so if you need to base something on time elapsed, create a start timestamp and calculate the difference.

##Adding Sound

I played around with a couple ways of adding sound for the beep noise that plays when the timer finishes. Originally I used an inline base 64 encoded audio file and played it using the Audio API, but I wasn’t a fan of having a big binary file in the middle of the code. Originally I used this bit of code:

function beep() {
  var sound = new Audio("data:audio/wav;base64,...");
  sound.play();
}

Looking further I found the AudioContext API for creating sounds programmatically. The new beep function required more code, but was much more flexible, and cut down on the file size a lot:

function beep (duration, frequency, volume) {
  var oscillator, gainNode;
  duration = duration || 500;

  oscillator = audioContext.createOscillator();
  gainNode = audioContext.createGain();

  oscillator.connect(gainNode);
  gainNode.connect(audioContext.destination);

  gainNode.gain.value = 0;
  oscillator.frequency.value = frequency;

  oscillator.start();

  gainNode.gain.setTargetAtTime(volume, audioContext.currentTime, 0.005);
  gainNode.gain.setTargetAtTime(0, audioContext.currentTime + (duration / 1000), 0.005);
};

The AudioContext API works by creating and hooking together various AudioNodes to create different tones. An OscillatorNode creates a wave function that sounds like a solid tone. A GainNode adjusts the volume of the oscillator node. The oscillator node is then connected to the gain node which is hooked up to the audio context output. The parameters for the sound are set, with gain setting the volume, and frequency setting the pitch of the tone. When I first tried starting and stopping the oscillator, it had the unfortunate side effect of creating a popping noise when it turned on and off, due to the sudden change of volume. To solve that, I used setTargetAtTime, which gradually changes the volume over time. It takes a target volume, an absolute time to change the volume at, and a value to set how quickly the sound should transition on an exponential basis. With that quick transition I was able to get rid of that popping noise, and the resulting beep was (while still purposefully annoying), less jarring.

##Making it Fast

Because the app is a simple single page Javascript utility, I wanted to make the page load as lightweight as possible. To do this I wanted to get the page down to a single request*, and that means inlining everything. For the most part it was pretty simple.

To handle the inlining and minification of the CSS and Javascript I just used the gulp-inline npm package.

I decided against using any frameworks of utility libraries so I wouldn’t have to manage a big inline library, and also to cut back on the file size of the page. That means using nothing but the pure native DOM API. The compatibility benefits of libraries like underscore or jQuery weren’t an issue as only modern browsers support canvas and updating the favicon dynamically anyways. It’s also nice to do a project pure every now and then to reconnect with the browser :).

Not all the inlining was trivial though. The most interesting part was inlining the web worker script. The web worker API takes a URL string to the worker script you want to use for the worker, so we can use the same data URI trick, but I didn’t want to have to re-encode the script every time I made an edit, so after doing some research I found that you can provide a local script by passing the string version of a function using toString to the web worker API by using the Blob data type. A Blob is a like a lightweight temporary file, and you can put any kind of data in it, including Javascript. After instantiating a Blob you can get a blob URI for it in the format of blob:(domain)/(hash) by passing it to the URL API using createObjectURL. After the temporary URL is created it acts like a normal URL. You can visit it, and include it, allowing you to use it as a worker script:

workerURL = (function () {
  var blob, intervalWorker, workerString, Builder;
  intervalWorker = function () {
    var interval = null;
    this.onmessage = function(event) {
      if (event.data.start) {
        interval = setInterval(function () {
          this.postMessage('');
        }, event.data.ms);
      }
      if (event.data.stop) {
        clearInterval(interval);
      }
    };
  };
  workerString = "("+intervalWorker.toString()+")();";
  if (Blob) {
    blob = new Blob([workerString], {type: 'application/javascript'});
  } else if (BlobBuilder || WebKitBlobBuilder || MozBlobBuilder) {
    Builder = BlobBuilder || WebKitBlobBuilder || MozBlobBuilder;
    blob = new Builder();
    blob.append(workerString);
    blob = blob.getBlob();
  }
  if (blob) {
    return URL.createObjectURL(blob);
  } else {
    return 'interval.js';
  }
})();

workerString needs to be wrapped with the parenthesis to execute the function, otherwise it will just define it in the worker and do nothing.

In this project, I use data URIs in image, and code format. I think it really shows off the power and versatility of this cool feature. Since you can use them pretty much anywhere you’re pretty much just limited to your imagination!

The result of all this? The final payload is a single request that gzips down to 4.2kb. The page will load pretty much as quickly as the host will respond. On my internet the page loads consistently in under 50 ms minus the DNS lookup, and sometimes under 20ms.

* Ok, I have a Google Analytics script to spy on you guys, but that’s not a blocking resource for running the app, so it doesn’t slow anything down.

##Parsing Input

Another feature I wanted was semantic parsing of a query string so I could start a timer quickly from the URL bar. This was one of the main features I liked from setting a timer on Google, so I wanted to make sure I replicated this for my own convenience.

To start out, I wanted to allow setting the timer via a query parameter. I jumped around a bunch of possible parsing solutions with the main consideration being how flexible I wanted to make it. I wanted it to be able to accept multiple ways of writing time units, such as “seconds”, “second”, “secs”, “sec”, “s”, etc. Since I wanted pretty much every permutation, I settled on a regular expression that checked the first two starting characters of the unit, and then optionally consumed the rest of the letters for that unit, ignoring omitted letters.

I also wanted to be able to put in fractions of units, such as “0.5”, or “1/2”. To do that, I used a very carefully placed eval. Since the only input it can take is the result of a regex match, it’s only as safe as the regex is, which only looks for integers, . for decimals, and / for fractions.

function executeSearch (search) {
  var searchRegex = /((?:\d*\.|\d+\s*\/\s*)?\d+)[\s+]*(se?c?o?n?d?s?|mi?n?u?t?e?s?|ho?u?r?s?|$)(?![a-z])/gi,
      hours = 0,
      minutes = 0,
      seconds = 0,
      match, unit;
  while (match = searchRegex.exec(search)) {
    unit = match[2] || '';
    switch (unit[0]) {
      case 'm':
        minutes = eval(match[1]);
        break;
      case 'h':
        hours = eval(match[1]);
        break;
      default:
        seconds = eval(match[1]);
        break;
    }
  }
  ...
}

Each time the regex executes, its keeps track of what position it has consumed up to thus far, so calling exec multiple times ends up finding all matching fragments in the location query string. Since it ignores any part of the string that doesn’t match, it also works for semantically written sentences such as “set a 10 minute and 30 second timer”.

I also wanted to enable tab to search to let you fill out the time in the address bar, which is enabled in chrome using an OpenSearch description document. To tell the browser where to find it, you include a special link tag in the head portion of the document:

<link type="application/opensearchdescription+xml" rel="search" href="/search.xml"/>

The search.xml file looks like this:

<?xml version="1.0"?>
<OpenSearchDescription xmlns="http://a9.com/-/spec/opensearch/1.1/">
   <ShortName>Favitimer</ShortName>
   <Description>Enter a period of time to set a timer.</Description>
   <Url type="text/html" method="get" template="http://www.favitimer.com/?q={searchTerms}"/>
</OpenSearchDescription>

This describes the pattern and functionality of the search to the browser.

##That Was Fun

So even though this was a small and simple project, there’s still a good deal of novel tech going on in it. It was fun to work with some of the new web technologies and really focus on small details in a very scoped side project. I’m pretty proud of how the code came out, so give it a look. I hope you learned something in this write up, and happy coding!