Australis for Add-on Developers – Part 2: CustomizableUI

Jorge Villalobos

41

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

XPI. Code on Github.

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

XPI. Code on Github.

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:

  1. Instead of depending on onViewShowing, the code inside the iframe 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.
  2. 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.

Australis view

SDK Add-ons

SDK add-ons developers can use this module, which encapsulates what CustomizableUI does for the rest.

References

41 responses

  1. Alexander wrote on :

    In the Add-on SDK I’m getting a ModuleNotFoundError even when wrapping the require call in a try/catch block.

    Do I need to create a version of the add-on that’s only compatible with Firefox 29 and higher?

  2. Sören Hentzschel wrote on ::

    Hi Alexander,

    you need the SDK 1.16. Australis will be available in the Beta channel in a few days, but there is still no beta of the new SDK 1.16, so we have to wait… You can pull the code from Github instead but AMO will not accept add-ons created with the SDK from Github.

    Tracking bug:
    https://bugzilla.mozilla.org/show_bug.cgi?id=961846

    1. Alexander wrote on :

      Oh, I was thinking that module had already made it into the SDK, thanks for pointing that out!

  3. Mindaugas wrote on :

    There appears to be an inherent race condition with view panel creation in the window listener of bootstrapped add-ons.

    I chose to create some XUL toolbarbuttons in my panelview and add it to #mainPopupSet in onOpenWindow.

    First window loads fine since panelview is created before the widget. However, opening an another window triggers widget code (probably onBuild?) too early, before panelview is created:
    console.error:
    [CustomizableUI]
    Could not find the view node with id: myapp-view, for widget: myapp-toolbar-button.

    This happens because we load a significant amount of add-on code before UI is created. That delay is apparently sufficient to trigger the race condition. I could try to rearrange things or use onBuild (custom widget only:( ) but it seems to me that this should be fixed at the platform level. Namely, there should be an onBeforeBuild event to allow creating panelviews in time.

    1. Mindaugas wrote on :

      Apparently listening for ‘DOMContentLoaded’ rather than ‘load’ solves the problem: https://bugzilla.mozilla.org/show_bug.cgi?id=978249

      1. Jorge Villalobos wrote on ::

        Yes, I ran into that problem while writing the demos for this article. That’s why I use DOMContentLoaded instead.

  4. Brett Zamir wrote on ::

    Firefox used to have a high degree of UI customizability. One could get icons to show up wherever one wished: on the bottom, in the toolbar, in multiple toolbars, in the bookmarks bar, etc. Especially with the abstraction opportunity provided by the SDK, why are more choices not being given for where buttons can appear? Why are power users being given the short shrift?

    Clearly other people are unhappy about this too, as not everyone favors the minimalistic design style of Chrome (some of us revile it), and the resulting extra clicks it requires to access one’s buttons of choice (yes, I see one can at least keep the button visible in the toolbar, but if that is the only place for the button, the toolbar will quickly become crowded (esp. if one wishes to keep the location bar long enough to be able to easily examine URLs)).

    I understand if there is a desire to provide a new default styling, but why does it appear that are there no attempts to accommodate the tastes of other users when there are no inherent technical limits toward doing so? Why does it seem time and time again that Firefox is willing to ignore its loyal power users and smaller developers, who really helped make it into what it has become, and plow ahead without efforts to accommodate?

    As I see it, Mozilla badly needs to disengage itself from its blind attachment to _absolute_ “meritocracy”, and include more components of real democracy (note, I am not suggesting to replace meritocracy), along the lines of:

    1. Regularly scheduled town hall meetings (keeping world time zones in mind) for high level news, feedback, and idea-sharing; maybe on an annual or bi-annual basis, including recommendations that may be offered by the convocation as a whole, with a majority vote of those present (including those attending online).
    2. Regularly scheduled community election of delegates who could make recommendations at annual meetings or such.

    …with either or both of these being run at local/national as well as international levels.

    …so that the impact on ALL users can be taken into account, and there is some feeling of moral pressure to do so beyond whatever the benevolent custodians imagine without due consultation should be Firefox’s direction (even while those custodians ought to indeed remain chosen based on merit and with the freedom to ultimately decide their conscience).

    This could help Mozilla:

    1. Share big planned direction changes in a widely publicized public forum beyond the mere placement on some private wiki page somewhere
    2. Gauge reactions in the community beyond the voices of those happening to engage in some discussion group somewhere
    3. Hear voices out to minimize complaints spilling over into the wrong forum (like my post here probably)
    4. Give a chance for consultation to occur in a more human setting.
    5. Give a greater sense of global community identity and raise public awareness of Mozilla as actually being a force “owned” by the public.

    Whereas the likes of Apple or Microsoft might have public unveilings of their plans, why doesn’t Mozilla have something similar, but with an aptly bi-directional component?

    Without such engagement, I’m afraid that Mozilla will, remain to many of us as merely the “least undesirable” option out there, benefiting from our participation but facing a sometimes unconcealed sense of resentment that our voices are not being heard.

    1. Jorge Villalobos wrote on ::

      I don’t see any shortage of options for power users, since there are various add-ons that modify Australis already. That includes bringing back the Add-on Bar. Granted, that requires one extra step for those who need it.

      The rest of your post I think would be very useful to repost and discuss in the governance list.

  5. vono wrote on ::

    Hi,

    I am currently implementing this solution in my add-on CookieKeeper, but I don’t like to have to rely on “hackish” solutions. As far as I’ve seen, I must use lots of hackish solutions…

    When clicking on my add-on toolbar icon, a menu is displayed. My concerns are about this popup menu:

    – localization: To make the localization work I had to, like you make a hack with your timer, instead of a timer, I choose to use the event “DOMContentLoaded”, which seems to me more clean. Then I manually, for each menu entries, search for the proper “” tag then change the “textContent”, with the one found in a bundle.This work fine, but it is ugly. Is it possible to specify, like for XUL, a DTD ?

    – Menu popup size: Like you have done in Australis-View-Demo/src/skin/toolbar.css I need to tell the width/height of the popup (view-panel). I don’t want the popup to have scroll-bar(s). But the popup size depends on the texts displays (which depends on the locale), and the text size. For different OS, user settings etc… the popup size may vary. I certainly can make the popup size large enough, but it do not look right, it is ugly. Is there a way to make the popup size to be adjusted from it’s content ?

    – Coherence: Even so Mozilla Firefox popup content are not very consistent (The download popup menu item “Show all download” do not look the same as the ones in the bookmarks toolbar menu for example), I think it is better to have all theses simple menu looks the same. The HTML purpose is not for creating UI, but display documents. I am far from being an HTML/CSS expert, and I found difficult to make my popup menu looks right (icon/text alignment, font, colors, etc.). Is there a guideline/template for an HTML menu to have a common look ?

    Thank you.

    1. Jorge Villalobos wrote on ::

      - localization: You can use XUL if that’s easier for you. I went with HTML because I didn’t need anything that XUL is better at and it was easier for using the audio element. On HTML, I don’t know any easy solutions unless perhaps using some library.

      – popup size: I didn’t play with the CSS enough, but it should be possible. The popup is XUL code, so it should take some CSS wrestling to get the popup to look right. This article might help you sort it out. Using DOM Inspector might also help.

      – coherence: there’s no written guide, since the docs are kind of limited at the moment. While I think it’s a good idea to be consistent, particularly with the popups that are being included in Firefox, you might need to get creative for your own content.

  6. Mindaugas wrote on :

    Found an issue with this blog post.
    In the example code provided here the created view is appended to #mainPopupSet rather than #PanelUI-multiView. This causes the button to fail when it lies in the AREA_PANEL on Firefox startup.

    The code hosted on github uses the correct ID.

    1. Jorge Villalobos wrote on ::

      It’s fixed now, thanks.

  7. Luke wrote on :

    Now that 28 is out, when will there be a beta/stable working SDK and Firefox for testing this?

    1. Jorge Villalobos wrote on ::

      Once Australis lands on Beta, the SDK should be stable to use. It includes some of these features, while others will have to wait until Firefox 30. This post should explain the state of the SDK. If you have any more questions, I recommend that you post them there.

  8. jon wrote on :

    Is there a way to use “Browser Toolbox” to inspect the current panelview? The problem is the panelview is hidden/destroyed when focus is lost. :-\

    1. Jorge Villalobos wrote on ::

      Not that I know of, unfortunately. Figuring out what happens within panels has always been a pain :(

  9. j. wrote on :

    Question: is it possible to modify a label via CustomizableUI API (i.e. without dealing with the XUL element)?

    1. Jorge Villalobos wrote on ::

      I don’t think so. The best solution in that case I think would be to have an onBuild listener so you can keep references to the DOM nodes being created, so you can modify them as needed.

  10. Jeronimo wrote on :

    There is some existent request in Bugzilla (I can’t find one) about add support for more properties for Widgets? As, by example, to associate a xul tooltip or xul broadcasters (without adding them manually inside some event).

    1. Jorge Villalobos wrote on ::

      Not that I know about. You can file the bug and add it as a dependency to this one, so that it doesn’t get lost.

  11. Noitidart wrote on ::

    A sincere thank you for this article Jorge!!! I didn’t know it existed and was driving myself insane trying to figure it out.

    What about a badeable widget? Do you recommend just to use pseudo-element before/after and style it like that?

    1. Jorge Villalobos wrote on ::

      pseudo-element sounds like a good approach. The API doesn’t provide anything like that.

      1. Noitidart wrote on ::

        I forked your project and gave it a badge on lower left. What do you think of it?

        I need to give it better control so can place bade on “top left”, “bottom right”, or “top right”. Right now it is bottom left. I’m thinking I can do bottom right by putting it as “after” pseudo-el instead of “before” but the top ones are tricky because we don’t know the height of the toolbarbutton. :( By using the before and after pseduo els it gets positioned on right most or left most, so wahtever resolution or whatever is the width it doesnt matter. It’s just the height thing gets me :(

  12. Noitidart wrote on ::

    CustomizeableUI jsm seems to be missing URL bar as an area:
    https://developer.mozilla.org/en-US/docs/Mozilla/JavaScript_code_modules/CustomizableUI.jsm#Area_constants

    1. Jorge Villalobos wrote on ::

      It’s not a valid area as far as I know.

  13. Noitidart wrote on ::

    Also how to create two part toolbarbutton with this JSM module?

    I just need to add to the class of the button this:
    https://gist.github.com/yajd/9381436

    basically adding a menu-item to the toolbar, is this possible?

    1. Noitidart wrote on ::

      basically i need to set type to “menu-button” but when i set type according to mxr it turns it to widget-type

    2. Jorge Villalobos wrote on ::

      You can still set the button type to the menu types, but I know there were some bugs in their behavior. I don’t know what the current status is, but the recommendation is to migrate to a view when possible.

      1. Noitidart wrote on ::

        Ah got it thanks for all the replies

  14. Noitidart wrote on ::

    Hi Jorge can you please teach us how to use type:’custom’ because i dont want my toolbarbutton clickable or hoverable. i made it not overflow by setting that to false.

    1. Noitidart wrote on ::

      nvm i figured it out, beutiful examples in the mxr code you linked to. thanks!

  15. Noitidart wrote on ::

    Hi Jorge,
    I have something to add to this tutorial. If a developer wants to make a widget this way (and not with custom way) and they don’t want the styling on hover/active (basically click) of it they can apply this style:

    http://forums.mozillazine.org/viewtopic.php?f=19&t=2821379

  16. j. wrote on :

    Is there a way to put a button in a popup window? window.open() popups seem to ignore CustomizableUI API. Don’t know if it is on purpose or a bug. thanks

    1. Jorge Villalobos wrote on ::

      Are you talking about popup windows that have a toolbar, or windows that are opened without toolbars?

  17. j. wrote on :

    Like the one Gmail opens when I click Shift+Compose.

    1. Jorge Villalobos wrote on ::

      Those popups are intended not to have any buttons, so you would need to do some custom manipulations for a button to appear in those cases.

  18. Chris Denton wrote on :

    Since this still seems to be the only good tutorial and documentation on using the new features of Australis I’d thought I’d add a note here. When placed in the menu, the viewpanel doesn’t resize to fit the menu area like the other panels do. To fix this you have to add:

    panel.setAttribute(“flex”, “1”)

    Btw, SDK support for a lot of Australis features still seems to be rudimentary, which is a shame.

  19. DaKo wrote on :

    How can you use the CustomizableUI method in a SDK add-on? I tried to use the new toggle button with a panel (that shows a small menu), but it seems that you can’t achieve the new “list look” with it when the button is in the new Firefox panel menu (the panel with my menu is then shown on the Firefox panel menu button).

    Using “CustomizableUI.createWidget()” creates a new button (without an icon), but I can’t figure out how you attach a panel to it in a SDK add-on. When I’m creating the panel with the iframe in the onBuild property, Firefox says that it doesn’t know the panel ID etc (which makes sense). I then tried to use a windows listener to create the panel etc. there (because the examples on this page are for restartless add-ons and do this in the bootstrap file), but that also doesn’t seem to work.

    Can you help me or give me some advice how to use the CustomizableUI in a SDK sdd-on?

    1. Jorge Villalobos wrote on ::

      CustomizableUI is meant to be used by non-SDK add-ons. The SDK APIs function as a wrapper around CustomizableUI, so they should generally have the same features. I recommend that you keep looking into the SDK APIs and try to get it working that way. If you keep having trouble, please ask in the forum.

      1. DaKo wrote on :

        Thanks for your answer. So there is currently no way to achieve the new “list view” in the Firefox menu panel (like the developer tools) in a SDK add-on? The ToggleButton from the SDK works, but it seems that you can’t achieve this “list view” when the button is in the Firefox menu panel.

        1. Jorge Villalobos wrote on ::

          I’m not familiar enough with the SDK, sorry. You can ask in the forum and they should be able to help you.