Lessons learned from creating a Chrome extension 📗
Tags: showdev, webdev, javascript, development
Published: 2021-01-22 01:07:48

---
title: Lessons learned from creating a Chrome extension 📗
published: true
date: 2021-01-15 00:15:00 UTC
tags: showdev, webdev, javascript, development
canonical_url: https://www.benjaminrancourt.ca/lessons-learned-from-creating-a-chrome-extension
cover_image: https://www.benjaminrancourt.ca/temp/59d82ca2-2000.jpeg
---
I recently had a side project where I had to gather information on some websites. It was a repetitive task that I had to do daily and it was quite boring. Since I thought it could be automated, I chose to give it a try by creating my **first Chrome extension**. 🧩

This post will serve me as a reflection article on some learning that I realized during this project. 🤔

**Note** : this extension was built using the [Manifest V2](https://developer.chrome.com/docs/extensions/mv2/manifest/), which will be replaced in some time by the [Manifest V3](https://developer.chrome.com/docs/extensions/mv3/intro/). Thus, some information in this post may be out of date or needs to be adapted for the next version, which should be released in January 2021.

## Callback hell 😈

While building this extension, I experienced something that I think had ceased to exist since the `async/await` functions and promises : **callback hell**. Every external function that I needed to call does not return a promise and takes a callback function... Oh my god, it was really a challenge to work with asynchronous code asynchronously!

Fortunately for us, the Manifest V3 should add [promises](https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/#promises) to its APIs and, eventually, all methods will support promises. If I had known this information earlier, I would have tried to begin directly with the next version! I should have read the [Chrome extensions guide](https://developer.chrome.com/docs/extensions/mv3/) before starting to create my extension! 😝

Let's see what new functions I used for my extension.

## browserAction

### Execute a function after clicking on the extension icon

For my extension, the `listener` function was my **entry point** , where the main logic is. I have not used its `tab` parameter, but after looking at it, it looks like it is the information about the current opened tab. I also add an `async` tag as my code is asynchronous. 🤘

```javascript
const listener = async (tab) => {};

// Fired when a browser action icon is clicked. Does not fire if the browser action has a popup.
chrome.browserAction.onClicked.addListener(listener);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/browserAction/#event-onClicked">chrome.browserAction.onClicked.addListener</a> function</figcaption>

For an example of the `tab` details, take a look at the chrome.tabs.get function below.

## tabs

### Create a tab

As one of the goals of my extension is to navigate to a list of URLs, I quickly use the function to create a new tab. In its simplest form, I only provide the absolute URL I want to visit, with the `url` parameter.

I recently added the `windowId` parameter to make sure the tabs are created in the same window, instead of the active window. It will alloy me to do other things in a separate window while my script is running. 🧭

```javascript
const createProperties = { url, windowId };
const callback = async (createdTab) => {};

// Creates a new tab
chrome.tabs.create(createProperties, callback);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/tabs/#method-create">chrome.tabs.create</a> function</figcaption>
### Execute a script inside a tab

Once the tab was created and fully loaded (after a minimal sleep), I could inject any JavaScript file into the page and retrieve with a couple of `document.querySelector` the information I was looking for.

```javascript
const details = {
  file: fileToExecute,
};
const callback = (result) => {};

// Injects JavaScript code into a page
chrome.tabs.executeScript(tabId, details, callback);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/tabs/#method-get">chrome.tabs.get</a> function</figcaption>

Unfortunately, I had no way to know if my script had finished running or not, as it was wrapped into an async [IIFE](https://developer.mozilla.org/en-US/docs/Glossary/IIFE) to have asynchronous functionalities. So I found a not very clean solution by renaming the tab title to a known value as the last line of my script.

### Get information about a tab

The `chrome.tabs.get` function gives a lot of information about a tab, but I found the most interesting are the three following properties:

- **status** : the tab's loading status (`"unloaded"`, `"loading"`, or `"complete"`)
- **title** : the title of the tab
- **url** : the URL of the main frame of the tab

```javascript
const callback = async (specifiedTab) => {};

// Retrieves details about the specified tab
chrome.tabs.get(tabId, callback);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/tabs/#method-get">chrome.tabs.get</a> function</figcaption>

```json
{
  "active": true,
  "audible": false,
  "autoDiscardable": true,
  "discarded": false,
  "favIconUrl":"",
  "height": 767,
  "highlighted": true,
  "id": 188,
  "incognito": false,
  "index": 1,
  "mutedInfo": {
    "muted": false
  },
  "pinned": false,
  "selected": true,
  "status": "complete",
  "title": "Extensions",
  "url": "chrome://extensions/",
  "width": 1440,
  "windowId": 1
}
```
<figcaption>Tab information from the <a href="chrome://extensions/">chrome://extensions/</a> page</figcaption>
### Delete a tab

Once my scripts were injected and executed inside the new tabs, I would manually close them all at first, but it got tedious as I added more and more URLs to check. So with the previous function, I decided to just get rid of the tab once I got all the info I need.

```javascript
// Closes one or more tabs
const tabIds = 10 || [10, 12];
chrome.tabs.remove(tabIds);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/tabs/#method-remove">chrome.tabs.remove</a> function</figcaption>
## webNavigation

### Get all frames of a tab

One of the pages I was interested in had information inside an iframe, so my initial script was not working as I did not have access to it. Fortunately, we could specify the `frameId` to target a specific frame of the tab. So, I use the `getAllFrames` function to find the frame I want with its hostname.

```javascript
const details = { tabId };
const callback = (frames) => {}

// Retrieves information about all frames of a given tab
chrome.webNavigation.getAllFrames(details, callback);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/webNavigation/#method-getAllFrames">chrome.webNavigation.getAllFrames</a> function</figcaption>

I initially tried to use the `allFrames` parameter of the `executeScript` function to inject the script into all frames of the selected tab, but I was not working for me. I now believe it was because the frame had not finished loading. Anyway, I still prefer to inject my script only where it is needed, rather than injecting it on every iframes on the page.

```json
[
  {
    "errorOccurred": false,
    "frameId": 0,
    "parentFrameId": -1,
    "processId": 612,
    "url": "chrome://extensions/"
  }
]
```
<figcaption>Frames information of chrome://extensions/ page</figcaption>
## runtime

### Send a message

While I was looking for a way to know when I could close a tab that had an iframe, I came across the `sendMessage` function. It allows us to send a message to our extension. So I end up sending a message with the current URL to my extension to let it know that the script has been executed successfully.

```javascript
 // Sends a single message to event listeners within your extension
chrome.runtime.sendMessage(messageObject);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/runtime/#method-sendMessage">chrome.runtime.sendMessage</a> function</figcaption>
### Listen for messages

To listen to messages, I just add the function below at the start of my extension and now I receive messages from my injected scripts. As it seems much better to depend on messages rather than updating the tab title, I plan to refactor that part someday.

```javascript
const callback = (message, sender) => {};

// Fired when a message is sent from an extension process
chrome.runtime.onMessage.addListener(callback);
```
<figcaption>Using the <a href="https://developer.chrome.com/docs/extensions/reference/runtime/#event-onMessage">chrome.runtime.onMessage.addListener</a> function</figcaption>

While writing this post, I also learned that the function has a `sender` argument which contains information about the frame AND the tab. I plan to use that information because it seems more reliable than my `document.URL` message. 😉

Here's an example of a `sender` argument below:

```json
{
  "id": "ngjdjkfidkkafkjkdadmdfndkmlbffjf",
  "url": "https://FRAME_HOSTNAME.com/FRAME_URI",
  "origin": "https://FRAME_HOSTNAME",
  "frameId": 1233,
  "tab":{
    "active": true,
    "audible": false,
    "autoDiscardable": true,
    "discarded": false,
    "favIconUrl": "https://TAB_HOSTNAME.com/favicon.ico",
    "height": 767,
    "highlighted": true,
    "id": 226,
    "incognito": false,
    "index": 4,
    "mutedInfo": {
      "muted": false
    },
    "pinned": false,
    "selected": true,
    "status": "complete",
    "title": "TAB_TITLE",
    "url":"https://TAB_HOSTNAME.com/TAB_URI",
    "width": 1440,
    "windowId": 1
  }
}
```
<figcaption>Example of the <code>sender</code> argument</figcaption>
## Conclusion

In retrospect, it was really fun learning to code a Chrome extension. I had never had the opportunity to try it before, both at work and in my personal projects. I hope to have another chance to build a more complex extension! 🤓

**Note** : as my extension is intended entirely for **private use** and has not been made reusable, so I do not plan on deploying it on the [Chrome Web Store](https://chrome.google.com/webstore) or releasing its source code. Sorry! 🔒