Die sichtbarste Funktion von TabTTL ist gleichzeitig die kleinste: ein Countdown direkt auf dem Icon jedes Tabs. Der abgelaufene Teil wird grau, als würde ein Uhrzeiger darüberstreichen. Das sieht einfach aus, aber damit es auf jeder Website funktioniert, brauchte es ein paar Tricks.
Der Browser lässt dich nicht auf Tabs zeichnen
Es gibt keine Extension-API für „zeichne auf das Icon dieses Tabs“. Ein Favicon ändert man nur so, wie die Seite selbst es tut: über ein <link rel="icon"> im <head>. Also läuft in jedem Tab ein kleines Content-Script, das kurzzeitig die Favicon-Verwaltung der Seite übernimmt.
Deaktivieren statt löschen
Der naive Ansatz entfernt die Icon-Links der Seite und fügt einen neuen hinzu. Das geht schief, sobald man aufhören will: Das Original-Markup ist weg. TabTTL benennt sie deshalb nur um:
for (const link of document.querySelectorAll<HTMLLinkElement>('link[rel~="icon" i]')) {
if (link.id === OWN_ID) continue;
link.dataset.tabttlRel = link.rel; // Original merken
link.rel = 'tabttl-disabled-icon'; // Browser ignorieren unbekannte rels
}Endet der Countdown, bekommt jeder Link sein ursprüngliches rel zurück. Ein Detail: Browser zeigen das letzte Icon weiter an, bis ein neuer Icon-Link eingefügt wird. Wiederherstellen heißt also, das Element zu klonen und auszutauschen, nicht nur das Attribut zurückzusetzen.
Den Frame zeichnen
Das Original-Icon wird über das Hintergrundskript geladen (so bleibt das Canvas frei von CORS-Problemen), in voller Größe auf ein 32 × 32-Canvas gezeichnet, und darüber kommt das Overlay:
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');Das Icon behält immer seine volle Größe. Eine frühere Version hat es verkleinert, um einen Ring darum zu legen, und damit sah jeder Tab kaputt aus. Overlays nutzen einen weichen Schatten statt harter Konturen, so sind sie auf hellen und dunklen Icons lesbar.
Seiten wehren sich
Gmail, Slack und viele andere tauschen ihr Favicon, um Ungelesenes anzuzeigen. Ein MutationObserver auf dem <head> bemerkt neue oder geänderte Icon-Links, deaktiviert sie wieder und markiert das Icon als geändert, damit der nächste Frame von der neuen Quelle zeichnet.
Nicht kodieren, was schon gezeichnet ist
toDataURL kostet eine PNG-Kodierung, und ein Countdown tickt. Jeder Render-Aufruf übergibt einen Schlüssel, der beschreibt, was er zeichnen würde (Stil, Restanteil auf den sichtbaren Schritt gerundet, Farbe). Haben sich weder Schlüssel noch Quell-Icon geändert, wird der Frame übersprungen, und so kosten die meisten Ticks nichts.
Die Seite so verlassen, wie man sie vorgefunden hat
Eine Erweiterung, die auf jeder Seite läuft, arbeitet in fremdem DOM. TabTTL ändert so wenig wie möglich, merkt sich genau, was es geändert hat, und kann alles davon zurücknehmen. So würde ich jede Erweiterung bauen, die fremde Seiten anfasst.