Entries tagged ‘crx’

I just gave my talk on Chrome Extensions at the Scandinavian Web Developer Conference:

The code for the extensions can be downloaded from http://files.11born.net/swdc/. Enjoy!

In our previous installment we discussed how to create a simple extension for Chrome, using content scripts to interact with web pages visited by the user. Content scripts can be somewhat limiting however, so this time we’ll look into how we can run our extension code at all times, not just when a page is loaded. Coincidentally this is also a nice introduction to the general approach taken by the Chrome team when implementing extensions.

As you might know, each tab in the Chrome browser runs in an isolated process. This is done both for stability and security but it also means that Chrome is very good at running many small “web browsers”. Extensions run in isolated processes as well. In fact, you could say that extensions run inside hidden tabs. To put it differently, an extension is just a web page you can’t see.

Let that sink in a bit. Chrome extensions are just web pages. They have HTML, they have the DOM, they have JavaScript. They have all the browser features you have in a normal website!

Chrome has quite a few more tricks up its sleeve than content scripts. One of them is background pages. A background page is one of those web pages you can’t see. This is how you specify one in the manifest.json file:

{
  "name": "Hello World",
  "version": "2.0",
  "background_page": "background.html"
}

background.html is the path to the background page in the extension directory. It can be called anything really, but by convention it’s called background.html. There can be only one background page per extension and it’ll exist as long as the browser is open (and the extension remains installed). You can even open the Web Inspector for a background page: simply click on its name in the Extensions overview.

Now, if the background page were really just another web page that happens to be hidden, it’d be rather useless. Web pages can’t talk to each other, so what could the background page possibly do!? This is where we get to the really great part about the Chrome extensions: there’s a special set of JavaScript APIs that can be used to give your extension way more power. These APIs are all inside the window.chrome object and are accessible from background pages and content scripts.

For example, the chrome.extension.getViews() method returns a list of window objects of the various tabs the extension has access to. Let’s try to recreate the Hello World example with a background page.

background.html:

<script>
chrome.extension.getViews().forEach(function(view){
  view.alert("Hello world");
});
</script>

Since the background page is an HTML document, we do need to wrap our code inside a <script> tag. Note that we can use Array.prototype.forEach (introduced in JavaScript 1.6) here. One of the niceties of Chrome extensions: more powerful JavaScript!

When you install this extension it will alert “Hello World” straight away, since this code runs when the background page loads. That’s nice, but not really what we had in mind. And, its alerted only once, so apparently the extension only has access to itself. chrome.extension.getViews() (see docs) doesn’t return all tabs, it only returns those that the extension is loaded into through a content script, plus its background pages. Since we don’t want to use a content script this time around, let’s see if we can get access to the tabs that are currently open.

As the background page is still isolated from the rest of the browser, it needs to talk through the Chrome APIs to access other tabs. For this to work though we need to request the appropriate permissions. To access the tabs, we need to request the tabs permission. Update the manifest file:

{
  "name": "Hello World",
  "version": "2.0",
  "background_page": "background.html",
  "permissions": ["tabs"]
}

We can get all tabs in a given window using chrome.tabs.getAllInWindow() (see docs). This is an asynchronous method, so we need to pass it a callback. Also, we pass null as the first argument to get the tabs in the current window:

chrome.tabs.getAllInWindow(null, function(tabs){});

You’ll find that many API methods in Chrome are asynchronous.

Here’s our updated background.html, which alerts “Hello World” for each open tab and includes the URL of each tabs current page for good measure:

<script>
chrome.tabs.getAllInWindow(null, function(tabs){
  tabs.forEach(function(tab){
    alert("Hello world\n" + tab.url);
  });
});
</script>

It’d be extra nice if we could run the alert() from within the tab itself. To do so we can use chrome.tabs.executeScript (see docs), which executes a script (either from a string or a file) inside a given tab:

<script>
chrome.tabs.getAllInWindow(null, function(tabs){
  tabs.forEach(function(tab){
    chrome.tabs.executeScript(tab.id, { code: "alert('Hello World')" });
  });
});
</script>

This, however, doesn’t work! It turns out that we don’t have permission to execute scripts inside the tab. If you open the Web Inspector for the background page, you’ll notice this:

Error during tabs.executeScript: Cannot access contents of url "http://supercollider.dk/blog". Extension manifest must request permission to access this host.

Remember how with the content script we had to specify which URLs it matched? We’ll do the same here, but add the URLs to the permissions instead:

{
  "name": "Hello World",
  "version": "2.0",
  "background_page": "background.html",
  "permissions": ["tabs", "http://*/*"]
}

Now when the alert comes up, Chrome focuses the tab the alert originated from. Success! (You might still see some error logs for https:// pages and the internal Chrome pages, these were not part of our permissions.)

This is all good, but it only works when the extension loads. How can we know when a tab loads? Well, there’s the chrome.tabs.onUpdated event (see docs) which fires when a tab starts loading and when the loading has completed:

<script>
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo, tab){
  if(changeInfo.status == "loading"){
    chrome.tabs.executeScript(tabId, { code: "alert('Hello World')" });
  }
});
</script>

