The CustomizableUI.jsm JavaScript code module allows you to interact with customizable buttons and items in Firefox's main window UI.

It is available in the Firefox window as the CustomizableUI property on the window. If you want to use it from a JSM or another context without a window reference, you need to import it yourself:

Components.utils.import("resource:///modules/CustomizableUI.jsm");

Introduction

The module is intended for two primary purposes:

Allow adding, moving and removing customizable widgets.

Manage the areas in which these widgets are shown.

Note that it is expressly not really aware about the specific UI used by users to make customizations. This is handled by CustomizeMode.jsm, which interacts with CustomizableUI through a listener mechanism.

Areas

Areas are parts of the user interface in which customizable widgets can be placed. CustomizableUI assumes that each area it is told about is present in every browser window. CustomizableUI is aware of two types of areas: toolbars and the menu panel.

Areas are registered using the registerArea method and unregistered using the unregisterArea method. When a customizable toolbar's XBL binding is constructed (generally, that is when a <toolbar customizable="true"/> node is appended to the document and isn't invisible), the binding will call into CustomizableUI and register the toolbar's node as being one of the concrete instances of its area.

For each area, CustomizableUI keeps track of a list of the widgets they contain, generally refered to as 'placements'. This is analogous to the old currentset attribute. If consumers make a change to the placements in an area, CustomizableUI will update the actual nodes in each area instance for them.

Toolbars generally function the same way they always have. However, they can now be 'overflowable', that is, if there are too many widgets to fit in the toolbar's horizontal space, the excess widgets will be placed in a panel accessible from an anchor (chevron) in the toolbar. In order to register such a toolbar, set the 'overflowable' property to true, and provide the id of the anchor in the 'anchor' property.

Widgets

Widget is the term that CustomizableUI uses for each of the items in a customizable area. Note that these are also abstract cross-window objects; CustomizableUI will manage the actual DOM manipulation involved with adding/moving/removing widgets in all windows for you.

There are three main types of widgets:

'legacy' XUL widgets, which are the way CustomizableUI represents widgets whose DOM representation is present (or overlaid) into a window;

API-style widgets, which are the new widgets CustomizableUI can create and manage for you. These come in 3 types themselves:

button

which are simple toolbar buttons which do something when clicked

view

which are toolbar buttons with a 'view' of further options. The view can be shown as its own panel when such a widget is in the toolbar, or as a sliding 'subview' of the menu panel when such a widget is in the menu panel.

custom

which are widgets that are custom-created by some JS supplied by the consumer

'special' widgets: these are the representations of XUL <toolbarseparator>, <toolbarspring> and <toolbarspacer> elements.

CustomizableUI provides APIs to add, move and remove all these different widgets, and mostly abstracts the DOM away from you.

The lifetime of your widget should be identical to the lifetime of the add-on - it's process-global, so if you call createWidget on bootstrap's "startup" and destroyWidget on bootstrap's "shutdown", that's enough.

Listeners

CustomizableUI provides a way to listen for various bits of customization happening. This can be useful if other parts of the code need to react to changes in the customization of the user interface.

In order to use this facility, you should create a plain JS object which defines some of the event handlers defined below. Not all event handler methods need to be defined. CustomizableUI will catch exceptions. Events are dispatched synchronously on the UI thread, so if you can delay any/some of your processing, that is advisable.

The following event handlers are supported:

onWidgetAdded(aWidgetId, aArea, aPosition)

Fired when a widget is added to an area. aWidgetId is the widget that was added, aArea the area it was added to, and aPosition the position in which it was added.

onWidgetMoved(aWidgetId, aArea, aOldPosition, aNewPosition)

Fired when a widget is moved within its area. aWidgetId is the widget that was moved, aArea the area it was moved in, aOldPosition its old position, and aNewPosition its new position.

onWidgetRemoved(aWidgetId, aArea)

Fired when a widget is removed from its area. aWidgetId is the widget that was removed, aArea the area it was removed from.

