loading...

Building your First Chrome Extension : IMDb Lookup

arbazsiddiqui profile image Arbaz Siddiqui Updated on ・7 min read

Link to original article.

Introduction

Browser extensions are programs which can modify and enhance your browsing experience. From small UI enhancements to automation, extensions can be used and built for a wide array of use cases. In this article we will look at a step by step guide to build a chrome extension.

What are we building? Well our extension will have two jobs, first will be to fetch IMDb details of any movie from the context menu (right click menu), like this :

The Dude Abides

Second will be demonstrating interaction with web pages and hence fetching IMDb ratings on Rotten Tomatoes home page when we click on extension button and will look like this :

IMDb ratings on Rotten Tomatoes

Notice how all movie titles are appended with ({imdbRating}) after we click on extension icon.

The reason for the dual purpose is to demonstrate how to build background extensions (background scripts), extensions which interact with active web page (content scripts) and how to communicate between the two (message passing).

You can find the complete code here.

Setup

Every chrome extension requires a manifest.json file. Think of it as configuration file which will tell chrome how to treat this extension. Lets just create a basic manifest file with :

{
  "name": "IMDb Lookup",
  "description": "Display IMDb rating from context menu and on rotten tomatoes",
  "version": "0.1",
  "manifest_version": 2
  "background": {
    "scripts": ["background.js"]
  },
  "browser_action": {
    "default_icon": "icon.png"
  },
}

name and description are self descriptive and will be same on the chrome web store when you publish your extension.

Background scripts are the javascript files which will be running in the background across all pages. They don't have access to current web page and hence can't access DOM for reading or manipulations but they do have access to all chrome APIs. As we need to create a new entry in the context menu of chrome, we will be using a background script.

browser_action is used to put icons in the main Google Chrome toolbar, to the right of the address bar. You will have to add some icon.png file to your working directory.

manifest_version 1 is deprecated by chrome and hence should start with 2.

With our manifest ready, lets create a background.js file to test if things are working :

//background.js
alert("Did it work?")

Running the extension

To run the extension we have built so far, go to chrome://extensions/ and toggle the Developer mode mode on. Click on Load unpacked and browse to the directory containing the extension.

Running the extension

It worked !

Every time you make some changes to the code, just click on reload button on your extension card and chrome will incorporate all the changes.

Building the background extension

Our use case here is that when we highlight any text and right click, the context that appears should have a new menu saying fetch IMDb details for selected text or something and when you click on this menu you should see the IMDb details like rating and year on a popup.

To do this we will be using chrome's context menu API. First we will have to require its permission by adding it in our manifest.json by adding this :

  "permissions": ["contextMenus"],

Then we can add the following to our background.js file.

//create a context menu
chrome.contextMenus.create({
    //string to display on menu
    'title': 'Search IMDB for "%s"',
    //contexts here is selection as we want to extract the highlighted text.
    'contexts': ['selection'],
    //the event handler
    'onclick': (context) => {
        const name = context.selectionText;
        alert(`Highlighted texts is : ${name}`)
    }
});

Reload your extension and test it out!

Get the highlighted text

So we are now able to get the highlighted text to our event handler and are free to make API calls. We are going to use OMDb API for fetching IMDb details. Make the following changes to your background.js :

//create a context menu
chrome.contextMenus.create({
    //string to display on menu
    'title': 'Search IMDB for "%s"',
    //contexts here is selection as we want to extract the highlighted text.
    'contexts': ['selection'],
    //the event handler
    'onclick': async (context) => {
        const name = context.selectionText;
        const response = await fetch(`https://www.omdbapi.com/?t=${name}&apikey=e48e70b4`)
        const {
            Title,
            Year,
            Runtime,
            Genre,
            Actors,
            imdbRating
        } = await response.json()
        const newLine = "\r\n"
        let message = `Title : ${Title}`
        message += newLine
        message += `Year : ${Year}`
        message += newLine
        message += `Runtime : ${Runtime}`
        message += newLine
        message += `Genre : ${Genre}`
        message += newLine
        message += `Actors : ${Actors}`
        message += newLine
        message += `IMDb Rating : ${imdbRating}`
        alert(message)
    }
});

I am putting my own OMDb's api key here so that you can follow the tutorial with least friction. However if this breaks the internet 🤞and this api key usage reaches the limit, you can claim your free api key from here.

We are making a simple GET call using fetch and then displaying the result. Lets try this out.

The Dude Abides

Thats it. We have successfully completed the first part of the tutorial.

Interacting with webpages

Lets look at our next use case, i.e. displaying IMDb rating next to movie titles on homepage of Rotten Tomatoes. We wont be able to do this inside our background.js file as it doesn't have access to active webpage and hence its DOM. To do this we will have to write content scripts. Content scripts are files that run in the context of web pages. They will have access to DOM and can read and manipulate it. Add the following to your manifest.json

