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");
The module is intended for two primary purposes:
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 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.
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:
<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.
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:
aWidgetId
is the widget that was added, aArea
the area it was added to, and aPosition
the position in which it was added.aWidgetId
is the widget that was moved, aArea
the area it was moved in, aOldPosition
its old position, and aNewPosition
its new position.aWidgetId
is the widget that was removed, aArea
the area it was removed from.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.onWidgetBeforeDOMChange
, but fired after the change to the DOM node of the widget.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!)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!)aArea
is the area that was reset, aContainer
the DOM node that was reset.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
).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.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.aWidgetId
is the widget that is being destroyed. Only fired for API-based widgets.aWidgetId
is the widget whose instance is being destroyed, aDocument
the document in which this is happening. Only fired for API-based widgets.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.aWindow
.aWindow
.void addListener(aListener); |
void removeListener(aListener); |
void registerArea(aAreaId, aProperties); |
void registerToolbarNode(aToolbar, aExistingChildren); |
void registerMenuPanel(aPanel); |
void unregisterArea(aAreaId, aDestroyPlacements); |
void addWidgetToArea(aWidgetId, aAreaId, [optional] aPosition); |
void removeWidgetFromArea(aWidgetId); |
void moveWidgetWithinArea(aWidgetId, aPosition); |
void ensureWidgetPlacedInWindow(aWidgetId, aWindow); |
void beginBatchUpdate(); |
void endBatchUpdate(aForceDirty); |
WidgetGroupWrapper createWidget(aWidgetSpecification); |
void destroyWidget(aWidgetId); |
WidgetGroupWrapper getWidget(aWidgetId); |
Array getUnusedWidgets(aWindow); |
Array getWidgetIdsInArea(aAreaId); |
Array getWidgetsInArea(aAreaId); |
String getAreaType(aAreaId); |
DOMElement getCustomizeTargetForArea(aAreaId, aWindow); |
void reset(); |
void undoReset(); |
void removeExtraToolbar(); |
Object getPlacementOfWidget(aWidgetId); |
bool isWidgetRemovable(aWidgetNodeOrWidgetId); |
bool canWidgetMoveToArea(aWidgetId); |
void getLocalizedProperty(aWidget, aProp, aFormatArgs, aDef); |
void hidePanelForNode(aNode); |
bool isSpecialWidget(aWidgetId); |
void addPanelCloseListeners(aPanel); |
void removePanelCloseListeners(aPanel); |
void onWidgetDrag(aWidgetId, aArea); |
void notifyStartCustomizing(aWindow); |
void notifyEndCustomizing(aWindow); |
void dispatchToolboxEvent(aEvent, aDetails, aWindow); |
bool isAreaOverflowable(aAreaId); |
void setToolbarVisibility(aToolbarId, aIsVisible); |
String getPlaceForItem(aElement); |
bool isBuiltinToolbar(aToolbarId); |
Add a listener object that will get fired for various events regarding customization.
Remove a listener added with addListener.
Register a customizable area with CustomizableUI.
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. |
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.
Register the menu panel node. This method should not be called by anyone apart from the built-in PanelUI.
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.
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.
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.
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.
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.
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.
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.
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.
A wrapper around the created widget.
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)
.
Get a wrapper object with information about the widget.
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.
Get an array of widget wrappers for all the widgets which are currently not in any area (so which are in the palette).
showInPrivateBrowsing
property).An array of widget wrappers.
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.
An array containing the widget IDs that are in the area.
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 null
s 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.
An array containing the widget wrappers and/or null values for the widget IDs placed in an area.
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.
TYPE_TOOLBAR
or TYPE_MENU_PANEL
depending on the area, null
if the area is unknown.
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.
The customize target DOM node for aArea
in aWindow
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.
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.
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.
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.
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).
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.
true
if the widget can be removed from its area, false otherwise.
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.
true
if this is possible, false
if it is not. The same caveats as for isWidgetRemovable
apply, however, if no windows are open.
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.
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
Given a node, walk up to the first panel in its ancestor chain, and close it.
Check if a widget is a "special" widget: a spring, spacer or separator.
true
if the widget is 'special', false
otherwise.
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.
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.
Notify listeners a widget is about to be dragged to an area. For use from Customize Mode only, do not use otherwise.
Notify listeners that a window is entering customize mode. For use from Customize Mode only, do not use otherwise.
Notify listeners that a window is exiting customize mode. For use from Customize Mode only, do not use otherwise.
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.
Check whether an area is overflowable.
true
if the area is overflowable, false
otherwise
Set a toolbar's visibility state in all windows.
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.
"toolbar"
if the node is in a toolbar,"panel"
if it is in the menu panel,"palette"
if it is in the (visible!) customization palette,undefined
otherwise.Check if a toolbar is builtin or not.
true
if the toolbar is builtin, false
otherwise.
For readability, the properties are split according to purpose:
Attribute | Type | Description |
areas |
Array |
Always returns an up to date Array of all the area IDs that are currently registered with CustomizableUI. |
inDefaultState |
bool |
Whether we're in a default state. Note that non-removable non-default widgets and non-existing widgets are not taken into account in determining whether we're in the default state. NB: this is a property with a getter. The getter is NOT cheap, because it does smart things with non-removable non-default items, non-existent items, and so forth. Please don't call unless necessary. |
canUndoReset |
bool |
Whether the "Restore Defaults" operation can be reverted. This is only true after "Restore Defaults" has been performed and no other UI changes happen after the "Restore Defaults" operation. |
Attribute | Type | Description |
AREA_NAVBAR |
String |
"nav-bar" , a constant reference to the ID of the navigation toolbar. |
AREA_TABSTRIP |
String |
"TabsToolbar" , a constant reference to the ID of the tabstrip toolbar. |
AREA_MENUBAR |
String |
"toolbar-menubar" , a constant reference to the ID of the menubar's toolbar. |
AREA_BOOKMARKS |
String |
"PersonalToolbar" , a constant reference to the ID of the bookmarks toolbar. |
AREA_ADDONBAR Deprecated |
String |
"addon-bar" , a constant reference to the ID of the add-on toolbar shim. Do not use, this will be removed as soon as reasonably possible. |
AREA_PANEL |
String |
"PanelUI-contents" , a constant reference to the ID of the menu panel. |
TYPE_TOOLBAR |
String |
"toolbar" , a constant indicating the area is a toolbar. |
TYPE_MENU_PANEL |
String |
"menu-panel" , a constant indicating the area is a menu panel. |
PROVIDER_XUL |
String |
"xul" , a constant indicating an XUL-type provider. |
PROVIDER_API |
String |
"api" , a constant indicating an API-type provider. |
PROVIDER_SPECIAL |
String |
"special" , a constant indicating dynamic (special) widgets: spring , spacer , and separator . |
SOURCE_BUILTIN |
String |
"builtin" , a constant indicating the widget is built-in. |
SOURCE_EXTERNAL |
String |
"external" , a constant indicating the widget is externally provided (e.g., by add-ons or other items not part of the builtin widget set). |
WIDE_PANEL_CLASS |
String |
"panel-wide-item" , the class used to distinguish items that span the entire menu panel. |
PANEL_COLUMN_COUNT |
Number |
3 , the (constant) number of columns in the menu panel. |
This is likely the code you need for your widget. Only rare need a type:custom
widget.
CustomizableUI.createWidget({ id: 'id_of_my_widget_within_customizableui_AND_dom', defaultArea: CustomizableUI.AREA_NAVBAR, label: 'My Widget', // type: 'button', //we don't need to type this, the default type is button tooltiptext: 'This is my widget created with CUI.jsm', onCommand: function(aEvent) { var thisDOMWindow = aEvent.target.ownerDocument.defaultView; //this is the browser (xul) window var thisWindowsSelectedTabsWindow = thisDOMWindow.gBrowser.selectedTab.linkedBrowser.contentWindow; //this is the html window of the currently selected tab thisWindowsSelectedTabsWindow.alert('alert from html window of selected tab'); thisDOMWindow.alert('alert from xul window'); } });
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.
var myWidgetListener = { onWidgetAdded: function(aWidgetId, aArea, aPosition) { console.log('a widget moved to an area, arguments:', arguments); if (aWidgetId != 'noida') { return } console.log('my widget moved'); var useIcon; if (aArea == CustomizableUI.AREA_PANEL) { useIcon = 'chrome://branding/content/icon32.png'; } else { useIcon = 'chrome://branding/content/icon16.png'; } var myInstances = CustomizableUI.getWidget('noida').instances; for (var i=0; i<myInstances.length; i++) { myInstances[i].node.setAttribute('image', useIcon); } }, onWidgetDestroyed: function(aWidgetId) { console.log('a widget destroyed so removing listener, arguments:', arguments); if (aWidgetId != 'noida') { return } console.log('my widget destoryed'); CustomizableUI.removeListener(myWidgetListener); } } CustomizableUI.addListener(myWidgetListener); CustomizableUI.createWidget({ id: 'noida', defaultArea: CustomizableUI.AREA_NAVBAR, label: 'My Widget', tooltiptext: 'This is my widget created with CUI.jsm' });
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)
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:
//start - use CustomizableUI.jsm to create the widget Cu.import('resource:///modules/CustomizableUI.jsm'); CustomizableUI.createWidget({ id: 'id_of_my_widget_within_customizableui_AND_dom', defaultArea: CustomizableUI.AREA_NAVBAR, label: 'My Widget', // type: 'button', //we don't need to type this, the default type is button tooltiptext: 'This is my widget created with CUI.jsm', onCommand: function(aEvent) { var thisDOMWindow = aEvent.target.ownerDocument.defaultView; //this is the browser (xul) window var thisWindowsSelectedTabsWindow = thisDOMWindow.gBrowser.selectedTab.linkedBrowser.contentWindow; //this is the html window of the currently selected tab thisWindowsSelectedTabsWindow.alert('alert from html window of selected tab'); thisDOMWindow.alert('alert from xul window'); } }); //end - use CustomizableUI.jsm to create the widget //start - use style sheet service to style our widget to give it an icon Cu.import('resource://gre/modules/Services.jsm'); var sss = Cc['@mozilla.org/content/style-sheet-service;1'].getService(Ci.nsIStyleSheetService); var css = ''; css += '@-moz-document url("chrome://browser/content/browser.xul") {'; css += ' #id_of_my_widget_within_customizableui_AND_dom {'; css += ' list-style-image: url("chrome://branding/content/icon16.png")'; //a 16px x 16px icon for when in toolbar css += ' }'; css += ' #id_of_my_widget_within_customizableui_AND_dom[cui-areatype="menu-panel"],'; css += ' toolbarpaletteitem[place="palette"] > #id_of_my_widget_within_customizableui_AND_dom {'; css += ' list-style-image: url("chrome://branding/content/icon32.png");'; //a 32px x 32px icon for when in toolbar css += ' }'; css += '}'; var cssEnc = encodeURIComponent(css); var newURIParam = { aURL: 'data:text/css,' + cssEnc, aOriginCharset: null, aBaseURI: null } var cssUri = Services.io.newURI(newURIParam.aURL, newURIParam.aOriginCharset, newURIParam.aBaseURI); //store this in a global var so you can call it when removing the widget sss.loadAndRegisterSheet(cssUri, sss.AUTHOR_SHEET); /**************/ // When you want to remove this widget run this code: // sss.unregisterSheet(cssUri, sss.AUTHOR_SHEET); //remove the style sheet we applied // CustomizableUI.destroyWidget('id_of_my_widget_within_customizableui_AND_dom'); //remove the widget /**************/
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.
CustomizableUI.createWidget({
id: 'navigator-throbber', //id in cui.jsm // SHOULD match id of that in dom (Line #11)
type: 'custom',
defaultArea: CustomizableUI.AREA_NAVBAR,
onBuild: function(aDocument) {
var toolbaritem = aDocument.createElementNS('http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul', 'toolbaritem');
var image = aDocument.createElementNS('http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul', 'image');
image.setAttribute('src', 'chrome://branding/content/icon16.png');
var props = {
id: 'navigator-throbber', //id in dom // SHOULD match id of that in cui.jsm (Line #2)
title: 'Activity Indicator',
align: 'center',
pack: 'center',
mousethrough: 'always',
removable: 'true',
sdkstylewidget: 'true',
overflows: false
};
for (var p in props) {
toolbaritem.setAttribute(p, props[p]);
}
toolbaritem.appendChild(image);
return toolbaritem;
}
});
When you want to remove this widget run this code:
CustomizableUI.destroyWidget('navigator-throbber-id-within-CustomizableUI-object');
The browser uses type custom for its zoom controls and edit controls in the Panel. The code is more advanced. It can be found summarized at this gist: https://gist.github.com/Noitidart/10902477