The bare minimum #
The objective here is to have an extension that automatically fills inputs with random data.
This won't really be useful here, but we'll also use react to make our extension's popup. This is just to give an idea of how you could use a web lib/framework to make your extensions.
Initial setup #
- On an empty folder, create a file named manifest.jsonwith the following content (more info on each prop here):1{ 2 "name": "Bare minimum: input auto-fill", 3 "description": "The bare minimum you'll need to get up and running with extensions", 4 "version": "1.0", 5 "permissions": ["activeTab"], 6 "browser_action": { 7 "default_title": "Auto-fill inputs", 8 "default_popup": "popup.html" 9 }, 10 "manifest_version": 2 11}
- Create a file named popup.htmlwith the following content:1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <title>Input auto-fill</title> 5</head> 6<body> 7 <h1>Hello World</h1> 8</body> 9</html>
- In firefox, open about:addons> cog button > Debug Add-ons > Temporary Extensions > Load Temporary Add-on... > select themanifest.jsonfile;
- You should have a new extension in your browser's extensions list, click it;
- This will show you a popup with the popup.htmlrendered.
Adding react #
- On the same folder created before, install react with npx create-react-app popup --template typescript;
- Delete popup.html;
- In popupfolder >package.json>buildscript, prefix the script withINLINE_RUNTIME_CHUNK=false;- It should looks something similar to "build": "INLINE_RUNTIME_CHUNK=false react-scripts build";
- We'll use this flag due to reported issues with CSP. There are alternatives, like modifying manifest.jsonwithcontent_security_policy, but this seems to be a safer option.
 
- It should looks something similar to 
- In manifest.jsonchangebrowser_action.default_popuptopopup/build/index.html;
- Inside the popupfolder, runnpm run build;
- Load the extensions again in firefox;
- You'll notice that nothing shows up when pressing the extension icon;
- Copy manifest.jsonintopopup/build/manifest.json(overwrite the existing file);
- Change popup/build/manifest.jsonbrowser_action.default_popuptoindex.html;
- In firefox, reload the extension with popup/build/manifest.jsoninstead;
- The extension works now, interesting;
- Open popup/build/index.htmland change all occurrences of/static/to/popup/build/static/;
- Reload the extension with manifest.jsonfrom the project's root;
- The extension works again:
- It seems the extension failed to load the first time because the root of the project was /instead of/popup/build/, this was probably due to the location of themanifest.jsonthat is loaded into firefox, which defines the root of the project;
- The react setup here is a bit of a pain because it means that we can only have js compiled by react. If we want to have a standalone script that runs in the background, we'll have to somehow copy it into the popup/buildfolder and pointmanifest.jsonto it. But that doesn't matter now, because we don't want that yet.
 
- It seems the extension failed to load the first time because the root of the project was 
 
- Copy 
- Change the root manifest.json'sbrowser_action.default_popupprop toindex.html;
- In the root of the project, create a file named build.shand put the following content:1#!/bin/sh 2cd popup 3 4npm run build 5cp ../manifest.json build/manifest.json
- Make build.shexecutable:chmod +x build.sh;
- On the root of the project, run npm init -y;
- In package.json, remove thetestscript and add abuildscript with./build.sh;
- Run npm run buildon the root of the project;
- Reload the extension with popup/build/manifest.json;
- The extensions is working.
Reading the active tab for inputs #
To parse the active tab's DOM, we need something called a content script (more info here). As was observed before, the only scripts manifest.json has access right now are the ones compiled by react, in other words, the ones inside the popup/build folder.
All ts files compiled by react are bundled into one or more js files, but we need our own dedicated js file to be used as a content script.
Creating the content script #
- Create a new folder on the root of the project called contentScripts;
- cdinto- contentScripts, run- npm init -y; npx -p typescript tsc --initto generate a- package.jsonand a- tsconfig.jsonfile;
- We are going to use esbuildto compile our files, so we need to install it:npm install -D esbuild;
- Lets create our content script file. This file will parse the active tab inputs soon, but for now, it'll just print hello:- Create a file named parseInputs.tsincontentScripts/srcwith the following content:1console.log("hello");
 
- Create a file named 
- In contentScripts/package.json, add the followingbuildscriptesbuild src/parseInputs.ts --outfile=../popup/build/contentScripts/parseInputs.js:- Notice that we are pointing to the popup/build/contentScripts/parseInputs.jsfile, this is because ourmanifest.jsonwill be inside thepopup/buildfolder;
- Yeah I know, it's a bit messy but it'll all come together nicely.
 
- Notice that we are pointing to the 
- Modify build.shto look like this:1#!/bin/sh 2cd popup 3 4npm run build 5cp ../manifest.json build/manifest.json 6 7cd ../contentScripts 8npm run build
- Add our content script path on the root of manifest.json:1"content_scripts": [{ 2 "matches": ["http://*/*", "https://*/*"], 3 "js": ["./contentScripts/parseInputs.js"] 4}]
- Run npm run buildon the root of the project;
- Reload the extension;
- Open a page in firefox and check the console, you'll see a hellomessage pop up;
- Our content script isn't doing anything exciting now, so let's add some triggers.
Triggering the content script #
- To be able to trigger our content script, we need something to trigger:
- Lets add the chrometype to both our projects so we can do some fun stuff. Run the following in both thepopupandcontentScriptsfoldersnpm install -D @types/chrome;
- Modify contentScripts/src/parseInputs.tswith the following code:1chrome.runtime.onMessage.addListener( 2 (message, sender, response) => console.log({message, sender, response}) 3);
 
- Lets add the 
- Now we need to trigger this from within our popup:
- Let's add a button to our popup, so we can trigger the content script message event listener popup/src/App.tsx>App>div:1<button 2 onClick={() => 3 chrome.tabs.query( 4 {active: true, currentWindow:true}, 5 ([tab]) => tab?.id && chrome.tabs.sendMessage(tab.id, { greeting: 'hello' }) 6 ) 7 } 8> 9 hello 10</button>
- Reload the extension;
- Open the popup and click the button;
- You'll see an object getting logged to the console.
 
- Let's add a button to our popup, so we can trigger the content script message event listener 
Auto-fill all the inputs! #
Let's modify our code to auto-fill all the inputs on the page with random data when the popup button is clicked.
- Replace contentScripts/src/parseInputs.ts's code with the following:1chrome.runtime.onMessage.addListener(() => 2 [...document.getElementsByTagName('input')] 3 .forEach(input => ( 4 input.value = Math.random().toString() 5 )) 6);
- Reload the extension;
- Open the popup and click the button;
- You'll see all the inputs auto-filled with random data.
Recap #
- We created a popup with react that has the default react app content and a single button at the bottom;
- When the popup button is pressed, we send a message using the browser's messaging system;
- This message is captured by the content script, which has access to the DOM of the active tab;
- When the content script receives the message, it parses the inputs and auto-fills them with random data.
That's it!
Hopefully it'll serve as a starting point for your future extensions.
Props #
- https://github.com/bitwarden/clients/blob/master/apps/browser/src/content/autofill.js
- https://scribe.froth.zone/@TusharKanjariya/getting-started-with-developing-browser-extensions-eb4a7d8658b3
- https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs
- https://developer.chrome.com/docs/extensions/mv3/getstarted/
- https://scribe.bus-hit.me/litslink/how-to-create-google-chrome-extension-using-react-js-5c9e343323ff