Hints and tips you need to know before starting to write a simple Chrome Extension

Have you ever wanted to stop an animated GIF? Like, when you are reading an article and the author has embedded some of those things into it which keep flashing and looping when you’re trying to concentrate on the text? It happens to me and it happens quite a lot recently which means that I had to do something about it, specifically a Chrome Extension.

I would like to share my experience building it. Keep in mind that this is not necessarily a guide or an how to build your first Chrome extensions: there are plenty of them already and I consider the official Google guide on the subject pretty good.

You can find the source code of my extension (Gifnope) on Github.

Update from the future self: this is a very old article and probably what's in here is not really relevant anymore.

So, you want to write a Chrome Extension…

First of all, create a directory where to put all the assets needed for your extension to run. Everything that we are going to do from now on, will happen in that directory.

Create a file named manifest.json. This file defines your extension to Chrome. The very basic skeleton of this file is something like this:

{
  "manifest_version": 2,

  "name": "Gifnope",
  "description": "A description of your intent",
  "version": "1.0"
}

Now a bit of theory: the user interface of my extension is just an additional option that I added to the contextual menu of every gif image on the page. To accomplish this I needed to do something in background, at the "browser level", so to speak. On the other hand, the code bound to the menu option ("Pause animation") would have to work on the actual content the user is looking at. This means that I also needed to work at the "content level".

Those needs unveil the need for two (categories of) scripts when writing an extension like this one. One is the "background script" and the other one is the "content script". They run in completely different context and environments and — most importantly — only the content script has access to the content of the page (and its DOM). The background script is used to bound your application UI to the the UI of the browser: you can for example add options to the context menu, or add icons next to the url bar (those are called "actions"). More thoroughly, the "background" concept extends to the "background pages", which is the name used by Chrome to identify the popups you often see when you click on one of your action buttons. My extension doesn’t use any of those, so let’s keep things simple.

We will need both scripts, which means that we need to explicitly declare both of them inside the manifest file:

...

"content_scripts": [{
  "matches": ["*://*/*"],
  "js": ["content.js"]
}],

"background": {
  "scripts": ["background.js"]
}

...

In plain english, what I can read from this declaration is in the following sentence:

Dear Chrome, I’ll use a content script which is called content.js and it needs to be loaded and run inside all the pages which URL matches :///* — which is just a fancy way to say "in all pages". I’ll also use a background script, and its name is background.js.

You may have noticed that all the scripts declaration accepts an array as an input; you could therefore organise a complex extension in a modular way.

I’d like to repeat an important general concept, about these scripts: they live separately and never touch each other; their scopes are sealed and any global symbol you may use inside one of them, will never leak somewhere else, don’t worry. This concept goes a little further with the content script: not only its global scope is sealed and safe (which means that its symbols won’t be accessible from a user script inside the page, and vice-versa), but also the event handlers. If you set an event handler on the window load event, for example, that event handler will run not matter what an user script will do. They will be different handlers, run in different worlds (sic, on Chrome’s docs). The important side effect of this behaviour is that you cannot preventDefault or stopPropagation from an event handler inside your content script: if there are event handlers defined in the user script, they will be run anyway.

Another important thing to keep in mind about the separation of contexts in the content and user scripts, is that you can safely use a version X of jQuery in your content script, while the user script would use version Y. No clashes, no damage..

So, yes, background and content script never touch each other. And yet, after 15 minutes that you dove into these world, you find yourself with the need to make them communicate.

As it turned out, you can quite easily communicate with your background script from the content script (and vice-versa) or even from an extension to another one! All that is needed is the (pretty) familiar concept of message passing APIs: you send a message, and there will be probably a potential receiver listening for it (your content/background script or another extension out there). Of course, all these message passing needs to be handled asynchronously.

How do you send a message to the extension script which resides inside a content in browser tab? Easy: send a message to that tab:

chrome.tabs.sendMessage(tab.id, { the object, content of the message }, callback);

yeah, it’s true, you need to have the id of the tab. This is generally a piece of information which is passed from Chrome itself to the callback you set on it’s UI element you are tinkering with. For example, when you add an item to the context menu…

var ctxMenuPauseId = chrome.contextMenus.create({
  title: "Pause animation",
  contexts: ["image"],
  onclick: pauseGif,
});

… your pauseGif callback will receive a tab object as a parameter, which is the tab the user currently is in.

In this particular case we have added the context menu option only on items that Chrome recognises as "images". But… we cannot ask Chrome to add this option on GIFs only. Bummer. We need help for that, and unfortunately this seems really a bit of an dirty hack to me: we’ll intercept the mousedown event from the content script (which is the only one with access to the DOM, remember?), check and see which element has been "clicked" and if it’s and image and not a gif we’ll send a message to our background script which in turn will disable the menu option. This is quick enough so that the user we’ll see a greyed and disabled "Pause animation" option in the menu.

chrome.extension.sendMessage({
  sender: "GIFNOPE",
  command: "DISABLE_MENU",
});

There are a couple of noticeable pieces of information here: first of all, we are using a different API to send the message, and this one doesn’t require a tab to be specified also because — well– our background script is not running in any tab. The second information is the format of the message, which is completely our own decision: I chose to use sender as the key to understand if the message if for my extension (remember that any extention can fire messages) and command one to specify what the background script needs to do upon receiving the message.

So now you have a context menu option which will send a message to the content script which will in turn pause the related GIF. Time to test the extension.

Install Chrome’s Apps and Extensions Developer Tool. This is a quite recent addition to the mix and using it is totally optional but quite handy. It is an App, not an extension: once installed you’ll find it among your Chrome’s Apps.

Once you run it, select the "Extensions" tab and use the "Load unpacked…" button. Your extension still lies on a directory, not packed into any particular format: select the dir and if everything is fine, the extension will load and you’ll be given some options for it: the one that you’ll use the most will be of course the "Reload" one.

First thing to know: if you use a console.log inside your background script you’ll not see anything where you expect it to show up. And again, this is because the background script has no access to the content page, where the devtools and its console resides. The console.log messages will show in the background page console: to access it, just use the "background page" link in the debug tool application.

Once everything is working perfectly, prepare to publish your application on the Google Chrome App store. You need three things:

  • a 128x128 icon picture of your app (you probably have it already)
  • a screenshot which represent what your extension is supposed to do (it is mandatory that the format of this picture is 1280x800px or 640x400)
  • 5$

The (one time) five dollars fee will buy you the "developer status" in the google world and the ability to publish on the store. Once you pay with a credit card (with Google wallet) the authorization will be granted right away.

Fill in the form with the metadata for your extension and publish it. You’ll need to wait another 10/15 minutes before Google will process the request.

After that your extension will be available to the world.


Written on April 27, 2015 by Claudio Cicali.

Originally published on Medium