Songbird API Design Guidelines

This document outlines some general principles to follow when designing effective, usable APIs. Then it describes the intended audiences of Songbird's Web Page and Extension APIs, and specific patterns to follow while extending those APIs so that they are consistent with the environments that they will be used in.

API Design Principles

Generally, APIs must be aesthetic and appropriate.

APIs should be orthogonal and symmetrical.
There should be exactly one way to do everything and it should make sense in the context of the rest of the API. For example, if there's a getWidth method there should probably be setWidth, getHeight and setHeight methods.
Method naming, argument ordering and error handling should be consistent.
For example, if some of the methods take a callback as their last argument, all callback arguments in the API should be the last argument. The API should either report errors as a return value or through exceptions, but not both.
Code that uses the API should read like prose. Words should not be abbreviated. It should be easy to understand code that uses the API without having read the documentation.
For example, this:
var toast = kitchen.toaster.cook(new Bread());
is better than this:
var toaster = kitchenManager.getAppliance(Appliance::TOASTER);
var toast = toaster.cook(breadFactory.createSlice());
The API should follow the terminology, conventions and idioms of the platform that is is built on.
For example, an API built on Cocoa should use Objective C, an API built on Twisted should provide an event-driven interface.
Use cases and target developers should be taken into consideration when the API is being designed and documented.
APIs that are targetted at beginners and simple use cases may look very different to APIs that are targetted at experienced developers and complicated problems. Thinking about the class of developer being targetted and specific use cases that the API needs to support can clarify many design decisions.
Developers should be able to continue to use the tools and libraries that they are familiar with when using the API. Their previous development experience should help them learn and understand the API quickly.

Web Page API

Songbird's Web Page API is exposed to scripts running in web pages loaded into the browser pane. It extends the W3C DOM interfaces that are already available to web page scripts. It should look and feel similar to modern DOM interfaces like WHATWG HTML5.

We should resist the temptation to make our API look like the APIs provided by JavaScript libraries. Each developer or project will have their own preferred library. If we use similar patterns to the DOM then any existing libraries will work on top of the Web Page API.

We should consider exposing some simple, common functionality through declaratively through semantic HTML or microformats.

Developer Personas

Both advanced and intermediate web developers will use the Web Page API. Advanced web developers will be familiar with writing their own JavaScript, manipulating the DOM and making Ajax calls either by calling DOM APIs directly or through one or more JavaScript library.

Intermediate web developers will be comfortable building web pages with HTML and copying JavaScript patterns they find on instructional web sites for simple interactions such as animation or form validation.

Terminology and Patterns

Lists of items are called either Collections or Lists. For historical reasons no term is preferred, but we should stick to one. They have a read only number attribute named length that returns the list's length.

See: NodeList and HTMLOptionsCollection

Lists can be indexed like an array or accessed via a method named item.

list[4] and list.item(4) both return the fourth item.
Lists often have mutation methods in the form addFoo, appendFoo, and removeFoo.

For example, DOM Node has appendChild and removeChild methods.

Methods to get retrieve a subset of a list look like getFooFromBar for a single item or getFoosFromBar for multiple items.

For example, DOM Document has document.getElementById('foo') which returns the one element in the document with id="foo" while document.getElementByTagName('div') will return all the <div> elements in the document.

Type names LookLikeThis. Constants are integer constants on interfaces and LOOK_LIKE_THIS. Method and attribute names lookLikeThis.

For example, DOM Node's name is capitalized, it has methods named insertBefore(newChild, refChild) and hasChildNode(), attributes named prefix and localName, and constants named ELEMENT_NODE and ATTRIBUTE_NODE.

Object creation is often done through methods that look like createFoo().

For example, DOM Document provides methods to create different kinds of DOM node, for example document.createElement('img'), document.createAttribute('src'), document.createTextNode('Hello, world').

Changes to object properties and method calls should be reflected visually immediately.

Getters and setters should be used when possible, but there's a pattern of getFoo / setFoo.

For example img.getAttribute('src') and img.setAttribute('src', 'http://www.songbirdnest.com/favicon.ico').

State change notification should take place through the DOM Events system. To be notified of state changes scripts should call addEventListener, to stop receiving notifications they should call removeEventListener. Event handlers may be objects which implement the W3C DOM EventHandler interface or be a functions that accepts a single Event argument. The context of the event that was triggered is available as properties of the event object. The event target is the object whose state changed.

Most examples of DOM Events are associated with HTML elements, but two good examples to follow are the DOM Window and DOM Storage interfaces.

Extension API

Songbird's Extension API builds on Mozilla's platform, especially its XPCOM interfaces. It is exposed to C++ and JavaScript through its IDL interfaces. We should consider building a FUEL-like library for JavaScript extension developers.

Developer Personas

The Extension API will be used by Firefox extension developers and desktop application developers. Firefox developers will have experience with XUL, JS and CSS and experience using some common Mozilla XPCOM interfaces (eg: nsITimer, nsIObserverService, nsIWebProgressListener). There is a wide range of skills and experience among Firefox extension developers, but whatever their skill level they should be able to apply that to Songbird. For example, if they know how to add toolbar buttons to Firefox it should be easy for them to add shuttle control buttons to Songbird, if they know how to write new protocol handlers for Firefox they should be able to add new media handlers to Songbird.

Desktop application developers will have experience with several desktop application frameworks (for example MFC, Cocoa, Gtk+, Swing, etc). They will learn the base platform knowledge from Mozilla's platform documentation.

Terminology and Patterns

Functionality is exposed through XPCOM interfaces. They are named according to the pattern namespaceIMyComponentName (ie: a Songbird interface would be named like sbIFooBar). They are defined using the XPIDL variant of IDL. IDL supports single inheritance which should be used when appropriate. Mozilla has an IDL style guide that should be followed.

Methods and attributes should be named in camelCase. Attributes should be used rather than methods to get and set the properties of objects.

Constants should be const unsigned integers named in ALL_CAPS_WITH_UNDERSCORES. Constants that are flags are often implemented as bit masks.

Objects which expose XPCOM interfaces are called components. Singleton components are called Services or Managers. Components are activated by contract ID in the form @domain.com/subsystem/component-name;1

Application-wide event and state change notification happens via the nsIObserverService. Callbacks come to objects which implement the nsIObserver interface method observe. observe takes three arguments: aSubject &emdash; the object being observed, aTopic &emdash; what changed about the object and aData &emdash; information about the change.

State and and event change notification on individual components is done through callback objects called either Observers or Listeners. Observers and Listeners are associated with and dissociated from components using the addObserver or addListener and the removeObserver or removeListener methods. Callbacks to methods are usually called Listeners and are passed as the last argument. Methods in Listener and Observer interfaces typically take the form onFoo and take the object that the method is being called for as the first argument.

Errors are returned via the nserror mechanism &emdash; returned from C++ methods and thrown by JavaScript methods.

Sequences should be returned as nsISimpleEnumerator when the items are XPCOM objects or as nsIStringEnumerator when the items are strings. Another option is nsIArray, but that is less commonly used.

Extensibility can be achieved through the category manager. Classes of component can be registered with the category manager at startup and instantiated as required. The convention is to make the value of the category manager entry the component's contract ID. Another solution is to have predictable contract Ids with query strings. Eg: @mozilla.org/rdf/datasource;1?name=bookmarks

References

XPCOM API (XUL Planet)
http://www.xulplanet.com/references/xpcomref/
XPCOM API (DevMo)
http://developer.mozilla.org/en/docs/XPCOM_API_Reference
Mozilla IDL Style Guide
http://developer.mozilla.org/en/docs/Mozilla_Coding_Style_Guide#IDL