Animated SVG as toolbar button

Is it possible to have an animated toolbar button with an SVG graphic?
In my trials I have seen, it is possible to use animated GIF images for toolbar buttons.
But the SVG files I tried so far, generaly display as button, but their animation does not work.
This is the code for the index.js (or main.js) that I used for my trials:

var { ActionButton } = require("sdk/ui/button/action");

var imgFileName = "button.svg";
//var imgFileName = "button.gif";

var button = ActionButton({
        id : "my-button",
        label : "my button",
        icon : "./" + imgFileName,
        onClick : function (state) {
            console.log("button '" + state.label + "' was clicked");
        }
    });

This is the content of one of the tried SVG files button.svg:

<svg version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
     width="24px" height="30px" viewBox="0 0 24 30" style="enable-background:new 0 0 50 50;" xml:space="preserve">
    <rect x="0" y="13" width="4" height="5" fill="#333">
        <animate attributeName="height" attributeType="XML"
            values="5;21;5"
            begin="0s" dur="0.6s" repeatCount="indefinite" />
        <animate attributeName="y" attributeType="XML"
            values="13; 5; 13"
            begin="0s" dur="0.6s" repeatCount="indefinite" />
    </rect>
    <rect x="10" y="13" width="4" height="5" fill="#333">
        <animate attributeName="height" attributeType="XML"
            values="5;21;5"
            begin="0.15s" dur="0.6s" repeatCount="indefinite" />
        <animate attributeName="y" attributeType="XML"
            values="13; 5; 13"
            begin="0.15s" dur="0.6s" repeatCount="indefinite" />
    </rect>
    <rect x="20" y="13" width="4" height="5" fill="#333">
        <animate attributeName="height" attributeType="XML"
            values="5;21;5"
            begin="0.3s" dur="0.6s" repeatCount="indefinite" />
        <animate attributeName="y" attributeType="XML"
            values="13; 5; 13"
            begin="0.3s" dur="0.6s" repeatCount="indefinite" />
    </rect>
</svg>

I have tried several different animated SVG files.
Generally I managed to get them displayed in the toolbar, but I have never succeeded to make them animated.
Is there something I need to insert in the SVG file or in the index.js to make it work, or is it not possible after all?

Very cool. I think it’s possible but i think you’ll have to actually mess with the XUL using CustomizableUI.jsm and custom build to build your button. If you can’t get SVG namespace working in XUL (which i think you should be able too) you can put an iframe and load it like that.

Thank you very much for your reply!
With your idea I’ve found out many things.
First of all, it was my mistake speaking about the toolbar.
I did not mean the ui/toolbar.
I meant the one bar, were the text box for entering the URL is placed.
I don’t know the official name for this toolbar. I would call it the main toolbar.
When I create an ActionButton without an additional toolbar, the button will be placed at the right side in the main toolbar.
This is what I wanted, but with your idea I made some experiments.
I modified index.js this way:

var { ActionButton } = require("sdk/ui/button/action");
var { Toolbar } = require("sdk/ui/toolbar");
var { Frame } = require("sdk/ui/frame");

var gifButton = ActionButton({
        id : "gif-button",
        label : "GIF Button",
        icon : "./button.gif"
    });

var svgButton = ActionButton({
        id : "svg-button",
        label : "SVG Button",
        icon : "./button.svg"
    });

var frameButton = Frame({
        url : "./button.html"
    });

var toolbar = Toolbar({
        title : "Player",
        items : [gifButton, svgButton, frameButton]
    });

var mainButton = ActionButton({
        id : "main-button",
        label : "Main Button",
        icon : "./button.svg"
    });

And I created button.html:

<html>
<body>
    <object type="image/svg+xml" data="button.svg" width="16" height="16">@<!-- fallback image --></object>
</body>
</html>

Running this code, first I have seen this (red arrows and words added for clarifying):

  1. The gifButton showed correctly my animated GIF.
  2. The svgButton showed a static image from button.svg.
  3. The frameButton showed @ which is the fallback image in this case (see button.html).
  4. The mainButton showed the same static image from button.svg like the svgButton.

To these results I have the following questions and remarks:

  1. The gifButton works as expected.
  2. The svgButton works like the mainButton, which is what I would have expected.
  3. The frameButton does not show any image. That means, that either the frame (is it a iframe?) does not work, or that I have not implemented it correctly.

I was playing around with the browser, because I wanted small screen shots for this reply.
By accident I found, that in some cases both, the svgButton and the mainButton (that have the same SVG image) finally are working:

I found out, that when I decrease the browser width so much, that the “More Tools…” icon appears in the Main Toolbar (see below):


… and then I increase the browser width again, the svgButton and the mainButton will work from then on.

Now there are some questions in which I would be very interessted.

  1. Why does the frameButton not work?
  2. Why do the svgButton and the mainButton work correctly, only when the “More Tools…” has bee visible before?
  3. Is there a Method or a Workaround to get svgButton and the mainButton working, without having to manipulate the browser size?
  4. Besides: Is there a possibility, to place the ui/toolbar within the main toolbar (therefore not occupying a whole row for it)?

So after all it seems, that the buttons in the main toolbar actually can display animated SVG, but the browser needs a kind of a trigger for this to happen.

2 Likes

Wow fantastic research/sharing, give me a bit to read over that and think about it.

Now I have a programmatic workaround.
In my trials I’ve seen, that animation with SVG files starts working, when the mainButton has disappeared and then appeared again.
I’m not sure if a resize of the mainButton would suffice. However I haven’t found any resize function to try this.
So I made a function (activateMainButton) to replace the icon file of the mainButton to a different file and then setting the original SVG file again.
I’ve seen that this is working, but not yet when the code in index.js is executed the first time.
I’m not sure, if there is a onReady function for the main script. I haven’t found any. I think only the content scripts have them.
Therefore I added a page-worker just to get it’s ready signal.
I’m still not sure, if my usage of SVG files as button icons is correct, i.e. that a workaround is necessary due to an implementation fault.
If somebody can correct my primary implementation or has a better workaround, I’m very glad to hear about.
If there is an implementation fault in the Addon SDK, somebody from the mozilla team would have to fix it.
Here is my final code so far:

var { ActionButton } = require("sdk/ui/button/action");
var { Toolbar } = require("sdk/ui/toolbar");
var { Frame } = require("sdk/ui/frame");

var gifButton = ActionButton({
        id : "gif-button",
        label : "GIF Button",
        icon : "./button.gif"
    });

var svgButton = ActionButton({
        id : "svg-button",
        label : "SVG Button",
        icon : "./button.svg"
    });

var frameButton = Frame({
        url : "./button.html"
    });

var toolbar = Toolbar({
        title : "Player",
        items : [gifButton, svgButton, frameButton]
    });

var mainButton = ActionButton({
        id : "main-button",
        label : "Main Button",
        icon : "./button.svg"
    });

function activateMainButton() {
    mainButton.state(mainButton, { "icon" : "./button.gif" });
    mainButton.state(mainButton, { "icon" : "./button.svg" });
};

// It seems to be to early. The mainButton can't be activated yet.
// activateMainButton();

// When a page-worker is loaded, the mainButton can be activated.
var pageWorker = require("sdk/page-worker").Page({
    contentURL : "./button.html",
    contentScript : "self.port.emit('activate')",
    contentScriptWhen : "ready"
});
pageWorker.port.on("activate", function () {
    activateMainButton();
    pageWorker.destroy();
});
2 Likes

Awesome work there! I didn’t get a chance to dig into this :frowning: it would be nice if we can find a no workaround way.