Running Add-on Code at First Run (and Upgrade)

Running code when your add-on is first installed or upgraded is pretty common. You might need to install a toolbar button or display a web page. I've seen a few different ways to do this, so I thought I'd show how I do it in my add-ons.

The best way to demonstrate this is with some sample code. I'll make some comments on at the end.

var firstrun = Services.prefs.getBoolPref("extensions.YOUREXT.firstrun");

var curVersion = "0.0.0";

if (firstrun) {
  Services.prefs.setBoolPref("extensions.YOUREXT.firstrun", false);
  Services.prefs.setCharPref("extensions.YOUREXT.installedVersion", curVersion);
  /* Code related to firstrun */
} else {
  try {
    var installedVersion = Services.prefs.getCharPref("extensions.YOUREXT.installedVersion");
    if (curVersion > installedVersion) {
      Services.prefs.setCharPref("extensions.YOUREXT.installedVersion", curVersion);
      /* Code related to upgrade */
    }
  } catch (ex) {
    /* Code related to a reinstall */
  }
}

Some comments on the code.

  • YOUREXT is some arbitrary string to uniquely identify your add-on. It can be the add-on ID or just some name.
  • I usually put this code in the JS file included in my XUL overlay. I don't put it in a module. This makes it much easier to test it, since I can simply reset the preference and open a new window.
  • I used Services.jsm in this example to make it simple. I don't use Services.jsm, I roll my own custom Services file. I'll show how in a future post.
  • During development, I hardcode my version to 0.0.0. In my previous add-ons, I queried the version from the extension manager, but someone pointed out to me that this was a waste of code. Now what I do is parse install.rdf to get the current version and replace 0.0.0 with that version at runtime.
  • I use a simple comparison for my versions, but if your versions are more complex (involving letters for instance), you should use nsIVersionComparator.
  • There is no need for a try/catch around my call to get the firstrun preference because I put this line in my default preferences file.
    pref("extensions.YOUREXT.firstrun", true);
    
  • In order to tell when a user has reinstalled my add-on, I remove the installedVersion preference when my add-on is uninstalled. I leave the firstrun preference behind.
  • If you're going to display a web page at first run, here's how you do it:

    window.setTimeout(function(){
      gBrowser.selectedTab = gBrowser.addTab("http://example.com");
    }, 1000);
    

    I found that if you don't use a timeout, the page will intermittently not display.

Hopefully this code is useful to you. Let me know in the comments if you have any suggestions.

Please note: I reserve the right to delete comments that are offensive or off-topic.

Leave a Reply

Your email address will not be published. Required fields are marked *

