Okay, let's write a Chrome extension. I've done this before, but couldn't really remember how, and since "manifest version 3" for Chrome extensions is some sort of big deal that changes all the rules, I might as well start over.

Google does have a "Hello World" tutorial here, but it's important to note that it's what at least this answer at SO calls a popup extension. (It also has some broken links to, eg, its sample extension icon, but you can get it all from the GitHub repo they link to.)

Getting the devtools window up for a popup extension, like the one you create in this "hello, world!" tutorial, is kind of a pain. You seem to have to first invoke your extension to show the popup, then right-click the popup, and finally select Inspect, which isn't super-easy to do. In fact, getting the correct dev tools window open is continually a source of pain for me. Popup needs inspect, an "action-only" background process extension needs it opened from the chrome://extensions page, and a "normal" extension that executes in a tab's context needs the "conventional" dev tools to that tab.

Let's take a closer look.

Create an action-only extension

If you want to simply perform an action (aka, "run a script") when the extension's taskbar button is clicked, you follow this example from Google's docs. Though it's "action only", the info for what to execute ยญdoesn't live in the action object but in the background prop. Again, I'm not really intending this to be thought of as a background task in the traditional sense; I just want something to happen on user-intervention (here, the extension's button on the top of Chrome's, um, chrome is clicked), but, you know, when in Mountain View...

An action-only extension reduces to this:

manifest.json

{
  "manifest_version": 3,
  "name": "Click action",
  "description": "Base Level Extension",
  "version": "1.0",
  "action": {
    "default_icon": "hello_extensions.png"
  },
  "background": {
    "service_worker": "background.js"
  },
}

Then simply add an event listener for the extension's "button" to your background.js file (or whatever file name you inserted for service_worker) like this:

function go(tab) {
    // do stuff. But not like `alert` b/c it's not in a page context.
}

chrome.action.onClicked.addListener(go);

To be clear, that's all you need to have for an extension, those two files, manifest.json and background.js (or, again, whatever name you use in the service_worker value) and maybe an icon, here hello_extensions.png stolen from Google's Hello World tutorial repo, all in a folder loaded via the extensions page with developer mode turned on.

In this case, without a popup, you open devtools by clicking the link within Inspect views service worker on the Extensions page (that is, what's displayed from the chrome://extensions/ pseudo-url), which a little closer to what's described for opening dev tools for "Background Scripts" in the SO question linked above. This is a lot easier (imo) than the popup version. Wish they had devtools available for popups from the extensions page as well.

That said, once you have it up, though there is a "refresh" button for extensions, you can make edits to the javascript file and have it refresh during your next action run (afaict), so you're up and running for a while once the tools are open.


Manipulating the tab

Okay, chances are pretty good that you want to interact with the content of the page. I don't, but let me point you in the right direction. You're going to want to get used to your new friend, chrome.scripting and the "scripting" and "activeTab" permissions.

That might be most easily done by reviewing Google's Page Redder extension sample over here on GitHub.

In brief, the code is this:

service-worker.js

function reddenPage() {
  document.body.style.backgroundColor = 'red';
}

chrome.action.onClicked.addListener((tab) => {
  if (!tab.url.includes('chrome://')) {
    chrome.scripting.executeScript({
      target: { tabId: tab.id },
      function: reddenPage
    });
  }
});

manifest.json

{
  "name": "Page Redder",
  "action": {},
  "manifest_version": 3,
  "version": "0.1",
  "description": "Turns the page red when you click the icon",
  "permissions": ["activeTab", "scripting"],
  "background": {
    "service_worker": "service-worker.js"
  }
}

Now, if you want to access in-scope dev tools, you need to open them as you normally would from that tab with F12.


Allowing the user to select options

The next thing I wanted was to add user-defined options. Interesting that it requires storage permissions. I hadn't initially wanted to add that, since the idea was that fewer permission types means a more easily trusted extension, but if I have to to have options, that frees up all sorts of other stuff, like persistent variables. I would feel badly about doing this, but if Google is going to make me...

(Not that I'd do anything super-nefarious, but maybe I'd store the last time I showed an advertisement page or might store than that've paid a subscription with some sort of key or something. My point is that the possibilities are many, and before I thought I wouldn't allow myself to consider them.)

Anyhow... I'm not showing a full page UI (though that's an option too), so I'd include a new UI file (options.html in this setup) and use this:

Anย embedded optionsย page allows users to adjust extension options without navigating away from the extensions management page inside an embedded box. To declare embedded options, register the HTML file under theย "options_ui"ย field in the extension manifest, with theย "open_in_tab"ย key set toย false.

manifest.json:

{
  "name": "My extension",
  ...
  "options_ui": {
    "page": "options.html",
    "open_in_tab": false
  },
  ...
}

Asking for money

I haven't really given this a good look yet, but I did learn that, unlike Safari on Mac, Google no longer lets you charge for the installation of an extension from their store. There's what appears to be an amazingly well-written howto for building a Stripe-integrated license checker for free at Cloudflare at this blog post, "How to create a paid Chrome browser extension" from earlier this year.

Labels: , ,