loading...
Cover image for Learn the most useful Chrome APIs by creating Block Site Chrome extension

Learn the most useful Chrome APIs by creating Block Site Chrome extension

penge profile image Pavel Bucka Updated on ・7 min read

This article will explain you how to use the most useful Chrome APIs by creating a simple Chrome extension called Block Site.

Agenda:

  • the APIs you will learn in this tutorial are called "storage" and "tabs"
  • the full list of APIs available to Chrome extensions is here
  • Block Site, which we are about to create to learn and practice the two APIs, is available here

Introduction

Block Site is a simple Chrome extension that improves your productivity by blocking the access to time-consuming websites (like during the working hours) as you specify in Options. Block Site is by default disabled and doesn't block any website until you say so in Options.

Options refers to "options_page" which the extension can have. To open Options, generally you right-click on the extension icon in the toolbar and choose Options from the menu. The same will apply to Block Site.

"storage" API will be needed to store the websites user wants to block the access to, to boost his productivity, and it will also be used to store an option to quickly disable or enable the blocking at any given moment.

"tabs" API will be needed to get the tab url we are trying to open, and based on whether the domain is on the blocked list or not, we close the tab instantly, or let it proceed.

Let's now create the extension Block Site.

First steps

$ mkdir block-site
$ cd block-site
$ touch manifest.json

We have now created a new folder called block-site and prepared a file manifest.json which every extension has to have.

Update manifest.json to contain following:

{
  "manifest_version": 2,
  "name": "Block Site",
  "description": "Blocks access to time-consuming websites (as you specify) to improve your productivity.",
  "version": "1.0",
  "options_page": "options.html",
  "permissions": ["storage", "tabs"],
  "background": {
    "scripts": ["background.js"],
    "persistent": false
  }
}

The structure of this file is explained here. Any other fields besides "manifest_version", "name" and "version", are optional and we add them as needed.

Explanation:

  • "options_page" is a page to open when you right-click the extension in the toolbar, and select Options from the menu. In our case, we will use this page to set a list of websites to block, and also to easily enable/disable the blocking.

  • "permissions" lists permissions needed by the extension. It can be requesting API access as in our case, or also a match pattern. More about possible options here. Permissions are requested by the extension when installing. Based on the requested permissions, Chrome may display a warning as explained here.

  • "background" sets the script to be run in the background. In our case, it will be a place where we put the main logic to stop the blocked websites from opening. Because Chrome extensions are event-based, background script is a good place to put event-based scripts, especially if they don't require an UI (like blocking the websites). It's also a good place to put any heavy calculation that could slow down the UI. As you can see, the background is set to be no persistent. That means, when not needed, the script is unloaded from memory. More about background scripts here.

Create Options page (use "storage")

Create options.html and give it a simple markup like this:

<!DOCTYPE html>
<html>
<head>
<title>Block Site</title>
</head>
<body>

<h1>Block Site</h1>

<textarea id="textarea" rows="10" cols="30" spellcheck="false"></textarea>
<br>

<button id="save">Save</button>
<strong>Enabled?</strong><input id="checkbox" type="checkbox">

<script src="options.js"></script>
</body>
</html>

The UI is pretty simple. We have 3 elements:

  • #textarea to specify the websites to block
  • #save button to save the modified #textarea
  • #checkbox to enable or disable the blocking

Create options.js and give it this content:

const textarea = document.getElementById("textarea");
const save = document.getElementById("save");
const checkbox = document.getElementById("checkbox");

save.addEventListener("click", () => {
  const blocked = textarea.value.split("\n").map(s => s.trim()).filter(Boolean);
  chrome.storage.local.set({ blocked });
});

checkbox.addEventListener("change", (event) => {
  const enabled = event.target.checked;
  chrome.storage.local.set({ enabled });
});

window.addEventListener("DOMContentLoaded", () => {
  chrome.storage.local.get(["blocked", "enabled"], function (local) {
    const { blocked, enabled } = local;
    if (Array.isArray(blocked)) {
      textarea.value = blocked.join("\n");
      checkbox.checked = enabled;
    }
  });
});

We can see chrome.storage.local being used, which is made available by having the "storage" permission.

When we click on #save, we save the list of blocked sites in #textarea under the key blocked. Before saving them, we remove any empty lines or trailing whitespaces.

