LJ Archive

At the Forge

Chrome Extensions

Reuven M. Lerner

Issue #223, November 2012

Create applications inside the Chrome browser with standard Web technologies: HTML, CSS and JavaScript.

Back when Netscape was a rising star in the high-tech world, cofounder Marc Andreessen announced that the browser was a new form of operating system, within which people could create applications. Rather than writing apps for Windows, or the Macintosh, or even Linux (a laughable idea back then), we would write them for the browser. This seemed like a far-fetched idea at the time, but it obviously has become the case. Today, it is the norm to speak of a “Web application”, meaning something that is delivered via the browser, but whose code sits on a server. This is what I think of when someone says “Web application”, and it has been a while since I really thought seriously about even writing a desktop application.

That said, there's certainly an advantage to working with desktop applications. They work more smoothly with other applications; they can interact with the filesystem, and they just have a more natural look and feel. This is changing, especially given the capabilities that HTML5 brings to the table and the ways that browsers are becoming integrated into the overall user experience, rather than being one of many applications running on the computer.

What I really like is the relative simplicity of creating a Web application, including using the technologies that are the Web's bread and butter—HTML, CSS and JavaScript—and which I use, at least for client-side development, on a day-to-day basis.

Firefox has offered developers the chance to write extensions for a long time. However, I must admit that I wasn't thrilled with the idea of learning an entirely new language and paradigm (Mozilla's XUL). The Greasemonkey extension for Firefox has long been a favorite of mine, making it possible for me to make client-side changes and customizations to Web sites of all sorts. But, it wasn't completely integrated into the browser, and it required installation and configuration beyond what most people are willing to accept.

Extensions in Google Chrome (or the open-source Chromium), by contrast, use Web technologies and are built in to the browser, making it truly possible to extend the browser in a number of different ways by loading packages of HTML, CSS and JavaScript.

This month, I look at the different types of extensions you can write with Chrome and consider when it's better to write an extension than a Web application, as well as show how to develop a simple extension of your own.

Creating an Extension

As I mentioned previously, a Chrome extension is a combination of HTML, CSS and JavaScript. There are different types of extensions; right now, let's concentrate on a browser extension, which puts an icon in the top-right corner of the browser, which produces a pop-up and also can interact with the contents of the browser window.

Creating an extension is actually quite simple and can be done from within any directory on your computer. Create a new directory, and in it, create a file called manifest.json. As the file extension indicates, this file (which gives Chrome information about your extension) is written in JSON (JavaScript object notation), which is natural and easy to pick up by anyone familiar with JavaScript. The manifest tells Chrome how to load the extension, what permissions it should have and what elements should be displayed within the browser window.

For example, here is a simple extension manifest for the extension I'm building for this article:

{
  "name": "ATF sample extension",
  "version": "1.0",
  "manifest_version": 2,
  "description": "Description of my ATF sample extension",
  "browser_action": {
    "default_icon": "atf.png"
  }
}

As you can see, manifest.json contains a number of name-value pairs, as you would expect from a JSON or JavaScript object. The names are set by the Chrome extension standard document, and although most of the values are strings, there are cases when they will contain numbers (for example, the manifest_version), objects (for example, the browser_action) or even arrays.

According to the standard document (see Resources), the only required fields in manifest.json are “name” (containing the extension name) and “version” (indicating the extension version). However, Google also says that as of Chrome 18, “developers should specify 2” for the version number, and that seems like a reasonable idea to me.

Because this extension is a browser action, you need to specify this name-value pair, stating “browser_action” as the name and a JSON object as its value. That value, which can (and will) contain several additional name-value pairs, currently has just one, namely “default_icon”, which indicates what icon should be displayed in Chrome's toolbar to the right of the address bar. default_icon is a string containing a filename, which should be a PNG graphic of the correct size (19x19) that represents your extension.

