23
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.
Lets go!
JSON file that tells Chrome what the extension does, what permissions it needs and the files it will use.
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.
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.
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"
}
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:
- a HTML file called popup.html. This file will contain the markup for the extension's dropdown menu
- a JS file called popup.js.This is our content script
- a CSS file called popup.css to style the elements in our dropdown
- A folder for images (extension icon and others - optional)
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
}
}
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"
}
}
}
Open the Chrome browser and hit the following URL:
chrome://extensions
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.
For this extension, we will be using the following permissions:
- 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.
- 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.
- 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"]
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");
});
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>
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;
}
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.
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);
}
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,
});
});
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.
23