You can listen to Chrome API events by calling addListener on the event object. You pass a callback function, which is called with specific arguments depending on the event. As you can see this is not the typical DOM event model, it’s better geared to the API use cases.

In this case tabId is the identifier for the tab, changeInfo contains some information about the update, and tab is a reference to the tab object itself (just like the tabs returned by chrome.tabs.getAllInWindow()).

At this point we’ve reproduced the original Hello World example, but now through a background page. In our next installment we’ll look more closely at the security constraints on Chrome extensions.

Earlier this year Google’s Chrome browser landed official support for extensions. This is incredibly exciting, not only because extending browsers is cool, but because Chrome’s extensions are fully based on open web standards. And who knows about open web standards? Yup, web hackers.

In other words, if you can build a website, you can build a Chrome extension. They don’t consist of anything else but HTML, CSS, JavaScript, HTML5ish features such as localStorage and <canvas>, and some APIs (implemented in JavaScript) to interact with the browser itself. It’s much easier than developing extensions for Firefox, which has a complicated setup and configuration and the XUL language to work with Firefox’s user interface. (Admittedly, Mozilla is trying to rectify this through Jetpack, which I haven’t looked into closely, but is not included by default in Firefox.) Another advantage of Chrome over Firefox is that Chrome can reload extensions without restarting the browser, which is a fair bit nicer!

Let’s see about building a very simple extension that will alert “Hello world” for every page you load. This will help cover the basics of extension development. First, open up the Extensions overview, which is under the Tools menu in the toolbar. This page shows the extensions you’ve already installed. It also has a Developer mode, which you can select by clicking on the “Developer mode” link in the top right. This should show a few extra options: “Load unpacked extension…”, “Pack extension…” and “Update extensions now”.

We’ll focus on “Load unpacked extension…” first. If you click on it a folder selector dialog will come up. It only lets you select folders, which is great because a Chrome extension is nothing more than a folder with a few files! In the folder selector, go to your Documents folder and open it. Chrome will try to open the Documents folder as an extension, which, of course, is silly. Luckily it finds out quickly and gives this error message:

Could not load extension from ‘/Users/mark/Documents’. Manifest file is missing or unreadable.

This then brings us to what a Chrome extension really is: a manifest file inside a folder. More specifically, a file called manifest.json, that contains a valid JSON object describing your extension. At the very minimum, a manifest file looks like this:

{
  "name": "Hello World",
  "version": "1.0"
}

Create a hello_world directory somewhere on your computer, and place the above in manifest.json (inside that directory). Now in Chrome, click on “Load unpacked extension…” again and open the hello_world directory you just created. You’ll see how the extension is added to the list of installed extensions. Congrats, you just build your first Chrome extension!

Uhm, okay, technically the extension doesn’t do anything yet. There are a few different ways an extension can interact with the browser, but for now we’ll just look at content scripts. These allow you to run JavaScript for pages visited. If you’ve ever written a user script for Greasemonkey you’ll be familiar with content scripts, since they do pretty much the same thing. In fact, Aaron Boodman, who originally started Greasemonkey, is a main driving force behind Chrome extensions.

An extension can define multiple content scripts, each of which can load multiple JavaScript files. First create alert.js in the hello_world folder, it should contain:

alert("Hello world");

To load this for all web pages we use the following manifest:

{
  "name": "Hello World",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["http://*/*"],
      "js": ["alert.js"]
    }
  ]
}

