Getting Started with Chrome Extension Development
Table of Contents
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:
- Communication between extension components: Enables communication between different components of the extension, including the background page, content scripts, and popup windows.
- Communication between extension and web pages: The extension can exchange data and invoke functionality with web pages through the Message Passing API.
- 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.