A Brief Guide to the Basics of Chrome Extensions

Introduction

This article is a summary for those who can read and write basic JavaScript to quickly understand how Chrome extensions work.

Chrome extensions are not as difficult as they seem. The contents are HTML, CSS, and JavaScript, and no special languages are used. However, there are unique rules that differ slightly from regular web applications, and if you start writing without knowing them, you will likely get stuck without understanding why your code does not work.

This article briefly summarizes only the concepts you should grasp first, using actual extension code as examples. Once you understand what is written here, you should be able to build extensions step by step while referring to the official documentation and sample code.

Basic Design Philosophy

The basic design philosophy is that multiple components run in their own isolated environments and communicate via messages only when necessary to perform processes.

📖 Overview of Chrome extensions

Message Passing

To reference variables or call methods between files, the message passing API is used.
(Direct referencing or calling is not possible.)

📖 Message passing

Available APIs Depend on the Component (File)

For example, Content Scripts are mostly limited to front-end APIs, so advanced features must be requested indirectly through the Service Worker. On the other hand, the Service Worker can use many APIs responsible for background processing, such as server communication.

📖 List of APIs directly accessible by Content Scripts

Main Components

(1) UI Pages (popup.html / popup.css / popup.js)

There are no special notes regarding this.

(2) Content Scripts

Handles the front-end, such as DOM manipulation.

📖 Content Scripts

// content.js

// Message passing
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
  if (message.action !== 'extractMetadata') return
  const title = document.title
  // Get DOM values using standard browser APIs
  const published_at = document
    .querySelector('meta[property="article:published_time"]')
    ?.getAttribute('content')
    ?.slice(0, 10)

  sendResponse({
    title: title,
    published_at: published_at
  })
})

(3) Service Worker

  • Handles the back-end
  • Event-driven
  • Cannot access the DOM directly

📖 Basics of Extension Service Workers
📖 Service Worker event handling

  1. Send a message in response to a tab selection event
// popup.js
async function lookupBuzz(url) {
  try {  // Message passing
    const response = await chrome.runtime.sendMessage({
      action: 'lookupBuzz',
      url: url
    })
    if (!response) {
      throw new Error('Cannot retrieve buzz')
    }
    return response
  } catch (error) {
    throw new Error(`chrome api error: ${error.message}`)
  }
}

📖 chrome.runtime.sendMessage() reference

  1. Receive the message in the Service Worker and call the function specified by the action
// background.js
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
   // Based on the action
  if (message.action === 'lookupBuzz') {
    fetchBuzz(message.url)
      .then((response) => {
        sendResponse(response)
      })
      .catch((error) => {
        sendResponse({ error: error.message })
      })
    return true
  }
})

// Advanced APIs can be called in the Service Worker
async function fetchBuzz(url) {
  const response = await fetch(....)

📖 chrome.runtime.onMessage reference
📖 Why return true is required for asynchronous responses (Message passing)


About activeTab

A mechanism that grants temporary access rights triggered by explicit user actions.

  1. Access rights to the currently open tab are granted the moment the extension is invoked.
  2. The rights are lost the moment the tab is switched (specifically, navigating to another domain) or closed.

While the rights are active, it is possible to inject scripts, retrieve tab information (URL, title, etc.), and listen to network requests.

📖 activeTab permission

// Example of getting property values of the current tab
const tabId = activeTab.id

// Example of processing by getting the tabId when switching tabs
chrome.tabs.onActivated.addListener(async (activeInfo) => {
  const tab = await chrome.tabs.get(activeInfo.tabId)
  await updateBuzzIcon(tab)
})

📖 chrome.tabs API reference


manifest.json

The configuration file for the extension.

📖 Manifest file format
📖 Declare Permissions

{
  "name": "fjord-buzz-extension",
  "version": "1.0",
  "manifest_version": 3, // Current version

  "background": {
    "service_worker": "background.js",
    "type": "module" // Required to import config.js
  },

  "description": "Google extension for fjord buzz page",

  "content_scripts": [
    {
      "matches": [
        "http://*/*",
        "https://*/*"
      ],
      
      "js": [
        "content.js" 
      ],

      // Inject script after page load is complete
      "run_at": "document_idle"
    }
  ],

  "permissions": [
    "cookies",
    // Permission to get information (like url and title) from the source currently displayed in the tab
    "tabs"
  ],

  "host_permissions": [
    "http://localhost:3000/*"
  ],

  // Extension icon
  "action": {
    "default_icon": {
      "16": "icons/buzz_icon16.png"
    },

    // Text shown when hovering over the icon
    "default_title": "fjord-buzz-extension",

    // Specify the file to open when the icon is clicked
    "default_popup": "popup/popup.html"
  }
}

📖 content_scripts manifest key details
📖 chrome.cookies API reference
📖 chrome.action API reference


Illustration by Shahid Mehmood on Unsplash

End of the article

No comments yet.