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.json
with 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.html
with 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.json
file; - You should have a new extension in your browser's extensions list, click it;
- This will show you a popup with the
popup.html
rendered.
Adding react #
- On the same folder created before, install react with
npx create-react-app popup --template typescript
; - Delete
popup.html
; - In
popup
folder >package.json
>build
script, 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.json
withcontent_security_policy
, but this seems to be a safer option.
- It should looks something similar to
- In
manifest.json
changebrowser_action.default_popup
topopup/build/index.html
; - Inside the
popup
folder, runnpm run build
; - Load the extensions again in firefox;
- You'll notice that nothing shows up when pressing the extension icon;
- Copy
manifest.json
intopopup/build/manifest.json
(overwrite the existing file); - Change
popup/build/manifest.json
browser_action.default_popup
toindex.html
; - In firefox, reload the extension with
popup/build/manifest.json
instead; - The extension works now, interesting;
- Open
popup/build/index.html
and change all occurrences of/static/
to/popup/build/static/
; - Reload the extension with
manifest.json
from 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.json
that 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/build
folder and pointmanifest.json
to 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_popup
prop toindex.html
; - In the root of the project, create a file named
build.sh
and put the following content:1#!/bin/sh 2cd popup 3 4npm run build 5cp ../manifest.json build/manifest.json
- Make
build.sh
executable:chmod +x build.sh
; - On the root of the project, run
npm init -y
; - In
package.json
, remove thetest
script and add abuild
script with./build.sh
; - Run
npm run build
on 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
; cd
intocontentScripts
, runnpm init -y; npx -p typescript tsc --init
to generate apackage.json
and atsconfig.json
file;- We are going to use
esbuild
to 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.ts
incontentScripts/src
with the following content:1console.log("hello");
- Create a file named
- In
contentScripts/package.json
, add the followingbuild
scriptesbuild src/parseInputs.ts --outfile=../popup/build/contentScripts/parseInputs.js
:- Notice that we are pointing to the
popup/build/contentScripts/parseInputs.js
file, this is because ourmanifest.json
will be inside thepopup/build
folder; - Yeah I know, it's a bit messy but it'll all come together nicely.
- Notice that we are pointing to the
- Modify
build.sh
to 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 build
on the root of the project; - Reload the extension;
- Open a page in firefox and check the console, you'll see a
hello
message 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
chrome
type to both our projects so we can do some fun stuff. Run the following in both thepopup
andcontentScripts
foldersnpm install -D @types/chrome
; - Modify
contentScripts/src/parseInputs.ts
with 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