Creating Your First Chrome Extension


Have you ever wanted to customize your browsing experience or add new functionalities to Chrome? Chrome extensions allow you to do just that! In this article, we will walk you through the process of creating your very first Chrome extension. No prior experience with Chrome extensions is required, so let’s get started.

What is a Chrome Extension?

A Chrome extension is a small software program that customizes the browsing experience. It can change the browser’s behavior, add new features, or modify the content of websites. Extensions are built using web technologies like HTML, CSS, and JavaScript.

Prerequisites

Before we get started, make sure you have a basic understanding of HTML, CSS, and JavaScript.

  • Basic Understanding of HTML, CSS, and JavaScript: Familiarity with the fundamental concepts of HTML for structuring web pages, CSS for styling, and JavaScript for adding interactivity is essential. This tutorial assumes a basic knowledge of these languages.
  • Web Browser: Use a modern web browser like Google Chrome, Mozilla Firefox, or Microsoft Edge.

For the purpose of this tutorial, you can download the source files to help you follow along:

Note: As you test the source files, ensure that you load them within a suitable environment such that all the files are loaded. For offline use, we recommend that you use Live Server and vscode to load the files. Simply open the files in Vscode and run “index.html” using Live Server.

Demo

Here is what we are going to create. A simple chrome extension to capture all downloadable files on a web page and enable the user to download them easily.

Basic Architecture of Chrome Extensions

In general, chrome extensions consist of five key components: the manifest file, background scripts content scripts and various UI elements like popups and options pages. These elements work together to customize and enhance the browsing experience. Getting to know the basic architecture of Chrome extensions is super important for building them smoothly. Let’s take a look at each of the key components:

  1. Manifest File (manifest.json):
    • This file is the blueprint of your extension. It provides important information such as the extension’s name, version, description, and permissions. It also specifies the files and resources your extension uses.
  2. Background Scripts:
    • These are JavaScript files that run in the background and manage the extension’s lifecycle. They can listen for browser events, make network requests, and manage state. Background scripts can be persistent or event-driven (non-persistent).
  3. Content Scripts:
    • These scripts are injected into web pages and can interact with the DOM of the pages. They allow your extension to read and modify the content of web pages. Content scripts have limited access to Chrome APIs but can communicate with other parts of the extension.
  4. UI Elements:
    • Extensions can have various user interface components such as:
      • Browser Actions: These add an icon to the browser toolbar and can have a popup.
      • Page Actions: These add an icon to the address bar that can be shown or hidden depending on the page.
      • Popups: These are HTML files that are displayed when the user clicks the extension icon.
      • Options Pages: These allow users to configure the extension’s settings.
      • Side Panels: These provide a persistent UI that can be displayed alongside web content.
  5. Permissions:
    • Permissions are declared in the manifest file and specify what APIs the extension can use and what data it can access. Examples include access to the user’s tabs, bookmarks, or browsing history.
  6. Icons:
    • Icons represent the extension in the Chrome toolbar, extension management page, and the Chrome Web Store. These are typically provided in different resolutions (16×16, 48×48, 128×128).

Step 1 : Setting the Project

Let’s start by organizing our project directory and setting up the necessary files:

  1. Create a Project Directory: Open your preferred development environment and create a new directory named easyfile-search-extension.
  2. Project Structure: Inside your project directory, create the following files:
    • manifest.json: Defines extension metadata and behavior.
    • popup.html, popup.css, popup.js: Interface for the extension’s popup.
    • background.js: Handles background tasks and events.
    • content.js: Content script that interacts with web pages to extract file information.
    • Additional assets like icons (logo.png).

Step 1 : Setting the Project

Lets create the manifest file for the project. The manifest file is key as it configures how your extension behaves and interacts with Chrome.

{
    "name":"EasyFile Search",
    "version":"1.0",
    "description":"Chrome extension to find downloadable files on website.",
    "permissions":["tabs","downloads"],
    "host_permissions":[],
    "background":{
        "service_worker":"background.js"
    },
    "icons":
    {
        "16":"assets/logo.png",
        "32":"assets/logo.png",
        "64":"assets/logo.png"
    },
    "content_scripts":[
        {
        "matches":["<all_urls>"],
        "js": ["content.js" ]
        }
    ],
    "web_accessible_resources":[
        {
            "resources":[  "assets/assets/logo.png"],
            "matches":["<all_urls>"]
        }
    ]
    ,
    "action":
        {
          
        

        "default_icon":
        {
            "16":"assets/logo.png",
            "32":"assets/logo.png",
            "64":"assets/logo.png"
        },
        "default_title":"EasyFile Search",
        "default_popup":"popup.html"
        },

        
        "manifest_version":3
    


}

