.Djibril MUG

Build your first chrome extension (Part 2)

avatar

Djibril Mug

6 Nov 2023

6 min read


Build your first chrome extension (Part 2)

Here we go again, I published the first episode on how to create a basic popup page for our project and explained some core basic concepts, if you did not read the first article I highly recommend you go through the first episode and then come back after. I will be here waiting for you๐Ÿ˜‡

For those who went through the first article, you noticed that everything was hardcoded, as a website blocker users should have the ability to block and unblock any website they want from their local storage, to achieve this functionality we shall need two things, content scripts and background scripts.

Please note that this is not the only way you can achieve this, I decided to go with this approach because it allows us to see in action some of the most important topics(background script and content script) first let's see what content scripts even mean๐Ÿ˜….

Content scripts

A content script, according to Chrome's documentation, is a JavaScript file that can be injected into web pages. It operates within the context of a webpage, allowing it to interact with the Document Object Model (DOM) of that page. This enables the content script to read details from the webpage or make alterations to its content using standard DOM manipulation techniques. Content scripts are typically used in Chrome extensions to enhance or modify the behavior of web pages. They run in isolation, meaning they have their own JavaScript global scope and do not interfere with the page's existing scripts.

configuration

To allow script injection from your extension you will need to ask such permission in the manifest.json file and here is how you achieve that.

The first step consists of creating a Javascript file that will contain your script, with the provided folder >structure from the first episode, the file will be within the scripts folder, I named it controllers.js but you > can give it any name of your choice

manifest.json

{
   "manifest_version": 3,
   "name": "simple blocker",
   "version": "1.0",
   "action": {
      "default_popup": "popup.html",
      "default_icon": {
         "128": "images/blocker.png"
      }
   },
   "content_scripts": [
      {
         "matches": [
            "<all_urls>"
         ],
         "js": [
            "scripts/controller.js" //update with your file path 
         ]
      }
   ],
   "web_accessible_resources": [//more on web_accessible_resources  here https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/web_accessible_resources 
      { 
         "resources": [
            "styles/content_script.css" //replace this with your file path. this file will contain css for your content script
         ],
         "matches": [
            "<all_urls>" //this ressource will be available in all pages users visit 
         ]
      }
   ]
}

After setting all configurations let's write the scripts, the code we are going to write will be injecting a button in all websites users visit, and that button will soon be used for blocking websites.

controller.js

const  blockWebSite  =  ()  =>  {
 console.log('coming soon ๐Ÿ˜Ž')
}

 
const  createControllers  =  ()  =>  {
    const link = document.createElement("link");
    link.rel = 'stylesheet';
    link.type = 'text/css';
    link.href = chrome.runtime.getURL('styles/content_script.css')

    const container = document.createElement('div');
    container.classList.add("controls-container");
    container.innerHTML = `
    <button>block this sit</button>
    `;

    container.querySelector("button").addEventListener("click", () => blockWebSite());
    document.head.appendChild(link)
    document.body.appendChild(container);
}

window.addEventListener("load",  async  ()  =>  {
createControllers()
checkIfBlocked();

})

In the above code, the function I used to append HTML elements in all websites users visit, for styling those elements is to use chrome.runtime.getURL() function. This is commonly used in Chrome extensions to dynamically load stylesheets into web pages.

With our button in place let's know to introduce background scripts and add more functionalities to our project

Background scripts

In the context of browser extensions or add-ons, a background script refers to a program or script that runs in the background of the browser. It is not directly linked to any tab or page the user is viewing. Instead, it continues to run even when all tabs are closed.

Here is what you can do, intercept requests, manage tabs, access browser storage, message passing, access runtime APIS, and much more. here is the official resource.

before anything open your manifest file and update your configurations with the following configurations.

manifest.json

{
   "manifest_version": 3,
   "name": "simple blocker",
   "version": "1.0",
   "action": {
      "default_popup": "popup.html",
      "default_icon": {
         "128": "images/blocker.png"
      }
   },
   "content_scripts": [
      {
         "matches": [
            "<all_urls>"
         ],
         "js": [
            "scripts/controller.js"
         ]
      }
   ],
   "background": { //add background scripts 
      "service_worker": "background/event.js" //your file 
   },
   "web_accessible_resources": [
      {
         "resources": [
            "styles/content_script.css"
         ],
         "matches": [
            "<all_urls>"
         ]
      }
   ],
   "permissions": [
      "tabs",
      "storage"
   ]
}

The scripts below will consist of blocking selected websites, removing a blocked website from blocked ones, and checking if the visited website is blocked. If blocked the website will be automatically closed

event.json


