User Tools

Site Tools


developer_center:creating_extensions

Creating Extensions

Required Knowledge: Javascript, basics of object-oriented programming

Introduction

Why shouldn't your music player be as individual as your musical taste? We want to make adding new features to Nightingale almost as easy as adding items to your playlist. To that end, we're using the same extension mechanism as Mozilla-based browsers like Firefox. If you are already familiar with XUL and JavaScript you will be extending Nightingale in no time.

But why stop there? Because Nightingale is a media player and not a web browser, we aren't going to make any assumptions about the type of window you are extending. Unlike Firefox, we allow you to overlay into any window type. Instead of allowing a single main XUL, as with Firefox, we encourage you to create lots of different XUL files and to present whatever media player interface you'd like. For example, we provide two: a mini-player and a standard player.

Building a simple extension to add functionality is fairly easy. In this simple tutorial, we'll show you how you can create a Nightingale extension that overlays custom user interface elements into the Nightingale UI.

Before you start

Nightingale makes use of the same extension mechanism as Firefox.

The source code for this tutorial may be browsed at the Songbird Source Browser

And the latest version of the extension is available here: Play/Pause/Stop Add-On

What is XUL?

The Nightingale UI is built with XUL (an acronym for “XML User Interface Language”) which is pronounced ”zool” and is similar in many respects to XHTML. XUL was originally created to make it easier to design and extend the UI for the Mozilla browser and it serves the same purpose in Nightingale.

XUL can be used to create most modern UI layouts, including input controls such as dialog boxes, toolbars, menubars, and more (including the Nightingale miniplayer and mainplayer).

Chrome

The set of Nightingale UI elements (e.g., menus, dialog boxes) are referred to as the Nightingale chrome. The UI elements are bundled into a chrome package and are referenced using the chrome URI:

chrome://songbird-pauseplaystop/content/overlayMain.xul

The chrome URI may be broken down into the following components:

  • Scheme: chrome
  • Package name: songbird-pauseplaystop
  • Data type: content
  • Filepath: overlayMain.xul

The package names are defined in the chrome.manifest file.

Overlays

Perhaps the most basic requirement for a media player is to provide shuttle controls so that the user can easily control the operation of the player. Certain controls have become standard, such as pause, play, and stop but there are many other possibilities. Developers may want to add rewind, fast-forward, or scrubbing controls; or simply rearrange their order or position on-screen. The “Pause/Play/Stop” extension is implemented as a XUL overlay. Overlays allow an extension to add and/or modify UI elements as well as inject arbitrary script to be run in the context of the layout being overlaid. Overlays are the primary method by which an extension developer implements additional functionality.

Overlaying Into Different Feathers

Many media players allow users to customize their appearance through the use of skins or themes. At Nightingale, we refer to these skins as feathers. Because Nightingale is intended to load many different feathers, with many different types of layouts and id attributes, the standard Mozilla overlay style is not sufficient. Therefore, Nightingale supports overlaying entire categories of layouts by windowtype, as well as overlaying directly inside of the custom Nightingale elements that are used to create a Feathers layout. In this way, an extension programmer can say they want to target the Servicepane or the Library and be sure that the extension's content will appear in the Servicepane and Library no matter what Feathers may have been loaded. More information on this topic can be found in the section further below discussing the chrome.manifest file.

The Extension Wizard

The Nightingale Developer Tools Add-on provides a great tool called the Extension Wizard, which can be accessed via the Tools menu under Create Extension…. The Extension Wizard walks you through the initial setup of the files and framework needed to get your basic extension off the ground, such as populating your chrome.manifest, install.rdf, and all the nitty gritty details (as covered below in the rest of this guide). Additionally, it helps create framework for extensions to use Nightingale features such as Custom Views/Media Pages, Display Panes, Toolbar Buttons, Preferences, etc.

Extension file layout

This sample extension defines five files laid out as follows:

chrome.manifest
chrome/content/overlayMenu.xul
chrome/content/overlayMain.xul
chrome/content/overlayMain.js
install.rdf

Below, we'll cover each file in order.

chrome.manifest

The chrome.manifest file contains instructions that specify user interface elements for the extension. It provides the locations of files and what they're used for. We'll first present the full file contents here, and then walk through line by line and explain in detail each command/line.

# Define our content namespace -- The folder MUST end with a /
content songbird-pauseplaystop chrome/content/