Lets breakdown it part of the manifest file:

  • Name (“name"): Specifies the name of your extension as it appears in the Chrome Web Store and browser toolbar.
  • Version (“version“): Indicates the current version of your extension. Update this value when releasing new versions with added features or bug fixes.
  • Description (“description“): Provides a brief overview of what your extension does, helping users understand its purpose and functionality.
  • Permissions (“permissions“): Declares which Chrome APIs your extension requires access to. In this example, "tabs" allows access to browser tabs for querying active tabs, and "downloads" permits downloading files using the Chrome Downloads API.
  • Host Permissions (“host_permissions“): Optionally specifies which host permissions your extension needs to access external domains or resources. This array remains empty in our example, as our extension operates within the context of any webpage ("<all_urls>").
  • Background (“background“): Defines a service worker script ("background.js") that runs in the background, handling events like extension startup, network requests, and persistent data storage. This script is essential for managing extension functionality that doesn’t require user interaction, such as initiating downloads in response to user actions.
  • Icons (“icons“): Specifies icons of different sizes (16x16, 32x32, 64x64 pixels) that represent your extension in the Chrome interface. These icons ensure your extension is visually identifiable in the browser toolbar, extensions menu, and Chrome Web Store listings.
  • Content Scripts ("content_scripts“): Defines scripts ("content.js") injected into web pages that match specified URLs ("<all_urls>"). Content scripts interact with and modify the content of web pages to enhance functionality or gather information, such as extracting file links in our EasyFile Search extension.
  • Web Accessible Resources (“web_accessible_resources“): Lists resources ("assets/logo.png") accessible to web pages matching "<all_urls>". This ensures that content such as images or scripts bundled with your extension can be loaded and displayed correctly on any webpage where your extension operates.
  • Action (“action“): Specifies the extension’s behavior when the user interacts with its icon in the browser toolbar.
  • Default Icon (“default_icon“): Defines icons displayed in the toolbar at different sizes.
  • Default Title (“default_title“): Sets the tooltip text displayed when hovering over the extension icon.
  • Default Popup (“default_popup“): Links to the HTML file ("popup.html") that defines the popup interface when the extension icon is clicked. This interface provides a user-friendly way to interact with the extension’s features directly from the browser.

Step 2: Create the Popup

Let’s create the popup that will be displayed when the user clicks the extension icon. Create a file named popup.html in your directory and add the following code. This HTML file includes a simple UI with a filter input and display field for the detected files. We also link a JavaScript file, popup.js, which will handle the functionality of the extension in the background.

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="bootstrap-4.4.1.css" rel="stylesheet" type="text/css"></link>
    <script type="text/javascript" src="jquery-3.4.1.min.js"></script>
    <script type="text/javascript" src="bootstrap-4.4.1.js"></script>
    
    <link href="popup.css" rel="stylesheet" type="text/css"></link>
    <title>EasyFile Search</title>
</head>
<body>
    <div class="main shadow-lg bg-light bg-gradient">
        <span class="h4 text-primary w-100 text-center">EasyFile Search</span>
        <span class="filter">
            <input placeholder="filter.e.g. .pdf" type="text" id="filter" class="form-control"></input>
            <img src="assets/logo.png" class="logo brand" title="25scripts.com"></img>
        </span>
        <span class="status">
             <div class="progress" id="progress">
                <div class="progress-bar progress-bar-striped progress-bar-animated bg-primary" role="progressbar" aria-valuenow="100" aria-valuemin="0" aria-valuemax="100" style="width: 100%"></div>
              </div>
              <span style="display: none;" id="message">No files found</span>
        </span>
        
    <span class="view" id="view">
        
   

    </span>

        <span class="props rounded">
               
                <div  class="link text-primary lead brand">By 25scripts.com<div>
        </span>

        </div>


        
        <div style="display:none;">
            <div class="file_widget rounded" id="file_view_template" title="template.pdf">
                <span class="icon">
                <svg  xmlns="http://www.w3.org/2000/svg" width="32" height="32" fill="#aaa" class="bi bi-file-earmark" viewBox="0 0 16 16">
                    <path d="M14 4.5V14a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V2a2 2 0 0 1 2-2h5.5zm-3 0A1.5 1.5 0 0 1 9.5 3V1H4a1 1 0 0 0-1 1v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1V4.5z"/>
                  </svg>
                  <span id="file_type_icon" class="text-success"></span>
                </span>
                <span class="file_name text-truncate" id="file_name">template.pdf</span>
                <span id="download_button" class="btn btn-sn btn-primary"  name="">Download</span>
            </div>
        </div>
        <script type="text/javascript" src="popup.js"></script>
     
</body>
</html>

Now create a css file called “popup.css”, to add some basic CSS styles as shown below.

body{
    margin: auto;
    font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
}
.main{
    min-width: 400px;
    min-height: 400px;
    max-height: 650px;
    max-width: 400px;
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    padding: 10px;
    gap:10px;
   
}
.main>*{
    width: 100%;
}
.filter{
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    gap:10px;
}
.filter>input{
width: 100%;
}

.view{
 background:#ccc;
 min-height: 300px;
 max-height: 300px;
 overflow-y: scroll;
 border:1px solid #bbb;
 padding:5px;
 gap:10px;
 display: flex;
 flex-direction: column;
 flex-wrap: nowrap;
}
.file_widget{
    width: 100%;
    background: #ffffff;
    padding: 5px;
    cursor: pointer;
    display: flex;
    flex-direction: row;
    flex-wrap: nowrap;
    max-width:100%;
    text-indent: 10px;
    gap:10px;
}
.file_widget>.file_name{
    width: 100%;
    max-width: 210px;
    overflow: hidden;
    padding: 5px;
}
.icon{
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
}
.icon>#file_type{
    font-size:0.8rem;
    font-weight: bold;
}
.icon>svg{
    margin-left:5px;
}

.props{
    width: 100%;
}
.props>div{
    display: flex;
    flex-direction: column;
    flex-wrap: nowrap;
    padding: 10px;
    gap:10px;
    width: 100%;
}
.logo{
    width: auto;
    height: 50px;
    cursor: pointer;
}
.logo:hover{
    opacity:0.6;
}
.link{
   min-width: 100%!important;
   text-align: center;
   cursor: pointer;
}
.status{
    width: 100%;
}
#download_button{
    max-height: 40px;
}

