In the previous Australis post, I covered what you would need to change in your extension to adapt to the new toolbars in the Australis theme. In this installment I’ll cover a very exciting addition that facilitates the creation of toolbar widgets: the CustomizableUI module.
While the size of the module and its docs may seem overwhelming, in reality it’s very easy to use if you just intend to add a button to one of the customizable areas in Firefox. I made a couple of simple extensions to try it out and demonstrate how it works.
The demos only work on Australis, so to try them out you need Firefox 29 (currently on Aurora) or Firefox 30 (Nightly).
1. Hello Demo
For the first demo I went with the classic “Hello World” example. I also wanted to make it a restartless extension, and this is where I think CustomizableUI really shines. Creating a restartless add-on that adds a button to the toolbar has never been this easy.
First you need to import the module, of course:
Cu.import("resource:///modules/CustomizableUI.jsm");
Then you create the widget in your startup process using the create widget function:
CustomizableUI.createWidget(
{ id : "aus-hello-button",
defaultArea : CustomizableUI.AREA_NAVBAR,
label : "Hello Button",
tooltiptext : "Hello!",
onCommand : function(aEvent) {
let win = aEvent.target.ownerDocument.defaultView;
win.alert("Hello!");
}
});
The ID of the widget is the same ID that the button will have in the DOM. The default area is where the widget will be added, in this case the main toolbar. The list of possible areas can be found in the doc. You’ll usually want either AREA_NAVBAR or AREA_PANEL for the menu panel.
The command code is straightforward: use the event parameter to get to the browser window and then show a simple alert.
And then of course you want to clean it up in the shutdown process:
CustomizableUI.destroyWidget("aus-hello-button");
With only this you already have a functional toolbar button add-on. The button can be customized and moved around, and its position will be remembered. It works wherever you put it. But it’s missing all CSS, so it doesn’t have an icon yet (it’s also missing localization, but that can be added with a string bundle).
For a regular add-on that requires a restart, adding the styles is simple enough. Create a chrome.manifest file, declare a skin and then a style that applies to browser.xul:
style chrome://browser/content/browser.xul chrome://aus-hello/skin/toolbar.css
For a restartless add-on this is a little trickier because style manifest declarations aren’t supported. You still need the manifest and the skin, but the style needs to be injected, in this case using the stylesheet service:
let io =
Cc["@mozilla.org/network/io-service;1"].
getService(Ci.nsIIOService);
this._ss =
Cc["@mozilla.org/content/style-sheet-service;1"].
getService(Ci.nsIStyleSheetService);
this._uri = io.newURI("chrome://aus-hello/skin/toolbar.css", null, null);
this._ss.loadAndRegisterSheet(this._uri, this._ss.USER_SHEET);
This loads the stylesheet for all documents, so it’s prudent to limit the styles to the document we want in the CSS file:
@-moz-document url("chrome://browser/content/browser.xul") {
/* Code goes here*/
}
And then clean everything up:
if (this._ss.sheetRegistered(this._uri, this._ss.USER_SHEET)) {
this._ss.unregisterSheet(this._uri, this._ss.USER_SHEET);
}
Note: loading the stylesheet this way is inefficient. The second demo takes a better approach using the loadSheet
function, but it would have required too much additional code for this example.
That’s it! With the 16×16 and 32×32 icons as recommended in part 1, you should have a functional Hello World add-on.
2. View Demo
Another type of widget you can create is a view. This is a button with a popup panel when in the toolbar, and a side panel when in the menu panel. The Bookmarks and History widgets in Australis versions are this type of widget.
To create the widget, the code is only slightly different:
CustomizableUI.createWidget(
{ id : "aus-view-button",
type : "view",
viewId : "aus-view-panel",
defaultArea : CustomizableUI.AREA_NAVBAR,
label : "Hello Button",
tooltiptext : "Hello!",
onViewShowing : function (aEvent) {
// initialize code
},
onViewHiding : function (aEvent) {
// cleanup code
}
});
The differences are the type (which defaults to button
), the viewId
and the onViewShowing
and onViewHiding
events.
viewId
is the ID of a <panelview>
that must exist in the browser window DOM. This is very easy to do in a regular extension where you can just create an overlay with the view and whatever content you want in it. For a restartless extension you’ll need a window listener and inject the view and its contents into the DOM for all windows. To make things simple, I decided to use a XUL iframe
as the only child element, so I don’t have to build everything dynamically.
let panel = doc.createElement("panelview");
let iframe = doc.createElement("iframe");
panel.setAttribute("id", "aus-view-panel");
iframe.setAttribute("id", "aus-view-iframe");
iframe.setAttribute("type", "content");
iframe.setAttribute("src", "chrome://aus-view/content/player.html");
panel.appendChild(iframe);
doc.getElementById("PanelUI-multiView").appendChild(panel);
The onViewShowing
and onViewHiding
events should be used to set up the view before it appears, and to clean it up after it disappears.
This demo is meant to detect links to MP3 or OGG files on web pages and use the <audio>
element to play them. So, the code in onViewShowing
should scan the currently active page for these links and pass the paths of the audio files to the player inside the iframe
. Should be easy, right?
There’s a small problem. When the view is shown, the view node is moved from where you put it to a different part of the DOM. This extraction and reinsertion resets the elements inside the panel. On top of that, this happens after onViewShowing
is called. So, manipulating the iframe
in onViewShowing
is not going to work because all changes will be reset when the view is actually shown. There are a couple of ways of solving this:
- Instead of depending on
onViewShowing
, the code inside theiframe
can detect when it is loaded and run the code itself. However, that also means stepping out of that document to obtain the links in the active tab, so it takes a lot of extra code. - Add a timeout to the
onViewShowing
code so it runs slightly later and when the iframe is available for modifications. This is a hacky solution, but also the simplest, so I went with this one since it’s only a demo.
With that little hack in, the extension works well. You can try it out on Web Archive pages like this one.
SDK Add-ons
SDK add-ons developers can use this module, which encapsulates what CustomizableUI does for the rest.
References
- Source code for both demos: Hello Demo, View Demo.
- CustomizableUI module doc.
- CustomizableUI source code.
- Widget examples in Firefox code.
- Module for SDK add-ons.
Alexander wrote on
Sören Hentzschel wrote on
Alexander wrote on
Mindaugas wrote on
Mindaugas wrote on
Jorge Villalobos wrote on
Brett Zamir wrote on
Jorge Villalobos wrote on
vono wrote on
Jorge Villalobos wrote on
Mindaugas wrote on
Jorge Villalobos wrote on
Luke wrote on
Jorge Villalobos wrote on
jon wrote on
Jorge Villalobos wrote on
j. wrote on
Jorge Villalobos wrote on
Jeronimo wrote on
Jorge Villalobos wrote on
Noitidart wrote on
Jorge Villalobos wrote on
Noitidart wrote on
Noitidart wrote on
Jorge Villalobos wrote on
Noitidart wrote on
Noitidart wrote on
Jorge Villalobos wrote on
Noitidart wrote on
Noitidart wrote on
Noitidart wrote on
Noitidart wrote on
j. wrote on
Jorge Villalobos wrote on
j. wrote on
Jorge Villalobos wrote on
Chris Denton wrote on
DaKo wrote on
Jorge Villalobos wrote on
DaKo wrote on
Jorge Villalobos wrote on