# Overlay XUL into ALL menubars
overlay chrome://songbird/content/xul/menuOverlay.xul chrome://songbird-pauseplaystop/content/overlayMenu.xul

# Overlay XUL into ALL windows of type Songbird:Main that use the target XBL
overlay windowtype:Songbird:Main chrome://songbird-pauseplaystop/content/overlayMain.xul

content (URI->file path mapping)

The first thing you need to do in your manifest file is register your content folder with the system, so you can specify your overlay files (note that you must include the trailing slash):

content songbird-pauseplaystop chrome/content/

This creates a content namespace named songbird-playpausestop and we tell it to look in the folder found at chrome/content/ in relationship to the manifest file. Then any file in that folder (or below it in the hierarchy) is available from the root URI chrome://songbird-pauseplaystop/content/ Other commands like skin or locale would create chrome://songbird-pauseplaystop/skin/, etc:

skin songbird-pauseplaystop classic/1.0 chrome/skin/

Please refer to our other articles for Skin and Locale options. In the following examples, you can see how we use the chrome://songbird-pauseplaystop/content/ root URI to access all of the files in the chrome/content/ folder.

overlay (Specifying the overlay file)

We want to overlay our extension's file chrome://songbird-pauseplaystop/content/overlayMenu.xul into all menus in the application. This is achieved by targeting the special .xul target file for menu items, chrome://songbird/content/xul/menuOverlay.xul:

overlay chrome://songbird/content/xul/menuOverlay.xul chrome://songbird-pauseplaystop/content/overlayMenu.xul

overlay (Specifying the overlay windowtype)

We also want to overlay the file chrome://songbird-pauseplaystop/content/OverlayMain.xul into all Nightingale feathers. We do this by targeting all windows of type Songbird:Main:

overlay windowtype:Songbird:Main chrome://songbird-pauseplaystop/content/overlayMain.xul

overlayMenu.xul

The first thing you'll want to to do is put up an extra menu item in the tools menu, so the user may select it and execute your code. Again, we'll present the contents of the file in its entirety, and then explain in detail each line following.

<?xml version="1.0"?>
<!DOCTYPE window SYSTEM "chrome://songbird/locale/songbird.dtd">
 
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  id="songbird_menu_overlay"
  xmlns:html="http://www.w3.org/1999/xhtml"
  xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
  xmlns:xbl="http://www.mozilla.org/xbl">
 
  <!--
  This file is enabled by the chrome.manifest line:
  overlay chrome://songbird/content/xul/menuOverlay.xul chrome://songbird-pauseplaystop/content/overlayMenu.xul
  And will be applied to __ALL__ properly implemented menubar instances.
  You can browse the menuOverlay file here:
  http://publicsvn.songbirdnest.com/browser/trunk/app/content/xul/menuOverlay.xul
  -->
 
  <!-- Into the "Tools" menu, before the Prefs Separator -->
  <menupopup id="menu_ToolsPopup">
  <menuitem
    label="!!! OVERLAY MENUITEM !!!"
    insertbefore="menu_PrefsSeparator"
    oncommand="onMyOverlayMenuitem()"/>
  </menupopup>
 
  <!-- You can load a script file, or just put script inline like this. -->
  <script>
    <![CDATA[
    function onMyOverlayMenuitem() {
          alert('Woo Hoo, I made a menuitem!');
    }
    ]]>
  </script>
 
</overlay>

File identification

You must start your file, like all Mozilla XML files, with <?xml> and you can load our .dtd (or your own, or however many you like) with the DOCTYPE identifier:

<?xml version="1.0"?>
<!DOCTYPE window SYSTEM "chrome://songbird/locale/songbird.dtd" >

Declaring the overlay

You must have an element of type <overlay> as your root element. The XML namespaces may sometimes be necessary to target different types of elements in your declarations, later. It is best to preserve them.

<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        id="songbird_menu_overlay"
        xmlns:html="http://www.w3.org/1999/xhtml"
        xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        xmlns:xbl="http://www.mozilla.org/xbl">

Defining the <menupopup>

The only important part of the direct children of the <overlay> element is the id attribute. The overlay system will search the parent document for an element with that id attribute. However, I could have written <anythingIwant id=“menu_ToolsPopup”> and everything would still have worked.

<menupopup id="menu_ToolsPopup">
<menuitem
        label="!!! OVERLAY MENUITEM !!!"
        insertbefore="menu_PrefsSeparator"
        oncommand="onMyOverlayMenuitem()"/>