Step 3: Add Functionality with JavaScript

Now, let’s add some functionality to our extension. Create a file named popup.js and add the following code:

const filter={
    "data":"",
    "init":false
}

//request file links
function getLinks(){
chrome.tabs.query({active: true, currentWindow: true}, function(tabs){
    setStatus(false);
    chrome.tabs.sendMessage(tabs[0].id, {type:"get_files"}, function(response) {
        displayFiles(response.files);
    });
});
}
//lets load the tab is ready
chrome.tabs.onUpdated.addListener(function (tabId , info) {
    if (info.status === 'complete') {
       getLinks();
    }
  });
//lets the files obtained 
function displayFiles(data){
    var template= document.getElementById("file_view_template");
    var parent=document.getElementById("view");
    parent.innerHTML="";
    for(let i=0;i<data.length;i++){
         var file=data[i];
         if(file.file_name.toLowerCase().includes(filter.data.toLowerCase())==true||filter.data==""){
        
         var widget=template.cloneNode(true);
         widget.setAttribute("title",file.file_name);
         $(widget).children("#file_name").html(file.file_name);
         $(widget).children(".icon").children("#file_type_icon").html(file.file_type.toUpperCase());
         var button=$(widget).children("#download_button");
         button.on("click",function(){
            initiate_download(file.link);
         });
         parent.appendChild(widget);
        }
    }
    setStatus(true,data.length);
}
//lets set status message and progress 
function setStatus(status=false,length=0){
    if(status==false){
        $("#message").hide();
        $("#progress").show();

    }
    else{
        $("#progress").hide();
        var message=$("#message");
        if(length==0){
            message.html("No files found");
        }
        else{
            message.html(length+" files found")
        }
        message.show();
    }
}
//filter files
function setFilter(value){

 if(value){
    
    if(value!=""){
   
        filter.data=value;
    }
    else{
        filter.data="";
    }
    
 }
 else{
    filter.data="";
 }
 if(filter.init==true){
    getLinks();
}
}
//initiate file download
function initiate_download(link){
    chrome.downloads.download({
        url: link
      });
   
}
//initiate popup function
function init(){
    //lets hide the progress bar
    $("#progress").hide();
    //lets add events to the filter input
    $("#filter").on("input",function(){ setFilter(this.value)})
   //when popup is open get the file links
    getLinks();
    filter.init=true;
    //lets set brand link actions
    $(".brand").on('click',function(){
        chrome.tabs.create({url:"https://www.25scripts.com"});
    })
}
//initiate popup
init();