Example on how the list of blocked sites in #textarea can look like:

facebook.com
instagram.com
youtube.com
twitter.com
reddit.com

When we click on #checkbox, we save the boolean under the key enabled, to tell if the blocking should be enabled or not.

When the page is loaded, we read blocked and enabled, and set the UI accordingly.

A closer look on "storage"

Using "storage" made chrome.storage.local available, but what is it actually? And is that all?

It turns out, "storage" gives us access one step further, to chrome.storage which is documented here.

chrome.storage is similar to localStorage, in terms of its API and storage limitations. The main benefit comes from it being asynchronous and having an onChanged listener that can be used to synchronize the UI or otherwise react to changes in data.

chrome.storage gives us 3 storage areas:

  • chrome.storage.local that is best for storing the data locally
  • chrome.storage.sync that supports to store and synchronize (although very limited in size) the data across other computers where the extension is installed and same Google Account is used
  • chrome.storage.managed which is like read-only area for administrator purposes only

The most commonly used storage out of these three is chrome.storage.local.

The most common methods across these storages are get, set, and remove. See the documentation here.

Create Background script (use "tabs")

Now when we have the Options page ready, which can set blocked (array of websites to block) and enabled (boolean if blocking should be applied or not), it is time to work with these in the background.

Create background.js and give it this content:

chrome.runtime.onInstalled.addListener(function () {
  chrome.storage.local.get(["blocked", "enabled"], function (local) {
    if (!Array.isArray(local.blocked)) {
      chrome.storage.local.set({ blocked: [] });
    }

    if (typeof local.enabled !== "boolean") {
      chrome.storage.local.set({ enabled: false });
    }
  });
});

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo) {
  const url = changeInfo.pendingUrl || changeInfo.url;
  if (!url || !url.startsWith("http")) {
    return;
  }

  const hostname = new URL(url).hostname;

  chrome.storage.local.get(["blocked", "enabled"], function (local) {
    const { blocked, enabled } = local;
    if (Array.isArray(blocked) && enabled && blocked.find(domain => hostname.includes(domain))) {
      chrome.tabs.remove(tabId);
    }
  });
});

At the top we can see chrome.runtime being used. More about this API here. List of all available APIs here. And list of all possible permissions here.

As we can see, not every API requires a permission. Some APIs are generally available in extensions, like chrome.runtime.

chrome.runtime.onInstalled.addListener calls a given callback any time the extension is installed or updated. What we do here, we simply check if blocked and enabled are of a correct format, and if not, we reset them.

The more interesting, is the use of chrome.tabs. Most of this API is generally available as well.

A closer look on "tabs"

chrome.tabs which is described here, opens many options like creating a new tab, updating an existing tab, or reacting to various events about tabs. Most of the API is generally available, and doesn't require a "tabs" permission.

We are using "tabs" permission to get the access to url and pendingUrl inside the onUpdated event. This way, we can detect if the address we try to open matches any website from the blocked list, and if yes, we close the tab instantly as a way of blocking the access.

pendingUrl is quite new (available since Chrome 79), and it captures the url we indent to open before the tab committed to it. pendingUrl precedes url. url is more like a fallback. One tab can go through many events.

To close the tab that would navigate to a blocked site, we use chrome.tabs.remove and provide it with a tabId.

Testing Block Site

Block Site is now ready to test.

Open chrome://extensions in a new tab and navigate to block-site folder to load the extension. If no errors were made, extension should be successfully loaded.

Open any website you'd like to block, see that it works as usual.

Now, right-click on Block Site icon and select Options to open. Type in the website you would like to block and hit Save and Enabled.

Now, try to open the blocked site again. It should not be possible! Try disabling the blocking via Options and playing around with the used APIs by checking the values from the console.


Thank you very much for reading the article. I hope you enjoyed it and it left you full of excitement to continue learning. Cheers!

A summary of used resources:

Here are the extensions I made on Web Store:

Posted on by:

Discussion

markdown guide
 

Great read and very helpful when I want to create my own version of this extension! I think if I'm going to implement this thru a time-based blocking instead of like a switch-based blocking 😅

Also, please don't close the tab! I feel like someone is hacking me 😂

 

Cool JM! :D Let me know what you then create! Time-based blocking sounds great. Like from 9-3 let's say. Should be pretty easy to extend and make it work that way.

Thanks!