Saturday, April 17, 2010

1 Item Remaining Problem In IE

There are a bunch of different bugs in IE which manifest themselves as a page which doesn't finish loading and displays "X items remaining" (or "1 item remaining") in the status bar. C.M. Palmer on Stack Overflow has a pretty good answer for one of these bugs. I recently found an answer to another.

One of the symptoms of this particular bug (and probably others) is that it only happens in certain environments. I finally got access to one client's environment where this happened, and was able to track it down. Intermittently, and for no apparent reason, IE would fail to load the following script:

<script defer src=//:></script>

Yes, that's a script element with a src of "//:". This ugly hack is actually the best way to simulate the DOMContentLoaded event in IE (the event which signals that the HTML page has been parsed and its document object-model is fully built), as Dean Edwards and friends have figured out (they also tried src values of javascript:void(0), javascript:false, and //0 before settling on //:).

Fortunately, there's another trick that works for this purpose, as Diego Perini discovered: skip this ugly script element, and poll to check if document.body.doScroll() is working yet. The downside is that because it requires a timer to call doScroll() every X milliseconds, you won't be notified of the DOM's readiness quite as quickly as when using the "//:" hack (whereby you listen for onreadystatechange events on the script element — so you're notified of the readiness immediately, rather than having to poll for it).

The upside, of course, is that the doScroll() method works more reliably, with no "1 item remaining" for which to wait indefinitely. Another problem I had noticed in the past with the script element method was that IE sometimes would fire the onreadystate complete event before the DOM was loaded (and I had worked around that by checking if the page's footer element existed, and if not, waiting until it did). I haven't found any holes with this doScroll() technique yet, though, so I'm happy so far.

The 1.6.x version of the Prototype JavaScript Framework used the script technique (and I'm sure other libraries have, or still do); the 1.7 version has been updated with the doScroll() method. Here's the specific code it uses for the doScroll() technique (I've added comments where it uses Prototype-specific code, in case someone wants to adapt it to a non-Prototype environment):

(function() { /* Support for the DOMContentLoaded event is based on work by Dan Webb, Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */ var timer; function fireContentLoadedEvent() { if (document.loaded) return; if (timer) window.clearTimeout(timer); document.loaded = true; // raise the Prototype dom:loaded function // (your custom ondomcontentloaded code goes here) document.fire('dom:loaded'); } function checkReadyState() { if (document.readyState === 'complete') { // Prototype for removeEventListener()/detachEvent() document.stopObserving('readystatechange', checkReadyState); fireContentLoadedEvent(); } } function pollDoScroll() { try { document.documentElement.doScroll('left'); } catch(e) { // Prototype for setTimeout() timer = pollDoScroll.defer(); return; } fireContentLoadedEvent(); } if (document.addEventListener) { document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false); } else { // Prototype for addEventListener()/attachEvent() document.observe('readystatechange', checkReadyState); if (window == top) // Prototype for setTimeout() timer = pollDoScroll.defer(); } // Prototype for addEventListener()/attachEvent() Event.observe(window, 'load', fireContentLoadedEvent); })();

Saturday, April 3, 2010

These Aren't the Droids You're Looking For

So a few weeks ago Google contacted me about the Device Seeding Program for Top Android Market Developers — they wanted to give me (and a bunch of other developers) a free phone for having a popular app on the Android Market (mine is Ring Toggle). I thought "Awesome!", and signed up.

Throughout most of the world, Google is sending developers the Nexus One. In the US, they're sending half the developers (by random distribution) Nexus Ones, and half Motorola Droids. They shipped me my phone yesterday, and when I opened the box, it was a Droid.

I thought "Awesome!", and I shut down my old HTC G1, pulled out the SIM card and popped open the Droid. I hunted around inside the Droid's battery compartment, but I couldn't figure out where to stick the SIM. So I googled it — and it turns out the Droid is hardwired to Verizon's nextwork. Mother fucker!

Right now I've got six months left on my plan with T-Mobile, where I'm paying $30 for like 300 minutes and $25 for the data plan (plus 400 text messages). The cheapest equivalent plan with Verizon is $60 for 450 minutes and unlimited text messages, and $30 for the data plan. So to actually use this new Droid, I have to pay $90 a month on top of my existing T-mobile plan for the next six months. Thanks Google, thanks a bunch.

I know I shouldn't be angry at Google — giving away free android phones is really cool of them — but I can't help feeling that instead of a pat on the back, I got a kick in the nuts. And the thing is, after a few minutes of awkwardness when I turned on the Droid and followed the prompts to "activate" it by calling Verizon (the call-center guy was really nice, but even more confused than I was — turns out there's no point in calling Verizon because they have no clue what's going on), I found that the Droid is a really awesome android device. It's so much more responsive than my G1, and it's got the slicker 2.0 android OS (I'm stuck on 1.6).

So I'm completely sick because I want so badly to use this new Droid instead of my old G1, but I have a hard time justifying adding a second phone plan on top of the one I already have, especially since I don't really use my phone for phone purposes all that much. But at the same time, I can't justify lugging around two phones in my pocket, one to use as a phone and the other as an ipod. And I know I'm eventually going to give in and pay both T-Mobile and Verizon a bunch of money for the privilege of having a second-rate cellular-data plan and occasional phone service, just so I can carry around my super sweet new Droid in my pocket.

So I think a couple of big evil companies just played a Jedi mind trick on me.

Sunday, March 28, 2010

On Donner and Blitzen

The first company I worked for was pretty much a Windows-only shop. They used Exchange for email, VSS for version control, and Visual Studio as an IDE. This was the first time I had ever used Windows, and I disliked it (although not as badly as I thought I would) — but I didn't really have a choice. Since 2004, though, I've been fortunate enough to be able to use Linux on my primary dev box at work. My current company (and all the ones I've ever worked for) still uses Exchange, so I've struggled with how to deal with this over the years.

Now Dasher! Now Dancer!

When I first started using Windows, I didn't know the difference between Outlook and Outlook Express (apparently the former is an Exchange client and the other is a generic SMTP/POP client), and so I used Outlook Express (because I wanted my mail faster, of course). After a few months I finally figured out why I couldn't "accept" meeting requests: I needed to use Outlook for that. So I switched to Outlook proper; and even after I moved my primary dev box to Linux, I still kept a Windows machine on the local Windows domain to keep using Outlook (and also to test IE, of course).

Now Prancer and Vixen!

But switching back and forth between my Windows and Linux boxes got old pretty quick. First I tried Eudora as a SMTP/POP client (which I had used on the old Mac OS before OS X). That was okay, but POP is pretty primitive and usually doesn't work well (it's hard to keep the local read/deleted states in sync with the server, so you usually just end up downloading everything locally and deleting it on the server — which means you better have a good local backup story).

On Comet! On Cupid!

Then I tried Evolution as an IMAP client, which works (at least with Exchange) much better than POP. You can keep mail in sync on the server, and even use Exchange's server-side folder structure. The two big things missing still, though, are contacts and meetings. Evolution is supposed to be able to integrate more tightly with Exchange (possibly including these two features) via Exchange's web interface, but I've never been able to get that to work. So I just used Exchange's web interface whenever I wanted to lookup a person in Exchange, or to schedule/respond-to a meeting.

On Donner

Eventually I got restless, and decided to try Thunderbird. I can't say that it's really any better or worse than Evolution, just somewhat different. It's what I use now, but it still doesn't have contacts and meetings (although for a while I got it little bit of contact integration by syncing it with the corporate LDAP server). But then one day I found Lightning (and you're welcome for taking so long to get to the point of this post).

And Blitzen! (aka Finally, the Point of this Post)

Lightning, an extension for Thunderbird, allows just enough integration with Exchange's meeting functionality to be really really useful to me. Lightning is mainly a calendaring extension, but it's also able to parse Exchange's meeting-request emails — it displays a button right in the email to add the meeting-request to your Lightning calendar. So now I finally have a way to organize my meeting schedule (other than to highlight the meeting-request emails in my inbox — plus in the past Exchange unhelpfully moved these emails to a separate Calendar folder if I used the Exchange web interface to accept the meeting).

Here are some pics of the Lightning "Today Pane" (displayed on the right-side of my normal email listings), and the full Lightning "Calendar" interface (which is pretty much exactly what you'd expect and need from a calendering UI). My apologies for the super-gay Thunderbird persona (I can't seem to login to the Personas website in Thunderbird, so for now I'm stuck with picking one of the "popular" personas).

Lightning "Today Pane"
Lightning "Calendar"

On 64-Bit Linux

The one gotcha with this (and kind of my motivation for writing this post) is that the version of Lightning from addons.mozilla.org doesn't work on 64-bit Linux. Fortunately, Ubuntu has this documented and taken care of on their ThunderbirdLightning Community Documentation page. To make it work, I just had to download the 64-bit lightning.xpi, and install it manually (via the Install... button in the Extensions tab of the Add-ons dialog, which you can get at via the Tools > Add-ons menu item). Now dash away, dash away, dash away all!

Saturday, February 20, 2010

Private Branches in Subversion

For anyone still learning svn, this is what I've been doing when I create and use private branches (aka "feature" branches).

For me, the main motivation for creating a private branch is to stash some code on which I've been working when I have to switch over to something else before I've finished (or can't check it in yet for some other reason). You could also use a private branch to collaborate on (or just share) some experimental feature with another developer, or to submit a bugfix that needs to be reviewed before checking it into the mainline (and of course you can use the same switching/merging techniques with release branches as you do with private branches).

Here are two other tutorials for using branches (although some of Evan Weaver's "best practices," like deleting the trunk of the repository, are probably not actually best practices in practice):

1. Decide where to put the private branch in source control.

Some projects might just use the standard branches directory for this. Other projects have a sibling of the trunk called experimental or exp or just ex specifically for these kind of experimental branches. I'll use exp in this writeup, and I'll put it in a repository named my-project — although it can be anywhere (outside of the trunk or whatever part of the tree from which you're branching, obviously).

So, assuming you already have the my-project repo set up, first create a temporary working copy for exp in a temp directory (checkout with --depth empty to avoid pulling down the whole repo):

$ mkdir /tmp/my-project $ cd /tmp/my-project $ svn checkout —depth empty http://example.com/svn/my-project .

Then create the exp folder, and check it in:

$ mkdir exp $ svn commit -m 'created an experimental section for my-project'

2. Create the private branch.

Ideally, you've already got the working copy of the trunk locally, and updated it to the latest revision. In that case, you can just branch from the head revision of the trunk. You use the svn copy command to do this, specifying the source url (the trunk) and the destination url (the new branch root: my-new-branch). This is completely a server-side operation, so it doesn't matter from where you invoke it. Also, copying is very cheap in subversion, so it should execute quickly (if you ignore the usual network latency):

$ cd ~/projects/my-project $ svn update $ svn copy http://example.com/svn/my-project/trunk http://example.com/svn/my-project/exp/my-new-branch -m 'branch from trunk at 9269'

In practice, your working copy may be a few days out of date; in that case you should branch from the trunk revision to which you last updated (or update your working copy to the head):

$ svn copy -r 8189 http://example.com/svn/my-project/trunk http://example.com/svn/my-project/exp/my-new-branch -m 'branch from trunk at 8189'

3. Switch your working copy to the private branch.

Now that the branch has been created, you can switch the working copy of the tree you currently have checked out from the trunk to the new branch. If you want to switch your entire working copy, just use the switch command from the working copy root, and specify the url of the branch to which to switch:

$ cd ~/projects/my-project $ svn switch http://example.com/svn/my-project/exp/my-new-branch

You can also switch just part of the tree, like just one specific component on which you want to work:

$ cd ~/projects/my-project/foo/bar $ svn switch http://example.com/svn/my-project/exp/my-new-branch/foo/bar

Note that switch is very similar to update — it will update your working copy (at least the files you haven't modified locally) to match the revision of the branch to which you switched. If I just created my-new-branch from the head revision of the trunk, but I haven't updated my working copy in a while, when I switch from the trunk to my-new-branch, it's going to pull down all the files that have been updated since I last sync'd. If your working copy is messy or out of sync, you're probably better off switching just specific projects/components/folders/files within the branch, rather than the whole thing.

4. Commit to the private branch.

You can commit as often as you want to the private branch. Descendants of directories that have been switched automatically are committed to the branch to which their ancestors have been switched. For example, in my local my-project folder, the root of my working copy is pointed to the trunk, but the bar directory is switched to my-second-branch, and a.html is switched to my-first-branch:

projects my-project: ^/my-project/trunk foo bar: ^/my-project/exp/my-second-branch/foo/bar baz a.html: ^/my-project/exp/my-first-branch/foo/bar/baz/a.html b.html

If I commit a.html, it will be committed to my-first-branch; and if I commit b.html, it will be committed to my-second-branch. I haven't actually tried this out, but if try to commit both at the same time (ie by committing from any ancestor folder), I believe each will be committed to the correct branch (a.html to my-first-branch and b.html to my-second-branch) in the same atomic checkin. The same holds for other svn commands, like update, diff, etc: files will be compared to the branch/revision to which you've switched their nearest ancestor.

You can always tell which branch (and revision) to which you have switched a given file with the svn info command. Among other things, it will print out the URL of the corresponding file in source control, and the revision (of the branch) to which you last updated:

$ cd ~/projects/my-project $ svn info
... URL: http://example.com/svn/my-project/trunk ... Revision: 9269 ...
$ svn info foo/bar | grep URL
URL: http://example.com/svn/my-project/exp/my-second-branch/foo/bar

5. Merge updates from the trunk to the private branch.

If you've been working on your branch for a while, you may want to get the latest updates from the trunk to see how they work with your branch (and then continue working on your branch). You use the merge command for this. If you want to merge the entire trunk to your switched branch, it's pretty straightforward. You used to have to specify the revisions from which you originally branched (or the last revision you merged), but the latest version of svn (starting with 1.6?) can figure it out for you automatically — so you only have to specify the url of the branch from which to merge:

$ cd ~/projects/my-project $ svn merge http://example.com/svn/my-project/trunk

You can also use the merge command to merge just specific revisions or specific files from the trunk:

$ svn merge -r 9269:10035 http://example.com/svn/my-project/trunk/foo/bar/baz/a.html foo/bar/baz/a.html

You can always skip this step if you don't care to get the latest from the trunk while you're working — you can always check that your new code works with the trunk when you merge back from the private branch to the trunk (in the next step), anyway.

6. Merge the private branch back into the trunk.

Finally, when you're ready to merge back to the trunk, make sure you've committed everything you're going to commit to the private branch. Then switch your working copy back to the trunk:

$ cd ~/projects/my-project $ svn switch http://example.com/svn/my-project/trunk

If you've switched individual subdirectories in your working copy, you have to switch them back individually:

$ svn switch http://example.com/svn/my-project/trunk/foo/bar foo/bar $ svn switch http://example.com/svn/my-project/trunk/foo/bar/baz/a.html foo/bar/baz/a.html

Now merge in the changes from your private branch:

$ svn merge http://example.com/svn/my-project/exp/my-new-branch

If you've done any merging from the trunk to the private branch (step 5), you should also use the --reintegrate flag so svn knows it can avoid merging back changes from the branch to the trunk that it previously had merged from the trunk to the branch. Finally, commit the merged changes to the trunk:

$ svn commit -m 'merged my-new-branch into trunk'

7. Delete the private branch.

You don't have to delete the branch, but once svn has merged from the trunk to the branch and back again, it may have problems tracking further merges between the two branches. You can continue developing with the private branch if you really want to — you'll just have to be much more careful when you merge. The easiest thing simply is to create a new private branch.

If you do delete the branch and ever need, for some reason, to get back to it, it will of course still be there — you can just switch back to it with the svn switch command (or check it out as a separate working copy), as long as you know the revision range in which it existed (which you can always dig out of the logs):

$ svn delete http://example.com/svn/my-project/exp/my-new-branch $ mkdir ~/projects/my-project-old $ cd ~/projects/my-project-old $ svn checkout -r 10036 http://example.com/svn/my-project/exp/my-new-branch

Sunday, February 14, 2010

IE VPCs on Ubuntu

I love the fact that Microsoft makes these Internet Explorer Application Compatibility VPC Images available, but I hate the fact that they're so darn hard to setup on linux. Plus the last few times they've rev'd the images, they've packaged them in a way where the licensing breaks if you don't run them in Microsoft's VPC software (obviously available for Windows only). And of course, each of the different VPCs (for ie6, ie7, and ie8) use the same disk uuid, which doesn't play nicely with VirtualBox. So this is what I end up doing:

Initial Setup

1) Download the latest VPCs.

2) Install unrar to extract the images:

sudo apt-get install unrar

3) Extract each image:

unrar e -o+ IE6-XP-SP3.exe

(I also move and rename them to something convenient, like ie6.vhd.)

4) Install VirtualBox:

sudo apt-get install virtualbox-ose virtualbox-guest-additions

Rinse and Repeat

After 3 days or reboots of the image, these VPC images will stop working in VirtualBox, so if you're not careful you'll have to repeat these steps fairly often (fortunately, VirtualBox's snapshotting capabilities can help here). Keep massaging the scalp until you've produced a thick, luxurious lather:

5) Clone the vhds, to give them universally unique uuids (apparently Microsoft doesn't quite get the meaning of uuid). Also, this'll give you a pristine image when you need to repeat the process:

VBoxManage clonehd ie6.vhd ie6cloned.vhd

6) Fire up the VirtualBox ui:

virtualbox

(I actually usually do this via the desktop menubar: Applications > Accessories > VirtualBox OSE.)

7) Add each of the vhds (you can also do this via VBoxManage commands, but I'm too lazy to figure them out):

  1. Click the New toolbar item. Click Next.
  2. Enter a name (like ie6) and click Next.
  3. Select the Base Memory Size (256 MB for ie6/7; 512 MB for ie8), click Next.
  4. Select Use existing hard disk, click the little Folder icon.
  5. Click the Add toolbar item.
  6. Browse to the (cloned) .vhd file, select it, and click Open.
  7. Select the vhd you just added, and click Select.
  8. Click Next, then click Finish.

8) Run each image (select the image, click the Start toolbar item).

9) After XP boots, click No to the Windows Product Activation prompt, then ignore all the other whiny messages (resist the temptation to freaking close them already!), and select the Devices > Install Guest Additions... menu item from the image's menubar (the linux window's menubar, not XP's — to get out of XP, press the right-control key). Click the Continue Anyway when Windows tells you that installing this software will usher in the End Times. Don't reboot when it finishes — fix a few other things first.

10) Because I use the Dvorak keyboard layout, the first thing I do in XP is change the keyboard layout, via the Start > Control Panel > Regional and Language Options control panel; flip to the Languages tab, click Details...; then click Add..., select Dvorak, click OK, make it the Default, OK, OK, OK already!

11) Next I get rid of XP's screensaver, via the Start > Control Panel > Display Properties control panel. Flip to the Screen Saver tab, and select (None) from the Screen saver drop down (and click OK when you're done with the display settings). Also choose a larger screen size (Settings tab) and try to find a desktop background that doesn't make you want to puke.

12) Still ignoring XP's whimperings, open a command prompt (Start > All Programs > Accessories > Command Prompt), and enter:

D:\VBoxWindowsAdditions-x86.exe /extract /D=C:\Drivers

13) Now find one of those Hardware Update Wizard dialogs that XP keeps popping up. If you couldn't take it and closed them all, open Start > Administrative Tools > Computer Management, then click Device Manager, then double-click Ethernet Controller, and finally click the Reinstall Driver... button. Follow these steps:

  1. Select the No, not this time radio-button, click Next.
  2. Select the Install from a list or specific location (Advanced) radio buton, click Next.
  3. Select the Search for the best driver in these locations radio-button, unselect the Search removable media check-box, select the Include this location in the search check-box, and type in C:\Drivers\x86\Network\AMD (or browse to it, if you're visually inclined).
  4. Click Next, click Finish.

Now we're finished with non-ie stuff! Ignore any other Hardware Wizards or other Forces of Darkness that XP sends your way.

14) For ie7, run the Install the IE Explorer 7 Readiness Toolkit item on your desktop.

15) Finally, run ie! You should see some crappy Microsoft home page — this means you've got the networking stuff hooked up right. Select Tools > Internet Options, and click the Use blank button so you never have to see this again. Then flip to the Advanced tab, and unselect the Disable script debugging (Internet Explorer) check-box, so the debugger will come up whenever you run across a javascript error.

OK, now reboot Windows. You've only got one more boot, so don't ever restart again! Once you boot up, save a snapshot with VirtualBox so you can always go back to this happy place.

See Also

Lots of other people have already gone down this road; especially helpful are: