Restartless Firefox Add-ons, Part 5: Logging

This is a simple one for most of you, but I found someone asking the question "How do I dump messages from bootstrap.js" on stackovercrap so I thought it would make a good topic to quickly blog about.

First of all what I'm about to describe is exactly how one would log messages and report errors from a JavaScript code module, so if you already know how to do this, then stop reading and bounce.

If you are lost without window.dump or don't know what that is, then give this page https://developer.mozilla.org/en/window.dump a quick read. There is a note that says:

"dump is commonly used to debug JavaScript. Privileged code can also use Components.utils.reportError and nsIConsoleService to log messages to the Error Console."

Bingo! Give the page on nsIConsoleService a re-read. There is a code example on the top of that page that you can use to get a console service, then the page describes it's various methods, such as logStringMessage. You can use the logStringMessage method in window.dump's stead.

The window.dump page also mentioned Components.utils.reportError which can be used for logging error messages to the Error Console.

The way that I prefer to access the console service is via the Services.jsm JavaScript code module, like so:

Components.utils.import("resource://gre/modules/Services.jsm");
Services.console.logStringMessage("this is a console message, look for it in the messages tab of the Error Console.");

Restartless Firefox Add-ons, Part 4: Localization (l10n)

Alright, part four has been a long time coming, but I finally got around to typing out an localization include for restartless Firefox 4 addons which I'm ready to talk about. If you haven't read part 2 it's all about how to use includes, so read it first, if you don't know what a restartless addon is, then start with part 1.

To start off with I wrote this include for Restartless Restart, and I consider that to be my demo.

Overview

Alright, so you should know how to use includes in a restartless addon now, and I shouldn't have to explain why localizing your addon is important, and you have an example; here are some notes:

To begin with you will need to copy this l10n.js file in to your addon, then include it at startup (in your addons bootstrap.js file) and create the all important underscore function (literally "_(aKey)"), like so:

Setup

function startup(data) AddonManager.getAddonByID(data.id, function(addon) {
include(addon.getResourceURI("includes/l10n.js").spec);
l10n(addon, "filename.properties", "en-US");
}

So the example above assumes that you copied the l10n.js into a includes/ subdirectory, and that "filename.properties" is the name of the files within a /locale/en-US/ subdirectory for example, so the path to the english (US) translation for the text in your addon would be "/locale/en-US/filename.properties" (I would recommend using something other than "filename"). After l10n() is called it will create (and return) a global function named "_", which can be used to obtain the appropriate localized text. The 3rd parameter for l10n is the default locale that should be used when all other attempts fail. A failed attempted could be caused by a missing locale file (say you haven't yet translated your addon for the user's locale for instance), or the locale file could exist but not have the requested key, or the key could exist but be blank.

Using _

_ takes 2 arguments aKey, and aLocale which is optional.

aKey is the key for the text in your .properties file.

aLocale allows you to attempt to grab a specific translation before trying to get a translation from the user's locale, and lastly trying the default locale specified by the addon developer in the l10n call above (if that 3rd argument of l10n isn't provided then "en" is used). I use this in Restartless Restart to provide the user with the ability to override their browser's locale and get another translation. So I use the ja-JP translation for Restartless Restart on my Firefox profiles these days just because.

Conclusion

If you have a restartless addon, and you haven't localized it yet, then get to it!

Restartless Firefox Add-ons, Part 2: Includes

Today I'm going to write about including files into your bootstrap.js file's scope. There are a few different types of files that you'll want to include, and I'm only going to talk about three of them today, importing javascript modules provided by Firefox or other extensions, and including (using loadSubScript) files packaged with your add-on, or any other.

Importing Modules

This is pretty simple, Components is available, so Components.utils.import is available. So import javascript modules like so:

Components.utils.import("resource://gre/modules/Services.jsm");

Note: I highly recommend reviewing the Services.jsm module, it's a handy one.

Includes

There are many reasons why you'd want to access other files packaged with your add-on, one of them being if your bootstrap.js file starts getting large then you'll probably want to break that code up in to multiple files and include them in the bootstrap.js file. Another would be if your add-on needs to get the file location of a image or some other asset packaged with the add-on.

In order to figure out the location of files packaged with an add-on, the AddonManager.jsm & Services.jsm module will be needed, import it with the following code:

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

Then add the following implementation of a include function:

/* Includes a javascript file with loadSubScript
*
* @param src (String)
* The url of a javascript file to include.
*/
(function(global) global.include = function include(src) (
Services.scriptloader.loadSubScript(src, global)))(this);

Now in you'll want to call AddonManager.getAddonByID() with the add-on's id, which you can get from the bootstrap.js file's startup method's data param, like so:

var img;
function startup(data) AddonManager.getAddonByID(data.id, function(addon) {
// Include some utility functions
include(addon.getResourceURI("includes/utils.js").spec);
// Remember some image's url
img = addon.getResourceURI("images/star.png").spec;
});

I should be blogging about some includes which'll make developing restartless add-ons much easier in following posts!

Restartless Firefox add-ons, part 1: Giving your add-on the bootstrap

Intro

Firefox 4 will introduce a new type of add-on to users, the restartless add-on! It's true that there are many problems that need to be resolved before all add-ons can be made restartless, which should help illustrate why it has taken as long as it has for Mozilla to introduce the feature. The capability is available now, and although it is more difficult to write a restartless add-on than it is to write a add-on that requires a restart to be installed/uninstalled/enabled/disabled I think most would agree that a restartless add-on is usually superior to one that requires a restart. So I've decided to start a short blog series on how to write restartless add-ons for Firefox in order to help those that are interested along the way as much as I can.

You should note that Mozilla is working on a add-on sdk which makes writing restartless add-ons much easier by providing APIs which allow you to abstract away most of the painful parts, which are the parts that I plan to describe in this series. So if you're interested in writing a restartless add-on without the add-on sdk, or if you're planning on writing modules/packages to be used by add-ons made with the add-on sdk, then you may find this series useful.

The Bootstrap

The core of a restartless add-on is it's bootstrap.js file, because it's this file which Firefox loads at startup, and it's this file that Firefox will notify about install/uninstall/enable/disable/etc events which are key to making a add-on restartless. In order to notify Firefox that your add-on includes a bootstrap.js file, and that you want it to be used you must include a em:bootstrap="true" in your install.rdf file. If you're unfamiliar with a add-on's install manifest file (aka install.rdf) then you can read about it here (it's basically just a metadata file for add-ons).

The bootstrap.js are a bit like a Web Worker in that it has it's own scope which has a special set of global variables and it is notified of events by defining specially named functions which are meant to receive these events. They are disimilar from Web Workers in that they don't run in their own thread, they can pass objects in and out (not just strings), and they have access to pretty much everything using the Components global object which is made available to them.

For a complete list of the functions bootstrap.js uses, there parameters, etc, then I suggest reading this article on Bootstrapped Extensions. The bottom line is that you need to define functions which will handle install/uninstall/enable/disable/startup type events. For instance the first function that you'll want to define is startup(), because this is the function which would be called after the add-on is installed, after it is enabled, or after the browser starts up. Because a add-on typically needs to know why the startup function was called there is an aReason argument passed in to the function (read the docs on how to use aReason). The second function that you'll likely require is the shutdown() function, this is a very important part of restartless add-ons, it's basically the cleanup function, it has to remove any evidence that your add-on was ever present in a live browser session. In other words if your add-on adds/does X to the browser, then the shutdown() function will remove/undo X, where X can be stuff like adding xul elements and/or attributes, adding javascript variables to other scopes, adding observers/event listeners, creating background operations, and various other things; all of which are extremely easy to forget to remove.

Note: If you'd like to make your add-on restartless and backwards compatible, then read this.

Summary

We now know a restartless add-on needs two files: first a install.rdf file, and second a bootstrap.js file; we also know that the latter will be notified of install/uninstall/enable/disable type events related to the add-on which we are expected to define in order to use.

It may surprise you how many add-ons could be made with these two files alone. Most user chrome scripts or jetpack prototype scripts could be written as a bootstrap.js file and made into a extension simply by adding a install.rdf file and zipping the two together as a .xpi file.

[More]

Scriptish 0.1b4 Can Update Your User Scripts

For a long time now both user script authors and Greasemonkey users have wanted Greasemonkey to be able to update user scripts. In absence of this feature user script authors have resorted to rolling their own code to do the updating, which has lead to a minefield of problems ranging from simple javascript errors to causing long downtimes on userscripts.org. Besides the obvious risks that third-party user script updaters have of not working, over using bandwidth, and harming the uptime of important websites, there is also a variability of design and usefulness which means it's hard for a user to know things that they should know, like:

  1. Which of their installed user scripts are update-able?
  2. Which of their installed user scripts have updates available?
  3. Where will they be notified of updates for there user scripts?
  4. What do they need to do in order to update a user script?
  5. How can they disable updates for a user script?

Hopefully it's now plain to see why a user script engine must update user scripts, and yet none that I know of do. Greasemonkey for Firefox does not. Google Chrome runs user scripts natively, but also does not update them. Those two engines are by far the most popular, but there are a few others, all of which do not update user scripts..

Well late last year I started discussing this feature with Olivier Cornu for his fork of Greasemonkey which he called Webmonkey. Months later I decided to start a Greasemonkey fork of my own, which I call Scriptish. Then, as this year went by I discovered Mozilla's plans for the new Addon manager and the stars seemed to align.

So a couple of days ago I finally got around to implementing user script updating in Scriptish, and released the feature in Scriptish 0.1b4 last night; now I'd like to discuss it a little bit for those that are interested.

User Script Updating in Scriptish

@updateURL

You may specify a URL for Scriptish to check for updates using the @updateURL metadata block key. The @updateURL must be a secure url (ie: starts with "https://"), otherwise it will be ignored.

The optional use of ".meta.js"

To save bandwidth, and improve response times, you may optionally provide a ".meta.js" version of your user script as the @updateURL, which should be the same as the user script, but only containing the user script's metadata block. If you provide a @updateURL that ends with ".meta.js", then Scriptish will check then ".meta.js" file for updates, and download an update from the same url where ".meta.js" is replaced with ".user.js".

The option to use the download URL as the update URL if no @updateURL was provided.

If you go to the options window for Scriptish you will see a preference which allows you to use a user script's download URL as the update URL if there was no @updateURL defined; this option is turned off by default.

Using ".meta.js" by default from userscripts.org

If the update URL that should be used to check for updates is a userscripts.org URL, then the ".meta.js" version of the user script will be used by default.

The GM_updatingEnabled constant

All versions of Scriptish that provide user script updating will include a GM_updatingEnabled constant in the user script sandbox, this means that if you are a user script author that has implemented a updater for your script already, then your code can check if this variable is defined and equal to 'true' before attempting to check for updates.

Conclusion

A user script engine that updates user scripts has been long desired, so I'm proud to introduce the feature for the first time in Scriptish, and I hope you give it a try!

Why is Scriptish only for Firefox 4?

One little tidbit I didn't go over yesterday about Scriptish was why I decided not to support any version of Firefox less than 4.0, so I thought I should mention my reasons behind that decision.

The primary reason I did it was to shave a large amount of time off of the time to release tbh. Another reason was that by raising the min version of Firefox supported to 4.0 I was able to delete large amounts of legacy code, which means performance gains. The final reason was that there was a Add-on Manager API being introduced in Firefox 4 which allows Firefox extensions to easily add extension types to the add-on manager, which means Scriptish can rely on that entirely, and ditch even more legacy code, Dave Townsend (Mossop) describes how to extend the add-on manager here.

Scriptish Beta! A New Greasemonkey

My favorite browser extension for quite a while has been Greasemonkey (GM), I loved how simple it made customizing the web with JavaScript (JS) which allows all users to both filter out the crap that site owners try to provide us (ie: ads, share links, suggested junk) and add new useful features anywhere you that wish. Greasemonkey allowed us to customize the waves as we surfed the web, as well as making a few modifications to the board, and I loved it for that.

Some problems arose with Greasemonkey though, because it was built for Firefox (FF) 1.5, and even the latest GM version (0.8.6) claimed to support 1.5, which meant that Greasemonkey the GM maintainers did not want to introduce new features that would not be available in FF 1.5, and they did not want to take advantage a of newer version, like say FF 3.0's JavaScript Modules (JSM), which would allow GM to use much less memory and improve performance when a user is using multiple windows, and on startup because less JS would need to be loaded at startup.

The good news for GM is that with the next major release, 0.9 (which contributors including myself have been working on for quite some time now) is that the minimum version of Firefox that Greasemonkey will support will be FF 3.0, which means it can now take advantage of the benefits that FF 3.0 offers, finally. The trouble I found while trying to contribute to GM is that the maintainers don't really want to alter the code base, they're actually seem to be fine with doing the minimum necessary and bug fixing for the rest of the future, at least that is how it seemed to me after writing quite a few patches which were rejected, either outright, or by degradation (meaning I would have to basically redo everything, on crappy architecture, because they've sat on their hands for so long). Patches such as using JSM, reg exp @includes, @icon, and a few more.

So the reason that I decided to bypass Greasemonkey and start working on a fork, which I now call Scriptish, is because I was tired of beating my head against a wall, I wanted to have and use the Greasemonkey that I always wanted asap, so I'm making it now. I've always enjoyed working with others, and I hope that I can convince some GM contributors to start working on Scriptish in the future.

Scriptish

Scriptish is a fork of GM, you can think of it as a superset of Greasemonkey, it can do whatever GM does, and more. Some of it's new features are:

  • @author - displayed to users in the addon manager
  • @contributor - displayed to users in the addon manager
  • @homepage or @homepageURL - displayed to users in the addon manager
  • @icon or @iconURL - Include a icon for your user script which is displayed in the addon manager and for notifications from the script.
  • @screenshot - displayed to users in the addon manager.
  • @match - a include pattern introduced by Google Chrome user scripts.
  • @noframes - a simple way to prevent a user script from running in iframes.
  • GM_worker - use a Worker within user scripts.
  • GM_notification - send a Growl style notification to the user.
  • GM_setClipboard - save data to the clipboard from a user script.
If you'd like to do some reading on these features, then checkout the Scriptish wiki, which should explain them all in detail.

Other changes include major reorganization of the internal code, using JSM, in a way that only loads code into memory that will actually be used, by loading the req'd code the first time that it is going to be used. I was also able to remove a bunch of legacy code, because Scriptish will only support Firefox 4.0 or higher for the moment.

Give it a try!

If you'd like to give Scriptish a try, then you can download Scriptish here. If you want to get involved here are some links for you:

I hope you like it, I'll be working on it for the next few months at least, pretty hard I imagine; I'd like to make it restartless, and implement user script updates & communication asap. If you think you'd like to open it up and hack on it, then please do!!

User Script Tip: Using jQuery

A very common question that I see asked on stackoverflow.com, the greasemonkey-users mailing list, and in #greasemonkey on freenode, is how to use third party libraries with a user script.

The common response is to use @require, but these days people want their user scripts to work on Google Chrome too, and unfortunately Google Chrome user scripts do not support a lot of the Greasemonkey metadata block, including @require.. so another method is required.

Cross Browser Solutions

Use script element & setTimeouts

One method that has been mentioned elsewhere is to create a script element and add it to the page, and use setTimeout's to wait until the third party js, in this example jQuery, is loaded. The example in the link I provided is for Greasemonkey, but with little effort it can be made to work with Google Chrome.

Use script element & associated onload event

This method creates script element for jQuery (or some other third party script), and appends it to the body element, and listens to the load event to figure out when the library is loaded and ready to be used.

Google Chrome does not allow user scripts to access javascript objects in the page scope from the user script's scope, so in order to use jQuery at all on Google Chrome you must load jQuery into the page scope and inject your script into the page scope. So this is what my example below does essentially, more specifically it stringifys a callback function which is called when jQuery is done loading.

Example

A Jetpack Module for the Microformats API

After a blog post I wrote last month, "The Context Menu Module & Microformats", Michael Kaply wrote a comment reminding me that there is an API for Microformats built in to Firefox.

So I spent a little time getting familiar with with the API and wrote a Jetpack module for it, which you can find here. This module just a wrapper to export the Microformats API, which can be seen in this file (only viewable from Firefox).

It might be the smallest module that I ever write =]

Get the Microformats Jetpack Module here.

The Principle of JavaScript Illumination

Every web developer knows that their site should work when the site's users disables JavaScript, but I constantly find this principal is violated when it is simple thing to obey. When web UI developers violate this principal it shows an extreme laziness and a failure to understand good practices on their part; this can greatly worsen the user experience. So, I'd like to articulate what I'm going to call The Principle of JavaScript Illumination which is an idea I haven't seen articulated yet, but that everyone knows in their gut they should do, and yet I hardly ever see this principal adhered to in practice.

Definition

The Principle of JavaScript Illumination is a simple one, if some user interface element will not work when JavaScript is disabled, then have JavaScript add the element.

Implications

If you have a user interface (UI) element that uses JavaScript, then make sure there is a default functionality that will work when JavaScript is disabled. If the UI element cannot work at all without JavaScript, then you should make JavaScript add the element, this way if JavaScript is disabled then the user will not see UI elements that will not work, causing a bad user experience.

Examples

Facebook Chat

When you log in to Facebook with JavaScript disabled, the chat widget is displayed to you on the bottom right of the screen, but it doesn't do anything, it's completely useless to you and it's just in the way, so why the heck is it displayed to the user?

Kampyle

Kampyle provides some widget code for anyone to put on their site which adds a "Give Feedback" button to the bottom right of the user's screen, which the user can click on to give feedback for the site. The trouble is, when JS is disabled, or when the user uses NoScript to block assets from the kampyle.com domain, then the button doesn't work (see ROIRevolution's site for an example of this), so why the heck is it displayed to the user?

Conclusion

Be a good web citizen, and a smart web developer by obeying The Principle of JavaScript Illumination, because it's the right thing to do and because you're users will thank you for it.

More Entries

© Erik Vold 2007-2012. Contact Erik Vold. Top ^