If you remember in the first lesson we briefly mentioned about the background script and we looked into tab detection
But what is background script or service workers
Well they are short-lived JavaScript code that lives in the background so not in the popup and not injected into the webpage
And they execute JavaScript code until they reach the end of their job and then they terminate
How service workers work
Here are a few of the most common ways how you can start an extension service worker
They can listen to browser events such as tab changes downloads or adding a new bookmark
We can also trigger them by the extension lifecycle events such as installing or updating the extension
They can listen to messages that we can programmatically send from the popup or injected code on web pages
They can also be triggered by storage changes or they can listen to context menu events
You can find a list of all the listeners and how to start extension service workers on Google’s Chrome extension reference pages
But enough of the theory let’s get into practice
Setting up tab detection
I open my background.ts file which as you can see already had the tab activated listener
This defineBackground comes from WXT so everything that we put in this function will be added to our final background.ts
This is just how the WXT framework works
So what I want to do is call our changeIcon function here instead of logging which tab has been activated
export default defineBackground(() => {
browser.tabs.onActivated.addListener(async (activeInfo) => {
await changeIcon(activeInfo.tabId);
});
});
ActiveInfo.tabId
Perfect
Now when you switch tabs the icon updates automatically
But wait there’s more
We also need to handle when the URL changes within a tab
So let’s add another listener
export default defineBackground(() => {
// Tab activated (user switched tabs)
browser.tabs.onActivated.addListener(async (activeInfo) => {
await changeIcon(activeInfo.tabId);
});
// URL changed or page loaded
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url && tab.active) {
changeIcon(tabId);
}
});
});
This handles when you navigate to a new page within the same tab
We check if the URL changed and if the tab is active
Only then do we update the icon
Handling tab closure
What about when you close a tab
When a tab closes the browser activates a different tab
We should update the icon for that newly active tab
export default defineBackground(() => {
browser.tabs.onActivated.addListener(async (activeInfo) => {
await changeIcon(activeInfo.tabId);
});
browser.tabs.onUpdated.addListener((tabId, changeInfo, tab) => {
if (changeInfo.url && tab.active) {
changeIcon(tabId);
}
});
// Tab closed (update newly active tab)
browser.tabs.onRemoved.addListener(async () => {
const [tab] = await browser.tabs.query({
active: true,
currentWindow: true
});
if (tab?.id) {
await changeIcon(tab.id);
}
});
});
Perfect
Now we handle all the cases
Switching tabs navigating to new URLs closing tabs
The icon updates correctly in every scenario
Organizing the code
Now we’re calling changeIcon from multiple places
It makes sense to move it to a utility file so we can reuse it
// utils/changeIcon.ts
import { storage } from 'wxt/storage';
import getPageKey from './getPageKey';
const changeIcon = async (tabId: number) => {
const tab = await browser.tabs.get(tabId);
if (!tab.url) return;
const pageKey = getPageKey(tab.url);
const notesStorage = await storage.getItem('sync:notesStorage');
const noteData = notesStorage?.[pageKey];
if (noteData) {
await browser.action.setIcon({
tabId,
path: {
"16": "icon-full/16.png",
"32": "icon-full/32.png",
"48": "icon-full/48.png",
"96": "icon-full/96.png",
"128": "icon-full/128.png"
}
});
} else {
await browser.action.setIcon({
tabId,
path: {
"16": "icon/16.png",
"32": "icon/32.png",
"48": "icon/48.png",
"96": "icon/96.png",
"128": "icon/128.png"
}
});
}
};
export default changeIcon;
Now import it in both background and popup
import changeIcon from '@/utils/changeIcon';
What’s next
With tab detection working your extension now provides real-time visual feedback as users browse the web
The icon updates automatically when you switch tabs navigate to new pages or close tabs
In the next chapter we’ll learn about content scripts and how to inject code into web pages