Writing a Firefox Protocol Handler

So my plan for this year, in addition to posting more, is to alternate posts between Firefox related and non Firefox related. This one is Firefox related. Hopefully you'll find it interesting either way.

Have you ever thought about the things you type before the colon (:) in your browser? Like http, https, ftp or file? These are called schemes or protocols. Firefox add-ons can add new schemes or protocols to the browser. This post will show you how to do that. We're going to assume you have a basic knowledge of JavaScript, but have never written a Firefox add-on.

To create a protocol handler, we'll have to implement an interface, the nsIProtocolHandler interface. Interfaces are implemented in components. Components can be written in JavaScript and that's what we're going to do.

Before we create the component, we'll need to come up with a scheme to use. For our example, we're going to use t. I'm not using twitter because on Mac at least, the twitter scheme is used by the Twitter application. The scheme is going to be used to create our contract ID. The contract ID for a protocol looks like this:

@mozilla.org/network/protocol;1?name=<b>scheme</b>"

Besides a contract ID, we'll also need a UUID. This is just a unique identifier we'll use to identify our component. You can create one here.

Now that we have a contract ID and a UUID, we can start building our component. Most of what is involved in building a component is boilerplate, so we'll only cover the protocol specific features here. If you'd like to see the entire JavaScript file for the component, click here.

the nsIProtocolHandler requires that two attributes be set, scheme which we determined earlier and protocolFlags.

  scheme: "t",
  protocolFlags: nsIProtocolHandler.URI_NORELATIVE |
                 nsIProtocolHandler.URI_NOAUTH |
                 nsIProtocolHandler.URI_LOADABLE_BY_ANYONE,

There are a ton of options for protocolFlags, and some of them can be quite confusing. I picked the ones that I knew would work for my purposes. You need to go through the list and find the appropriate ones for you. The important one is URI_LOADABLE_BY_ANYONE. You must specify either URI_LOADABLE_BY_ANYONE, URI_DANGEROUS_TO_LOAD, URI_IS_UI_RESOURCE, or URI_IS_LOCAL_FILE in order for your protocol to work.

Now that we've specifed attributes, we need to implement a few functions. The first one we need to implement is newURI.

  newURI: function(aSpec, aOriginCharset, aBaseURI)
  {
    var uri = Cc["@mozilla.org/network/simple-uri;1"].createInstance(Ci.nsIURI);
    uri.spec = aSpec;
    return uri;
  },

Because our protocol is pretty simple, all we need to do is take the URL that was passed in (aSpec), create a new nsIURI object and pass it along.

newChannel is where most of our work is going to be done.

  newChannel: function(aURI)
  {
    var ios = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
    /* Get twitterName from URL */
    var twitterName = aURI.spec.split(":")[1];
    var uri = ios.newURI("http://twitter.com/" + twitterName, null, null);
    var channel = ios.newChannelFromURI(uri, null).QueryInterface(Ci.nsIHttpChannel);
    /* Determines whether the URL bar changes to the URL */
    channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
    return channel;
  },

We're going to take the URI that was passed in, split it to get the twitter name and then create a new URI for the browser to load. (t:MikeKaply becomes http://twitter.com/MikeKaply). At this point we do something a little hacky. I want the URL bar to have the twitter URL in it after the page is loaded, NOT t:MikeKaply. I discovered that by setting this internal request header ( X-Moz-Is-Feed) I get the results I want. There's probably a better way to do this, but I couldn't find it.

Now that we've completed the component, we need to package it as an add-on. This is a very simple add-on, so we only need three files. Here's what our XPI will look like:

install.rdf
chrome.manifest
  \components
     twitterService.js

chrome.manifest is used to tell Firefox 4 about our component. It will look like this:

component {YOURUUID-XXXX-XXXX-XXXX-XXXXXXXXXXXX} components/twitterProtocolService.js
contract @mozilla.org/network/protocol;1?name=t {YOURUUID-XXXX-XXXX-XXXX-XXXXXXXXXXXX}

twitterService.js is our component.

I recommend using the Add-on Builder at AMO to create your first XPI. Then you can just add or remove the pieces you need.