Once you have created manifest.json, create (or download) a 19x19 PNG icon, and put it inside the extension folder with the filename atf.png. With the extension directory, manifest.json and icon, you're now ready to load the extension into Chrome. Open your browser to chrome://chrome/extensions/—a special URL for extension management—and make sure the “developer mode” check box is set, so that you can load extensions without Google's permission and from your local disk. Once you have done that, a “Load unpacked extension” button should be available. Click on that, and then use the file-selection dialog to select the extension directory. (Don't select a file within the directory, but rather the directory itself.)

Once you have done this, your extension should show up in Chrome with the extension name, description and version number. If and when you update the extension, you can tell Chrome to reload it by clicking the reload link under the extension name.

Make the Extension Useful

Now that you've created a basic extension, let's try to make it useful. Extensions can do any number of things, from providing snapshots of other sites, to interacting with the overall browser environment, to interacting with the page currently being viewed.

Let's first create a pop-up. The manifest.json file already indicates what popup_icon will be. Let's add to that an HTML file, which then will be displayed when you click on your extension's icon. To do this, just set the “default_popup” value within manifest.json to the name of an HTML file within the extension directory. Then, create an HTML file with that name. For example:

{
    "name": "ATF sample extension",
    "version": "1.1",
    "manifest_version": 2,
    "description": "Description of my ATF sample extension",
    "browser_action": {
        "default_icon": "icon.png",
        "default_popup": "popup.html"
    }
}

Notice I also have increased the version number of the extension to make sure I can keep track of which version was created when. (If you're using a version-control system, such as Git, you can keep a tag or a commit note indicating when you updated the version number.)

Now that I've told Chrome that it should load popup.html whenever I click on the extension's icon, I really should create an HTML file named popup.html. Here's a simple one that you can include:


<!doctype html>
<html>
  <head>
    <title>ATF extension</title>
  </head>
  <body>
    <h1>Extension headline</h1>
    <p>I am an extension paragraph.</p>
  </body>
</html>

Now, if this file looks simple to you, that's the point. Extensions can become complex, but they don't have to be, particularly if you're doing something simple. Save the new version of manifest.json and popup.html inside the extension directory, reload the extension, and click on the extension icon. You should see the text pop up, albeit in an ugly black-and-white window.

If you want to add some nice styling to popup.html, you can do so with CSS. Create a file, popup.css, and place it (of course) in the extension directory:

h1 {
   color: blue;
}

Now, add a link to that stylesheet inside popup.html:


<!doctype html>
<html>
  <head>
    <title>ATF extension</title>
    <link rel="stylesheet" type="text/css" href="popup.css">
  </head>
  <body>
    <h1>Extension headline</h1>
    <p>I am an extension paragraph.</p>
  </body>
</html>

Sure enough, the h1 headline is now colored blue.

JavaScript in Extensions

All of this seems pretty straightforward—and it is. But if you're thinking that you can just stick some JavaScript inside the HTML file and have it execute, as would be the case in a normal HTML file...well, that's where things become a bit tricky and different. For security reasons (which I admittedly don't quite understand), JavaScript needs to be in a separate file, referenced from the HTML file. In such a case, the file looks like this:


<!doctype html>
<html>
  <head>
    <title>ATF extension</title>
    <script src="popup.js"></script>
    <link rel="stylesheet" type="text/css" href="popup.css">
  </head>
  <body>
    <h1>Extension headline</h1>
    <p>I am an extension paragraph.</p>
  </body>
</html>

What can be in your popup.js? Anything you want, actually. Here's a really simple (and annoying!) one:

alert("You have loaded the popup!");

Now clicking on the extension icon will produce a JavaScript alert. Once you have dismissed it by clicking the OK button, you will get your beautifully formatted HTML page, in popup.html.

What can you do within popup.js? Truth be told, you can do just about anything you want—modify text, retrieve content from other sites, calculate things and send information elsewhere. If you can do it in JavaScript, the odds are that you can do it within the browser. You even can use a library, such as jQuery, so long as your copy of jQuery is referenced and loaded from within the extension directory.

So, let's try something a bit bolder. Let's retrieve data from a Web site and insert it into the pop-up window, using jQuery. In order to do this, you'll need to modify your popup.html a bit:


<!doctype html>
<html>
  <head>
    <title>ATF extension</title>
    <script src="jquery.js"></script>
    <script src="popup.js"></script>
    <link rel="stylesheet" type="text/css" href="popup.css">
  </head>
  <body>
    <h1>Extension headline</h1>
    <p id="paragraph">I am an extension paragraph.</p>
  </body>
</html>

Notice how I've added the line referencing jquery.js in the extension directory. You also can reference one of the copies that Google or another company has put on-line, in order to improve caching and download speeds. I've also given an ID attribute of “paragraph” to the “p” tag in the HTML, which will make it easier to grab the paragraph and do something with it.

The biggest difference will be in popup.js. No longer will you just have a call to alert() in there. Instead, you'll actually use jQuery's Ajax facilities to retrieve information from a Web site and stick it into the pop-up window. You're going to do it in an ugly, brute-force way here, in order to see the results more obviously, but you easily can imagine an example that would go through the contents of a Web site more gracefully (or, perhaps, its RSS/Atom feed), picking out information that is of use and then displaying it. For example, you could create a browser extension that displays the current weather.

For this example, let's just have the browser go to a Web site, retrieve its contents and stick the raw content into the pop-up's “p” tag. Here's the updated popup.js:

alert("Popup -- before");

function showText(data) {
    $("#paragraph").text(data);
};

$.get('http://lerner.co.il/', showText);

alert("Popup -- after");

Now, the reason I put in the “before” and “after” alerts is not because I enjoy annoying my users, but because I find it instructive to see when things happen in the asynchronous Ajax world. (Hint: remove the calls to “alert” before you unleash this amazing extension on your users.) You define a function, showText, which adheres to jQuery's definition of what a function should look like, namely that it accepts (at least) one parameter, named “data”, which contains the contents of the URL you tried to retrieve. That's all you're going to do here, using the “text” method to stick in the HTML source. That means the end user will see the source; if you want something a bit more aesthetic, you can use the html() method rather than the text() method.

But, showText isn't invoked directly. Rather, it's invoked as a callback function, executing when your invocation of $.get(), a function that executes in the background (that is, asynchronously), returns the contents of the Web site. This could take one second or ten, but in most cases, it'll be pretty fast. However, the callback almost certainly will be invoked only after your second call to alert(). That is, you'll see the first alert() call, the second alert() call and then a change in the contents of the paragraph. Such event-based coding is the norm in the JavaScript world, and it can take a little time to get used to it. Notice that the second parameter is showText, the function itself, which then is invoked after a successful Ajax call.

If you now reload the browser extension and click on the button, you'll find...that nothing really happens. That is, you get the first and second calls to alert, but the paragraph doesn't change its contents. This is because you haven't told Chrome that it's okay to retrieve data from lerner.co.il, or from any other URL. Because retrieving data from an external URL is a potentially dangerous event, exposing you to things in the outside world, you need to allow its use explicitly. This is done by returning to manifest.json and adding a “permissions” key:

{
    "name": "ATF sample extension",
    "version": "1.1",
    "manifest_version": 2,
    "description": "Description of my ATF sample extension",
    "browser_action": {
        "default_icon": "icon.png",
        "default_popup": "popup.html"
    },
    "permissions": [
        "http://lerner.co.il/"
    ]
}

The “permissions” key can contain a large variety of items, from URLs (as in this case) to wild-card matches, to keywords that Google has defined. For example, if you'll want your extension to use such HTML5 abilities as geolocation or local storage, you'll need to indicate that here.

Now, all of this is nice if you want to modify popup.html, namely the pop-up that you get with your browser extension. What if you actually want to interact with the page itself, either reading from it or writing to it? The answer is that you can do this by writing not a “browser action”, as it is known in the Chrome world, but a “content script”.

Now, a content script requires a different manifest.json, but it also raises questions about how you can interact with a page that itself might have some JavaScript executing. The answer is that Chrome provides an interesting facility known as “isolated worlds”, in which two separate JavaScript environments—one on the page and the other in the browser—can operate independently, each with its own JavaScript library (and version of jQuery, if necessary), but interact simultaneously with the DOM and the contents of the page. Such isolation not only means that your content script can play with the contents of the page in a number of ways without worrying about interfering with existing JavaScript, but also that the page cannot “break out” of its sandbox, infecting or otherwise affecting the browser itself.

I should note that although I haven't used them in this article's examples, Chrome provides a wide variety of JavaScript methods and functionality through the “chrome” object, which you can access via the permissions key in manifest.json. Such methods give you access to (for example) the current tabs and windows, really allowing you to control and use the browser as an application platform, rather than just a mechanism for displaying content.

Conclusion

Chrome was designed to use Web technologies, and nowhere is that more obvious than the extension mechanism, which uses a combination of HTML, CSS and JavaScript to produce new user experiences. Now, browser extensions aren't a panacea; they break the idea that the Web is browser-independent and that everything can be downloaded on demand from a server. But if your entire organization will be using Chrome, or if you're looking for something that interacts with existing pages, or if you want to add capabilities to your browser, Chrome's extension mechanism makes it easy to experiment and try new ideas.

Reuven M. Lerner is a longtime Web developer, consultant and trainer. He is also finishing a PhD in learning sciences at Northwestern University. His latest project, SaveMyWebApp.com, went live this spring. Reuven lives with his wife and children in Modi'in, Israel. You can reach him at reuven@lerner.co.il.

LJ Archive