DEV Community

Cover image for Chrome Extensions 101
Akshaya Venkatesh
Akshaya Venkatesh

Posted on • Edited on

Chrome Extensions 101

This blog will discuss how to setup, build and run your own Chrome extension. At the end, you will be able to create a simple Day Count Down extension that will display the number of days left to a particular date.
Gif image showing the working of the extension

Lets go!

Chrome Extension Terminologies

manifest.json

JSON file that tells Chrome what the extension does, what permissions it needs and the files it will use.

Background script

A script that runs independent of and parallel to the web page the user is on. It is used for state management and always has only one active instance.

Content script

A script that runs in the context of the web page that the user is on. It can access, read and/or modify the DOM of the page that the user visits.

Setup

Create a folder for your extension, say Count-Down, and in it a file called manifest.json. In the file, add the manifest version, name of the extension, a description and the version of the extension to begin with. The file should now look similar to this.



{
  "manifest_version": 3,
  "name": "Count Down Days",
  "version": "1.0",
  "description": "Takes a date input and displays the number of days left until the given date"
}


Enter fullscreen mode Exit fullscreen mode

Files and Folder structure

Now we go about creating the rest of the elements.

  • A file called background.js in the root folder. This will be our background script.
  • A folder called content in the root folder which will hold:
    1. a HTML file called popup.html. This file will contain the markup for the extension's dropdown menu
    2. a JS file called popup.js.This is our content script
    3. a CSS file called popup.css to style the elements in our dropdown
  • A folder for images (extension icon and others - optional)

Link the files to the manifest.json

We will be referencing the background script and the HTML file in the manifest.json as follows.



"background": {
    "service_worker": "background.js"
  },
"action": {
    "default_popup": "content/popup.html",
    "default_icon": {
        "16": "images/icon16.png",   // optional
        "24": "images/icon24.png",   // optional
        "32": "images/icon32.png"    // optional
     }
  }


Enter fullscreen mode Exit fullscreen mode

The icon is initially set by the default_icon key in the action entry in the manifest.json file. This key takes a dictionary that contains size to image paths. If the icon is not given Chrome automatically assigns an icon.

The manifest.json should now look like this:



{
  "manifest_version": 3,
  "name": "Count Down Days",
  "version": "0.1",
  "description": "Takes a date input and displays the day count left to the given date ",
  "background": {
    "service_worker": "background.js"
  },
  "action": {
    "default_popup": "content/popup.html",
    "default_icon": {
      "16": "/images/timer.png",
      "128": "/images/timer.png",
      "48": "/images/timer.png",
      "256": "/images/timer.png"
  }
  }
}


Enter fullscreen mode Exit fullscreen mode

Running the Extension

Open the Chrome browser and hit the following URL:


 chrome://extensions 

Enter fullscreen mode Exit fullscreen mode

In the top right corner you should see a toggle button titled Developer mode.
Check the toggle.
Now you should see a set of options to load, pack and update extension.
Select the Load unpacked option.
From the file system, now select the root folder of the extension.
The extension will have loaded in the browser.

An image showing the Chrome extension loaded in the browser

Giving permissions to the Extension

For this extension, we will be using the following permissions:

  1. activeTab - gives access to the currently active Chrome tab. In our case we need this permission as we are adding to the current active tab.
  2. scripting - allows running scripts in the context of the current web page. We use this permission to inject listener events that perform the date operations.
  3. storage - allows the storage of objects in Chrome. We will use this permission to store a date string in Chrome storage.

Add the following line in the manifest.json

"permissions": ["activeTab" ,"storage", "scripting"]

Adding the logic

Open the background.js and add the following code:



let date = "08 15 2021";

chrome.runtime.onInstalled.addListener(() => {

    chrome.storage.sync.set({ date });

    console.log("Default Date set to Aug 15, 2021");

});


Enter fullscreen mode Exit fullscreen mode

chrome.runtime is an API that lets the extension retrieve the background page, listen and respond to events.
What we are essentially doing here is using the API to save a default date String in the Chrome storage. This value can be accessed by our content script later. We have also added a log statement which we will use for testing.

In the popup.html we add two buttons (one for displaying number of days left and the other to accept a new Date). We refer our styles - popup.css and content script popup.js in this file as follows.



<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="popup.css" />
  </head>

  <body>
    <div class="buttons">
      <button id="showDays">
        <img class="img-icon" src="../images/timer.png" alt="Sand Clock" />
      </button>
      <button id="changeDate">
        <img class="img-icon" src="../images/change-date.png" alt="Change Date Icon" />
      </button>
    </div>
    <script src="popup.js"></script>
  </body>
</html>


Enter fullscreen mode Exit fullscreen mode

I have used image icons for the buttons. Assets are available in the Github link below. Let's add some basic styles in the popup.css as follows.



button {
  height: 30px;
  width: 30px;
  outline: none;
  margin: 10px;
  border: none;
  border-radius: 2px;
}
button img {
  width: 100%;
  height: auto;
}


Enter fullscreen mode Exit fullscreen mode

These styles cannot be accessed by the current page. Once this is saved, we go back to the browser, in the chrome://extensions tab we find our extension. Each extension tile will have a refresh icon at the bottom-right corner.
Refresh the extension and hit the service worker hyperlink to view the logs of our service-worker, i.e., background.js. In this console we will now be able to see our Default date log.