28 thoughts on “Running Add-on Code at First Run (and Upgrade)

  1. I'm building a new extension for firefox, and I'm having difficulties doing the first run script. When I put

    service = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
    firstrun = service.prefs.getBoolPref("extensions.newflashswitcher.firstrun");

    I get service.prefs is undefined, I'm not sure if i'm doing this correct.

    • In my example, I was using the built-in Services module. So that's where Services.prefs comes from.

      If you're using the prefs service directly, it looks like this:

      var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService)
      firstrun = prefs.getBoolPref("extensions.newflashswitcher.firstrun");

      • Thanks for the reply, so basically I followed your instructions, but I'm using Console2 to debug the javascript, and I get the following error.

        Erro: Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIPrefBranch2.getBoolPref]
        Source file: chrome://newflashswitcher/content/newfs.js
        Line: 12

        I don't know why exactly I get this return failure. I looked at your example and I also get the same error.

        • You either have to put your firstrun pref in your default preference file or put a try/catch around it.

          See the fifth bullet at the end of the post.

          The default preferences file is in your add-on:

          defaults/preferences/prefs.js (it can actually have any name)

          • I created a preference file, but how do I load it into the application, I looked thru your example, and it's not in the chrome manifest, or in the service module, or in the javascript file. Does firefox automatically sees it?

          • The last problem I'm having is that I add the button to the addon-bar, and I have a part that

            document.getElementById("addon-bar").collapsed = false, when I first insert the button, the addon-bar won't show, I have to manually turn it on, I don't know if there is another settings so that when It can make it visible.

            The other question is that if I uninstall, and reinstall, I put some code around the catch bracket, but for some reason it won't execute the code.

    • Bruno, in reference to your last comment, are you saying setting collapsed to false isn't working for you?

      As far as the uninstall/reinstall, I'll need to see the code to help you figure it out.

  2. in extensions.YOUREXT.firstrun

    If I use a guid as my addon id, do I put that in YOUREXT? I don't understand what you are supposed to put here.

    • I've updated the post to clarify.

      YOUREXT is some arbitrary string to uniquely identify your add-on. It can be the add-on ID or just some name.

      • Thanks Mike for your help. I'm having the same issue as Bruno was above but I'm really not sure how he fixed his problem. I do have firstrun pref set in the defaults/preferences/prefs.js file, but I'm receiving the same error 'component returned failure code 0x8000ffff' right at the line where it calls Services.prefs.getBoolPref() for first run.

        • Per a conversation on stack overflow, the issue is the location of the preferences file.

          The defaults/preferences directory has to be at the top level of your add-on.

  3. Hi Mike,

    I followed your example and it works, but only for the first installation. For the subsequent installations, the "var firstrun" will always be false. How do I solve this problem?

    • That's correct. Subsequent installations won't be firstrun, they will be upgrades.

      You catch those in the else cases.

      Firstrun code only runs the very first time the add-on is installed.

      • Oh because I need my extension to be able to be uninstalled and then installed again, showing the firstrun page again. Is there anyway to do it?

        • Sure. What you would want to do is clear the preference when your add-on is uninstalled.

          What I do is just clear the installedVersion preferences on uninstall so I can detect this exact case. If I see firstrun but I don't see installedVersion, I know they uninstalled the add-on.

          let listener = {
          onUninstalling: function(addon) {
          if (addon.id == "YUOUR ADDON ID") {
          /* unset pref */
          }
          },
          onOperationCancelled: function(addon) {
          if (addon.id == "YOUR ADDON ID") {
          /* reset pref (they cancelled the onuninstall */
          }
          }
          }

          Components.utils.import("resource://gre/modules/AddonManager.jsm");
          AddonManager.addAddonListener(listener);

  4. Mike, thanks for your article, it was very useful. I have only one question: I call a first run checking function from my JS-file with "window.addEventListener('load', fRun.myExtension, false);". Am I doing right? I think it's correct, 'cause "load" event fires every time on browser's start (in case of attaching first run checking function to "onload" event in XUL, not in JS, it's not working correctly). Thanks!
    __________
    PS: sorry for my ill English - I'm from Russia :) .

    • Yes, load will be called at every start. But it shouldn't matter because you've set the firstrun value to true, so it will only ever be called the first time.

      • Hi, Mike, it's me again!

        Today I've found a brainchild (at least, I hope so): what if to add some useful feature, that user can see, where is a button was situated? Let me explain it step-by-step:
        1) in button's CSS file (let's call it "button.css") we add some keyframes, like this:

        /*::::: CSS rule in your "button.css" :::::*/

        @keyframes first-run
        {
        0%
        {
        opacity: 0;
        transform: scale(.7);
        }
        100%
        {
        opacity: 1;
        transform: scale(1);
        }
        }

        /*::::: End of CSS rule :::::*/

        2) inside the checking first run section, we add something like this:

        /*::::: JS rule in your "firstrun.js" :::::*/

        if (firstrun)
        {
        var fRunAlarm = document.getElementById('Id-Of-Your-Button');
        fRunAlarm.style.MozAnimationName = 'first-run';
        fRunAlarm.style.MozAnimationDuration = '.4s';
        fRunAlarm.style.MozAnimationIterationCount = '10';
        fRunAlarm.style.MozAnimationDirection = 'alternate';
        fRunAlarm.style.MozAnimationTimingFunction = 'linear';

        //continue the rest checking code
        }
        /*::::: End of JS rule :::::*/

        So, when Firefox restarts after installing a new button, an icon will blink, so user don't need to seek it by his eyes, where his/her new "plaything" is situated. I already realize it in my extension's update, and I think, that this feature is not so useless.

        Best regards!