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; cdintocontentScripts, runnpm init -y; npx -p typescript tsc --initto generate apackage.jsonand atsconfig.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