onWidgetBeforeDOMChange(aNode, aNextNode, aContainer, aIsRemoval)

Fired before a widget's DOM node is acted upon by CustomizableUI (to add, move or remove it). aNode is the DOM node changed, aNextNode the DOM node (if any) before which a widget will be inserted, aContainer the actual DOM container (could be an overflow panel in case of an overflowable toolbar), and aWasRemoval is true iff the action about to happen is the removal of the DOM node.

onWidgetAfterDOMChange(aNode, aNextNode, aContainer, aWasRemoval)

Like onWidgetBeforeDOMChange, but fired after the change to the DOM node of the widget.

onWidgetReset(aNode, aContainer)

Fired after a reset to default placements moves a widget's node to a different location. aNode is the widget's node, aContainer is the area it was moved into (NB: it might already have been there and been moved to a different position!)

onWidgetUndoMove(aNode, aContainer)

Fired after undoing a reset to default placements moves a widget's node to a different location. aNode is the widget's node, aContainer is the area it was moved into (NB: it might already have been there and been moved to a different position!)

onAreaReset(aArea, aContainer)

Fired after a reset to default placements is complete on an area's DOM node. Note that this is fired for each DOM node. aArea is the area that was reset, aContainer the DOM node that was reset.

onAreaNodeUnregistered(aArea, aNode, aReason)

Fired when an area's DOM node is unregistered. aArea is the area for which a node was unregistered, aNode the DOM node which was unregistered, and aReason indicates whether the area as a whole was unregistered (REASON_AREA_UNREGISTERED), or whether a window closed (REASON_WINDOW_CLOSED).

onAreaNodeRegistered(aArea, aNode)

Fired after an area node is first built when it is registered. This is often when the window has opened, but in the case of add-ons, could fire when the node has just been registered with CustomizableUI after an add-on update or disable/enable sequence.

onWidgetCreated(aWidgetId)

Fired when a widget with id aWidgetId has been created, but before it is added to any placements or any DOM nodes have been constructed. Only fired for API-based widgets.

onWidgetAfterCreation(aWidgetId, aArea)

Fired after a widget with id aWidgetId has been created, and has been added to either its default area or the area in which it was placed previously. If the widget has no default area and/or it has never been placed anywhere, aArea may be null. Only fired for API-based widgets.

onWidgetDestroyed(aWidgetId)

Fired when widgets are destroyed. aWidgetId is the widget that is being destroyed. Only fired for API-based widgets.

onWidgetInstanceRemoved(aWidgetId, aDocument)

Fired when a window is unloaded and a widget's instance is destroyed because of this. aWidgetId is the widget whose instance is being destroyed, aDocument the document in which this is happening. Only fired for API-based widgets.

onWidgetDrag(aWidgetId, aArea)

Fired both when and after customize mode drag handling system tries to determine the width and height of widget aWidgetId when dragged to a different area. aArea will be the area the item is dragged to, or undefined after the measurements have been done and the node has been moved back to its 'regular' area.

onCustomizeStart(aWindow)

Fired when opening customize mode in aWindow.

onCustomizeEnd(aWindow)

Fired when exiting customize mode in aWindow.

onWidgetOverflow(aNode, aContainer)

Fired when a widget's DOM node is overflowing its container, a toolbar, and will be displayed in the overflow panel.

onWidgetUnderflow(aNode, aContainer)

Fired when a widget's DOM node is not overflowing its container, a toolbar, anymore.

removeListener()

Parameters

registerArea()

Parameters

The name of the area to register. Can only contain alphanumeric characters, dashes (-) and underscores (_).

aProperties

The properties of the area. The following properties are recognized:

Property

Description

type

The type of area. Either TYPE_TOOLBAR (default) or TYPE_MENU_PANEL.

anchor

For a menu panel or overflowable toolbar, the anchoring node for the panel.

legacy

Set to true if you want CustomizableUI to automatically migrate the currentset attribute.

overflowable

Set to true if your toolbar is overflowable. This requires an anchor, and only has an effect for toolbars.

