Building a Chrome Extension, Tips and Tricks

Originally published on Medium, on Jan 07, 2019

I released my first Chrome Extension last week, Browser EQ. It’s a parametric audio equalizer that enables users to adjust the audio frequency content of the video streams they are watching.

A Short Demonstration of What Browser EQ Does

While I was building it, I had a chance to learn quite a bit about how to build an extension that uses the Chrome tabCapture API and the Web Audio API (among many others), this post aims to document a part of that experience.

Disclaimer: This is not a guide on how to build your first Chrome extension as there are many tutorials already available (some of them included in the resources section at the end of this article), but more of an insight about potential pitfalls to avoid and tricks you can use to make your development experience more enjoyable.

Where to Keep Your Application State

A Chrome extension usually has three main parts, a background script, a popup script, and a content script. Note that you can create and use additional scripts (or even libraries) alongside these scripts, you just need to make sure to include those on the extension manifest.

Where do you want to keep your application state will differ, based heavily on what you want your application to do in the first place. I needed mine to send some predefined parameter values to the UI on extension start, and also it needed to keep track of values that the user would be sending from the popup back to the application.

Since I didn’t need to interact with any of the DOM content on the page, creating and storing those values in the background script-instead of a content script-was the best choice in my case.

Keep in mind that a popup script is almost never an ideal place to keep your application state, since it actually resets (and rebuilds itself) each time you toggle it open. So, if you want to keep track of values, you should either store those on a dedicated background script, or in one of the storage options available, like chrome.storage or sessionStorage.


How to Check For The Active Tab

Browser EQ relies on capturing the audio stream of the current tab by using the tabCapture API. I needed a way to check if I was already capturing a tab, so I could pass back the values from the background script to UI, and also make sure to not try to capture the stream again.

One of the perks of using the tabCapture API is, it has the ability to return a list of tabs that have requested capture or are being captured via its getCapturedTabs method.

So, after getting the ID of the current tab by using the tabs API, all you need to do is find out if the current tab you are on has requested a capture and if it’s still actively being captured.

let currentTabId;

// Check if this is this tabID is in the capturedTabs array and if it's being actively captured
function getActiveTab(capturedTabs) {
  if (capturedTabs.some(tab => tab.tabId === currentTabId && tab.status === 'active')) {
    // Send parameter values to the UI to rebuild the popup if we are already capturing this tab
    activePort.postMessage(EQ.values);
  } 
}

// query the tabs API to get the ID of the current tab
chrome.tabs.query({ active: true, currentWindow: true }, tabs => {
  currentTabId = tabs[0].id;
});

// pass the getActiveTab function as a callback to getCapturedTabs method 
chrome.tabCapture.getCapturedTabs(getActiveTab); 

Message Passing

The communication between the scripts that make up your extension is made possible by message passing. There are two main patterns you can follow to keep your scripts in sync, simple one-time requests or long-lived connections.

For this application I chose to go with a long-lived connection, since the user would be manipulating and sending/receiving multiple parameter values real-time-as the extension is actively operating-I found that creating a port via the popup when its toggled…

// Open Port to the Background Script on Popup Toggle
const port = chrome.runtime.connect({
  // You don't have to pass a port name here
  name: 'Browser EQ'
});

// Get a reference to our volume Mute checkbox
muteVolume = document.getElementById('muteVolume');
// We also need a reference to our volume control slider
volumeControl = document.getElementById('volume');

// Send the current volume values and the mute trigger to background.js via the port
muteVolume.onclick = function(e) {
  let msg = {
    type: 'mute',
    currentVol: volumeControl.value,
    value: e.target.checked
  };
  port.postMessage(msg);
};

…and then passing that port to the background script to receive values to be the most reliable way to connect my scripts together.

// make the current port available in the outer scope
let activePort

// Listen for the incoming connection
chrome.runtime.onConnect.addListener(function(port) {
  activePort = port;
  // Do whatever you want with the port
  // You can immediately send a message back to Popup.js here like this
  port.postMessage("Hi EQ!");
  
  // Or you can choose to listen to further messages to receive values
  port.onMessage.addListener(function(msg) {
    // do something with the received msg, like this
     if (msg.type === 'mute') {
      voiceMute(msg);
    }
  }
}

Toggling a Popup, Two Ways

First and foremost, do you even need a user interface screen? Some extensions-like the Full Page Screen Capture-only rely on the toggle event to activate the extension, and some of them (like Browser EQ) actually need a multifaceted fully functional interface screens. If latter, you have two options to toggle a UI screen on extension activation.

First one is, just including the path to the html file of what you want to show as the default popup on the manifest.

{
  ...
  "browser_action": {
    "default_icon": "icon.png",
    "default_popup": "popup.html",
    "default_title": "Browser EQ"
  },
  ...
}

This way whenever the icon is clicked, the popup.html is triggered and shown

The second, is removing the default popup option from the manifest and instead creating a new window when the extension icon is clicked by using the windows API.

// Listen for the onClick action on the background script
chrome.browserAction.onClicked.addListener(function(tab) {
  // Create a new window instead of a popup
  chrome.windows.create({/* options */});  
});


// Alternatively you can create a new tab instead of a window
chrome.browserAction.onClicked.addListener(function(tab) {
  // Use the tabs API instead of windows
  chrome.tabs.create({/* options */});  
});

I went with the default popup option for Browser EQ, but in case you want to have a separate dedicated window that is not “attached” to the browser bar as a popup, now you know that there is a way to do that.


A (Relatively) Painless Way To Style The UI

Last but not least, if you are making a somewhat complex UI for your extension, you probably also want to style it. In which case, you will shortly realize that having to click on the extension icon every time you want to see the style changes you made on your code editor gets really old, really quick!

One way around that if opening the popup.html DevTools by right clicking on it when the popup is open and keeping it open while you are working on styling. Now if you F5 while you are focused on the DevTools inspector, it will refresh the popup screen and will show you the latest changes without having to click on the extension icon.

Another, a more user friendly way is, directly opening your popup html file by going to chrome-extension://YOUR_EXTENSION_ID_HERE/popup.html address in your browser. By doing it this way you can directly refresh the page without even having to open the devtools (although you can open that up as well), beware that whenever you reload your extension by using the reload function on chrome://extensions, this page will disappear, so if you do a complete reload (which you don’t have to do for CSS changes to take effect) you will have to open this page again.

TIP: You can find your extension ID by going to chrome://extensions


Well, there it is, most (definitely not all) of what I have learned when making a Chrome Extension. Please feel free to install and try the application for yourself and let me know what you think, and If you want to find out more about how to build Chrome Extensions or see a few examples, here are some of the many resources I have taken advantage of while building this.

Resources

Tutorials:

Chrome’s Official Getting Started Tutorial

Google Developers — Google Chrome Extensions: How to Build an Extension

The Coding Train — Chrome Extensions (Video Tutorial Series)

Sample Extensions:

Chrome’s Official Sample Extensions

The Coding Train — Example Extensions

Chrome-Audio-Capture

Useful Links:

Chrome tabCapture API

MDN Web Docs MediaStreamAudioSourceNode

How to Save Extension Popup Window Content (SO)

How to Communicate Between Popup.js and Background.js in a Chrome Extension (SO)

Development Playlist:

Louis Cole — F it up (Live Sesh)

Vulfpeck — Dean Town (Live from Here)

DJ Maseo (De La Soul) Boiler Room London DJ Set

KAYTRANADA Boiler Room Montreal DJ Set

Japanese Funk and Soul on Vinyl

Sama’ DJ Set | Boiler Room Palestine