nsString dave=fail;

I'm going to have something to say in a little while about two of my favourite topics: coping and failure, and on the meaning and implications of each.  They are linked in important ways.  However, today I'm going to model some of what I'll say then, and to do it my subject will be the Mozilla String API.

In the lead-up to Thunderbird 3, I'm trying to get a bunch of Mac integration and enhancement bugs fixed, specifically with regard to how new mail notification works.  I've got most of them out of the way now, and only a few left that need review or clean-up so I can post the patches.  It's been really smooth learning the Thunderbird code, and I've enjoyed myself (almost) every step of the way.

And then I started working on bug 460287.  Wouldn't it be nice if when you got a new mail notification via Growl, it let you know who it was from?  Of course it would, and I know just how to do it.  The only thing that stands in my way is the Mozilla String API in all its labyrinthine glory.  What I need to do is get the folder that just got new mail, extract the new messages, parse their headers to find the author string, extract the name portion (if present), notify observers that I'm doing this and give them a chance to veto, take those names and insert them into a localized string, and pass that up to growl for display.  Easy.

But, my current attempt crashes Thunderbird; or, I should say, it crashes it again, because I've fixed a bunch of crashes caused by my string use already.  As soon as I get done writing this I'm going to take another stab at fixing things, but first I want to write from a position of failure.  I want to write from the standpoint of failure vs. success because I want to show that failure is not something to be feared, but rather a form of information.

So why are strings in Mozilla so bloody hard?  What am I doing that is so prone to error?  The problem is that a string is not simply a sequence of characters.  Strings are hard because language is hard, and when you have software that supports so many languages, you can't make even basic assumptions based on your own understanding of what is and isn't possible.  Further, there are different ways to represent the same thing in memory.

To go back to what I'm trying to do, let me show you the representations of the strings I'm working with at each step of the way:

  1. Get the author for the current message -- char**
  2. Extract the name portion -- nsACString
  3. Notify observers of this notification for the author -- PRUnichar*
  4. Localize this string -- PRUnichar* + nsXPIDLString
  5. Pass this to growl for display -- nsAString
    This simplifies it a bit, but I'm too frustrated to spend more time explaining it.  Suffice it to say, the movement from string representations, specifically from narrow (UTF8) to wide (UTF16) strings, is something I don't have quite right yet.  I mean, if your program crashes when you get new mail, that is a sort of notification.  It just might be too strong a message.

I've had to deal with the String API before, and I don't mind telling you that I'm not an expert.  It's confused me every time I've used it.  Normally my use of it involves one function that needs an implicit widening/shortening of a string, and I manage to get it working after a bit of trial and error.  However, in this case my method of trial by fire meant that I was just getting burned.  The only way for me to do my work was for me to spend an evening studying the Mozilla XPCOM String Guide.  Looking at other code was no longer helpful, because I had too many variables to manage for good guesses to work.  In order to cope, I had to pause so I could really understand this stuff.

That guide is really incredible.  If it didn't exist, I'd have no hope of doing any of this work.  Even with it I'm still confused about some things.  But for the majority of what I'm doing, it explains it quite clearly.  After reading it top to bottom and sketching out on paper what I needed to do, I was finally able to get my strings to work.

Next, I needed to get the localization of the text being shown to the user working properly.  I wanted to do something very simple, namely, create a comma separated list of names and addresses.  "Have you thought about RTL [ed. right to left] languages?"  No.  "Have you looked to see if all languages use a comma for list separation?"  No.  "Did you know you're pluralizing your strings totally incorrectly?"  No.

Just how do you localize strings?  You work with nsIStringBundle and Property files.  In essence, what I needed to do was something like this:

biffNotification_messages=%1$S new messages from %2$S.
This says that I want to pass two strings and have them merged into this third string.  Specifically, I will pass the number of messages (%1$S) and the list of names (%2$S).  Localizers can translate this third string and move the position of those other strings around as makes sense in their target language.

But I wanted to be a bit more clever.  In JS there is some nice code to let you do pluralization of localized strings, and I wanted to do something similar, even though I still have to use a hack to special case the single message instance:

biffNotification_message=One new message from %2$S.

biffNotification_messages=%1$S new messages from %2$S.
I wanted to use this trick of only using one of the possible strings (i.e., omitting %1$S) so I could do this in my C++ and pass it to the stringbundle for formatting:
NS_ConvertUTF8toUTF16 utf16Authors(authors);
const PRUnichar formatStrings[2] = { numNewMsgsText.get(), utf16Authors.get() };
Crash: null pointer error.  At this point I assume my issue is the string conversion (yes, I need another PRUnichar
), so I start debugging that.  But after a while it's clear that this isn't the issue.  The stringbundle itself is the culprit.  It seemed as though it was being set to null while I was still using it, and within an if(bundle) block!

After swearing and abandoning my computer for lunch, I came at the problem again, this time asking for help.  Luckily I got the one man who really knows about this code (Pike) to confirm what I was already suspecting:  If you send an array of 2 strings to be merged with a properties string, you have to use both of them in the properties file:

biffNotification_message=%1$S new message from %2$S.

biffNotification_messages=%1$S new messages from %2$S.
Also, it's not possible to have a trailing space on a property value.  So instead of this for my localized comma separator:
(See the trailing space? Don't worry, neither does Mozilla)...I need to do this:
I'm writing this in order to talk about failure and coping--you shouldn't copy my code exactly as it is now, because it's wrong.  While I've been writing this I've had two bugmails, one rejecting my patch, and another with a crash stack and some interesting information about how I'm not properly handling the German umlaut.  I've got the failure down.  So what about coping?  I said at the beginning that failure is really information.  You don't fail and therefore become a failure.  You fail and in so doing you learn and gain more understanding.

My failure has caused me to go back and read the XPCOM String Guide, re-learn to leverage the knowledge in the community and its willingness to help, given me insight about what does and doesn't work with regard to localized strings, etc.  Failure is information if you use your mistakes to learn and teach something new.  I don't want to pretend that I enjoy being wrong, or like showing you that I've made mistakes.  I don't.  At the same time, I've come to understand that if I'm not making mistakes it means I'm not trying hard enough, and I'm not pushing myself far enough.

I write this in a moment of failure without the answer in clear view.  This failure is information I take with me as I move forward toward the solution.  I am not broken by my failure, but encouraged to know that I now have a handful of mistakes and some new information that are key to my continued growth.

Show Comments