post

Getting Started with Chrome Extension Development

· 4 min read · 835 words

This post focuses on practical development experience. The content is still rough and needs further refinement.

References:

https://developer.chrome.com/docs/extensions/get-started (Official tutorial)

https://github.com/sxei/chrome-plugin-demo

https://www.pipipi.net/24804.html

Methods investigated: background module (Chrome Extension module), Message Passing API (Chrome Extension API), Broadcast Channel (pub-sub pattern), SharedWorker API (shared background thread across tabs), localStorage/sessionStorage (local browser storage).

HelloWorld

manifest.json

{
  "manifest_version": 3,
  "name": "Hello Extensions",
  "description": "Base Level Extension",
  "version": "1.0",
  "action": {
    "default_popup": "hello.html",
    "default_icon": "hello_extensions.png"
  }
}

hello.html

<html>
  <body>
    <h1>Hello Extensions</h1>
  </body>
</html>

Navigate to chrome://extensions → Enable Developer Mode → click the Load unpacked button and select the extension directory.

Reload

Extension component Requires extension reload
The manifest Yes
Service worker Yes
Content scripts Yes (plus the host page)
The popup No
Options page No
Other extension HTML pages No

ReadTime

manifest.json

{
  "manifest_version": 3,
  "name": "Reading time",
  "version": "1.0",
  "description": "Add the reading time to Chrome Extension documentation articles",

  "icons": {
    "16": "images/icon-16.png",
    "32": "images/icon-32.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  },
  "content_scripts": [
    {
      "js": ["scripts/content.js"],
      "matches": [
        "https://developer.chrome.com/docs/extensions/*",
        "https://developer.chrome.com/docs/webstore/*",
        "https://jrqz-wu.com/*"
      ]
    }
  ]
}

Content scripts can use the standard Document Object Model (DOM) to read and change the content of a page. The extension will first check if the page contains the <article> element. Then, it will count all the words within this element and create a paragraph that displays the total reading time.

content.js

const article = document.querySelector("article");

// `document.querySelector` may return null if the selector doesn't match anything.
if (article) {
  const text = article.textContent;
  const wordMatchRegExp = /[^\s]+/g; // Regular expression
  const words = text.matchAll(wordMatchRegExp);
  // matchAll returns an iterator, convert to array to get word count
  const wordCount = [...words].length;
  const readingTime = Math.round(wordCount / 200);
  const badge = document.createElement("p");
  // Use the same styling as the publish information in an article's header
  badge.classList.add("color-secondary-text", "type--caption");
  badge.textContent = `⏱️ ${readingTime} min read`;

  // Support for API reference docs
  const heading = article.querySelector("h1");
  // Support for article docs with date
  const date = article.querySelector("time")?.parentNode;

  (date ?? heading).insertAdjacentElement("afterend", badge);
}

Cross-Tab Messaging

Content scripts (content.js) and background scripts (background.js).

A content script is injected into specific web pages when they load. The exact timing depends on the content_scripts configuration in manifest.json, including the matches and run_at properties.

  • matches: Defines which pages the content script should be injected into.

  • run_at: Defines at what stage of page loading the content script is injected. Options include:

    • "document_start": When the HTML document begins loading.
    • "document_end": When the HTML document is fully loaded and parsed (default).
    • "document_idle": When the remaining page resources (e.g., images) have finished loading.

The background script runs when the extension is installed or the browser starts, and remains active throughout the browser session. Manifest V3 uses a background service worker, which differs from the traditional persistent background page.

  • Service Worker: The background service worker starts when needed and terminates automatically when idle, helping conserve resources.

Message Passing API:

  1. Communication between extension components: Enables communication between different components of the extension, including the background page, content scripts, and popup windows.
  2. Communication between extension and web pages: The extension can exchange data and invoke functionality with web pages through the Message Passing API.
  3. Communication between extension and server: The extension can communicate with a server to exchange data and invoke remote functionality.

The Message Passing API provides the following:

Field Description
chrome.runtime.sendMessage() Sends a message to a specified extension or content script
chrome.runtime.onMessage() Listens for messages from a specified extension or content script
chrome.runtime.connect() Establishes a long-lived connection between an extension and content script for two-way communication
chrome.runtime.onConnect() Listens for connection requests from an extension or content script and returns a connection object

Demo:

manifest.json

{
    "manifest_version": 3,
    "name": "Cross Tab Messaging Example",
    "version": "1.0",
    "permissions": ["tabs", "activeTab", "scripting"],
    "background": {
      "service_worker": "background.js"
    },
    "content_scripts": [
      {
        "matches": ["https://jrqz-wu.com/*"],
        "js": ["contentA.js"],
        "run_at": "document_end"
      },
      {
        "matches": ["https://www.google.com/*"],
        "js": ["contentB.js"],
        "run_at": "document_end"
      }
    ]
  }

background.js — executed when the extension loads:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'sendToGoogle') {
      // Query all tabs
      chrome.tabs.query({ url: "https://www.google.com/*" }, (tabs) => {
        for (let tab of tabs) {
          // Send message to the Google tab
          chrome.tabs.sendMessage(tab.id, { action: 'messageFromTabA', data: message.data });
        }
      });
      sendResponse({ status: 'message sent to Google tab' });
    }
  });

contentA.js — executed when https://jrqz-wu.com/* is opened or refreshed:

console.log('hello from contentA');
// Get the content of the h1 element
const h1Content = document.querySelector('h1').innerText;

console.log('sent h1Content' + h1Content);
// Send message to background script
chrome.runtime.sendMessage({ action: 'sendToGoogle', data: h1Content }, (response) => {
    console.log(response.status);
});
console.log('sent h1Content' + h1Content);

contentB.js — executed when https://www.google.com/* is opened or refreshed:

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    if (message.action === 'messageFromTabA') {
      console.log('Received message from Tab A:', message.data);
      // Write to the Google search box
      const searchBox = document.querySelector('textarea[name="q"]') || document.querySelector('input[name="q"]');
      if (searchBox) {
        searchBox.value = message.data;
        searchBox.dispatchEvent(new Event('input', { bubbles: true })); // trigger input event
      }
    }
  });

Content script logs are visible in the Console of the corresponding tab. Background script logs can be found by inspecting the service worker on the Chrome Extensions page.