Playing tricks with web browser tabs

June 18, 20251047 words5 mins read

For the past few months I’ve been getting several emails and people were asking me about the tomfoolery regarding my website and the web browser tabs. Basically people were wondering how the tab title and favicon change (to Reddit, Instagram, TikTok) when the tab with my website gets deactivated, and when it’s reactivated it changes back to the initial text/image. Check it out yourself, click on another web browser tab, and then click back to this one. Interesting, eh? Annoying, eh? Can’t decide?

Well, it’s quite simple. Start by adding a link element to your page’s head and set its href attribute to an existing favicon in PNG format. This is supposed to be your default favicon, the one that gets displayed inside the web browser tab when the tab is focused. Make sure you have a <title> tag on your page, too.

<link rel="shortcut icon" type="image/png" href="favicon.png" />

If you have a ICO favicon, replace the image type, but web browsers are pretty smart and they can figure it out. An ICO file is a special image file used by the browser, the unique feature of an ICO file is that it is multilayered: each layer of the favicon holds a different size of the image. The common sizes for a ICO-formatted favicon are 16x16px, 32x32px, and 48x48px.

<link rel="shortcut icon" type="image/x-icon" href="favicon.ico" />

The code below does all the magic, you can place it inside a favicons.js file and load it in your page, it’s commented and I’m sure you’ll understand it just fine. I’m not stealing your nudes, I promise.

function favicons () {
	this.hidden = "hidden";
	this.visibilityChange = "visibilitychange";
	this.favicon = document.querySelector("[rel='shortcut icon']").href;
	this.title = document.title;

	/* Define a list of "services", each being a pair of favicon URL and the tab title to show. */
	this.services = {
		/* Reddit */
		reddit: () => {
			let title = "Reddit - Dive into anything";
			let favicon = "/img/favicons/reddit.png";
			return {
	  			title, favicon
			}
		},
		/* X, formerly known as Twitter */
		x: () => {
			let title = "X. It's what's happening / X";
			let favicon = "/img/favicons/x.ico";
			return {
	  			title, favicon
			}
		},
		/* Bluesky */
		bluesky: () => {
			let title = "Discover - Bluesky";
			let favicon = "/img/favicons/bluesky.png";
			return {
	  			title, favicon
			}
		},
		/* Wikipedia */
		wikipedia: () => {
			let title = "Wikipedia";
			let favicon = "/img/favicons/wikipedia.ico";
			return {
	  			title, favicon
			}
		},
		/* TikTok, where the hip kids are hanging out */
		tiktok: () => {
			let title = "Explore - Find your favourite videos on TikTok";
			let favicon = "/img/favicons/tiktok.ico";
			return {
				title, favicon
			}
		},
		/* Facebook, your grandma is most likely here */
		facebook: () => {
			/* Pick a random notification number between 1 and 100 */
			let count = Math.round(Math.random() * 99) + 1;
			let title = "(" + count + ") Facebook";
			let favicon = "/img/favicons/facebook.ico";
			return {
				title, favicon
			}
		},
		/* Instagram */
		instagram: () => {
			/* Pick a random notification number between 1 and 100 */
			let count = Math.round(Math.random() * 99) + 1;
			let title = "(" + count + ") Instagram";
			let favicon = "/img/favicons/instagram.png";
			return {
				title, favicon
			}
		}
	}

	/* Those "services" are enabled, which means the web browser tab can change
		favicon/title using their data. */
	this.enabledServices = [
		'x',
		'reddit',
		'bluesky',
		'instagram',
		'tiktok',
		'facebook',
		'wikipedia'
	];

	/* Initialization function, checks if the web browser has the specified event
		listeners and binds a handler to the existing one. */
	this.init = function () {
		if (typeof document.mozHidden !== 'undefined') {
			this.hidden = "mozHidden";
			this.visibilityChange = "mozvisibilitychange";
		} else if (typeof document.msHidden !== 'undefined') {
			this.hidden = "msHidden";
			this.visibilityChange = "msvisibilitychange";
		} else if (typeof document.webkitHidden !== 'undefined') {
			this.hidden = "webkitHidden";
			this.visibilityChange = "webkitvisibilitychange";
		}
		document.addEventListener(this.visibilityChange, this.handler.bind(this), false);
	}

	/* The default function which returns the initial favicon/title pair of the
		page, so it can get swapped back upon tab activation. */
	this.default = function () {
		let title = this.title;
		let favicon = this.favicon;
		this.update({
			title, favicon
		});
	}

	/* This function updates the favicon/title pair of the web browser tab when
		the tab is not focused (hidden from view). We're using a cache-busting
		technique so that the favicon is freshly loaded and not cached. */
	this.update = function (data) {
		let cacheBuster = "?v=" + Math.round(Math.random() * 10000000);
		let link  = document.createElement('link');
		link.type = "image/x-icon";
		link.rel = "shortcut icon";
		link.href = data.favicon + cacheBuster;
		document.getElementsByTagName("head")[0].querySelector("[rel='shortcut icon']").remove();
		document.getElementsByTagName("head")[0].appendChild(link);
		document.title = data.title;
	}

	/* The spoof() function randomizes the enabled "services" and picks one from
		them, updating the tab favicon and title. */
	this.spoof = function () {
		let i = Math.round(Math.random() * (this.enabledServices.length - 1));
		let service = this.enabledServices[i];
		if (service && this.services[service]) {
			this.update(this.services[service]());
		}
	}

	/* The default event handler which checks whether the current tab is hidden,
		and if it is then it swaps the favicon/title pair with one from the
		enabled "services". Otherwise, it shows the default favicon and title. */
	this.handler = function () {
		if (document[this.hidden]) {
			this.spoof();
		} else {
			this.default();
		}
	}

	/* Autorun the initialization function. */
	this.init();
}

Inside a DOMContentLoaded event handler, add the favicons() function so that it’s executed once the page finishes loading all the content.

document.addEventListener("DOMContentLoaded", function(event) {
	favicons();
});

Basically, make sure your page content looks similar to this (the other content is omitted for brevity), make sure you have the favicon images saved somewhere and the path (inside the favicons.js file) to them is correct:

<!doctype html>
<html lang="en">
	<head>
		<title>This is my awesome website!</title>
		<link rel="shortcut icon" type="image/png" href="favicon.png" />
		...
	</head>
	<body>
		...
		<script src="favicons.js"></script>
		<script>
		document.addEventListener("DOMContentLoaded", function(event) {
			favicons();
		});
		</script>
	</body>
</html>

If you want to add another “service”, for example FBI, get the favicon and the tab title from the official FBI website and add the code below into the services object:

		/* The Federal Bureau of Investigation */
		fbi: () => {
			let title = "FBI &#8212; Federal Bureau of Investigation";
			let favicon = "/img/favicons/fbi.png";
			return {
	  			title, favicon
			}
		}

Next, add the fbi “service” into the enabledServices array:

	this.enabledServices = [
		'x',
		'reddit',
		'bluesky',
		'instagram',
		'tiktok',
		'facebook',
		'wikipedia',
		'fbi'
	];

Now you know how to prank your website visitors for extra fun points (and angry emails)!