Differences between Firefox and Chrome Extensions

This entry was written during my time at IBM for a Firefox extension I had developed related to bookmarks

I’ve been using Google Chrome more often lately. I still prefer Firefox in a lot of ways, but you can’t beat the speed of Chrome.

One thing I miss in chrome is a lot of my extensions, including my project. I decided to take a look at how hard (if even possible) it would be to port my project to Chrome. It turns out, not as bad as I thought (though certainly not trivial).

Chrome’s approach to extensions is fundamentally different than Firefox. In Firefox an extension has pretty much full access to the entire browser – you can monitor and change every request the browser makes, you can modify the way tabs work, heck you can build the best javascript debugger on the planet. Chrome extensions are far more limited in what they can do. They run in their own sandbox, apart from the rest of the browser. You’re limited to a button with a dropdown (popup) display, an i con on the URL bar (also with a potential popup), adding items to context menus, and that’s about it. While you can get access to the websites the user accesses, and even modify them, you can’t fundamentally alter the browsers UI or behavior the way you can do with Firefox. This is not necessarily a bad thing – simplicity has it’s virtues – but it makes a big difference in the approach you take to the extensions.

Despite the differences in approach, the differences in writing an extension for one app or the other is not quite as big as I expected. Firefox extensions (for the most part) have their UI written in XUL(XML User Language), are scripted with JavaScript, and is themed with CSS. Chrome extensions have their UI written in HTML, are scripted with JavaScript, and themed with CSS. Well, hey, we have 2 out of 3, right? And while XUL and HTML are pretty different, they’re not that different, especially when talking into account the HTML5 features chrome supports.

Writing an extension for Chrome is very similar to writing a website. In fact, it’s pretty much identical. The only difference is that you have access to the chrome.* API, giving you some control over browser tabs, windows, history, etc. The downside is that you don’t have a rich featureset of widgets like you do with firefox ( trees, autocomplete, date controls, etc). On the other hand however, you can use javascript libraries like jquery or dojo, and not pollute or mess up the browser namespace. If you’re experienced with developing web applications, then it’s pretty easy to dive into creating an extension for chrome. Writing an extension for Firefox takes a bit more getting used to, learning a new UI language (similar to, but still different from HTML), new set of widgets, new set of quirks (although projects like jetpack are making it easier to write Firefox extensions).

Here’s a list of things I’ve found different going through and porting my project.

No for each loop

One of the nice things about writing a Firefox or Chrome extension is that both browsers are fairly standards compliant, and both support modern versions of javascript ( Version 1.8.1 as of Firefox 3.5. Chrome supports ECMA Script Edition 3, + roughly Javascript 1.7 ). This means stuff like properties, fun array methods and other little goodies. One of the things that Firefox supports is the for each loop, which is actually part of the E4X standard. This is a handy little item, it lets you loop through all of the items in an object, rather than the properties in an object. The catch? It’s not supported in chrome. I had to remove all of these loops in the my project’s code in order for chrome to even load my JavasScript.

No Preferences System

Firefox has a fairly nice preference system. Accessing it is a bit clunky, but the nice thing is that you can place an observer on any preference branch and get updates as soon as it changes.

In chrome there’s no such thing. There is localStorage, which you can use to store preferences, but if you want the observer functionality, you have to build it yourself.

I wrote a wrapper around localStorage to present a similar interface to Firefox for preferences, and added methods for registering observers. Because all of the pref changes that need observing are made by only by my project, I didn’t have to worry about observers getting updates if they changed through some other means.

No Password Storage

Firefox has nsILoginManager for storing passwords. By default these are stored unencrypted, but you can set a master password to encrypt them. In chrome, the only option for storing password is localStorage, which is unencrypted.

Different way to get the current window information

my project needs to know the URL of the current window so it can bookmark it. The way to access this information is different in each browser.

Firefox:

var info={url:getWebNavigation().currentURI.spec,
  title: getWebNavigation().document.title}
In chrome the method is asynchronous, and requires additional permissions in the chrome.manifest:
        chrome.tabs.getSelected(null, function _getWinInfo(tab){
                var info = {
                        url:tab.url,
                        title:tab.title
                };
            });

Different logging

My project has a bunch of built in logging. Much is for diagnostic purposes, but some is for important errors. For the most important errors Components.utils.reportError is used. This reports the error to the built in Firefox Error Console. It’s possible to report other errors to this console, but it’s more complicated.