content_scripts is an array, each item being a declaration of the actual content script. matches lets you specify on which pages you want your content script to work, by providing a set of match patterns. js specifies which JavaScript files (included in your extension folder) to load if the page matches. (And yes, technically speaking this content script only works with http:// sites, not https://.)

In the Extensions overview, click the “Reload” link underneath the “Hello World” extension. This will reload the extension. Then go to any website and you should see a “Hello World” alert as it loads.

You can also specify CSS files that should be included on the matched pages. Create red.css (again in the hello_world folder) containing the following:

html, body { background: red !important; background-image: none !important; }

Change the manifest to:

{
  "name": "Hello World",
  "version": "1.0",
  "content_scripts": [
    {
      "matches": ["http://*/*"],
      "css": ["red.css"],
      "js": ["alert.js"]
    }
  ]
}

Save and reload the extension. Again go to any webpage and it should now have a red background color.

Admittedly this example extension is rather contrived. In future articles we’ll build more advanced extensions, look at different UI concepts supported by Chrome extensions, Chrome’s security model and how you can publish your extensions for others to use. In the meantime, have a look at the official documentation for Chrome extensions.

As a final note, I’ll be speaking about Chrome Extensions on the Scandinavian Web Developer Conference in Stockholm on June 2nd. Go check it out!

On June 2nd and 3rd Stockholm will be home to the Scandinavian Web Developer Conference. Day one covers front-end & back-end development, day two covers the mobile web. I’m giving a talk titled Chrome: Browser Extensions for Web Hackers. There’s a 20% discount until the end of the month, so check out the insanely great lineup and get your ticket!

Chrome Extensions files (those with the .crx extension) are, in essence, signed ZIP files. The extension data is signed with your private key, while the public key is included in the .crx. A hash of this public key is used as the extension identifier when you install the extension. This identifier is important because you need it if you want to generate an update manifest. (Of course, if you’re hosting your extension on the extension gallery you won’t have to worry about this.)

Let’s say you’re hosting your extension yourself and you want to build the .crx file and the update manifest automatically. You could install the extension first and copy the identifier from the extensions list, but you can also calculate the identifier.

Chrome Extension developer Erik Kay explains the format on Stack Overflow:

To be precise, it’s the first 128 bits of the SHA256 of an RSA public key encoded in base 16.

Another random bit of trivia is that the encoding uses a-p instead of 0-9a-f. The reason is that leading numeric characters in the host field of an origin can wind up being treated as potential IP addresses by Chrome. We refer to it internally as “mpdecimal” after the guy who came up with it.

Here’s a short Ruby script to do exactly this:

require "openssl"
require "digest/sha2"

def pkey_to_id(pkey)
  # Key algorithm, found in <http://github.com/Constellation/crxmake>.
  algo = %w(30 81 9F 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00 03 81 8D 00).map{ |s| s.hex }.pack("C*")
  # Calculate public key, get hex hash of first 128 bits / 32 characters
  hash = Digest::SHA256.hexdigest(algo + OpenSSL::PKey::RSA.new(pkey).public_key.to_der)[0...32]
  # Shift hex from 0-9a-f to a-p
  hash.unpack("C*").map{ |c| c < 97 ? c + 49 : c + 10 }.pack("C*")
end

For example the extension id for this private key:

-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANRvspoXYkKCTQple
mUX/Umh8zgmmz7xJ/oUiQSca7vd/3eDvLRBTOy7o4NwRmIzDHr/AbDkVRzXDd6ale
oGBj8ABvAf+3XAO7KdFsjIxmSfUI5M7F8EsqTDT+AR+YGiGOrF/9s4ganiqGSyIuk
Fe1UWrxlUBHKcazzgSdpsOZ2pAgMBAAECgYBNHIJ7NpO/SqcGaBGGkq+pQ7USo8jk
jwsQ1tVprBHbLtklm9cqoy12HSJceqvBx3/3QYtul2NhxZpOPFTAjxFCsaBP5I8rn
fgFzdSvKZXtZmQ7DsYaglclYqSwhKtKM9j5xVbqEh4r0LXYAvM1dlRSJQrFtqhLiI
Nc5jyWL2HHxQJBAPMWXG8BOwo0yM3eb27Foz6jjO92iQ+pHgWNFoW+QZIhVUGHkIH
ESl94UF86k0uurVBO22oHNoeLAwZ1XLIulN8CQQDfuIbPu4WAGyshyT/rdk/MuRXd
gcnbUkwKbyYfiKsSLpmMryM9ugFqyO+AQNIGA1xQHEP3znxUE/Y1tyE74NZ3AkBih
dOc4gDN2Cry1Y6QdOX/A0ah34cZo8+ZLF/OgRgOZBgr4Qf+sFH8c8UPc6wzZm60N+
HSDW5abUsimPqi9SI9AkEAzhbaeXqxXHWqohEWRP5UPK8zqT3qiZOiYOpLIDlx/en
XoXWk7TPwIkK//lG4J7nozBN9uUYJ2hoZcRomD1bruQJBAI49v2hvt24JV870F8Lf
oEReK/26DpyMFfH7a4I4O2JkGX79M/aVXBbSbNlnl01Esoxsx9kYzksP4CpR7CdYH
og=
-----END PRIVATE KEY-----

is cigbjabahnfnnmplhmjeolnhobhfjggp.

The script uses OpenSSL::PKey::RSA.new(pkey).public_key to calculate the public key for the given private key. It then adds the key algorithm (a subject of which I understand not much more than what is described on Wikipedia) and takes the SHA256 hash in hexadecimal form. Of this hash it takes the first 32 characters, corresponding to the first 128 bits of the SHA256 hash.

Finally we need to shift from 0-9a-f to a-p. Technically this means that the extension id is not in hexadecimal format, but it is still in base 16. hash.unpack("C*") gives an array of ASCII character codes for the string. Each character code in the original 0-9a-f format is mapped to the corresponding code in the a-p format. We can exploit the ASCII table for this. Digits are character codes 48 to 57, while lowercase characters start at 97. Therefore, we can simply add 49 (the difference between 97 and 48) to each number and, since we’re shifting by ten characters, 10 to the letters a-f. Finally we pack("C*") the character codes back into the extension id.

I’ll be writing more about Chrome Extensions here, and am giving a talk on the subject at the Scandinavian Web Developer Conference at the end of May in Stockholm. I’ll also give a brief Chrome Extensions Primer at the Øresund JavaScript Meetup in Malmö this Wednesday.