2006/07/04
Creating an uninstall script for an extension
On my last post I explained one way in which you can create a post-install script for an extension. I used a preference to store the current version of the just-installed extension so that future upgrades can use this preference to maybe run different code in case of an upgrade from version X to version Y, as well as running different code when doing a fresh install.
Here's where we run into a little problem: when the extension is uninstalled, the preference remains because it doesn't have it's default value set, and Firefox keeps those just in case they're needed for future installs of the same or even other extensions. So, if I install version X, uninstall it, and then install version Y, it looks like I'm doing an upgrade from X to Y, when in reality I'm doing a fresh install. So now we're need of an uninstall script, to clear this preference.
Uninstall scripts can certainly be needed for many other purposes, specially in the more complex extensions. You may need to delete files you created and won't be using anymore, make sure your preferences are removed, or undo other changes that were done on install. I would say it's professional courtesy to cleanly remove your extension and not leave lots of junk behind.
So let's get to it. As with the install script, you'll need to overlay the main window, adding a script element:
First, notice the UninstallObserver object. It's declared as a javascript singleton because we don't need more than one instance. The register and unregister methods basically add and remove this object as an observer for the "em-action-requested" (the "em" stands for Extension Manager) and the "quit-application-granted" topics. This means that the object's observe method will be called every time an action is performed in the Extension Manager, such as installation, uninstallation, enabling, or disabling of extensions, and when the application is going to exit.
In our observe method, we look for the id (UUID) of our extension. This way we know we're dealing with our extension alone. Then we look for the actions we want to observe. In this case we're interested in the "item-uninstalled" action. When the extension is set to be uninstalled, our _uninstall flag will be set to true. Also note we listen to the "item-cancel-action" action. This is very important, because the user can actually cancel uninstallation by right-clicking on the extension in the Extension Manager and selecting the Enable option. This way we know for sure the flag represents the final decision of the user.
Note that we used the "quit-application-granted" topic instead of the "unload" event to trigger the uninstall script. I do it this way because closing the browser window is not the same as quitting the application. You can have more than one browser window open, or other windows open, such as View Page Source or Javascript Console. It's unlikely that going with the "unload" event will cause problems, but it's better to stay on the safe side. There is one downside, though. An observer is created for each browser window (again, it's unlikely to have more than one when you're uninstalling the extension), so the uninstall script will be called for all observers if the user chooses the Exit menu option, closing all windows at once (very, very unlikely). This means you should plan your uninstall script so that it can be executed more than once without causing any damage.
That's it! Now you have an nice uninstall script where you can do all the cleanup you need. This solution was based on code found on a comment in the Two Ells blog, but it does have a couple of improvements.
Here's where we run into a little problem: when the extension is uninstalled, the preference remains because it doesn't have it's default value set, and Firefox keeps those just in case they're needed for future installs of the same or even other extensions. So, if I install version X, uninstall it, and then install version Y, it looks like I'm doing an upgrade from X to Y, when in reality I'm doing a fresh install. So now we're need of an uninstall script, to clear this preference.
Uninstall scripts can certainly be needed for many other purposes, specially in the more complex extensions. You may need to delete files you created and won't be using anymore, make sure your preferences are removed, or undo other changes that were done on install. I would say it's professional courtesy to cleanly remove your extension and not leave lots of junk behind.
So let's get to it. As with the install script, you'll need to overlay the main window, adding a script element:
<overlay id="homepage-overlay"Clearly, you can use the same overlay and script you used for the post-install script, if that's the case. The add something like this:
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript"
src="chrome://myextension/content/overlay.js" />
</overlay>
const MY_EXTENSION_UUID = "{CEDB5187-8A58-4958-ACF5-F6CD3BEF1927}";
function initializeOverlay() {
UninstallObserver.register();
}
var UninstallObserver = {
_uninstall : false,
observe : function(subject, topic, data) {
if (topic == "em-action-requested") {
subject.QueryInterface(Components.interfaces.nsIUpdateItem);
if (subject.id == MY_EXTENSION_UUID) {
if (data == "item-uninstalled") {
this._uninstall = true;
} else if (data == "item-cancel-action") {
this._uninstall = false;
}
}
} else if (topic == "quit-application-granted") {
if (this._uninstall) {
/* uninstall stuff. */
}
this.unregister();
}
},
register : function() {
var observerService =
Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.addObserver(this, "em-action-requested", false);
observerService.addObserver(this, "quit-application-granted", false);
},
unregister : function() {
var observerService =
Components.classes["@mozilla.org/observer-service;1"].
getService(Components.interfaces.nsIObserverService);
observerService.removeObserver(this,"em-action-requested");
observerService.removeObserver(this,"quit-application-granted");
}
}
window.addEventListener("load", initializeOverlay, false);I remove all documentation from the code due to space constraints. I don't want these posts to be longer than necessary. Rest assured that I'm quite obsesive about proper documentation. I just think that these little tips are more readable without it. But I'll always explain the general idea behind the code I post.First, notice the UninstallObserver object. It's declared as a javascript singleton because we don't need more than one instance. The register and unregister methods basically add and remove this object as an observer for the "em-action-requested" (the "em" stands for Extension Manager) and the "quit-application-granted" topics. This means that the object's observe method will be called every time an action is performed in the Extension Manager, such as installation, uninstallation, enabling, or disabling of extensions, and when the application is going to exit.
In our observe method, we look for the id (UUID) of our extension. This way we know we're dealing with our extension alone. Then we look for the actions we want to observe. In this case we're interested in the "item-uninstalled" action. When the extension is set to be uninstalled, our _uninstall flag will be set to true. Also note we listen to the "item-cancel-action" action. This is very important, because the user can actually cancel uninstallation by right-clicking on the extension in the Extension Manager and selecting the Enable option. This way we know for sure the flag represents the final decision of the user.
Note that we used the "quit-application-granted" topic instead of the "unload" event to trigger the uninstall script. I do it this way because closing the browser window is not the same as quitting the application. You can have more than one browser window open, or other windows open, such as View Page Source or Javascript Console. It's unlikely that going with the "unload" event will cause problems, but it's better to stay on the safe side. There is one downside, though. An observer is created for each browser window (again, it's unlikely to have more than one when you're uninstalling the extension), so the uninstall script will be called for all observers if the user chooses the Exit menu option, closing all windows at once (very, very unlikely). This means you should plan your uninstall script so that it can be executed more than once without causing any damage.
That's it! Now you have an nice uninstall script where you can do all the cleanup you need. This solution was based on code found on a comment in the Two Ells blog, but it does have a couple of improvements.
Labels: extension, installation, observer, uninstall, uuid, xul
2006/07/02
Creating a post-install script for an extension
If you used XUL Planet's version of the XUL Tutorial, you must have come to realize that the extension installation part is significantly outdated. The installation process was significantly changed on Firefox, and the infamous install.js script has been put to sleep. It still lives in the old Mozilla Suite and Seamonkey, I think.
Here's the deal: extensions used to use a script (install.js) to perform certain installation operations. Most of them were really repetitive and predictable chrome registrations, while on some cases extension developers would add some specific post-install operations that were required for their extension to work properly.
All of that got the axe. Firefox extensions no longer include that script, and including it will have no effect on installation. It won't be executed, period. Chrome registration has been moved elsewhere (the chrome.manifest file) and post-installation scripts can no longer be bundled this way. It was a good move in my opinion. Most extensions don't require post-install scripts, so this makes life easier for the majority.
But what happens when you do need a post-install script? Well, don't worry. Creating one is not very complicated, and I'll explain how to do it.
Most (all?) extensions overlay the main browser window (chrome://browser/content/browser.xul). If yours doesn't, add one, it's not going to kill you. The same applies to extensions on other applications, as long as there's a main xul file you can overlay. The least you'll need to add is a script:
Secondly, we need to make sure our script is only executed once, right after installation. That's why I'm using a preference (myextension.install.finished) to serve as a flag that indicates whether the post-install script has been executed or not.
Note that I didn't use a boolean preference, but a string preference that holds the current version of the extension. The purpose of this is to make this script a little more robust. You never know how this script is going to change in future versions, so you may need to do different things when doing a fresh install or upgrading from version X to version Y. Things are just nicer this way. On the not-so-nice side, you'll have to remember to change that constant every time you change version numbers. You'll also want to clear this preference when the extension is being uninstalled. But that's the topic of the next blog: Creating an uninstall script for an extension.
Here's the deal: extensions used to use a script (install.js) to perform certain installation operations. Most of them were really repetitive and predictable chrome registrations, while on some cases extension developers would add some specific post-install operations that were required for their extension to work properly.
All of that got the axe. Firefox extensions no longer include that script, and including it will have no effect on installation. It won't be executed, period. Chrome registration has been moved elsewhere (the chrome.manifest file) and post-installation scripts can no longer be bundled this way. It was a good move in my opinion. Most extensions don't require post-install scripts, so this makes life easier for the majority.
But what happens when you do need a post-install script? Well, don't worry. Creating one is not very complicated, and I'll explain how to do it.
Most (all?) extensions overlay the main browser window (chrome://browser/content/browser.xul). If yours doesn't, add one, it's not going to kill you. The same applies to extensions on other applications, as long as there's a main xul file you can overlay. The least you'll need to add is a script:
<overlay id="homepage-overlay"Next thing you'll need is your overlay script (on overlay.js, or whatever name you prefer):
xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
<script type="application/x-javascript"
src="chrome://myextension/content/overlay.js" />
</overlay>
const MY_EXTENSION_VERSION = "0.1";A little explaining is in order. First of all, the initializeOverlay function is added as a load event handler. This will make the browser execute this function when it's main interface is about to be drawn and shown to the user. This means this function is executed shortly before the user can actually see the window. You can access DOM elements though, because the DOM for the window is already loaded.
function initializeOverlay() {
var pref =
Components.classes["@mozilla.org/preferences-service;1"].
getService(Components.interfaces.nsIPrefBranch);
var finished = "";
try {
finished = pref.getCharPref("myextension.install.finished");
} catch(e) {}
if (finished != MY_EXTENSION_VERSION) {
/* add post-install stuff here. */
pref.setCharPref("myextension.install.finished", MY_EXTENSION_VERSION);
}
}
window.addEventListener("load", initializeOverlay, false);
Secondly, we need to make sure our script is only executed once, right after installation. That's why I'm using a preference (myextension.install.finished) to serve as a flag that indicates whether the post-install script has been executed or not.
Note that I didn't use a boolean preference, but a string preference that holds the current version of the extension. The purpose of this is to make this script a little more robust. You never know how this script is going to change in future versions, so you may need to do different things when doing a fresh install or upgrading from version X to version Y. Things are just nicer this way. On the not-so-nice side, you'll have to remember to change that constant every time you change version numbers. You'll also want to clear this preference when the extension is being uninstalled. But that's the topic of the next blog: Creating an uninstall script for an extension.
Labels: extension, installation, preference, xul