In the world of web development, Chrome extensions are a pretty handy set of tools to have around.
Whether you use them to add headers to simple requests or to scrape important data from the DOM, extensions help provide extra functionality that makes life easier.
I started playing around with developing a Chrome extension for a use-case I had in mind at work. It was then that I stumbled upon the way we pass around certain data from a web page to an extension. And the lack of a simplified guide made me write this article.
We do have the Chrome API documentation, and it is indeed very thorough. But I consider myself to be more of a visual learner, and being able to visualize how we pass messages between the extension scripts helped simplify the overall development.
Note: This article makes use of Manifest V2 instead of V3. The major difference between the two is the usage of service workers in V3 instead of background pages and related actions.
Message Passing: Interaction Between Scripts
An extension, as the name suggests, is like a layer on top of the existing webpage you're trying to access. The browser acts as the container.
It mainly comprises the following scripts:
- Background Script - Provides persistence and handles background events
- Content Script - Scripts that run in isolation in the context of the web page
- Injected Script - Scripts that are programmatically injected into the web page
Normally, if you have to merely deal with the DOM content, then the way the extension is developed is relatively straightforward.
The raw HTML is already available to the content script and all you need to do is pass it to the popup script.
However, if you need to access the page's variables and functions, the process gets a little tricky.
The variables and functions available in the page context, say in the
To access a page's variables and functions, we inject scripts by appending them to the DOM. This makes the browser assume that it is run in the context of the web page. This in turn provides the injected script access to the local variables and functions.
Since Chrome extensions are event-driven because of their architecture, once the injected scripts have access to the page's variables and functions, they can pass it to the content script.
The content script then passes the these objects to the background page.
And finally, the popup script is able to call onto the background page using the Extension API and pass it to the Extension DOM.
Now, we will build a simple Performance Watcher extension that reads the performance data from the global window object of a page and maps the essential metrics for the user to see. Let's get into the code then.
Enough Talk, Show Me The Code
You can find the complete code repository for the project here. Let's quickly run through the primary files and the important functionalities they offer.
The Manifest File
Every Chrome Extension needs a
manifest file. It is basically a JSON-formatted file that provides a set of metadata so the browser can recognize the permissions that need to be granted and the likely operational reach of the extension.
Here is the manifest used for our application.
Some of the important properties we need to focus on are the following:
background- Takes an array of scripts that would be run in the background page.
content-scripts- Includes an array of content scripts we wish to run as part of the web page's context.
web_accessible_resources- An array of packaged resources expected to be used in a web page's context. For example, an image we intend to embed in a page or a custom script we want to inject.
permissions- Allows your extension to gain access to certain Chrome APIs like tabs in this case.
The Content Script
Content Scripts have easy access to the DOM of the web page. We make use of the content script to append our custom script –
inject-script.js – into the DOM.
The content script also simultaneously continues to listen for any message being sent upstream from the custom script.
As soon as we get a message from the injected script, we run a quick check on the data received and verify whether our extension is installed. Once done, we simply use Chrome's Runtime API to send the data received forward to the background page.
The Injected Script
The custom script can access global variables and functions like the
window object. We map only the properties we require.
The message from the custom script is communicated safely to the content script using the
window.postMessage function. In this case, a
setInterval function is used to dynamically update the properties we are observing.
The Background Script
The background script listens for any message transmitted by the content script using the Runtime API. The
window object of the background page is then updated with the
tab.id acting as the identifier.
The Popup Script
The background page is retrieved using the
getBackgroundPage method of the Extension API. The active tab's id is queried using the
tabs.query method of the Tabs API in order to correctly extract the relevant data.
In this way, we are able to finally receive and map the data we need –
performance in our case – efficiently in our extension.
The UI styling and other cosmetic code are available in the repository, for further reference.
Message passing is an essential concept when it comes to developing a Chrome extension. This is just one of the multiple ways in which you can communicate between scripts.
I spent a few hours in order to figure out how it would work for my use case. Hopefully, this simple walkthrough and the visual representation saves you some time.
I would suggest playing around with the code for a bit. If you have any questions, feel free to reach out to me on
In the meantime, keep coding.