Next step is to view the extension's dropdown. Open out a different tab, in the top-right corner of Chrome the new extension will now be a clickable option. On click of the same, we will be able to see the dropdown menu as follows.

Image showing dropdown with 2 options

Note: The Chrome extension CANNOT be opened on chrome:// URL.

The buttons will not do anything yet so let's add the listeners that will perform the magic.

In the popup.js add the following two functions



// Content script follows
function showDaysLeft() {
    // get the date string from Chrome storage
    chrome.storage.sync.get("date", ({ date }) => {

        // create a new div that will be appended to the body
        let daysElement = document.createElement("div");
        // adding styles to the new div
        daysElement.style.cssText = "position: absolute; color: black; top: 30px; left: 50%;  transform: translateX(-50%); background-color: pink; z-index: 99999; padding: 1rem; border-radius: 10px; box-shadow: 3px 3px 6px #00000060";
        //  Date.parse converts Date string to milliseconds
        // To get the number of days left we get the difference in milliseconds and divide by 86400000 (milliseconds in a day)
        noOfDaysLeft = parseInt((Date.parse(new Date(date)) - Date.parse(new Date())) / (86400000));
        let content = '';
        if (noOfDaysLeft < 0) {
            content = document.createTextNode("Deadline has already passed.Please set a new one. :D");


            alert(daysElement);
        } else {
            content = document.createTextNode(noOfDaysLeft + " days until go time! B)");

        }
        // Append the text node to the div
        daysElement.appendChild(content);
        // Append the div to the body tag
        document.body.appendChild(daysElement);
        setTimeout(() => {
            document.body.removeChild(daysElement)
        }, 3000);
    });

}

function resetDate() {
    let newDate = " ";
    let daysElement = document.createElement("div");
    daysElement.style.cssText = "position: absolute; color: black; top: 30px; left: 50%; transform: translateX(-50%); background-color: pink; z-index: 99999; padding: 1rem; border-radius: 10px; box-shadow: 3px 3px 6px #00000060";

    // Get the date string input through window.prompt
    newDate = window.prompt("Enter date in the dd/mm/yyyy format");
    dateArray = newDate.split("/");

    dateString = dateArray[1] + " " + dateArray[0] + " " + dateArray[2];

    newDate = Date.parse(new Date(dateString));
    let content = '';
    // Check if the format is right 
    if (newDate) {
        noOfDaysLeft = parseInt((Date.parse(new Date(newDate)) - Date.parse(new Date())) / (86400000));
        if (noOfDaysLeft < 0) {
            content = document.createTextNode("Are you time travelling to the past? I am not ready for you yet :D");


        } else {
            content = document.createTextNode("New date saved! \n" + noOfDaysLeft + " days until go time! B)");

            // save the new date
            chrome.storage.sync.set({ "date": newDate });
        }

    } else {
        content = document.createTextNode("Enter a valid date - date/month/full-year");

    }
    daysElement.appendChild(content);
    document.body.appendChild(daysElement);
    setTimeout(() => {
        document.body.removeChild(daysElement)
    }, 3000);


}



Enter fullscreen mode Exit fullscreen mode

The function logic is explained in the comments. Now we cannot directly attach the listeners to the buttons. We make use of the chrome.scripting API to inject the listeners into the current page as follows:



// Initialize buttons

let showDays = document.getElementById("showDays");
let changeDate = document.getElementById("changeDate");


// When the button is clicked, inject showDaysLeft and resetDate into current page

showDays.addEventListener("click", async () => {

    let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

    chrome.scripting.executeScript({

        target: { tabId: tab.id },

        function: showDaysLeft,

    });

});
changeDate.addEventListener("click", async () => {

    let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });

    chrome.scripting.executeScript({

        target: { tabId: tab.id },

        function: resetDate,

    });

});


Enter fullscreen mode Exit fullscreen mode

Note: Apart from the injected listeners other functions/variables cannot be directly run.

And we are done!😎 Now the extension is ready to be tested. Go back to the browser, refresh the extension and test the extension on a fresh tab. The output should be similar to the gif below.

Gif image showing the working of the extension

Hope this was helpful. The complete project is available on Github Please reach me on Twitter in case of questions or let me know in the comments below.✌️

Top comments (9)

Collapse
 
imcoderlg profile image
LG

But when I run the extension, there are errors, like popup.html got some error but it doesn't show it.
It just says this:
Uncaught (in promise) Error: Cannot access a chrome:// URL
Context
content/popup.html
Stack Trace
content/popup.html:0 (anonymous function)
Nothing to see here, move along.

Collapse
 
venkyakshaya profile image
Akshaya Venkatesh

You may be trying to access the extension from a "chrome://" URL. Open a different tab and retry.

Collapse
 
imcoderlg profile image
LG

Now it works! Thanks!

Thread Thread
 
venkyakshaya profile image
Akshaya Venkatesh

Yay! 😁😁

Collapse
 
imcoderlg profile image
LG

I added the permissions too.

Collapse
 
akshayvarshney6 profile image
Akshay Varshney

Awesome Article. Thanks for writing.

Collapse
 
venkyakshaya profile image
Akshaya Venkatesh

Thank you Akshay! Glad you liked it!

Collapse
 
imcoderlg profile image
LG

That. Is. Cool.

Collapse
 
venkyakshaya profile image
Akshaya Venkatesh

Thank you 🙌🙌