Extending an extension: How to create extensions on top of AllPeers

Introduction

AllPeers is great for sharing files with your friends and relatives, but there are situations when you think, “it would be perfect if only they would add [your missing favorite feature].” Maybe you want to share links using social bookmarking services, upload photos online, be reminded of your mother’s birthday, feed your dog... (Well, maybe not the last one.) We can’t add every imaginable feature ourselves, but because AllPeers is built on top of the Mozilla platform (which is famous for its extensibility), it should come as no surprise that you can extend AllPeers rather easily as well.

In this tutorial we describe how to create an extension for AllPeers, which we call an AllPeers “extra”. We use the example of an extra that hooks into the AllPeers share form and allows you to upload links directly onto del.icio.us whenever you share links with your friends. Click here to download the finished extension.

What do you need to know before you start? Some JavaScript and XML knowledge will be handy, and if you have successfully built a Firefox extension that certainly can’t hurt.

Prerequisites

“Start every day off with a smile and get it over with.” – W. C. Fields

Before you start building your extra, you will need to set up your development environment. You can develop an extra using a normal version of Firefox and AllPeers.

For more involved projects you might want to make your own debug build. In this case, you will need:

It’s a good idea to use a separate Firefox profile for development to avoid messing up your normal profile.

Extending a bit

“Aim low, so low that no one cares if you fail.” – Marge Simpson

To get started you can simply follow the steps for building any Firefox extension.

Adding an overlay to AllPeers share form

The share form (which you’ll find in the AllPeers source tree as the file peer/content/apShareForm.xul) is the most “extensibility ready” of all AllPeers forms. Three generic hooks (apTopContent, apMiddleContent and apBottomContent) are provided explicitly for you to hang your own UI elements on. But of course you can use any element in the form as with any XUL overlay.

You have to register your overlay code in the chrome.manifest of your extension:

overlay chrome://allpeers/content/peer/apShareForm.xul chrome://apdelicious/content/apShareForm.xul

Note that the chrome package of the extension we are building is “apdelicious”. Next you create the overlay file chrome/content/apShareForm.xul.

<?xml version="1.0"?>
<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
<overlay id="deliciousShareForm" xmlns:html="http://www.w3.org/1999/xhtml"
         xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 <xul:window id="apPeerShareForm">
 <xul:box id="apMiddleContent">
 <!-- Your XUL code for the middle part of the share form goes here -->
 </xul:box>
 
 </xul:window>
 
</overlay>

Adding Javascript

“Achievement is largely the product of steadily raising one’s levels of aspiration and expectation.” – Jack Nicklaus

You can make perfectly reasonable extension (e.g. adding a custom image on top of the AllPeers contact list) without JavaScript (or any programming for that matter), but adding actions to your extras isn’t too difficult. AllPeers has a wealth of objects and APIs which you can use.

Using the AllPeers API in your script

To use the AllPeers API in your scripts, you have to include several files. The most important one is {allpeers_path}/workbench/content/apCommon.js. Add the following line to your XUL overlay file:

<xul:script type="application/x-javascript" src="chrome://allpeers/content/workbench/apCommon.js"/>

Note that we refer the XUL namespace with the “xul” prefix so adapt accordingly if your XML document uses a different prefix. To include apCommon.js directly from JavaScript code, use the Mozilla JSSubscript loader:

Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
       .getService(Components.interfaces.mozIJSSubScriptLoader)
       .loadSubScript('chrome://allpeers/content/workbench/apCommon.js');

One particularly interesting function that apCommon.js module contains is apInclude. It saves you from repeating the previous code again whenever you need to include further JavaScript files in your own script. This saves you a bit of copying-and-pasting and prevents you from accidentally including a file twice:

apInclude('chrome://allpeers/content/workbench/apResourceUtils.js');
Getting the list of shared files

The share form is represented by the apPeerShareFormObj object. This object has two interesting methods (both without any parameters):

  • getSelectedFiles - this method returns an iterator for a list of resources.
  • getMessageText - the text of the message entered in the share form.

AllPeers represents all shared items as resources. Each resource has several properties attached to it. The precise properties depend on the type of resource (file, image, hyperlink, etc.), and property fields for each resource type are described in Relax NG templates (hint: look for file/schemas/ directory in the AllPeers source tree).

To walk through the list of shared resources, use the following code:

var selectedFiles = apPeerShareFormObj.getSelectedFiles();
while (selectedFiles.hasMoreElements()) {
    var resource = selectedFiles.getNext().QueryInterface(apIResource);
 
    //Determine the type of resource
    if (resource.isDerivedFrom('Link')) {
        // Do something with links
    }
    if (resource.isDerivedFrom('Image')) {
        // Handle images
    }
    // ...
}
Reacting to form events

During a form’s lifetime, several events occur: the form opens, closes (either by user pressing the “OK” button or by canceling the form) or is updated. You can react to these changes by adding listeners that are then called every time these events occur.

A listener is simply an object with methods for the events you want to react on. We’ll create a listener which reacts to closing the share form by pressing the “Share” button.

var apDeliciousShareFormListener = {
    onOk: function (aShareForm) {
        // Code handling the event
        // i.e. start posting links to del.icio.us
    }
}

The first (and the only) argument passed to the onOk function is the form object (in our case apPeerShareFormObj). Other methods you can implement to react on different events are:

  • onInit - called when the form is initialized.
  • onCancel - called when the form is closed by a “Cancel” button (or a button with the same logical meaning, though it might have a different name).
  • onUpdateContent - called when the form is being externally updated by some other code.

All the event handler methods take the form object as their first argument.

Asynchronous calls with an XPCOM component

“A poem is never finished, only abandoned.” – Paul Valery

The del.icio.us extension uses the “Share” button of the share form to post links to del.icio.us. This is done by instantiating an XMLHttpRequest object that uses the del.icio.us API to send the links. Until now, all our code has been placed in an overlay. There is a problem with this approach though. Since the XMLHttpRequest is performed asynchronously, it returns (reporting either success or failure) after the share form closes, which means that all overlay objects have already been destroyed. As a result, you will see a message like this if you look at the Firefox console:

###!!! ASSERTION: XPConnect is being called on a scope without a 'Components' property!

This is pretty much always bad. It usually means that native code is
making a callback to an interface implemented in JavaScript, but the
document where the JS object was created has already been cleared and the
global properties of that document's window are *gone*. Generally this
indicates a problem that should be addressed in the design and use of the
callback code.

The solution is to implement the code to send the links to del.icio.us as an XPCOM component. Basic steps on how to do this are covered in this tutorial.

In order for the objects to stay in the memory even after you close the form, you should use the XPCOM component as a service (i.e. access it via the getService method). Initialize this service with any information you need (in the case of del.icio.us, this would be username and password) and call the appropriate method for the action you are performing.

Caveats

The AllPeers API for extensions is still far from stable (or “frozen”). It may change at any point and is definitely incomplete from the perspective of the author of an AllPeers extra. Moreover, there isn’t as much documentation as we would like about creating AllPeers extras, so some digging through the AllPeers source code is currently necessary. We are keen to get feedback about what kinds of extras people are interested in building, what problems or lacks they encountered (if any) and what additional documentation they would like to see.

References

 
extras/del.icio.us.txt · Last modified: 2007/04/23 16:29 by matt
 
Recent changes RSS feed Creative Commons License Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki