Next week in our Open Source Mozilla Development class, the students will be building Firefox from source, and learning about the Mozilla build. In preparation for it, I worked on a couple bugs today, to get back into shape after a summer of reading books instead of compiler warnings.
Before I start my weekend, I want to take a minute to reflect on what the experience of working on, and within open source is like. I write this for the benefit of my students, who are just getting started doing the same thing. I'll have much to say in the coming weeks about the importance of failure, and also how to succeed. But both failure and success look the same from the proper distance--they are simply the attempt. This is the story of what it's like to make an attempt in the context of an open source project.
A while back I finished work to fix how Thunderbird does the message count on the dock icon (see bug 274688). Since that fix went in, people have been using the new code in Thunderbird Nightly builds, and they've noticed some more bugs. First, it appears that messages are being double-counted due to the new Smart Folder feature (see bug 516477). I need to fix this before Thunderbird 3 ships, so I took the bug (e.g., assigned it to myself) and started looking at the code. However, I got distracted throughout the day, and at one point read this neat post by Daniel Glazman on Planet Mozilla.
Daniel was interested in making it possible for XULRunner apps to put a number on the dock icon, and he mentioned that he had taken code from nsMessengerOSXIntegration.cpp. I spend a lot of time in this file, and it instantly made me wonder how he'd done it, reminding me of another bug I need to fix related to our current drawing code, which leaks memory (see bug 508001). I looked at his code and realized that he was going to suffer from the same memory leak that I am. I left a comment in his blog.
The next day Daniel emailed me, and then posted his code. Another person commented in his blog with details about how to do this using the new Leopard API, and then he posted a third version, which is really elegant and much more simple. After looking at his code I realized that I couldn't do what he'd done because it will only work on OS X version 10.5 and later (e.g., it won't work on 10.4, which Mozilla still supports). So I forgot about it.
Except, I couldn't seem to forget about it. What if I could use this same method? I decided to do a test. I took the current code, and his code, and merged them. To do it, I had to call Objective-C from within my C++ file. It's possible to mix C++ and Objective-C/C++ (we do it all over the place in Mozilla). What you have to do is make your .cpp file an Objective-C++ file (i.e., an .mm file) and then tell the build system about it. In your Makefile.in, you have to add your file to CMMSRCS instead of CPPSRCS.
I made the change and did a test. After some fiddling with the build system (it still wanted to build my .cpp because I hadn't deleted the old file), I had a working Thunderbird. When I ran it I saw the look-and-feel of a native OS X dock icon. Now I was convinced I needed to do this for real. But how to deal with the differences between OS X 10.4 and 10.5+?
I wasn't sure, so I did what you do in open source: I a) looked at the other Mozilla source code for clues; b) asked some people who might know. Looking through the code I saw lots of compile-time checks, but nothing at runtime. I needed to know when the app started-up what version I was on, and therefore which API to use. Next I tried asking on irc, in the #macdev channel. It was almost midnight, but luckily three people were around, and they each had a different idea for me: 1) I could consider using nsToolkit::OnLeopardOrLater(); 2) I could try doing what was done for the wifi scanning code (e.g., try, fail, fall back); 3) try to add this version check to another bug that was doing similar things. I read through all the options, and went to bed unsure of the best solution.
The next day I decided to ask two more people. One of them was the person who would eventually review my code, so his opinion mattered to me. The other was a friend who works for Mozilla and knows a lot about dependencies between various parts of the code. I really wanted to use nsToolkit::OnLeopardOrLater(), but it isn't exported (e.g., it's an internal API, and the .h file is not accessible to my class). They both suggested I just copy the code into my file, and later, when we move to 10.5+ support, remove it.
I made the change, and now the code supports both 10.4 with the existing drawing code, and 10.5+ with the new drawing API. I'm quite pleased with it, and hope that it gets accepted by my reviewer. Regardless of what happens, I learned a lot through this process. I learned how to mix C++ and Objective-C code, version differences between OS X APIs, build system tricks, how we do dynamic version checks in other parts of the code, etc. I was also reminded how much faster you can work when you work in a community setting vs. on your own. Through blogs, public source code, and irc I was able to get this done with only a couple hours of learning and work. It was time well spent, both for Thunderbird and for me.