"content_scripts": [{
    "matches": [
      "https://www.rottentomatoes.com/*"
    ],
    "js": ["content.js"]
  }],

This piece of configuration tells chrome to load content.js file into the webpage whenever the current webpage's URL matches https://www.rottentomatoes.com/*. As a result of this we will have access to webpage's DOM inside our content.js file.

Create a content.js file and add the following lines :

//content.js
alert("Did it work?")

Lets check if this works.

Alert on reload

It did work. The alert will only come when we are at rotten tomato website and not on any other website.

Building the content script

As we need to manipulate DOM, we might as well use jQuery. While not necessary at all, its a good idea to know how to use libraries inside chrome extensions. To do this download a version of jQuery from the jQuery CDN and put it in your extension’s directory. To load it, add it to manifest.json before content.js. Your final manifest.json should look like this:

{
  "name": "IMDb Lookup",
  "description": "Display IMDb rating from context menu and on rotten tomatoes",
  "version": "0.1",
  "manifest_version": 2,
  "background": {
    "scripts": ["background.js"]
  },
  "browser_action": {
    "default_icon": "icon.png"
  },
  "permissions": ["contextMenus"],
  "content_scripts": [{
    "matches": [
      "https://www.rottentomatoes.com/*"
    ],
    "js": ["jquery-2.2.4.min.js", "content.js"]
  }]
}

Now we can do the following in our content.js

const fetchRatings = () => {
    $(".media-lists__td-title").map(async function () {
        const name = this.innerText;
        const response = await fetch(`https://www.omdbapi.com/?t=${name}&apikey=e48e70b4`)
        const {imdbRating} = await response.json()
        this.innerText = `${name} (${imdbRating})`
    })
}

fetchRatings();

Its some jQuery magic and the same OMDb API call to fetch rating. Lets test this out.

IMDb ratings on reload

Voila! We are now able to see IMDb rating on Rotten Tomatoes.

But wait, this is not what we wanted. The DOM was supposed to be manipulated only when we click on our extension's icon on the toolbar and NOT by default.

We have a problem now. Clicking on extension's icon is a Chrome event and hence our content.js wont have access to it and hence wont be able to trigger the fetchRatings function. Our background.js file will have access to the chrome event but it doesn't have access to DOM and hence cant manipulate it.

If we can find some way to trigger content.js from background.js we will be able to achieve the desired behavior.

Message Passing

Message Passing is way of communicating between background scripts and content scripts. It allows us to fire events from background scripts and apply event listeners on content scripts and vice versa.

We first fire an event whenever our extension's icon is clicked on tool bar. We will use chrome's Browser Action API to listen to clicks and then fire our event. Add the following in background.js :

// Called when the user clicks on extension icon
chrome.browserAction.onClicked.addListener(function (tab) {
    chrome.tabs.query({
        active: true,
        currentWindow: true
    }, function (tabs) {
        const activeTab = tabs[0];
        // Send a message to the active tab
        chrome.tabs.sendMessage(activeTab.id, {
            "message": "start_fetching_ratings"
        });
    });
});

We listening to browserAction.onClicked and then firing a payload to active tab where our content.js is running. Lets replace content.js with an event listener :

chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
        if (request.message === "start_fetching_ratings") {
            alert("Message Received!")
        }
    }
);

Reload the extension and test it out.

Message received

Message received ! So we have found way to pass on trigger from background to foreground. The final flow becomes chrome event> background.js > content.js. Finally we can incorporate our logic inside the event handler so our final content.js becomes :

chrome.runtime.onMessage.addListener(
    function (request, sender, sendResponse) {
        if (request.message === "start_fetching_ratings") {
            fetchRatings()
        }
    }
);
const fetchRatings = () => {
    $(".media-lists__td-title").map(async function () {
        const name = this.innerText;
        const response = await fetch(`https://www.omdbapi.com/?t=${name}&apikey=e48e70b4`)
        const {imdbRating} = await response.json()
        this.innerText = `${name} (${imdbRating})`
    })
}

Lets test our final product.

IMDb ratings on Rotten Tomatoes

This is it. We have built the extension we wanted.

Conclusion

Browser extensions are extremely powerful and can change the way we browse internet. Some really successfully companies today started out as an extension. In this article we learnt how to build a background extension, how to build an extension which manipulates UI and how to build hybrid extension by communicating between the two. The complete code can be found here.

Like this Post?
You can find more on twitter: @arbazsiddiqui_
Or visit my website
Or join the newsletter
Thanks for reading!

Posted on by:

arbazsiddiqui profile

Arbaz Siddiqui

@arbazsiddiqui

I am a full stack software developer with keen interest in system design and distributed computing. I create open source projects and also write about software development.

Discussion

pic
Editor guide