</menupopup>

Once an element is properly targeted for overlay, all other attributes on the overlaying element will be applied to the overlay target. In addition, all of the children of the overlaying element will be added to the overlay target as children. If a child already exists with the same id attribute, only the attributes will be applied to the existing child. In this case, we are adding a <menuitem> element to the Nightingale Tools menu. In order to know what id attributes exist to be overlay targets, you must examine the implementation of the target you are overlaying. For menus, that is chrome://songbird/content/xul/menuOverlay.xul and can be found in our source (note: this particular overlay is now obsolete!). Specifically, on line 112, you can see where the Tools menupopup has id=“menu_ToolsPopup”

Positioning the menu entry

In order to properly position your children into their overlay target, you may add the special attribute insertbefore with the value of an id in the target document for your item to be placed before. Without the insertbefore attribute, your elements will be positioned after the other children already within the overlay target. You can see the <menuseparator id=“menu_PrefsSeparator”/> element that we place our <menuitem> before, here.

Using in-line Javascript

Just like in HTML, you may place JavaScript inline with the declaration of a <script> element.

<script>
<![CDATA[
function onMyOverlayMenuitem() {
        alert('Woo Hoo, I made a menuitem!');
}
]]>
</script>

This code simply defines a function that will be called from the oncommand attribute when the user selects our spiffy new <menuitem>. We have tried hard to ensure that wherever possible the id attributes used in our menus are identical to the id attributes used in the Firefox menus. This should facilitate porting Firefox extensions to Nightingale with a minimum of effort (though that topic is beyond the scope of this document).

overlayMain.xul

Next, it's important to know how to add your own elements into all the custom elements provided by Nightingale.

<?xml version="1.0"?>
<!DOCTYPE window SYSTEM "chrome://songbird/locale/songbird.dtd" >
 
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        id="songbird_main_overlay"
        xmlns:html="http://www.w3.org/1999/xhtml"
        xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
        xmlns:xbl="http://www.mozilla.org/xbl">
 
<!--
        This file is enabled by the chrome.manifest line:
        overlay windowtype:Songbird:Main chrome://songbird-pauseplaystop/content/overlayMain.xul
        And will be applied to __ALL__ properly implemented windows of windowtype="Songbird:Main"
-->
 
<!-- Import your JavaScript code. -->
<script type="application/x-javascript"
        src="chrome://songbird-pauseplaystop/content/overlayMain.js"/>
 
<!--
        This overlay will land __INSIDE__ of the <sb-player-control-buttons/> element,
        no matter what .xul layout is used to show the control buttons element.
-->
 
<xul:hbox id="sb-player-control-buttons">
        <xul:sb-player-pause-button
                id="sb-player-control-buttons-pause"
                insertbefore="sb-player-control-buttons-playpause"
                popupanchor="topleft" popupalign="bottomleft"/>
 
        <xul:sb-player-play-button
                id="sb-player-control-buttons-play"
                insertbefore="sb-player-control-buttons-playpause"
                popupanchor="topleft" popupalign="bottomleft"/>
 
        <xul:sb-player-stop-button
                id="sb-player-control-buttons-stop"
                insertbefore="sb-player-control-buttons-playpause"
                popupanchor="topleft" popupalign="bottomleft"/>
 
        <xul:sb-player-playpause-button
                id="sb-player-control-buttons-playpause" popupanchor="topleft"
                popupalign="bottomleft" hidden="true"/>
</xul:hbox>
 
<xul:hbox id="sb-mini-player-controls">
        <xul:sb-player-pause-button
                id="mini_btn_pause" class="miniplayer"
                insertbefore="sb-mini-player-controls-playpause" popupanchor="topleft"
                popupalign="bottomleft"/>
 
        <xul:sb-player-play-button
                id="mini_btn_play" class="miniplayer"
                insertbefore="sb-mini-player-controls-playpause" popupanchor="topleft"
                popupalign="bottomleft"/>
 
        <xul:sb-player-stop-button
                id="mini_btn_stop" class="miniplayer"
                insertbefore="sb-mini-player-controls-playpause" popupanchor="topleft"
                popupalign="bottomleft"/>
 
        <xul:sb-player-playpause-button
                id="sb-mini-player-controls-playpause" popupanchor="topleft"
                popupalign="bottomleft" hidden="true"/>
</xul:hbox>
 