The rest of logging in my project is provided by a home-grown logging service. Extensions in Firefox can write to disk, so that’s what the project’s logging service does. Chrome doesn’t allow extensions to write to disk. It does have a console object however, that can be used for logging at various levels (a la firebug).

To prevent having to change a bunch of code, I created a bridge to reportError:

//Fake out the report error method so we can still use it in code

    Components = {
            utils:console
    }
    Components.utils.reportError=console.error;

Different system for localization

Chrome’s localization system sucks.  The FF version isn’t great either, but I really hate the chrome one.  In FF you have two different ways to localize strings – in XUL, you can use DTDs.  This is a very strange use of DTD, but in practice it’s pretty straightforward.  In your XUL, you reference some string with an entity reference like &confirmButtonLabel;.  In your DTD you will have an entry like so:

<!ENTITY confirmButtonLabel “Do It!”>

The other method is through javascript, using stringbundles.  You create a .properties file with strings (similar to java), get reference to the bundle, and then call

somebundle.getString("somekey")
You can also call getFormattedString to fill in placeholders in a string.

In chrome, there is only one localization system.  You create a weird JSON formatted messages.json for each language, and pull strings from that file.  You get a string by calling

chrome.i18n.getMessage("somekey")
The reason I say this sucks is that there’s no way to automatically substitute strings in an HTML file.  You have to do it all with script.  If your popup is just some fixed HTML, you have to figure out your own substitution system.  This is annoying, and a feature sorely lacking from chrome.  The messages.json format is also weird, and there’s no existing tools that I could find to work with it.

No Prompt Service

Firefox has the nsIPromptService which makes simple dialogs easy.  You can crate things from a simple alert (with control of the title) to a more complex dialog with multiple custom labeled buttons.  I haven’t ported the functionality needed to do this just yet, but when I do, I will probably use jQuery UI.

No Built In Widgets

Firefox has all kinds of built in widgets.  The main one I was missing was a typeahead and search textbox. I used jQuery UI for the typeahead, and wrote a custom search textbox to emulate the behavior of the FF version.

getElementsByTagName is not scoped to the default namespace

In Firefox, when dealing with a namespaced document (like an atom feed), doing feed.getElementsByTagName is limited to the default namespace of the document. In chrome, it looks in all the namepsaces. This is a problem, because in dogear feeds there are two link elements. They’re not really the same element, because they’re in different namepsaces, but apparently that doesn’t matter to chrome.
<link href="http://signature.innovate.ibm.com/index" />
<snx:link linkid="9a18b409-3188-41be-a87c-815d4de784ec"/>
To be fair, the dom spec doesn’t say which way browsers should implement this method. I can’t really think of an instance where the chrome behavior would be useful. While both those things above are in a tag named link, they have completely different meanings. That’s the whole reason for having namespaces in the first place.

Fortunately the solution isn’t that difficult, you just have to call getElementsByTagNameNS, passing in the default atom namespace. It’s an annoying thing to have to change, however.

Less tolerance for twice defined variables

Another problem I quickly encountered was issues with variables defined twice. Javascript only has two levels of scope, function or local scope, and global scope (JS 1.7 now has block level scope, but never you mind that). No mater how nested it might be, once a variable is defined in a function, it exists throughout the entire function. This means that it’s actually wrong to declare it again (though you’ll get no error if you do so). Firefox and Chrome behave differently if you do so. Here’s an example of an issue I had
if(someCondition){
    var something="xyz";
}else{
    var something="123";
}
alert(something);

In Firefox, if someCondition is true, the result is the alert will display “xyz”. This is valid because once something is defined, it’ll exist throughout the function.

In chrome, the value of something ended up being undefined (actually, if you run this example, chrome will say xyz. Mine must have been more specific, but was similar in concept). The reason wasn’t clear until I looked in fireb Developer Tools. What was happening is two something variables were being defined, and chrome decided to use the second, uninitialized one. One way to fix this is to remove the second var:

if(someCondition){
    var something="xyz";
}else{
    something="123";
}
alert(something);

alert(something);but this makes the Java/C++ developer in me cringe. Instead, I did a more proper looking:

var something;
if(someCondition){
    something="xyz";
}else{
    something="123";
}

alert(something);
(Note I couldn’t actually come up with a sample that replicates this problem — maybe it was really another issue and I just thought this was the issue.)