Archive for November, 2009

Absolutizing URLs in Javascript

Tuesday, November 17th, 2009

Occasionally it becomes useful to get the absolute form path of a URL from a relative one. You might be dynamically changing links on a page, or loading scripts from the same location as scripts that have already been loaded.

Computing an absolute URL by hand is problematic: you need to deal with any <base> elements in the document and properly implement the relative path canonicalization that browsers already do.

It turns out that on standards-compliant browsers based on Gecko, WebKit or Opera, the href property of anchor elements is magical. If you assign a URL fragment to it, it comes back as a fully-qualified URL when you read it back.

Ok, that’s great, but what about IE?  Well, it turns out that IE will fully-qualify URLs returned by the href property, but only if that anchor tag was created by the parser. Using createElement(‘a’) and setting href won’t trigger this code path.  There’s a trick we can use to work around this limitation, however.  You can force IE to use the parser to create elements by assigning innerHTML of another element.  This runs the element creation through the magic construction path that correctly sets up the attribute/property mapping.

Here’s a snippet of a function that will correctly canonicalize any URL you pass to it:

<script>
function canonicalize(url) {
    var div = document.createElement('div');
    div.innerHTML = "<a></a>";
    div.firstChild.href = url; // Ensures that the href is properly escaped
    div.innerHTML = div.innerHTML; // Run the current innerHTML back through the parser
    return div.firstChild.href;
}
</script>

You can use this script for other interesting purposes, like determining the base path for the current page (returns “/” for “/foo” and “/foo/” for “/foo/”). It gets the relative URL for the path “_#”, which removes any anchors, query strings or filenames from the current URL.

<script>
function getBasePath() {
    return canonicalize("_#").slice(0,-2);
}
</script>

The above code was tested on Safari, Chrome, Firefox 3.5 and IE6.

Fix for GWT Hosted mode crash with Safari 4.0.4

Monday, November 16th, 2009

My local tests started failing soon after upgrading my machine to Safari 4.0.4. Some research and pointers from the GWT folk pointed me at the root cause, a WebKit bug which is now fixed.

Kelly Norton pointed me at a quick fix:

  1. Download the latest WebKit nightly from here
  2. Save it locally
  3. Add the following environment variable to your testing .launch targets (using the path to your new WebKit.app, of course). Tests run from Ant or the command line will need to use the appropriate tasks or shell commands:
    Name: DYLD_FRAMEWORK_PATH
    Value: /Applications/path-to-your-local-webkit/WebKit.app/Contents/Frameworks/10.5
    
  4. Snow Leopard users will need to use 10.6 in the path above.

The proper fix should arrive in WebKit 4.0.5 at some point, but this will keep you running for now.

Jim Douglas has posted more detailed instructions on how to configure your launch targets on the GWT issue here: Issue #4220: GWT crash (Safari 4.0.4)

Packing Chrome extensions in Python

Monday, November 9th, 2009

We’re just about to release our DotSpots extension for Chrome and I’ve been working on integrating the CRX packaging into our build process. CRX files are basically ZIP files with an RSA signature and public key tacked on to the front of it. Generating these files requires you to use the Chrome –pack-extension argument (which in turn requires you to deploy the 100MB+ Chrome binaries to your build machine).

The existing code to pack a Chrome extension in Python is pretty dated: it will only generated the insecure CRX version 1 format that doesn’t use a cryptographic signature. There’s some Ruby code to pack a version 2 extension, but it requires a lot of dependencies that aren’t installed by default on OSX or in Fedora.

I’ve written some code in Python that uses openssl under the hood to do the grunt work. It cuts some corners by requiring you to pre-zip your files, but you’ll get better results from 7zip -9 than Python’s internal zip code anyways. Pass it three arguments: The input ZIP file, the PEM key (generated when you manually pack the extension in Chrome for the first time) and the output file.

#!/usr/bin/python
# Cribbed from http://github.com/Constellation/crxmake/blob/master/lib/crxmake.rb
# and http://src.chromium.org/viewvc/chrome/trunk/src/chrome/tools/extensions/chromium_extension.py?revision=14872&content-type=text/plain&pathrev=14872

import sys
from array import *
from subprocess import *

arg0,input,key,output = sys.argv

# Sign the zip file with the private key in PEM format
signature = Popen(["openssl", "sha1", "-sign", key, input], stdout=PIPE).stdout.read();

# Convert the PEM key to DER (and extract the public form) for inclusion in the CRX header
derkey = Popen(["openssl", "rsa", "-pubout", "-inform", "PEM", "-outform", "DER", "-in", key], stdout=PIPE).stdout.read();

out=open(output, "wb");
out.write("Cr24")  # Extension file magic number
header = array("l");
header.append(2); # Version 2
header.append(len(derkey));
header.append(len(signature));
header.tofile(out);
out.write(derkey)
out.write(signature)
out.write(open(input).read())

print "Done."