<!--
        More overlays inside of XBL elements.
        Here we're sticking extra strings in the metadata labels.
-->
<xul:hbox id="sb-player-artist-label">
        <xul:label value="&metadata.artist; - "
                insertbefore="sb-player-artist-label-label"
                class="faceplate-text"/>
</xul:hbox>
 
<xul:hbox id="sb-player-album-label">
        <xul:label value="&metadata.album; - "
                insertbefore="sb-player-album-label-label"
                class="faceplate-text"/>
 
</xul:hbox>
 
<xul:hbox id="sb-player-title-label">
        <xul:label value="&metadata.title; - "
                insertbefore="sb-player-title-label-label"
                class="faceplate-text"/>
</xul:hbox>
 
<xul:hbox id="sb-player-numplaylistitems-label">
        <xul:label xvalue="Items? "
                insertbefore="sb-player-numplaylistitems-label-label"/>
 
</xul:hbox>
 
</overlay>

Loading a Javascript source file

Just like in HTML, you may load external script files in addition to simple inline scripts.

<script type="application/x-javascript"
        src="chrome://songbird-pauseplaystop/content/overlayMain.js"/>

The overlayMain.js file is documented below.

Defining the button layout

Here, we're targeting any Feathers layout that contains <sb-player-control-buttons> because the definition of that element contains a <xul:hbox id=“sb-player-control-buttons”> – in general, any of our custom elements will contain an inner element with an id of the same name as the custom element. We can achieve our goal of replacing the play/pause button with three separate play/pause/stop buttons by hiding the play/pause button.

<xul:hbox id="sb-player-control-buttons">
        <xul:sb-player-pause-button
                id="sb-player-control-buttons-pause"
                insertbefore="sb-player-control-buttons-playpause"
                popupanchor="topleft" popupalign="bottomleft"/>
 
        <xul:sb-player-play-button
                id="sb-player-control-buttons-play"
                insertbefore="sb-player-control-buttons-playpause"
                popupanchor="topleft" popupalign="bottomleft"/>
 
        <xul:sb-player-stop-button
                id="sb-player-control-buttons-stop"
                insertbefore="sb-player-control-buttons-playpause"
                popupanchor="topleft" popupalign="bottomleft"/>
 
        <xul:sb-player-playpause-button
                id="sb-player-control-buttons-playpause" popupanchor="topleft"
                popupalign="bottomleft" hidden="true"/>
</xul:hbox>

You can see the definition of the <sb-player-control-buttons> element, here. Note that the <xul:sb-player-playpause-button> already existed, and we are just adding the hidden=“true” attribute to it. To know what id attributes exist that may be overlay targets in the windowtype:Songbird:Main, you should look at our XBL definitions.

overlayMain.js

Complex extension functionality is implemented in JavaScript. The overlayMain.js file is loaded by the overlayMain.xul overlay file as shown earlier.

// Do some slick handling of JS functionality.  Set event handlers, etc.
// tag your function names with your extension's name to avoid namespace collisions!!!
// Want to know if someone asked an item in a playlist to be played?
function PausePlayStop_onPlaylistPlay( aEvent ) { 
        var playlist = aEvent.originalTarget;
        var number = "unknown";
 
        try {
                // Events from the "inner" playlist will be wrapped
                while ( playlist.wrappedJSObject )
                        playlist = playlist.wrappedJSObject;
 
                // Once we have a "real" playlist, we can ask for things like length
                number = playlist.mediaListView.length;
        } catch(e) { alert( "ERROR: " + e ) }
 
        // And then do something with that info, here.
        alert( playlist.nodeName + " has " + number + " item(s)" );
}
 
// Unload our listeners!
function PausePlayStop_onUnload( aEvent ) {
        window.removeEventListener( "unload", PausePlayStop_onUnload, false );
        document.removeEventListener( "playlist-play", PausePlayStop_onPlaylistPlay, true );
}
 
// ALWAYS listen for the window.unload event to be able to remove our listeners.
window.addEventListener( "unload", PausePlayStop_onUnload, false );
 
// Listen for the "playlist-play" event to know when a user has started playback from the playlist.
document.addEventListener( "playlist-play", PausePlayStop_onPlaylistPlay, true );

This code makes use of XPCOM components provided by Nightingale, and is driven by standard DOM manipulations (addEventListener(), etc) like you would use in HTML. In this script, we're watching for the playlist-play event to be fired in the system and doing something randomly interesting with the playlist that launched it. You can find the documentation for our component interfaces, hereFIXME

