Bare minimum input auto fill browser extension

· ranguna's blog

# 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

  1. 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}
    
  2. 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>
    
  3. In firefox, open about:addons > cog button > Debug Add-ons > Temporary Extensions > Load Temporary Add-on... > select the manifest.json file;
  4. You should have a new extension in your browser's extensions list, click it;
  5. This will show you a popup with the popup.html rendered.

# Adding react

  1. On the same folder created before, install react with npx create-react-app popup --template typescript;
  2. Delete popup.html;
  3. In popup folder > package.json > build script, prefix the script with INLINE_RUNTIME_CHUNK=false;
    1. It should looks something similar to "build": "INLINE_RUNTIME_CHUNK=false react-scripts build";
    2. We'll use this flag due to reported issues with CSP. There are alternatives, like modifying manifest.json with content_security_policy, but this seems to be a safer option.
  4. In manifest.json change browser_action.default_popup to popup/build/index.html;
  5. Inside the popup folder, run npm run build;
  6. Load the extensions again in firefox;
  7. You'll notice that nothing shows up when pressing the extension icon;
    1. Copy manifest.json into popup/build/manifest.json (overwrite the existing file);
    2. Change popup/build/manifest.json browser_action.default_popup to index.html;
    3. In firefox, reload the extension with popup/build/manifest.json instead;
    4. The extension works now, interesting;
    5. Open popup/build/index.html and change all occurrences of /static/ to /popup/build/static/;
    6. Reload the extension with manifest.json from the project's root;
    7. The extension works again:
      1. 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 the manifest.json that is loaded into firefox, which defines the root of the project;
      2. 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 point manifest.json to it. But that doesn't matter now, because we don't want that yet.
  8. Change the root manifest.json's browser_action.default_popup prop to index.html;
  9. 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
    
  10. Make build.sh executable: chmod +x build.sh;
  11. On the root of the project, run npm init -y;
  12. In package.json, remove the test script and add a build script with ./build.sh;
  13. Run npm run build on the root of the project;
  14. Reload the extension with popup/build/manifest.json;
  15. 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

  1. Create a new folder on the root of the project called contentScripts;
  2. cd into contentScripts, run npm init -y; npx -p typescript tsc --init to generate a package.json and a tsconfig.json file;
  3. We are going to use esbuild to compile our files, so we need to install it: npm install -D esbuild;
  4. Lets create our content script file. This file will parse the active tab inputs soon, but for now, it'll just print hello:
    1. Create a file named parseInputs.ts in contentScripts/src with the following content:
      1console.log("hello");
      
  5. In contentScripts/package.json, add the following build script esbuild src/parseInputs.ts --outfile=../popup/build/contentScripts/parseInputs.js:
    1. Notice that we are pointing to the popup/build/contentScripts/parseInputs.js file, this is because our manifest.json will be inside the popup/build folder;
    2. Yeah I know, it's a bit messy but it'll all come together nicely.
  6. 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
    
  7. Add our content script path on the root of manifest.json:
    1"content_scripts": [{
    2   "matches": ["http://*/*", "https://*/*"],
    3   "js": ["./contentScripts/parseInputs.js"]
    4}]
    
  8. Run npm run build on the root of the project;
  9. Reload the extension;
  10. Open a page in firefox and check the console, you'll see a hello message pop up;
  11. Our content script isn't doing anything exciting now, so let's add some triggers.

# Triggering the content script

  1. To be able to trigger our content script, we need something to trigger:
    1. Lets add the chrome type to both our projects so we can do some fun stuff. Run the following in both the popup and contentScripts folders npm install -D @types/chrome;
    2. Modify contentScripts/src/parseInputs.ts with the following code:
      1chrome.runtime.onMessage.addListener(
      2   (message, sender, response) => console.log({message, sender, response})
      3);
      
  2. Now we need to trigger this from within our popup:
    1. 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>
      
    2. Reload the extension;
    3. Open the popup and click the button;
    4. You'll see an object getting logged to the console.

# 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.

  1. 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);
    
  2. Reload the extension;
  3. Open the popup and click the button;
  4. You'll see all the inputs auto-filled with random data.

# Recap

That's it!

Hopefully it'll serve as a starting point for your future extensions.

# Props