defaultPlacements

An array of widget IDs making up the default contents of the area.

defaultCollapsed

(INTERNAL ONLY) Only applied to TYPE_TOOLBAR areas. Set to true if the toolbar should be collapsed by default. Defaults to true. Set to null to ensure that reset/inDefaultState don't care about the toolbar's collapsed state.

registerToolbarNode()

Register a concrete node for a registered area. This method is automatically called from any toolbar in the main browser window that has its customizable attribute set to true. There should normally be no need to call it yourself.

Note that ideally, you should register your toolbar using registerArea before any of the toolbars have their XBL bindings constructed (which will happen when they're added to the DOM and are not hidden). If you don't, and your toolbar has a defaultset attribute, CustomizableUI will register it automatically. If your toolbar does not have a defaultset attribute, the node will be saved for processing when you call registerArea. Note that CustomizableUI won't restore state in the area, allow the user to customize it in customize mode, or otherwise deal with it, until the area has been registered.

registerMenuPanel()

Register the menu panel node. This method should not be called by anyone apart from the built-in PanelUI.

Parameters

aPanel

the panel DOM node being registered.

unregisterArea()

Unregister a customizable area. The inverse of registerArea.

Unregistering an area will remove all the (removable) widgets in the area, which will return to the panel, and destroy all other traces of the area within CustomizableUI. Note that this means the contents of the area's DOM nodes will be moved to the panel or removed, but the area's DOM node(s) themselves will stay.

Furthermore, by default the placements of the area will be kept in the saved state (!) and restored if you re-register the area at a later point. This is useful for add-ons that get disabled and then re-enabled (e.g., when they update).

You can override this last behavior (and destroy the placements information in the saved state) by passing true for aDestroyPlacements.

Parameters

aName

the name of the area to unregister.

aDestroyPlacements

whether to destroy the placements information for the area, too.

addWidgetToArea()

Add a widget to an area.

If the area to which you try to add is not known to CustomizableUI, this will throw.

If the area to which you try to add has not yet been restored from its legacy state (currentset attribute), this will postpone the addition.

If the area to which you try to add is the same as the area in which the widget is currently placed, this will do the same as moveWidgetWithinArea.

If the widget cannot be removed from its original location, this will no-op.

Otherwise, this will fire an onWidgetAdded notification, and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification for each window CustomizableUI knows about.

Parameters

aWidgetId

the ID of the widget to add

aAreaId

the ID of the area to add the widget to

[optional] aPosition

the position at which to add the widget. If you do not pass a position, the widget will be added to the end of the area.

removeWidgetFromArea()

Remove a widget from its area.

If the widget cannot be removed from its area, or is not in any area, this will no-op.

Otherwise, this will fire an onWidgetRemoved notification, and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification for each window CustomizableUI knows about.

Parameters

aWidgetId

the ID of the widget to remove

moveWidgetWithinArea()

Move a widget within an area.

If the widget is not in any area, or if the widget is already at the indicated position, this will no-op.

Otherwise, this will move the widget and fire an onWidgetMoved notification, and an onWidgetBeforeDOMChange and onWidgetAfterDOMChange notification for each window CustomizableUI knows about.

Parameters

aWidgetId

the ID of the widget to move

aPosition

the position to move the widget to. Negative values or values greater than the number of widgets will be interpreted to mean moving the widget to respectively the first or last position.

ensureWidgetPlacedInWindow()

Ensure a XUL-based widget created in a window after areas were initialized moves to its correct position. This is roughly equivalent to manually looking up the position and using insertItem in the old API, but a lot less work for consumers. Always prefer this over using toolbar.insertItem() (which might no-op because it delegates to addWidgetToArea) or, worse, moving items in the DOM yourself.

NB: why is this API per-window, you wonder? Because if you need this, presumably you yourself need to create the widget in all the windows and need to loop through them anyway, and there's no point in attempting to do this for all windows if the widget hasn't been created yet in those windows.

Parameters

aWidgetId

the ID of the widget that was just created

aWindow

the window in which you want to ensure it was added.

beginBatchUpdate()

Start a batch update of items. During a batch update, the customization state is not saved to the user's preferences file, in order to reduce (possibly sync) IO.

Calls to begin/endBatchUpdate may be nested.

Callers should ensure that NO MATTER WHAT HAPPENS they call endBatchUpdate once for each call to beginBatchUpdate, even if there are exceptions in the code in the batch update. Otherwise, for the duration of the Firefox session, customization state is never saved. Typically, you would do this using a try...finally block wrapped around your insertion code.

endBatchUpdate()

End a batch update. See the documentation for beginBatchUpdate above.

State is not saved if we believe it is identical to the last known saved state. State is only ever saved when all batch updates have finished (that is, there has been 1 endBatchUpdate call for each beginBatchUpdate call). If any of the endBatchUpdate calls pass aForceDirty=true, we will flush to the prefs file.

Parameters

aForceDirty

force CustomizableUI to flush to the prefs file when all batch updates have finished.

createWidget()

Create a widget.

To create a widget, you should pass an object with its desired properties. For a list of supported properties, see API-provided widgets.

Parameters

aProperties

the specification for the widget

Return value

Note: if your extension is compatible with versions of Firefox below 29, i.e. before CustomizableUI was introduced, then you have to make sure that in case of running on newer browsers, XUL overlay does not contain description of the button that you are creating with createWidget() method. Otherwise, conflicting static XUL overlay button and dynamic CustomizableUI buttons might cause the created button to be unstable or create a double button. To avoid that, update your Chrome Manifest file with links to two different versions of XUL overlay, using Manifest Flags.

Note: unlike button described with XUL overlay, widget-button created via createWidget() method cannot have references to other overlay elements transferred directly as parameters. For example, if there is a need to create a button with dynamically assignable tooltip, the node for static tooltip should be deleted as soon as the widget will be created, and node-reference to popup tooltip element has to be added. For that, this function has to be used (see more here):

onCreated: function(node)
{
node.removeAttribute("tooltiptext");
node.tooltip = "[reference to popup tooltip element, described in XUL overlay, or created in any other way]";
},

destroyWidget()

Destroy a widget

If the widget is part of the default placements in an area, this will remove it from there. It will also remove any DOM instances. However, it will keep the widget in the placements for whatever area it was in at the time. You can remove it from there yourself by calling CustomizableUI.removeWidgetFromArea(aWidgetId).

Parameters

aWidgetId

the ID of the widget to destroy

getWidget()

Parameters

aWidgetId

the ID of the widget whose information you need

Return value

A wrapper around the widget as described above, or null if the widget is known not to exist (any more). NB: non-null return is no guarantee the widget exists because we cannot know in advance if an XUL widget exists or not.

getUnusedWidgets()

Get an array of widget wrappers for all the widgets which are currently not in any area (so which are in the palette).

Parameters

aWindowPalette

the palette (and by extension, the window) in which CustomizableUI should look. This matters because of course XUL-provided widgets could be available in some windows but not others, and likewise API-provided widgets might not exist in a private window (because of the showInPrivateBrowsing property).

Return value

getWidgetIdsInArea()

Get an array of all the widget IDs placed in an area. This is roughly equivalent to fetching the currentset attribute and splitting by commas in the legacy APIs. Modifying the array will not affect CustomizableUI.

NB: will throw if called too early (before placements have been fetched) or if the area is not currently known to CustomizableUI.

Parameters

aArea

the ID of the area whose placements you want to obtain.

Return value

An array containing the widget IDs that are in the area.

getWidgetsInArea()

Get an array of widget wrappers for all the widgets in an area. This is the same as calling getWidgetIdsInArea and .map()-ing the result through CustomizableUI.getWidget. Careful: this means that if there are IDs in there which don't have corresponding DOM nodes (like in the old-style currentset attribute), there might be nulls in this array, or items for which wrapper.forWindow(win) will return null.

NB: will throw if called too early (before placements have been fetched) or if the area is not currently known to CustomizableUI.

Parameters

aArea

the ID of the area whose widgets you want to obtain.

Return value

An array containing the widget wrappers and/or null values for the widget IDs placed in an area.

getAreaType()

Check what kind of area (toolbar or menu panel) an area is. This is useful if you have a widget that needs to behave differently depending on its location. Note that widget wrappers have a convenience getter property (areaType) for this purpose.

Parameters

aArea

the ID of the area whose type you want to know

Return value

TYPE_TOOLBAR or TYPE_MENU_PANEL depending on the area, null if the area is unknown.

getCustomizeTargetForArea()

Obtain the DOM node that is the customization target for an area in a specific window.

Areas can have a customization target that does not correspond to the node itself. In particular, toolbars that have a customizationtarget attribute set will have their customization target set to that node. This means widgets will end up in the customization target, not in the DOM node with the ID that corresponds to the area ID. This is useful because it lets you have fixed content in a toolbar (e.g. the panel menu item in the navbar) and have all the customizable widgets use the customization target.

Using this API yourself is discouraged; you should generally not need to be asking for the DOM container node used for a particular area. In particular, if you're wanting to check it in relation to a widget's node, your DOM node might not be a direct child of the customize target in a window if, for instance, the window is in customization mode, or if this is an overflowable toolbar and the widget has been overflowed.

Parameters

aArea

the ID of the area whose customize target you want to have

aWindow

the window where you want to fetch the DOM node.

Return value

The customize target DOM node for aArea in aWindow

reset()

Reset the customization state back to its default.

This is the nuclear option. You should never call this except if the user explicitly requests it. Firefox does this when the user clicks the "Restore Defaults" button in customize mode.

undoReset()

Undoes a previous reset, restoring the state of the UI to the state prior to the reset. This can only be called immediately after reset(). If any of the UI is changed after calling reset(), then undoReset will be a no-op.

removeExtraToolbar()

Remove a custom toolbar added in a previous version of Firefox or using an add-on. NB: only works on the customizable toolbars generated by the toolbox itself. Intended for use from CustomizeMode, not by other consumers.

Parameters

aToolbarId

the ID of the toolbar to remove

getPlacementOfWidget()

Get the placement of a widget. This is by far the best way to obtain information about what the state of your widget is. The internals of this call are cheap (no DOM necessary) and you will know where the user has put your widget.

Parameters

aWidgetId

the ID of the widget whose placement you want to know

Return value

A JS Object of the form:

{
area: "somearea", // The ID of the area where the widget is placed
position: 42 // the index in the placements array corresponding to your widget
}

OR null if the widget is not placed anywhere (that is, it is in the palette).

isWidgetRemovable()

Check if a widget can be removed from the area it's in.

Note that if you're wanting to move the widget somewhere, you should generally be checking canWidgetMoveToArea, because that will return true if the widget is already in the area where you want to move it (!).

NB: oh, also, this method might lie if the widget in question is a XUL-provided widget and there are no windows open, because it can obviously not check anything in this case. It will return true. You will be able to move the widget elsewhere. However, once the user reopens a window, the widget will move back to its 'proper' area automagically.

Parameters

aWidgetId

the widget ID or DOM node to check

Return value

true if the widget can be removed from its area, false otherwise.

canWidgetMoveToArea()

Check if a widget can be moved to a particular area. Like isWidgetRemovable but better, because it'll return true if the widget is already in the right area.

Parameters

aWidgetId

the widget ID or DOM node you want to move somewhere

aArea

the area ID you want to move it to.

Return value

true if this is possible, false if it is not. The same caveats as for isWidgetRemovable apply, however, if no windows are open.

getLocalizedProperty()

Get a localized property off a (widget) object.

NB: this is unlikely to be useful unless you're in Firefox code, because this code uses the builtin widget stringbundle, and can't be told to use add-on-provided strings. It's mainly here as convenience for custom builtin widgets that build their own DOM but use the same stringbundle as the other builtin widgets.

Parameters

aWidget

the object whose property we should use to fetch a localizable string

aProp

the property on the object to use for the fetching

aFormatArgs

(optional) any extra arguments to use for a formatted string

aDef

(optional) the default to return if we don't find the string in the stringbundle

Return value

The localized string, or aDef if the string isn't in the bundle.

If aDef is not provided, and if aProp exists on aWidget, we'll return that, otherwise we'll return the empty string

hidePanelForNode()

Given a node, walk up to the first panel in its ancestor chain, and close it.

Parameters

aNode

a node whose panel should be closed

isSpecialWidget()

Check if a widget is a "special" widget: a spring, spacer or separator.

Parameters

aWidgetId

the widget ID to check.

Return value

true if the widget is 'special', false otherwise.

addPanelCloseListeners()

Add listeners to a panel that will close it. For use from the menu panel and overflowable toolbar implementations, unlikely to be useful for consumers.

Parameters

aPanel

the panel to which listeners should be attached.

removePanelCloseListeners()

Remove close listeners that have been added to a panel with addPanelCloseListeners. For use from the menu panel and overflowable toolbar implementations, unlikely to be useful for consumers.

Parameters

aPanel

the panel from which listeners should be removed.

onWidgetDrag()

Notify listeners a widget is about to be dragged to an area. For use from Customize Mode only, do not use otherwise.

Parameters

aWidgetId

the ID of the widget that is being dragged to an area.

aArea

the ID of the area to which the widget is being dragged.

notifyStartCustomizing()

Notify listeners that a window is entering customize mode. For use from Customize Mode only, do not use otherwise.

Parameters

aWindow

the window entering customize mode

notifyEndCustomizing()

Notify listeners that a window is exiting customize mode. For use from Customize Mode only, do not use otherwise.

Parameters

aWindow

the window exiting customize mode

dispatchToolboxEvent()

Notify toolbox(es) of a particular event. If you don't pass aWindow, all toolboxes will be notified. For use from Customize Mode only, do not use otherwise.

Parameters

aEvent

the name of the event to send.

aDetails

optional, the details of the event.

aWindow

optional, the window in which to send the event.

isAreaOverflowable()

Check whether an area is overflowable.

Parameters

aAreaId

the ID of an area to check for overflowable-ness

Return value

true if the area is overflowable, false otherwise

setToolbarVisibility()

Set a toolbar's visibility state in all windows.

Parameters

aToolbarId

the ID of the toolbar whose visibility should be adjusted

aIsVisible

whether the toolbar should be visible

getPlaceForItem()

Obtain a string indicating the place of an element. This is intended for use from customize mode. You should generally use getPlacementOfWidget instead, which is cheaper because it does not use the DOM.

Giving the button an icon non-style sheet method

The style sheet method (below) is one way to add an icon. The other way is to watch for when your widget is dropped into an area, and give an appropriate icon. This way is simpler because you don't have to maintain a style sheet.

It is important we add the listener before creating the element, because otherwise, the icon will not be set as the buttons are added, then we register the listener. So if we register listener first, then it catches the initial adds. When the button is in the panel area, it needs a 32x32 icon, and when in other places it needs a 16x16 icon. One issue with this method, is that when the icon is removed to the "customize toolbox" (when you right click and say customize and it opens that tab), it doesn't give it the 32x32 icon it needs (i have to figure out how and update the docs, its probably onWidgetRemoved or something)

Giving the button an icon via style sheet method

The above code creates your widget with click functionality. However it will not have an icon. We have to register a style sheet for that. This example here uses nsIStyleSheetService to do so. This is a complete example that can be copied and pasted into scratchpad:

CreateWidget - Custom Type - Simple

This shows a simple example of how to make a widget with type custom. This example creates a simple button; type custom should not be used for such a simple widget. If this is your need see the example above CreateWidget - Button Type. Note: the DOM node you construct should have the same ID as the id property described above, so that CustomizableUI can find the node again later.