The above JavaScript code helps extension to interact with the active tab in the browser to fetch and display file links, and allows users to filter and download those files. Here’s an explanation of the main components and functions:

Explanation of the Code:

  • Initial Setup:
const filter = {
    "data": "",
    "init": false
};
  • filter is an object used to store filter criteria (data) and initialization state (init).
  • Function getLinks():
    • This function queries the active tab to send a message ({type: "get_files"}) requesting file links.When a response is received (response.files), it calls displayFiles() to render the files in the popup.
function getLinks() {
    chrome.tabs.query({ active: true, currentWindow: true }, function(tabs) {
        setStatus(false); // Sets status to show progress bar
        chrome.tabs.sendMessage(tabs[0].id, { type: "get_files" }, function(response) {
            displayFiles(response.files); // Displays files received from content script
        });
    });
}
  • Event Listener chrome.tabs.onUpdated:
    • Listens for tab updates (onUpdated) and triggers getLinks() when the tab is fully loaded (when info.status === 'complete').
chrome.tabs.onUpdated.addListener(function(tabId, info) {
    if (info.status === 'complete') {
        getLinks(); // Retrieves links when the tab is fully loaded
    }
});
  • Function displayFiles(data):
    • Renders file links in the popup based on the received data.Uses a template (file_view_template) to dynamically create HTML elements for each file.Provides download functionality (initiate_download()).
