In the last part of this three parts post series we are going to wrap up the creation of our Chrome extension. In this post I’m going to explain how the background script, which was added in the previous post, communicates with the popup view. We will also create some small UI to our extension.
If you didn’t read the previous parts you can go to the following links:
Send Me a Message
In the previous post we created a background script that gathers network traffic information for a specific website. On the other hand, we didn’t add any UI to reflect that information. But, how can I notify the background script to pass me data when the extension popup is being opened?
This is where the Chrome extension messaging API comes into play. If you want to add communication between different parts of an extension, you will have to send messages and in the receiving end to listen to messages. How can we do that you ask? We use the chrome.runtime.sendMessage and chrome.runtime.onMessage API functions.
The chrome.runtime.sendMessage function is used to send one time messages from one part of the extension to another part. The function receives a message object which can be any JSON serializable object and an optional callback to handle the response from the other part. For example, here is a simple usage of the function:
chrome.runtime.sendMessage({message: "hi"}, (response) => {
console.log(response.message);
});
The chrome.runtime.onMessage function is used to register a listener on the receiving end to messages sent by the chrome.runtime.sendMessage function. The function receives a callback function to run when a message arrives. That callback gets 3 arguments:
- request — details about the request.
- sender — the sender of the request.
- sendResponse — helper function that enables to send response back to the sender.
Let’s take a look at a simple onMessage example:
chrome.runtime.onMessage.addListener(
(request, sender, sendResponse) => {
if (request.message === "hi")
sendResponse({message: "hi to you"});
});
Now that we understand a little about extension inner communication, we can add some communication abilities in our extension.
In the background script, add the following onMessage listener registration:
chrome.runtime.onMessage.addListener((msg, sender, response) => {
switch (msg.type) {
case 'popupInit':
response(tabStorage[msg.tabId]);
break;
default:
response('unknown request');
break;
}
});
This piece of code is listening to messages and sends back the relevant data when the popup is initialized (opened). This completes the background script part, so let’s move on to the popup UI part.
Finalizing The UI
Up Until this point all we did was to handle the background process using a background script. Now we are ready to write some React code to handle the popup view. Open the App.js file that was created by create-react-app. We will change the App implementation to the following code:
/*global chrome*/
import React, { Component } from 'react';
import './App.css';
import TrafficContainer from "./components/TrafficContainer";
import {getCurrentTab} from "./common/Utils";
class App extends Component {
constructor(props) {
super(props);
this.state = {
traffic: {}
};
}
componentDidMount() {
getCurrentTab((tab) => {
chrome.runtime.sendMessage({type: 'popupInit', tabId: tab.id}, (response) => {
if (response) {
this.setState({
traffic: Object.assign(this.state.traffic, response)
});
}
});
});
}
render() {
return (
<div className="App">
<header className="App-header">
<h1 className="App-title">Welcome to WebTraffic</h1>
</header>
<p className="App-intro">
<TrafficContainer traffic={this.state.traffic}/>
</p>
</div>
);
}
}
export default App;
In the code, we are adding a traffic state to hold the data we will receive from the background script. When the component is mounted (the componentDidMount life cycle function), we send a message to the background script asking the traffic data. This data will be used later in the render function to render the TrafficContainer component.
Note: The code comment /*global chrome*/ is used for the build process to indicate that chrome is a global variable. Forgetting to add it will result in a failing build.
Add an Utils.js file under a common folder and add to it the following code:
/*global chrome*/
export function getCurrentTab(callback) {
chrome.tabs.query({
active: true,
currentWindow: true
},
(tabs) => {
callback(tabs[0]);
});
}
The getCurrentTab is a helper function that returns the current tab through the callback it receives.
Last but not least, here is the implementation of the TrafficContainer component which will exists in the components folder:
import React, { Component } from 'react';
export default class TrafficContainer extends Component {
constructor(props) {
super(props);
}
static renderNetworkTrafficData(requests) {
if (requests) {
return Object.keys(requests).map((key) => {
const {url, requestDuration, status} = requests[key];
return (<li>{`url ${url} took ${requestDuration}ms with status ${status}`}</li>);
});
}
return '';
}
render() {
return (
<ul>
{TrafficContainer.renderNetworkTrafficData(this.props.traffic.requests)}
</ul>
);
}
}
As you can see this is a simple React component that just renders a list by iterating on all the requests it got.
Now that the extension is ready, you can build it using npm run build. Then, reload the extension in Chrome extension list. Once you did all of these you can navigate to a MDN web page and then open the popup to find your result.
Summary
In this three parts series we created a simple Chrome extension. We learned about the extension background script by adding a way to monitor web requests. We used the extension messaging mechanism for popup and background script communication. We used React to render the popup view.
I hope that you enjoyed the ride. You can find the full extension code here.