install.rdf

Installation information for the “Play/Pause/Stop” extension is provided in the install.rdf file:

<?xml version="1.0"?>
<RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
<Description rdf:about="urn:mozilla:install-manifest">
        <em:id>pauseplaystop@songbirdnest.com</em:id>
        <em:name>Pause/Play/Stop Buttons</em:name>
 
        <em:version>0.0.10</em:version>
        <em:creator>Pioneers of the Inevitable</em:creator>
        <em:description>Changes the single Play button in Nightingale to separate Play/Pause/Stop buttons</em:description>
        <em:homepageURL>http://addons.songbirdnest.com/extensions/detail/48</em:homepageURL>
 
        <em:targetApplication>
                <Description>
                        <em:id>songbird@songbirdnest.com</em:id>
                        <em:minVersion>0.3pre</em:minVersion>
                        <em:maxVersion>0.3.*</em:maxVersion>
 
                </Description>
        </em:targetApplication>
</Description>
 
</RDF>

Each extension must have a unique identifier specified as an <em:id> element. In this example, the ID is pauseplaystop@songbirdnest.com. The ID may also be a GUID in the form of {xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx} (see Generating GUIDs). NOTE: MAKE SURE TO CHANGE THE ID TO A UNIQUE VALUE FOR YOUR EXTENSION! IF YOU USE A GUID, YOU MUST PUT THE BRACES {} AROUND IT! Extensions also have versions specified as an <em:version> element, 0.0.10 in this example. Every time you create a new version of your extension, you should increment this value. This allows the Nightingale Add-Ons website to automatically provide updates to users who have a prior version of your extension installed (currently broken?). Extensions also must specify what applications they extend using an <em:targetApplication> element. In this example, the target application is Songbird, specified using its ID songbird@songbirdnest.com. The minimum and maximum versions are also specified as 0.3pre and 0.3+ respectively. When the extension is displayed in the Nightingale extension manager, the displayed extension name is taken from the <em:name> element. A description taken from the <em:description> element is also displayed. The information from the install.rdf file is also used by the Add-Ons website to prepopulate the information on an extension's download page (still needs to be implemented on our add-on wiki?).

Packaging the extension

Extensions are packaged as XPI files. XPI files are created by packaging the extension files as a ZIP file with a file extension of .xpi. The following is an example of how to package up the XPI file on a Un*x system

$ cd pauseplaystop/src
$ zip -r ../pauseplaystop.xpi .
adding: chrome/ (stored 0%)
adding: chrome/content/ (stored 0%)
adding: chrome/content/overlayMenu.xul (deflated 58%)
adding: chrome/content/overlayMain.xul (deflated 62%)
adding: chrome/content/overlayMain.js(deflated 60%)
adding: chrome.manifest (deflated 58%)
adding: install.rdf (deflated 59%)
$

On a Microsoft Windows™ operating system, you can use a compression tool like WinZip to create a .ZIP archive, and then simply rename the file so its file extension is .xpi instead of .zip

Installing the extension

To install the extension, launch Nightingale and select the Tools>Add-Ons… menu. Click the Install… button and navigate to and select the pauseplaystop.xpi file. Alternatively, you can just drag and drop the pauseplaystop.xpi file into the Nightingale browser to install it. Relaunch Nightingale, and you should see a !!! OVERLAY MENUITEM !!! menu item in the Tools menu. Select it and you will see the alert we created. Oh yes, and there should also be Pause/Play/Stop buttons in the mainwin.

Further reading

Since Nightingale extension packages are simply zipped files, you can look at other extensions as examples. See the Nightingale extensions page for examples. You can also see the source for some of Nightingale's extensions on GitHub. While not fully compatible with Nightingale, Firefox extensions should also provide useful examples.

XUL documentation may be found at the Mozilla developer site.

To check out the Nightingale development plans, visit our roadmap.

Check out the Nightingale SDK API Documentation if you need to find out more information about the APIs (such as properties, methods, etc.)

And lastly, please consider adding locale support to your extension and having it localised @ BabelZilla! FIXME

If you have questions, flip through the messages on the Nightingale Forum, or post your own questions. You can also chat with Nightingale developers in realtime on IRC.

developer_center/creating_extensions.txt · Last modified: 2015/05/14 23:03 by zjays