Firefox Extension Sidebar Removal

I’m writing a Firefox extension that includes a sidebar. Firefox conveniently remembers the status of things like sidebars and toolbars when you close the application down. So if you have a sidebar displayed when you close out of Firefox, when you restart Firefox, the sidebar is still displayed. This is handy until you uninstall the extension and shut down Firefox with the sidebar still visible, as documented in this bug. What happens in this case is that when you restart Firefox after uninstalling the plugin, it comes up with a blank sidebar. In order to get rid of the sidebar, you have two basic options:

  • Reinstall the extension, restart Firefox (the sidebar will display the extension’s data again), close the sidebar, close Firefox, open Firefox back up, and uninstall the extension.
  • As documented here, you can go to localstore.rdf in your profile, hunt for a line dictating whether or not the sidebar appears, and delete it before restarting Firefox.

Neither of these options is particularly user-friendly. So I googled around a bit and found this post by Alex Sirota, which provided some general information about writing a cleanup script for an extension. Alex also responded to a further inquiry about writing an uninstall script and pointed me to an extension of his in which he’s implemented just such a thing. His script didn’t do the sidebar fix, but it helped me get to where I could call sidebar uninstall code when the browser shuts down.

So all that was left was to figure out how to edit localstore.rdf once I had determined that a given extension was unloading and had been slated for uninstallation and then make this code run on uninstall. The code listed below is kind of wacky because I’m using two different methods for manipulating datasources. In the first part, I’m manipulating the extensions datasource directly because I scraped the code from Alex’s extension. In the second part, I’m using the RDFDS library available at XUL Planet. Eventually, I’ll probably go back and use one method or the other, but this works for now. So without further ado, here’s the code:

function unload_overlay(){
    //Get various things needed to deal with RDF.
    var RDFService = Components.classes["@mozilla.org/rdf/rdf-service;1"].getService(Components.interfaces.nsIRDFService);
    var Container = Components.classes["@mozilla.org/rdf/container;1"].getService(Components.interfaces.nsIRDFContainer);
    var extensionDS= Components.classes["@mozilla.org/extensions/manager;1"].getService(Components.interfaces.nsIExtensionManager).datasource;

    //Get nodes and arc needed to figure out whether the given extension is slated to be uninstalled.
    var root = RDFService.GetResource("urn:mozilla:extension:root");
    var nameArc = RDFService.GetResource("http://www.mozilla.org/2004/em-rdf#name");
    var toBeUninstalledArc = RDFService.GetResource("http://www.mozilla.org/2004/em-rdf#toBeUninstalled");

    Container.Init(extensionDS,root);

    //Now iterate over the elements to find the toBeUninstalled value for the extension in question ("Extension Name," which you should change to match your extension name).
    var found = false;
    var elements = Container.GetElements();
    while (elements.hasMoreElements()) {
        var element = elements.getNext().QueryInterface(Components.interfaces.nsIRDFResource);
        var name = "";
        var toBeUninstalled = "";

        var target = extensionDS.GetTarget(element, nameArc ,true);
        if (target) {
            name = target.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
        }

        target = extensionDS.GetTarget(element, toBeUninstalledArc ,true);
        if (target) {
            toBeUninstalled=target.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
        }

        //If we find the right value, set the found flag to true.
        if (toBeUninstalled && (toBeUninstalled == "true") && (name == "Extension Name")) {
            found = true;
        }
    }

    //Ok, our extension is slated to be uninstalled, so do some more stuff.
    if(found==true){
        try{
            //Here we're using the RDFDS class, so the code for this should be pulled in at some point.

            //Get the localstore datasource
            var ds=new RDFDataSource("rdf:local-store");

            //Get the sidebar-box node.
            var node=ds.getNode("chrome://browser/content/browser.xul#sidebar-box");

            //Get that node's properties and remove all targets, which typically include "src," "width," and "sidebarcommand"
            //Removing all properties apparently causes the node itself to be deleted, which is the result we're actually going for.
            var properties=node.getProperties();
            while(properties.hasMoreElements()){
                prop=properties.getNext();
                node.removeTarget(prop.getValue(),node.getTarget(prop.getValue()));
            }

            //Now save and refresh the datasource.
            ds.save();
            ds.refresh();
            //alert('Uninstalling sidebar');

        }
        catch(e){
            alert(e);
        }
    }
}

In order to cause this code to be run when the browser unloads, you need to add the following line somewhere in your js:

addEventListener("unload", unload_overlay, false);

So here’s essentially what goes on in this script:

  • The extension includes a browser overlay, so any unload listeners added within the extension are added to the browser as a whole.
  • Thus our unload event listener is added to the browser as a whole, causing unload_overlay() to be called any time the browser is closed.
  • The browser has to be closed in order for extensions to be installed or uninstalled.
  • Accordingly, when we uninstall the extension and shut down the browser, unload_overlay() is called.
  • Unload_overlay() checks the extensions rdf to see if our extension has been slated for uninstall (in which case its toBeUninstalled value will be set).
  • If so slated, the code finds the sidebar-box node in the localstore.rdf datasource and deletes it.
  • This prevents the sidebar from being displayed on the next load.

I should probably add code that checks for the src attribute of the sidebar-box element in localstore.rdf and deletes the node only if it’s set to the current extension’s source. Currently, the uninstall code will probably delete any sidebar that’s open upon browser close, which might not always be a valid action (say a user keeps his or her bookmarks sidebar open; this might close that, which would be a pain for the user).

At any rate, here’s a draft of a way to circumvent the Firefox sidebar bug listed above.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s