Now that we've created our add-on, we can package it and install it in Firefox. An XPI file is just a ZIP file renamed, so you can use any ZIP program to create the XPI. You can click here to install the XPI for this example.

If you type t:MikeKaply, you'll see it redirect to http://twitter.com/Mike Kaply (actually http://twitter.com/#!/MikeKaply with the new Twitter URLs).

Congratulations, you've written a protocol handler.

Please note: I reserve the right to delete comments that are offensive or off-topic.

Leave a Reply

Your email address will not be published. Required fields are marked *

16 thoughts on “Writing a Firefox Protocol Handler

  1. > There’s probably a better way to do this, but I couldn’t find it.

    Set the originalURI on the HTTP channel to the URI you want to show up in the url bar.

      • Maybe I misunderstood what you're trying to do. You want the url bar to show the http:// URI that actually got loaded? Or the t:whatever URI that the user typed?

          • If you just remove this:

            channel.setRequestHeader("X-Moz-Is-Feed", "1", false);

            It will not change the URL. That was the way it was working before I did that.

            • Hi Mike, thank you for this article! Has something changed, though, in recent versions of Firefox regarding the visible URI rewrite (that you triggered with X-Moz-Is-Feed)? I'm trying to keep the added protocol in the URL bar without rewriting it to what it's translated to (so that in your example, it would stay "t:MikeKaply" rather than "http://twitter.com/MikeKaply"), but it seems to rewrite it automatically now. I tried commenting out the "X-Moz-Is-Feed" line as you mention above, but it still rewrites it. I also tried setting X-Moz-Is-Feed to 0 in the setRequest Header call, but still the same results. Thanks in advance if you have any ideas for fixing it so that it stays "t:MikeKaply"!

  2. Thank you! I've had occasion to do this a few times, but I never had the motivation to work through all the specifics.

  3. Pedantic: paste-o:

    The contract ID for a protocol looks like this: @mozilla.org/network/protocol;1?name=scheme"

    Bold didn't work inside the syntax highlighter (you probably meant anyway :D) and there's that trailing double quotation mark.

    See also http://www.nexgenmedia.net/docs/protocol/ -- an older, more detailed version of this. The "X-Moz-Is-Feed" and originalURI approaches didn't work for me. There's an argument for leaving it that way (maintaining abstraction can be good) but see https://developer.mozilla.org/en/XUL_School/Intercepting_Page_Loads#Content_Policy for another WYSIWYG way that does work, and Firefox add-on "URN support" (https://addons.mozilla.org/en-us/firefox/addon/urn-support/) for a working example of it which you can download and play with.

    If you implement a custom resolver for some private URI space (e.g. Tag URIs, or a custom URN namespace if you're naughty) you might also want to consider No Referrer (https://addons.mozilla.org/en-us/firefox/addon/no-referrer-misspelled-referer/) to stop outward links from resolved content from revealing more to remote servers than you might expect about your filesystem layout or what domain name you own (in the case of Tag URIs like tag:my.domain.com,2011:mytag). If resolving to local files you may find you need to add a policy exception (http://kb.mozillazine.org/Links_to_local_pages_don%27t_work).

    Straying away from demonstrating custom protocols, if you really wanted "t:" to work this way you might consider the LocationMorph add-on (https://addons.mozilla.org/en-US/firefox/addon/locationmorph/); I use that to autocorrect searches that the URL bar would otherwise intercept as protocols before they reached my Google keyword search preference, such as "site:kaply.com protocol".

  4. I'm trying to reproduce your work, using the cfx tool from command line Add On builder at the AMO site you link to, but I'm getting nothing. I've put up my full code at the site in my URL, raw text and tar.gz. Any ideas what I'm doing wrong?

    • I'll be honest. I have zero experience with using the Add-on builder.

      I'd suggest you ask in #jetpack IRC about how to integrate.

  5. With Firefox 20+ , we can do channel.redirectTo(uri) instead of setting the X-moz-is-feed header. It does an internal redirection and the real twitter URL appears in the url bar ;-)