Posted on June 6, 2011
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}
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:
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")
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")
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
getElementsByTagName is not scoped to the default namespace
<link href="http://signature.innovate.ibm.com/index" /> <snx:link linkid="9a18b409-3188-41be-a87c-815d4de784ec"/>
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
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);