2008-11-30

simpler, easier string bundle API

In the process of improving localizability in Snowl today, I put together a StringBundle JS module to make it easier to access string bundles from other JS modules (and JS XPCOM components and chrome JS, for that matter).

The module has a simple, easy-to-use API with a single get method that retrieves both plain and formatted strings:
let strings = new StringBundle("chrome://example/locale/strings.properties");
let foo = strings.get("foo");
let barFormatted = strings.get("bar", [arg1, arg2]);
for each (let string in strings.getAll())
dump(string.key + " = " + string.value + "\n");
However, it also supports the API for the stringbundle XBL binding to make it easier to move from the binding to the module:
let strings = document.getElementById("myStringBundleElement");
new StringBundle("chrome://example/locale/strings.properties");
let foo = strings.getString("foo");
let barFormatted = strings.getFormattedString("bar", [arg1, arg2]);
let enumerator = strings.strings;
while (enumerator.hasMoreElements()) {
let string = enumerator.getNext().QueryInterface(Ci.nsIPropertyElement);
dump(string.key + " = " + string.value + "\n");
}
The module is available with the other useful JS modules in the jsmodules project on mozdev.org. Documentation is embedded inside the module as well as on the project wiki.

2008-11-26

Third (and Final) Preview of Snowl 0.2

I've pushed a new preview release of the next version of Snowl. It includes an updated design for the "river of news" view with many improvements suggested by Alex Faaborg and others.



It also supports sending tweets in addition to receiving them via an interface built into the river and stream views. Just click on the "write a message" icon to expose it:



And it supports multiple Twitter accounts (thanks to Boris Zbarsky, who helped resolve a final vexing issue with HTTP auth), so if you happen to have more than one, you can keep track of all of them:



alta88 also contributed a bunch of improvements and bug fixes, including a context menu for sources with some common commands (like Refresh and Unsubscribe). And Atul Varma contributed a patch that will make it easier to plug in support for other sources of messages.

Finally, I fixed a big performance bug that was slowing down database queries for message content linearly with the database's growth in size. It looked something like this (courtesy beneneuman):



This preview is feature complete, which means no additional features will land in 0.2. From here on out, it's only important bug fixes, minor look and feel enhancements, localization support, and other low risk/polish/completeness issues until the release of 0.2.

So try it out, and let me know if you run into any of those kinds of problems (or any others, for that matter, as 0.3 development will be gearing up soon).
Link
Install | Discuss | Chat | Bug Reports | Report a Bug | Source Code

Warning: this version is a primitive implementation with many bugs, and subsequent versions will include changes that break functionality and delete all your messages, making you start over from scratch.

2008-11-19

making OS X stop prompting me for my wireless password

I've long had problems automatically reconnecting to a certain WPA Enterprise wireless network from my MacBook Pro running Mac OS X 10.5.

Despite deleting the network from System Preferences > Network > AirPort > Advanced... > Preferred Networks, deleting its password from the login and System keychains using Applications > Utilities > Keychain Access, and then saving the network and credentials anew, the OS would prompt me to reenter my password for it every time I reconnected to it.

Some digging around on the web suggested that folks having a variety of Mac OS X wireless networking problems can sometimes solve them by deleting various configuration files, including /Library/Preferences/SystemConfiguration/com.apple.airport.preferences.plist.

I checked that file, and strangely, it listed a very old password for that wireless network. Turning off AirPort, deleting that file (I made a backup first just in case), and turning AirPort back on has resolved the problem. I can now reconnect to the network repeatedly (including after a reboot) without being prompted for my password.

2008-11-17

news: links to Google Groups

(Note: updated with a more reliable representation of the link.)
(Note: updated again to support news:<newsgroup-name> links.)

Here's an obscure hack.

Firefox 3 supports web protocol handlers (f.e. sending mailto: links to Gmail), while Google Groups archives Usenet. But as far as I can tell, Google Groups doesn't support news: links, even though Firefox can send them to it. It does, however, support retrieving messages by their IDs or newsgroup names, which news: links contain.

So how could we get Google Groups to load news: links passed to it by Firefox?

The solution is to pass them to an intermediary that extracts the IDs/names from the links and passes them to Google Groups. Here's a data: URL that does that:
data:text/html,\
<html><body><script>\
var url = '%s';\
var stripPrefix = /^news:(\\/\\/[^\\/]+\\/)?/;\
if (/@/.test(url))\
window.location = 'http://groups.google.com/groups?selm='+url.replace(stripPrefix, '');\
else\
window.location = 'http://groups.google.com/group/'+url.replace(stripPrefix, '');\
</script></body></html>

Unfortunately, it isn't easy to configure Firefox to use this URL as the news: protocol handler. Manually hacking mimeTypes.rdf is most unpleasant, while window.navigator.registerProtocolHandler only registers http(s): URLs.

But it's possible to construct a javascript: URL that does the same thing registerProtocolHandler does, namely call the handler service and register the handler with it directly.

The only trick is that you have to enter and run it in the Error Console, which has chrome privileges. Otherwise the URL won't have the privileges it needs to register the handler.

Here's a javascript: URL that registers the handler:

javascript: var Cc = Components.classes; var Ci = Components.interfaces; var handler = Cc['@mozilla.org/uriloader/web-handler-app;1'].createInstance(Ci.nsIWebHandlerApp); handler.name = 'Google Groups'; handler.uriTemplate = "data:text/html, <html><body><script> var url = '%s'; var stripPrefix = /^news:(\\/\\/[^\\/]+\\/)?/; if (/@/.test(url)) window.location = 'http://groups.google.com/groups?selm='+url.replace(stripPrefix, ''); else window.location = 'http://groups.google.com/group/'+url.replace(stripPrefix, ''); </script></body></html>"; var eps = Cc['@mozilla.org/uriloader/external-protocol-service;1'].getService(Ci.nsIExternalProtocolService); var handlerInfo = eps.getProtocolHandlerInfo('news'); handlerInfo.possibleApplicationHandlers.appendElement(handler, false); handlerInfo.alwaysAskBeforeHandling = true; var hs = Cc['@mozilla.org/uriloader/handler-service;1'].getService(Ci.nsIHandlerService); hs.store(handlerInfo); window.location = "data:text/html, <body> <p>The news: link handler for Google Groups has been installed. Try it with these links:</p> <ul> <li><a href='news:mozilla.support.firefox'>news:mozilla.support.firefox</a></li> <li><a href='news://news.mozilla.org/mozilla.support.firefox'>news://news.mozilla.org/mozilla.support.firefox</a></li> <li><a href='news:37604264.65F502CD@netscape.com'>news:37604264.65F502CD@netscape.com</a></li> <li><a href='news://news.mozilla.org/37604264.65F502CD@netscape.com'>news://news.mozilla.org/37604264.65F502CD@netscape.com</a></li> </ul> </body>";


To use it, copy the URL, open the Error Console (Tools > Error Console or Ctrl/Command+Shift+J), paste the URL into the evaluation field, and press the Evaluate button.

Once you've done that, try it out with these links:When you click one, Firefox will ask you what you want to do with the link, and Google Groups should be an option on the list.

For the curious, here's a nicely formatted version of the code inside that javascript: URL, which is based on WCCR_addProtocolHandlerButtonCallback:
javascript:
var Cc = Components.classes;
var Ci = Components.interfaces;

var handler = Cc['@mozilla.org/uriloader/web-handler-app;1'].createInstance(Ci.nsIWebHandlerApp);
handler.name = 'Google Groups';
handler.uriTemplate = "data:text/html,\
<html><body><script>\
var url = '%s';\
var stripPrefix = /^news:(\\/\\/[^\\/]+\\/)?/;\
if (/@/.test(url))\
window.location = 'http://groups.google.com/groups?selm='+url.replace(stripPrefix, '');\
else\
window.location = 'http://groups.google.com/group/'+url.replace(stripPrefix, '');\
</script></body></html>";

var eps = Cc['@mozilla.org/uriloader/external-protocol-service;1'].getService(Ci.nsIExternalProtocolService);
var handlerInfo = eps.getProtocolHandlerInfo('news');
handlerInfo.possibleApplicationHandlers.appendElement(handler, false);
handlerInfo.alwaysAskBeforeHandling = true;

var hs = Cc['@mozilla.org/uriloader/handler-service;1'].getService(Ci.nsIHandlerService);
hs.store(handlerInfo);

window.location = "data:text/html,\
<body>\
<p>The news: link handler for Google Groups has been installed.\
Try it with these links:</p>\
<ul>\
<li><a href='news:mozilla.support.firefox'>news:mozilla.support.firefox</a></li>\
<li><a href='news://news.mozilla.org/mozilla.support.firefox'>news://news.mozilla.org/mozilla.support.firefox</a></li>\
<li><a href='news:37604264.65F502CD@netscape.com'>news:37604264.65F502CD@netscape.com</a></li>\
<li><a href='news://news.mozilla.org/37604264.65F502CD@netscape.com'>news://news.mozilla.org/37604264.65F502CD@netscape.com</a></li>\
</ul>\
</body>";

Of course Google Groups could make this hack moot by simply supporting news: links and protocol handler registration natively. For info on how to do that, Google Groups, see the news: URL standard in RFC 1738 and these subsequent IETF drafts, then read the Devmo docs on window.navigator.registerProtocolHandler.

Working at >Play

Saturday I staffed the Mozilla Labs booth at the >play conference expo along with my colleagues Atul Varma (who also blogged about it) and Jay Patel.  The event went really well, with a steady stream of visitors to our booth who were curious about Mozilla Labs projects and Mozilla in general.

Profit is not a Four Letter Word Here

I noticed early on that my standard spiel about Mozilla being nonprofit wasn't having the effect it normally has.  Usually people nod approvingly when I explain that we are a mission-driven organization and community, but this time I was getting blank stares or even slight airs of disapproval.

I suppose that's because the conference was sponsored by a business school, and its attendees are mostly aspiring businesspeople, so nonprofit endeavors like ours don't have the same cachet they do amongst the web and open source hackers I usually meet at conferences.

The Pitches & Audience Reactions

Nevertheless, everyone was really friendly and positive about Firefox, and many of them had heard of at least some of the labs projects we were demoing (Weave, Ubiquity, Snowl, and the Concept Series, among others).

Everyone I asked also knew what open source was, and they were overall very positive about our plan to get more designers involved in open source development through the Concept Series.

And the booth duty was great for refining my descriptions of those projects.  There's nothing like saying the same thing over and over again to separate the essence of the point from the redundant, misunderstood, or just plain unnecessary cruft.  By the middle of the day, my pitches were crisp and tight.

Miscellaneous Firefox Feedback

One visitor told me he switched back to Safari because it lets him email a whole page (not just a link) with one click.

Another attendee asked for a lighter-weight variant of the master password feature that protects Show Passwords in the Saved Passwords dialog but doesn't prompt you to enter the master password each session, so you can protect your passwords from being retrieved by others without the annoyance of having to enter the master every time you restart your browser.

A third person asked for a way to see only unvisited search results when searching the web, so he can check occasionally to see if there's new information about a search term without having to scroll through all the stuff he's already seen.

And a professor suggested we could grow marketshare among students by pitching Firefox as the best browser for the popular iLearn online education software, as she solves many of her students' problems accessing her online classes by getting them to switch browsers.

2008-11-13

egg timer for lightning talks

Yesterday, for the Lightning Talks portion of the Mozilla Labs Meetup, I couldn't find a good online egg timer, so I created a simple one and put it up at eggtimer.org.  Since then I've found lightningtimer.net, so I guess it wasn't necessary, but it's there now anyway.

2008-11-10

<jstemplate>


<jstemplate>
<description>Hello world!</description>
<description>{"Hello world!"}</description>
<description value="{'Hello world!'}"/>
</jstemplate>


<description>Hello world!</description>
<description>Hello world!</description>
<description value="Hello world!">




<description>Good { new Date().getHours() > 11 ? "afternoon" : "morning" }.</description>


<description>Good morning.</description>




<?js for each (let i in [1, 2, 3]) { ?>
<description>There's no place like home.</description>
<?js } ?>


<description>There's no place like home.</description>
<description>There's no place like home.</description>
<description>There's no place like home.</description>




<script type="application/javascript">
let people = [
{ name: "Jim", rank: "celebrated", cerealBrand: "Special K" },
{ name: "Midge", rank: "heard of", cerealBrand: "Trix" },
{ name: "Genie", rank: "unknown", cerealBrand: "Muesli" },
];
</script>

<jstemplate>
<table xmlns="http://www.w3.org/1999/xhtml">
<tr>
<th>Name</th>
<th>Rank</th>
<th>Cereal Brand</th>
</tr>
<?js for each (let person in people) { ?>
<tr>
<td>{person.name}</td>
<td>{person.rank}</td>
<td>{person.cerealBrand}</td>
</tr>
<?js } ?>
</table>
</jstemplate>


<table xmlns="http://www.w3.org/1999/xhtml">
<tr>
<th>Name</th>
<th>Rank</th>
<th>Cereal Brand</th>
</tr>
<tr>
<td>Jim</td>
<td>celebrated</td>
<td>Special K</td>
</tr>
<tr>
<td>Midge</td>
<td>heard of</td>
<td>Trix</td>
</tr>
<tr>
<td>Genie</td>
<td>unknown</td>
<td>Muesli</td>
</tr>
</table>



The code is available. Your thoughts & comparisons to JavaScript Templates, Better JavaScript Templates, JSmarty, seethrough and others are welcome!