chrome.runtime.onMessage.addListener(async (request, sender, sendResponse) => {

    if (request.action === 'block') {
        chrome.storage.local.get(['blocked_sits'], (result) => {

            const blockedSites = result.blocked_sits?.length ? result.blocked_sits : []; //check if there are some data in the local storage 


            blockedSites.push(request.host); //push new url within the list of blocked sites 

            chrome.storage.local.set({ blocked_sits: blockedSites }, function () {
                chrome.tabs.query({
                    currentWindow: true,
                    active: true
                }, function (tabs) {
                    chrome.tabs.remove(tabs[0].id);//close current tabe 
                });

                chrome.tabs.create({ url: "pages/forbidden.html" }); //redirect to a forbidden page after blocking (make sure that you have this file in your directory)
            });
        })
        return true; //this is crucial if you plan to return a response body(very importan)
    }

    if (request.action === 'check_if_blocked') { //check it the visited website is blocked(if yes then close the current tabe)
        chrome.storage.local.get(['blocked_sits'], function (result) {
            console.log(result);
            
            const checkIfBlocked = result.blocked_sits?.length && result.blocked_sits.includes(request.host) ? true : false;

            if (checkIfBlocked) {
                chrome.tabs.query({ //close the current page or tabe 
                    currentWindow: true,
                    active: true
                }, function (tabs) {
                    chrome.tabs.remove(tabs[0].id);
                });

                chrome.tabs.create({ url: "pages/forbidden.html" });
            }

        })
    }

});

I'm sure if you are new to Chrome extensions you are like what is this mess๐Ÿ˜ณ ? Let's break it down.

Message listening : (method) This method is used to listen to sent messages from other parts of the extension. The request parameter contains the request body sent by the sender. sender parameter contains information about the sender, this typically includes the tab's ID, sender URL, and more, and at the end, the send response callback function is used to return a response to the sender if needed

Request action: Within each request body I add a property called action, this will help identify which request has been sent and how to respond to it. you can name the property whatever you want since request bodies are just normal objects in addition I send the host of the website that is about to get blocked. this will make more sense when we start with message-passing

Tab management: In this single article it's very difficult to demonstrate everything you can do with tabs, but at least I introduce the basics you may want to do with tabs. The method chrome.tabs.query is used to query tabs that meet the required conditions, this method returns all call-back function that has the returned tabs in an array, and each tab element has information about itself this can be its ID. After getting the ID of the current tab I use the method chrome.tabs.remove to close the tab and open our forbidden page with chrome.tabs.create, for this one make sure you set the correct path of the page depending on your folder structure.

Local storage: Working with the local storage in Chrome extension may be different from what we used to do, to make this article as short as possible I decided to not enter deeply into this topic but for much more information please use the following resource, and you can check comments on each local storage implementation I left some useful comments.

message passing

What is the point of implementing background scripts if we can't interact with them? By definition message passing in Chrome extension is a communication mechanism used to exchange data and trigger events through different parts of a Chrome extension. this can be from content script to background or popup script to background scripts.

If you remember very well we created we created a button using content script capabilities, let's connect it to the background script and make sure when pressed the opened website gets blocked.

Open the file containing your content script and update it with the following code.

controller.js

const  blockWebSite  =  ()  =>  {

chrome.runtime.sendMessage({ action:  "block",  host:  window.location.host },  function (response) {

if (chrome.runtime.lastError) {

console.error(chrome.runtime.lastError);

} else {

//do whatever you want

}

})

}

  

const  checkIfBlocked  =  ()  =>  {

chrome.runtime.sendMessage({ action:  "check_if_blocked",  host:  window.location.host }, (response) => {

if (chrome.runtime.lastError) {

console.error(chrome.runtime.lastError);

} else {

// do whatever you want...

}

});

};

  

const  createControllers  =  ()  =>  {

const  link  =  document.createElement("link");

link.rel  =  'stylesheet';

link.type  =  'text/css';

link.href  =  chrome.runtime.getURL('styles/content_script.css')

  

const  container  =  document.createElement('div');

container.classList.add("controls-container");

container.innerHTML  =  `

<button>block this sit</button>

`;

  

container.querySelector("button").addEventListener("click", () =>  blockWebSite());

document.head.appendChild(link)

document.body.appendChild(container);

}

  

window.addEventListener("load",  async  ()  =>  {

createControllers()

checkIfBlocked();

})

Within the above code, we have two functions, both used for passing messages to the background script, the block_site function is used to ask the background script to block the opened website, and for the check_if_block function, I think the function name speaks for itself self ๐Ÿ˜…

Well, that was a very long one, if you learned something from this article just drop a small comment and share the content so that other people can gain access to this document, at the beginning of this series I said our application will be presentable, and currently we miss some functionalities, in the coming episode we shall complete with the project and deploy it on the chrome store.

The next episode is right here