// Function to display files based on data received
function displayFiles(data) {
    // Get the template and parent element from the DOM
    var template = document.getElementById("file_view_template");
    var parent = document.getElementById("view");
    
    // Clear previous content in the parent element
    parent.innerHTML = "";
    
    // Iterate over each file in the data array
    for (let i = 0; i < data.length; i++) {
        var file = data[i];
        
        // Check if the file name includes the filter data (case insensitive)
        // or if filter data is empty, to decide whether to display the file
        if (file.file_name.toLowerCase().includes(filter.data.toLowerCase()) == true || filter.data == "") {
            // Clone the template to create a new widget
            var widget = template.cloneNode(true);
            
            // Set title attribute of the widget to the file name
            widget.setAttribute("title", file.file_name);
            
            // Set file name and file type icon in the widget
            $(widget).children("#file_name").html(file.file_name);
            $(widget).children(".icon").children("#file_type_icon").html(file.file_type.toUpperCase());
            
            // Add click event handler for the download button
            var button = $(widget).children("#download_button");
            button.on("click", function() {
                // Call function to initiate download with file link
                initiate_download(file.link);
            });
            
            // Append the widget to the parent element
            parent.appendChild(widget);
        }
    }
    
    // Set progress/status (e.g., display number of files)
    setStatus(true, data.length);
}
  • Function setStatus(status, length):
    • Manages status messages and progress display in the popup.Shows progress bar (#progress) or status message (#message).
// Function to set status message and toggle visibility of elements
function setStatus(status = false, length = 0) {
    // If status is false (indicating loading or in-progress state)
    if (status == false) {
        // Hide the message element
        $("#message").hide();
        // Show the progress element
        $("#progress").show();
    }
    // If status is true (indicating completion or result state)
    else {
        // Hide the progress element
        $("#progress").hide();
        // Get reference to the message element
        var message = $("#message");
        // If length of files found is 0, display "No files found" message
        if (length == 0) {
            message.html("No files found");
        }
        // Otherwise, display the number of files found
        else {
            message.html(length + " files found");
        }
        // Show the message element with updated content
        message.show();
    }
}
  • Function setFilter(value):
    • Updates filter.data based on user input (value).Calls getLinks() to refresh file links when filter.data changes.
// Function to set filter value and trigger action based on conditions
function setFilter(value) {
    // Check if 'value' is truthy (not undefined, null, or false)
    if (value) {
        // Check if 'value' is not an empty string
        if (value != "") {
            // Assign 'value' to the filter data property
            filter.data = value;
        } else {
            // If 'value' is an empty string, reset filter data to empty
            filter.data = "";
        }
    } else {
        // If 'value' is not provided or false, reset filter data to empty
        filter.data = "";
    }

    // Check if filter initialization flag is true
    if (filter.init == true) {
        // Call the 'getLinks' function to display the file links
        getLinks();
    }
}
  • Function initiate_download(link):
    • Initiates file download using Chrome’s chrome.downloads.download() API.
function initiate_download(link){
    chrome.downloads.download({
        url: link
      });
   
}
  • Function init():
    • Initializes the popup:
      • Hides the progress bar.Adds an event listener to the filter input (#filter) to trigger setFilter() on user input.Calls getLinks() to fetch initial file links.Sets event handlers ($(".brand")) to open a new tab with a specified URL.
function init(){
    //lets hide the progress bar
    $("#progress").hide();
    //lets add events to the filter input
    $("#filter").on("input",function(){ setFilter(this.value)})
   //when popup is open get the file links
    getLinks();
    filter.init=true;
    //lets set brand link actions
    $(".brand").on('click',function(){
        chrome.tabs.create({url:"https://www.25scripts.com"});
    })
}
  • Initialization:
    • Finally we call init() to initialize the popup when the extension is loaded.

Step 4: Implementing Functionality

Content Script (content.js):

This script runs within the context of web pages to extract file information based on specified file extensions.

function extractFileInfo(url) {
  // Create a URL object
  const parsedUrl = new URL(url);
    
  // Extract the file name from the pathname
  const fileNameWithParams = parsedUrl.pathname.substring(parsedUrl.pathname.lastIndexOf('/') + 1);
  
  // Remove any query parameters or hash fragments from the file name
  const fileNameEncoded = fileNameWithParams.split('?')[0].split('#')[0];
  
  // Decode the file name to handle URL encoded characters
  const fileName = decodeURIComponent(fileNameEncoded);
  
  // Extract the file type from the file name
  const fileType = fileName.substring(fileName.lastIndexOf('.') + 1);
  
  return {
      file_name: fileName,
      file_type: fileType,
      link:url
  };
}
function findAllFiles(){

     // Define the array of file extensions for downloadable files
     const fileExtensions = ['.pdf', '.doc', '.docx', '.xls', '.xlsx', '.zip', '.rar', '.7z', '.tar', '.gz', '.mp3', '.mp4', '.avi', '.mkv'];

     // Get all <a> tags on the page
     const links = document.querySelectorAll('a');
     //where to store link urls
     const urls=[];
     // Filter links that end with any of the specified file extensions
     for(let i=0;i<links.length;i++){
        var link=links[i];
        var href = link.getAttribute('href');
        if(href && fileExtensions.some(ext => href.toLowerCase().endsWith(ext))){
                urls.push(extractFileInfo(href));
        }
     }
 
    return urls
     
     

}
// receive command to get file links
chrome.runtime.onMessage.addListener(
    function (message, sender, senderResponse) {
        if (message.type === "get_files") {
            var files=findAllFiles();
            senderResponse({"files":files})

        }
        //command to download file
        else if (message.type === "download_file"){
            downloadFile(message.url);
            senderResponse("Ok");
        }

    }
)

Explanation:

  • extractFileInfo(url) Function: This function takes a URL as input and extracts detailed file information. It first parses the URL to create a URL object, then extracts the file name from the URL’s pathname, removes any query parameters or hash fragments, decodes the file name to handle URL encoding, and finally determines the file type based on the file extension. It returns an object containing the decoded file name (file_name), file type (file_type), and the original URL (link).

  • findAllFiles() Function: This function identifies all downloadable file links on the current webpage. It defines an array of file extensions commonly associated with downloadable files. Using JavaScript DOM manipulation, it fetches all <a> tags on the page, filters those whose href attribute ends with any of the specified file extensions, and collects detailed file information using the extractFileInfo() function for each matching link. It returns an array of objects, each containing information about a downloadable file.

  • Chrome Extension Messaging (chrome.runtime.onMessage.addListener): This part of the code listens for messages from the Chrome extension’s background script. When a message of type "get_files" is received, it calls findAllFiles() to gather all downloadable file links on the current webpage and sends them back as a response. When a message of type "download_file" is received, it initiates the download of the file specified by message.url using the downloadFile() function and sends an acknowledgment response ("Ok"). This messaging mechanism allows the extension to interact with and manipulate file downloads on webpages.

Step 5: Testing and Debugging

Finally, lets try the extension. To test the extension, follow the steps below.

  1. Testing:
    • Load your extension by navigating to chrome://extensions/, enabling Developer mode, and loading your unpacked extension.
    • Test your extension on different websites to ensure it functions correctly.
  2. Debugging:
    • Use Chrome Developer Tools to debug scripts and inspect runtime behavior.

Conclusion

Congratulations! You have just created your first Chrome extension. This simple project demonstrates the basics of Chrome extension development. From here, you can explore more advanced features such as background scripts, options pages, and interaction with various Chrome APIs.

Leave a Reply

Your email address will not be published. Required fields are marked *