Replacing background or event pages with a service worker (Manifest v2 to v3). Created by Exmo – a monetization platform for browser extensions.
Previous post
Note: Manifest V3 is generally supported in Chrome 88 or later. For extension features added in later Chrome versions, refer to the API reference documentation for support information. If your extension relies on a specific API, you can specify a minimum chrome version in the manifest file.
A service worker replaces the extension’s background or event page to ensure that background code operates off the main thread. This enables extensions to run only when necessary, thus conserving resources.
Background pages have been a fundamental component of extensions since their inception. Essentially, background pages provide an environment independent of any other window or tab, allowing extensions to observe and respond to events.
This guide outlines the tasks for converting background pages to extension service workers. For more information on extension service workers, refer to the tutorial “Handle events with service workers” and the section “About extension service workers.”
In some contexts, extension service workers may be referred to as ‘background scripts
.’ While extension service workers do operate in the background, labeling them as background scripts can be somewhat misleading as it implies identical capabilities. The differences are outlined below.
Service workers have several differences compared to background pages:
You’ll need to make several code adjustments to accommodate the differences between background scripts and service workers. Initially, the way a service worker is specified in the manifest file differs from how background scripts are specified. Additionally:
XMLHttpRequest()
interface should be replaced with calls to fetch()
since service workers are not backward compatible with XMLHttpRequest().
In Manifest V3, background pages are replaced by a service worker. The manifest changes are as follows:
background.scripts
” with “background.service_worker
” in the manifest.json. Note that the “service_worker
” field takes a string, not an array of strings.background.persistent
” from the manifest.json.Manifest V2
{
...
"background": {
"scripts": [
"backgroundContextMenus.js",
"backgroundOauth.js"
],
"persistent": false
},
...
}
Manifest V3
{
...
"background": {
"service_worker": "service_worker.js",
"type": "module"
}
...
}
The “service_worker
” field accepts a single string. You will only need the “type” field if you use ES modules (using the import keyword). Its value should always be “module”. For more information, refer to Extension service worker basics.
Some extensions require access to the DOM and window objects without visibly opening a new window or tab. The Offscreen API supports these scenarios by allowing extensions to open and close hidden documents, packaged with the extension, without disrupting the user experience. Except for message passing, offscreen documents do not share APIs with other extension contexts but function as full web pages for extensions to interact with.
To use the Offscreen API, create an offscreen document from the service worker.
chrome.offscreen.createDocument({
url: chrome.runtime.getURL('offscreen.html'),
reasons: ['CLIPBOARD'],
justification: 'testing the offscreen API',
});
In the offscreen document, carry out any actions that you would have previously performed in a background script. For instance, you could copy the text selected on the host page.
let textEl = document.querySelector('#text');
textEl.value = data;
textEl.select();
document.execCommand('copy');
Use message passing to facilitate communication between offscreen documents and extension service workers.
The web platform’s Storage interface (accessed via window.localStorage
) cannot be used in a service worker. To address this, you have two options:
localStorage
with Another Storage Mechanism:chrome.storage.local
namespace can handle most use cases, though other options are also available.localStorage
Calls to an Offscreen Document:localStorage
to another mechanism, follow these steps:runtime.onMessage
handler.chrome.storage
:chrome.storage
for your data.runtime.sendMessage()
to initiate the conversion routine.runtime.onMessage
:runtime.onMessage
handler in the offscreen document, call the conversion routine to migrate the data.Additionally, there are some nuances regarding how web storage APIs work in extensions. For more information, refer to the documentation on Storage and Cookies.
By following these steps, you can effectively migrate and manage your extension’s storage without relying on localStorage within a service worker.
Registering a listener asynchronously (e.g., inside a promise or callback) is not guaranteed to work in Manifest V3. Consider the following code:
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.browserAction.setBadgeText({ text: badgeText });
chrome.browserAction.onClicked.addListener(handleActionClick);
});
This code works with a persistent background page because the page is constantly running and never reinitialized. However, in Manifest V3, the service worker is reinitialized when the event is dispatched. This means that when the event fires, the listeners may not be registered (since they are added asynchronously), causing the event to be missed.
To avoid this issue, move the event listener registration to the top level of your script. This ensures that Chrome can immediately find and invoke your action’s click handler, even if your extension hasn’t finished executing its startup logic.
Here is the revised code:
chrome.action.onClicked.addListener(handleActionClick);
chrome.storage.local.get(["badgeText"], ({ badgeText }) => {
chrome.action.setBadgeText({ text: badgeText });
});
By registering listeners synchronously at the top level of your script, you ensure that your extension’s event handlers are ready to be invoked as soon as the events are dispatched.
XMLHttpRequest()
cannot be called from a service worker, whether it’s part of an extension or not. To adapt, replace any calls to XMLHttpRequest()
in your background script with calls to the global fetch()
function.
Here’s how to convert your code:
XMLHttpRequest()
const xhr = new XMLHttpRequest();
console.log('UNSENT', xhr.readyState);
xhr.open('GET', '/api', true);
console.log('OPENED', xhr.readyState);
xhr.onload = () => {
console.log('DONE', xhr.readyState);
};
xhr.send(null);
fetch()
const response = await fetch('https://www.example.com/greeting.json');
console.log(response.statusText);
Key Point: If you previously utilized XMLHttpRequest() or fetch() to fetch executable code, this approach is no longer viable. All executable code must now be integrated within your extension package for enhanced security measures. For additional details, refer to the guidelines provided in the "Improve extension security" documentation.
Service workers are ephemeral, meaning they can start, run, and terminate repeatedly during a user’s browser session. Consequently, data is not readily available in global variables since the previous context was torn down. To address this, leverage storage APIs as the source of truth. The following example demonstrates how to achieve this:
Manifest V2 Background Script
let savedName = undefined;
chrome.runtime.onMessage.addListener(({ type, name }) => {
if (type === "set-name") {
savedName = name;
}
});
chrome.browserAction.onClicked.addListener((tab) => {
chrome.tabs.sendMessage(tab.id, { name: savedName });
});
For Manifest V3, replace the global variable with a call to the Storage API:
Manifest V3 Service Worker
chrome.runtime.onMessage.addListener(({ type, name }) => {
if (type === "set-name") {
chrome.storage.local.set({ name });
}
});
chrome.action.onClicked.addListener(async (tab) => {
const { name } = await chrome.storage.local.get(["name"]);
chrome.tabs.sendMessage(tab.id, { name });
});
By utilizing the Storage API in Manifest V3, you can ensure that states are persisted across service worker instances, thereby maintaining consistency and reliability in your extension.
It’s common to utilize delayed or periodic operations using the setTimeout()
or setInterval()
methods. However, these APIs can fail in service workers because the timers are canceled whenever the service worker is terminated. To address this, consider using the Alarms API.
Manifest V2 Background Script
// 3 minutes in milliseconds
const TIMEOUT = 3 * 60 * 1000;
setTimeout(() => {
chrome.action.setIcon({
path: getRandomIconPath(),
});
}, TIMEOUT);
Manifest V3 Service Worker
async function startAlarm(name, duration) {
await chrome.alarms.create(name, { delayInMinutes: 3 });
}
chrome.alarms.onAlarm.addListener(() => {
chrome.action.setIcon({
path: getRandomIconPath(),
});
});
Ensure that alarm listeners are registered at the top level of your script, similar to other listeners. By employing the Alarms API in Manifest V3, you can ensure the execution of delayed or periodic operations without interruptions due to service worker termination.
Service workers are inherently event-driven and may terminate due to inactivity to optimize the performance and memory consumption of your extension. However, exceptional cases may necessitate additional measures to prolong the lifespan of a service worker.
During lengthy service worker operations that do not invoke extension APIs, there is a risk of the service worker shutting down prematurely. Examples include:
To extend the service worker’s lifetime in such cases, you can periodically call a trivial extension API to reset the timeout counter. Note that this approach is reserved for exceptional cases, and in most scenarios, there are better, platform-idiomatic methods to achieve the same result.
The following example demonstrates a waitUntil()
helper function that ensures your service worker remains active until a given promise resolves:
async function waitUntil(promise) {
const keepAlive = setInterval(chrome.runtime.getPlatformInfo, 25 * 1000);
try {
await promise;
} finally {
clearInterval(keepAlive);
}
}
waitUntil(someExpensiveCalculation());
Key Point: An official API similar to waitUntil() is currently under discussion in the WECG. For further details, refer to the ongoing discussion on GitHub.
In exceptional circumstances, such as enterprise or educational use cases, there may be a need to indefinitely extend the lifetime of a service worker. While this practice is specifically permitted for such scenarios, it is not supported for general use. It’s essential to note that the Chrome extension team reserves the right to take action against extensions that employ this technique outside of approved contexts.
To keep a service worker alive continuously, you can periodically call a trivial extension API. Here’s a code snippet demonstrating how to implement this approach:
/**
* Tracks the last time a service worker was active and extends its lifetime by
* updating extension storage with the current time every 20 seconds.
* It's still important to handle unexpected termination, such as if the
* extension process crashes or the service worker is manually stopped.
*/
let heartbeatInterval;
async function runHeartbeat() {
await chrome.storage.local.set({ 'last-heartbeat': new Date().getTime() });
}
/**
* Initiates the heartbeat interval to maintain service worker activity.
* Use this method sparingly when performing tasks that require persistence,
* and call stopHeartbeat once those tasks are completed.
*/
async function startHeartbeat() {
// Perform the heartbeat once at service worker startup.
runHeartbeat().then(() => {
// Then repeat every 20 seconds.
heartbeatInterval = setInterval(runHeartbeat, 20 * 1000);
});
}
async function stopHeartbeat() {
clearInterval(heartbeatInterval);
}
/**
* Retrieves the timestamp of the last heartbeat stored in extension storages,
* or returns undefined if the heartbeat has not been recorded before.
*/
async function getLastHeartbeat() {
return (await chrome.storage.local.get('last-heartbeat'))['last-heartbeat'];
}
By utilizing this mechanism, you can ensure the continuous operation of your service worker in specific sanctioned environments, such as enterprise or educational settings.
A manifest in the context of Chrome extensions is a JSON file named manifest.json that includes essential information about the extension such as its name, version, permissions, and other details required for it to run properly.
To migrate a Chrome extension from manifest v2 to manifest v3, developers need to update their manifest file according to the specifications of manifest version 3. This may involve changing the structure of the manifest and adjusting any APIs used in the extension.
Permissions in a Chrome extension manifest specify which APIs and resources the extension can access. Developers need to declare the necessary permissions in the manifest to ensure that the extension functions correctly without any security issues.
A service worker is a script that runs in the background of a browser and helps the extension manage network requests, caching, and other tasks. Using a service worker can improve the performance and responsiveness of the extension.
Browser actions in a Chrome extension are UI elements like buttons that appear in the browser’s toolbar. Developers can define browser_action properties in the manifest to create and customize the appearance and behavior of these elements.
Content Security Policy (CSP) is a set of directives that help prevent common vulnerabilities in web applications, including extensions. By configuring CSP headers in the manifest or headers, developers can mitigate risks such as XSS attacks and data injection.
Upgrading Chrome extensions to manifest v3 may pose challenges such as adapting to the new.
You’ve spent tons of time on your extension: optimized the code, squashed bugs, polished the…
In the browser extension development community, particularly among Firefox users, there is a bias against…
Affiliate marketing is a powerful tool for monetizing browser extensions. It requires no direct costs…
Your browser extension already delivers value to users, but have you considered making it a…
Creating a successful extension is a significant achievement, but one of the toughest challenges begins…
Developers looking for innovative ways to generate revenue can turn to Affiliate Marketing to Monetize…
This website uses cookies to enhance your browsing experience. By continuing to use our site, you consent to our use of cookies.
Terms and Conditions