{"id":6973,"date":"2014-03-06T13:32:09","date_gmt":"2014-03-06T21:32:09","guid":{"rendered":"http:\/\/blog.mozilla.org\/addons\/?p=6973"},"modified":"2014-03-12T09:37:30","modified_gmt":"2014-03-12T16:37:30","slug":"australis-for-add-on-developers-2","status":"publish","type":"post","link":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/","title":{"rendered":"Australis for Add-on Developers \u2013 Part 2: CustomizableUI"},"content":{"rendered":"<p>In the <a href=\"https:\/\/blog.mozilla.org\/addons\/2013\/12\/12\/australis-for-add-on-developers-1\/\">previous Australis post<\/a>, 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&#8217;ll cover a very exciting addition that facilitates the creation of toolbar widgets: <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/JavaScript_code_modules\/CustomizableUI.jsm\"><strong>the CustomizableUI module<\/strong><\/a>.<\/p>\n<p>While the size of the module and its docs may seem overwhelming, in reality it&#8217;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.<\/p>\n<p>The demos only work on Australis, so to try them out you need Firefox 29 (<a href=\"http:\/\/www.mozilla.org\/firefox\/aurora\/\">currently on Aurora<\/a>) or Firefox 30 (<a href=\"http:\/\/nightly.mozilla.org\/\">Nightly<\/a>).<\/p>\n<h3>1. Hello Demo<\/h3>\n<p><strong><a href=\"http:\/\/xulforge.com\/mozilla\/aus-hello.xpi\">XPI<\/a>. <a href=\"https:\/\/github.com\/jvillalobos\/Australis-Hello-World\">Code on Github<\/a>.<\/strong><\/p>\n<p>For the first demo I went with the classic &#8220;Hello World&#8221; 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.<\/p>\n<p>First you need to import the module, of course:<\/p>\n<pre lang=\"javascript\">Cu.import(\"resource:\/\/\/modules\/CustomizableUI.jsm\");<\/pre>\n<p>Then you create the widget in your startup process using the <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/JavaScript_code_modules\/CustomizableUI.jsm#createWidget%28%29\">create widget<\/a> function:<\/p>\n<pre lang=\"javascript\">CustomizableUI.createWidget(\r\n  { id : \"aus-hello-button\",\r\n    defaultArea : CustomizableUI.AREA_NAVBAR,\r\n    label : \"Hello Button\",\r\n    tooltiptext : \"Hello!\",\r\n    onCommand : function(aEvent) {\r\n      let win = aEvent.target.ownerDocument.defaultView;\r\n\r\n      win.alert(\"Hello!\");\r\n    }\r\n  });<\/pre>\n<p>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 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/JavaScript_code_modules\/CustomizableUI.jsm#Area_constants\">list of possible areas<\/a> can be found in the doc. You&#8217;ll usually want either AREA_NAVBAR or AREA_PANEL for the menu panel.<\/p>\n<p>The command code is straightforward: use the event parameter to get to the browser window and then show a simple alert.<\/p>\n<p>And then of course you want to clean it up in the shutdown process:<\/p>\n<pre lang=\"javascript\">CustomizableUI.destroyWidget(\"aus-hello-button\");<\/pre>\n<p>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&#8217;s missing all CSS, so it doesn&#8217;t have an icon yet (it&#8217;s also missing localization, but that can be added with a string bundle).<\/p>\n<p>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 <a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Chrome_Registration#style\">style<\/a> that applies to browser.xul:<\/p>\n<pre lang=\"bash\">style chrome:\/\/browser\/content\/browser.xul chrome:\/\/aus-hello\/skin\/toolbar.css<\/pre>\n<p>For a restartless add-on this is a little trickier because style manifest declarations aren&#8217;t supported. You still need the manifest and the skin, but the style needs to be injected, in this case using the stylesheet service:<\/p>\n<pre lang=\"javascript\">let io =\r\n  Cc[\"@mozilla.org\/network\/io-service;1\"].\r\n    getService(Ci.nsIIOService);\r\n\r\nthis._ss =\r\n  Cc[\"@mozilla.org\/content\/style-sheet-service;1\"].\r\n    getService(Ci.nsIStyleSheetService);\r\nthis._uri = io.newURI(\"chrome:\/\/aus-hello\/skin\/toolbar.css\", null, null);\r\nthis._ss.loadAndRegisterSheet(this._uri, this._ss.USER_SHEET);<\/pre>\n<p>This loads the stylesheet for all documents, so it&#8217;s prudent to limit the styles to the document we want in the CSS file:<\/p>\n<pre lang=\"javascript\">@-moz-document url(\"chrome:\/\/browser\/content\/browser.xul\") {\r\n\/* Code goes here*\/\r\n}<\/pre>\n<p>And then clean everything up:<\/p>\n<pre lang=\"javascript\">if (this._ss.sheetRegistered(this._uri, this._ss.USER_SHEET)) {\r\n  this._ss.unregisterSheet(this._uri, this._ss.USER_SHEET);\r\n}<\/pre>\n<p><strong>Note: <\/strong>loading the stylesheet this way is inefficient. The second demo takes a better approach using the <code>loadSheet<\/code> function, but it would have required too much additional code for this example.<\/p>\n<p>That&#8217;s it! With the 16&#215;16 and 32&#215;32 icons as recommended in <a href=\"https:\/\/blog.mozilla.org\/addons\/2013\/12\/12\/australis-for-add-on-developers-1\/\">part 1<\/a>, you should have a functional Hello World add-on.<\/p>\n<h3>2. View Demo<\/h3>\n<p><strong><a href=\"http:\/\/xulforge.com\/mozilla\/aus-view.xpi\">XPI<\/a>. <a href=\"https:\/\/github.com\/jvillalobos\/Australis-View-Demo\">Code on Github<\/a>.<\/strong><\/p>\n<p>Another type of widget you can create is a <em>view<\/em>. 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.<\/p>\n<p>To create the widget, the code is only slightly different:<\/p>\n<pre lang=\"javascript\">CustomizableUI.createWidget(\r\n  { id : \"aus-view-button\",\r\n    type : \"view\",\r\n    viewId : \"aus-view-panel\",\r\n    defaultArea : CustomizableUI.AREA_NAVBAR,\r\n    label : \"Hello Button\",\r\n    tooltiptext : \"Hello!\",\r\n    onViewShowing : function (aEvent) {\r\n      \/\/ initialize code\r\n    },\r\n    onViewHiding : function (aEvent) {\r\n      \/\/ cleanup code\r\n    }\r\n  });<\/pre>\n<p>The differences are the type (which defaults to <code>button<\/code>), the <code>viewId<\/code> and the <code>onViewShowing<\/code> and <code>onViewHiding<\/code> events.<\/p>\n<p><code>viewId<\/code> is the ID of a <code>&lt;panelview&gt;<\/code> 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&#8217;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 <code>iframe<\/code> as the only child element, so I don&#8217;t have to build everything dynamically.<\/p>\n<pre lang=\"javascript\">let panel = doc.createElement(\"panelview\");\r\nlet iframe = doc.createElement(\"iframe\");\r\n\r\npanel.setAttribute(\"id\", \"aus-view-panel\");\r\niframe.setAttribute(\"id\", \"aus-view-iframe\");\r\niframe.setAttribute(\"type\", \"content\");\r\niframe.setAttribute(\"src\", \"chrome:\/\/aus-view\/content\/player.html\");\r\n\r\npanel.appendChild(iframe);\r\ndoc.getElementById(\"PanelUI-multiView\").appendChild(panel);<\/pre>\n<p>The <code>onViewShowing<\/code> and <code>onViewHiding<\/code> events should be used to set up the view before it appears, and to clean it up after it disappears.<\/p>\n<p>This demo is meant to detect links to MP3 or OGG files on web pages and use the <code>&lt;audio&gt;<\/code> element to play them. So, the code in <code>onViewShowing<\/code> should scan the currently active page for these links and pass the paths of the audio files to the player inside the <code>iframe<\/code>. Should be easy, right?<\/p>\n<p>There&#8217;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 <em>after<\/em>\u00a0<code>onViewShowing<\/code> is called. So, manipulating the\u00a0<code>iframe<\/code> in\u00a0<code>onViewShowing<\/code> 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:<\/p>\n<ol>\n<li>Instead of depending on <code>onViewShowing<\/code>, the code inside the\u00a0<code>iframe<\/code> 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.<\/li>\n<li>Add a timeout to the\u00a0<code>onViewShowing<\/code> 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&#8217;s only a demo.<\/li>\n<\/ol>\n<p>With that little hack in, the extension works well. You can try it out on Web Archive pages <a href=\"https:\/\/archive.org\/details\/mp3_1_1535-58\">like this one<\/a>.<\/p>\n<p><a href=\"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png\"><img decoding=\"async\" loading=\"lazy\" class=\"aligncenter size-full wp-image-6980\" alt=\"Australis view\" src=\"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png\" width=\"278\" height=\"185\" srcset=\"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png 278w, https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view-252x167.png 252w\" sizes=\"(max-width: 278px) 100vw, 278px\" \/><\/a><\/p>\n<h3>SDK Add-ons<\/h3>\n<p>SDK add-ons developers can use <a href=\"https:\/\/developer.mozilla.org\/en-US\/Add-ons\/SDK\/Low-Level_APIs\/ui_button_action\">this module<\/a>, which encapsulates what CustomizableUI does for the rest.<\/p>\n<h3>References<\/h3>\n<ul>\n<li>Source code for both demos: <a href=\"https:\/\/github.com\/jvillalobos\/Australis-Hello-World\">Hello Demo<\/a>, <a href=\"https:\/\/github.com\/jvillalobos\/Australis-View-Demo\">View Demo<\/a>.<\/li>\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/docs\/Mozilla\/JavaScript_code_modules\/CustomizableUI.jsm\">CustomizableUI module doc<\/a>.<\/li>\n<li><a href=\"http:\/\/mxr.mozilla.org\/mozilla-central\/source\/browser\/components\/customizableui\/src\/CustomizableUI.jsm\">CustomizableUI source code<\/a>.<\/li>\n<li><a href=\"http:\/\/mxr.mozilla.org\/mozilla-central\/source\/browser\/components\/customizableui\/src\/CustomizableWidgets.jsm\">Widget examples in Firefox code<\/a>.<\/li>\n<li><a href=\"https:\/\/developer.mozilla.org\/en-US\/Add-ons\/SDK\/Low-Level_APIs\/ui_button_action\">Module for SDK add-ons<\/a>.<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>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&#8217;ll &hellip; <a class=\"go\" href=\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/\">Read more<\/a><\/p>\n","protected":false},"author":173,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[388,44,295],"tags":[28092,278877,278873,278876,42919],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v22.5 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Australis for Add-on Developers \u2013 Part 2: CustomizableUI - Mozilla Add-ons Community Blog<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Jorge Villalobos\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"6 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"WebPage\",\"@id\":\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/\",\"url\":\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/\",\"name\":\"Australis for Add-on Developers \u2013 Part 2: CustomizableUI - Mozilla Add-ons Community Blog\",\"isPartOf\":{\"@id\":\"https:\/\/blog.mozilla.org\/addons\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png\",\"datePublished\":\"2014-03-06T21:32:09+00:00\",\"dateModified\":\"2014-03-12T16:37:30+00:00\",\"author\":{\"@id\":\"https:\/\/blog.mozilla.org\/addons\/#\/schema\/person\/a098261b4b5510d408ff31f492606925\"},\"breadcrumb\":{\"@id\":\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#primaryimage\",\"url\":\"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png\",\"contentUrl\":\"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png\",\"width\":278,\"height\":185,\"caption\":\"Australis view\"},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/blog.mozilla.org\/addons\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Australis for Add-on Developers \u2013 Part 2: CustomizableUI\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/blog.mozilla.org\/addons\/#website\",\"url\":\"https:\/\/blog.mozilla.org\/addons\/\",\"name\":\"Mozilla Add-ons Community Blog\",\"description\":\"\",\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/blog.mozilla.org\/addons\/?s={search_term_string}\"},\"query-input\":\"required name=search_term_string\"}],\"inLanguage\":\"en-US\"},{\"@type\":\"Person\",\"@id\":\"https:\/\/blog.mozilla.org\/addons\/#\/schema\/person\/a098261b4b5510d408ff31f492606925\",\"name\":\"Jorge Villalobos\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/blog.mozilla.org\/addons\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/6d1966118f16e4b99a6e3ad07883be33?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/6d1966118f16e4b99a6e3ad07883be33?s=96&d=mm&r=g\",\"caption\":\"Jorge Villalobos\"},\"description\":\"Jorge is the Product Manager for addons.mozilla.org\",\"url\":\"https:\/\/blog.mozilla.org\/addons\/author\/jvillalobosmozilla-com\/\"}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Australis for Add-on Developers \u2013 Part 2: CustomizableUI - Mozilla Add-ons Community Blog","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/","twitter_misc":{"Written by":"Jorge Villalobos","Est. reading time":"6 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/","url":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/","name":"Australis for Add-on Developers \u2013 Part 2: CustomizableUI - Mozilla Add-ons Community Blog","isPartOf":{"@id":"https:\/\/blog.mozilla.org\/addons\/#website"},"primaryImageOfPage":{"@id":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#primaryimage"},"image":{"@id":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#primaryimage"},"thumbnailUrl":"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png","datePublished":"2014-03-06T21:32:09+00:00","dateModified":"2014-03-12T16:37:30+00:00","author":{"@id":"https:\/\/blog.mozilla.org\/addons\/#\/schema\/person\/a098261b4b5510d408ff31f492606925"},"breadcrumb":{"@id":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#primaryimage","url":"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png","contentUrl":"https:\/\/blog.mozilla.org\/addons\/files\/2014\/02\/view.png","width":278,"height":185,"caption":"Australis view"},{"@type":"BreadcrumbList","@id":"https:\/\/blog.mozilla.org\/addons\/2014\/03\/06\/australis-for-add-on-developers-2\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/blog.mozilla.org\/addons\/"},{"@type":"ListItem","position":2,"name":"Australis for Add-on Developers \u2013 Part 2: CustomizableUI"}]},{"@type":"WebSite","@id":"https:\/\/blog.mozilla.org\/addons\/#website","url":"https:\/\/blog.mozilla.org\/addons\/","name":"Mozilla Add-ons Community Blog","description":"","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/blog.mozilla.org\/addons\/?s={search_term_string}"},"query-input":"required name=search_term_string"}],"inLanguage":"en-US"},{"@type":"Person","@id":"https:\/\/blog.mozilla.org\/addons\/#\/schema\/person\/a098261b4b5510d408ff31f492606925","name":"Jorge Villalobos","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/blog.mozilla.org\/addons\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/6d1966118f16e4b99a6e3ad07883be33?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/6d1966118f16e4b99a6e3ad07883be33?s=96&d=mm&r=g","caption":"Jorge Villalobos"},"description":"Jorge is the Product Manager for addons.mozilla.org","url":"https:\/\/blog.mozilla.org\/addons\/author\/jvillalobosmozilla-com\/"}]}},"_links":{"self":[{"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/posts\/6973"}],"collection":[{"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/users\/173"}],"replies":[{"embeddable":true,"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/comments?post=6973"}],"version-history":[{"count":0,"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/posts\/6973\/revisions"}],"wp:attachment":[{"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/media?parent=6973"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/categories?post=6973"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.mozilla.org\/addons\/wp-json\/wp\/v2\/tags?post=6973"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}