Skip to content

How TabTTL paints a live clock wipe onto any website's tab icon, and puts the original back without a trace.

JP 2 min read

TabTTL's most visible feature is also its smallest: a countdown drawn right on each tab's icon. The elapsed part turns grey, as if a clock hand were sweeping across it. That looks simple, but getting it to work on every website took a few tricks.

The browser won't let you draw on a tab

There is no extension API for "draw on this tab's icon". The only way to change a favicon is the way the page itself does it: a <link rel="icon"> element in its <head>. So TabTTL runs a small content script in each tab that becomes, briefly, the page's favicon manager.

Disable, don't delete

The naive approach removes the page's icon links and adds a new one. That breaks the moment you want to stop: the page's original markup is gone. So TabTTL renames them instead:

ts
for (const link of document.querySelectorAll<HTMLLinkElement>('link[rel~="icon" i]')) {
	if (link.id === OWN_ID) continue;
	link.dataset.tabttlRel = link.rel; // remember the original
	link.rel = 'tabttl-disabled-icon'; // browsers ignore unknown rels
}

When the countdown stops, each link gets its original rel back. One subtlety: browsers keep showing the last icon until a new icon link is inserted, so restoring means cloning the element and swapping it in, not just flipping the attribute back.

Drawing the frame

The original icon is fetched (through the background script, which avoids CORS-tainted canvases), drawn at full size onto a 32 × 32 canvas, and the overlay is painted on top:

ts
ctx.clearRect(0, 0, SIZE, SIZE);
if (this.icon) ctx.drawImage(this.icon, 0, 0, SIZE, SIZE);
overlay(ctx, SIZE);
link.href = this.canvas.toDataURL('image/png');

The icon always keeps its full size. An earlier version shrank it to fit a ring around it, which made every tab look broken. Overlays use a soft drop shadow instead of hard outlines, so they read on light and dark icons alike.

Pages fight back

Gmail, Slack and many others swap their favicon to show unread counts. A MutationObserver on <head> watches for new or changed icon links and disables them again, then marks the page's icon as changed so the next frame redraws from the new source.

Don't encode what you've already drawn

toDataURL costs a PNG encode, and a countdown ticks. Each render passes a key describing what it would draw (style, remaining fraction rounded to the visible step, color). If neither the key nor the source icon has changed, the frame is skipped, so most ticks cost nothing.

Leave the page as you found it

An extension that runs on every page is working in someone else's DOM. TabTTL changes as little as it can, remembers exactly what it changed, and can put all of it back. I'd build any extension that touches other people's pages the same way.

#browser-extensions#canvas#typescript

About the project

Expired 3
J by-jp.de
D D Docs: Alarms API
G G Pull request #42
F F Flight deals
R R Recipe: ramen
Live

TabTTL

Auto-close tabs you stopped using, in Firefox and Edge.

A Firefox and Edge extension that groups, closes or unloads tabs you haven’t used for a while. Each tab counts down on its icon; sites you revisit get more time.

View project →

Keep reading