This week, I’ve had the pleasure of diving into browser extensions, particularly focusing on creating a tool to help users identify potential bias on any webpage.
See the “finished” extension here (feel free to install and give it a like): https://chromewebstore.google.com/search/diversetalent
The process has been mostly straightforward, so I wanted to share a quick guide on how to get started with your first browser extension.
TLDR; here is a repo with a simple example of a browser extension: Browser Extension Template
The How: Building Your First Extension
Getting Started
To kick things off, you can use a simple and easy-to-follow template available on GitHub: Browser Extension Template. This template includes the essential files needed for a basic extension which showcases how to communicate between the popup and the current webpage.
Key Files Explained
A browser extension generally contains the following key files:
- manifest.json – Tells the browser what files are included with your extension and what permissions your extension needs.
- popup.html – Optional. If you need to display a popup window when someone clicks your extension button in the toolbar.
- content.js – Optional. The js to be injected into the current browser page so that you can access the DOM.
- background.js – Optional. A place to add code that can respond to events, even when your popup is not showing.
The github repo contains more files than the ones listed above.
- manifest.json: This is the blueprint of your extension. It includes metadata such as the extension’s name, version, description, and the permissions it requires.
{
"manifest_version": 3,
"name": "Browser Extension Template",
"version": "0.1.0",
"description": "This is a template extension project for a browser extension.",
"icons": {
"16": "icon.png",
"32": "icon.png",
"48": "icon.png",
"128": "icon.png"
},
"side_panel": {
"default_path": "popup.html"
},
"permissions": ["activeTab", "sidePanel"],
"action": {
"default_title": "Browser Extension Template",
"default_popup": "popup.html"
},
"background": {
"service_worker": "background.js",
"type": "module"
},
"web_accessible_resources": [
{
"matches": ["<all_urls>"],
"resources": ["icon.png"]
}
],
"content_scripts": [
{
"all_frames": false,
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["popup.css"]
}
]
}
- popup.html: This is the HTML file for the popup interface that appears when the extension icon is clicked.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Page Title</title>
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="stylesheet" type="text/css" media="screen" href="popup.css" />
<script src="popup.js" type="module"></script>
</head>
<body style="min-height: 400px">
<h1>Popup</h1>
<button id="clickme">
Click me to scrape all content from current page
</button>
<div class="response-text" id="response"></div>
</body>
</html>
- popup.js: This JavaScript file handles the logic for the popup interface.
(async () => {
console.log('popup.js loaded');
const getTabSelectedText = async () => {
console.log('Popup: getTabSelectedText clicked');
// Send a message to the content script to get the selected text
const tabs = await chrome.tabs.query({ active: true, currentWindow: true });
if (tabs.length === 0) return; // Exit if no active tab found
chrome.tabs.sendMessage(tabs[0].id, { type: 'GET_PAGE_CONTENT' }, (response) => {
console.log('Popup: Received response:', response);
const responseElement = document.getElementById('response');
if (responseElement) responseElement.innerText = response?.content || 'No content';
});
};
document.addEventListener('DOMContentLoaded', () => {
// Add event listener for the getTabSelectedText button
const clickMeButton = document.getElementById('clickme');
if (clickMeButton) clickMeButton.addEventListener('click', getTabSelectedText);
});
chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
if (request.type === "SELECTED_TEXT") {
// The page html content
const content = request.content;
const responseElement = document.getElementById('response');
if (responseElement) responseElement.innerText = content;
}
return true; // Indicate that sendResponse will be called asynchronously
});
})();
- content.js: This script runs in the context of the webpage and contains the logic for detecting bias.
(async () => {
"use strict";
console.log('browser extension content.js loaded');
const sendSelectedText = () => {
const selectedText = window.getSelection()?.toString();
if (selectedText) {
console.log('SENDING SELECTED_TEXT message:', selectedText);
chrome.runtime.sendMessage({ type: 'SELECTED_TEXT', content: selectedText }, (response) => {
if (chrome.runtime.lastError) {
console.error('Error sending message:', chrome.runtime.lastError.message);
}
});
}
};
document.addEventListener('mouseup', function (event) {
console.log('mouseup event');
sendSelectedText();
});
chrome.runtime.onMessage.addListener(
function (request, sender, sendResponse) {
if (request.type === "GET_PAGE_CONTENT") {
// Extract the page's text content and the current selection
const content = document.body.innerText;
const selectedText = window.getSelection()?.toString() || '';
sendResponse({ type: 'SEND_PAGE_CONTENT', content, selectedText });
}
return true; // Keep the message channel open for asynchronous response
}
);
})();
- background.js: This script handles background tasks and manages the extension’s lifecycle.
// Handles background tasks for the extension
(() => {
console.log('background.js loaded');
// Ensure the sidePanel API is available before attempting to use it
if (chrome.sidePanel) {
chrome.runtime.onInstalled.addListener(() => {
// Automatically open the side panel when the extension's action is clicked
chrome.sidePanel.setPanelBehavior({ openPanelOnActionClick: true });
});
} else {
console.warn('This version of Chrome does not support sidePanel API.');
}
// Define a function to handle incoming messages
const handleMessage = (request, sender, sendResponse) => {
console.log('Message received:', request);
switch (request.type) {
case 'SELECTED_TEXT':
// Process the selected text, e.g., save it or call an API
console.log('Background: Selected text:', request.content);
break;
// Add more cases as needed
default:
console.warn('Unhandled message type:', request.type);
}
// Indicate that sendResponse will be called asynchronously
return true;
};
// Listen for messages from content scripts or popup
chrome.runtime.onMessage.addListener(handleMessage);
})();
Build & Debug
To run the code, just follow these steps.
- Open your browser and navigate to
chrome://extensions/
. - Enable Developer Mode.
- Click on
Load unpacked
and select the cloned repository of your extension. - To make changes, edit the relevant JS or HTML files, then refresh the extension and the webpage to see your updates in action.
- To see debug messages, open the Chrome dev tools for the target webpage, popup and/or service/worker.
Store Submission
- Update the
manifest.json
file with the appropriate details (name, version, description, etc.). - Zip the contents of your extension’s directory.
- Go to the Chrome Web Store Developer Dashboard.
- Click on
Add new item
. - Upload the zip file.
- Add the necessary details and click on
Publish
.
Conclusion
Creating a browser extension can be a fun and rewarding project, opening up numerous possibilities to enhance user interactions on the web. With this guide, you should have a solid foundation to build your own extensions and even publish them for others to use. Happy coding!