Archive for April, 2010
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.
