Archive for January, 2010
Chrome Extensions files (those with the .crx extension) are, in essence, signed ZIP files. The extension data is signed with your private key, while the public key is included in the .crx. A hash of this public key is used as the extension identifier when you install the extension. This identifier is important because you need it if you want to generate an update manifest. (Of course, if you’re hosting your extension on the extension gallery you won’t have to worry about this.)
Let’s say you’re hosting your extension yourself and you want to build the .crx file and the update manifest automatically. You could install the extension first and copy the identifier from the extensions list, but you can also calculate the identifier.
Chrome Extension developer Erik Kay explains the format on Stack Overflow:
To be precise, it’s the first 128 bits of the SHA256 of an RSA public key encoded in base 16.
Another random bit of trivia is that the encoding uses a-p instead of 0-9a-f. The reason is that leading numeric characters in the host field of an origin can wind up being treated as potential IP addresses by Chrome. We refer to it internally as “mpdecimal” after the guy who came up with it.
Here’s a short Ruby script to do exactly this:
require "openssl"
require "digest/sha2"
def pkey_to_id(pkey)
# Key algorithm, found in <http://github.com/Constellation/crxmake>.
algo = %w(30 81 9F 30 0D 06 09 2A 86 48 86 F7 0D 01 01 01 05 00 03 81 8D 00).map{ |s| s.hex }.pack("C*")
# Calculate public key, get hex hash of first 128 bits / 32 characters
hash = Digest::SHA256.hexdigest(algo + OpenSSL::PKey::RSA.new(pkey).public_key.to_der)[0...32]
# Shift hex from 0-9a-f to a-p
hash.unpack("C*").map{ |c| c < 97 ? c + 49 : c + 10 }.pack("C*")
end
For example the extension id for this private key:
-----BEGIN PRIVATE KEY-----
MIICdwIBADANBgkqhkiG9w0BAQEFAASCAmEwggJdAgEAAoGBANRvspoXYkKCTQple
mUX/Umh8zgmmz7xJ/oUiQSca7vd/3eDvLRBTOy7o4NwRmIzDHr/AbDkVRzXDd6ale
oGBj8ABvAf+3XAO7KdFsjIxmSfUI5M7F8EsqTDT+AR+YGiGOrF/9s4ganiqGSyIuk
Fe1UWrxlUBHKcazzgSdpsOZ2pAgMBAAECgYBNHIJ7NpO/SqcGaBGGkq+pQ7USo8jk
jwsQ1tVprBHbLtklm9cqoy12HSJceqvBx3/3QYtul2NhxZpOPFTAjxFCsaBP5I8rn
fgFzdSvKZXtZmQ7DsYaglclYqSwhKtKM9j5xVbqEh4r0LXYAvM1dlRSJQrFtqhLiI
Nc5jyWL2HHxQJBAPMWXG8BOwo0yM3eb27Foz6jjO92iQ+pHgWNFoW+QZIhVUGHkIH
ESl94UF86k0uurVBO22oHNoeLAwZ1XLIulN8CQQDfuIbPu4WAGyshyT/rdk/MuRXd
gcnbUkwKbyYfiKsSLpmMryM9ugFqyO+AQNIGA1xQHEP3znxUE/Y1tyE74NZ3AkBih
dOc4gDN2Cry1Y6QdOX/A0ah34cZo8+ZLF/OgRgOZBgr4Qf+sFH8c8UPc6wzZm60N+
HSDW5abUsimPqi9SI9AkEAzhbaeXqxXHWqohEWRP5UPK8zqT3qiZOiYOpLIDlx/en
XoXWk7TPwIkK//lG4J7nozBN9uUYJ2hoZcRomD1bruQJBAI49v2hvt24JV870F8Lf
oEReK/26DpyMFfH7a4I4O2JkGX79M/aVXBbSbNlnl01Esoxsx9kYzksP4CpR7CdYH
og=
-----END PRIVATE KEY-----
is cigbjabahnfnnmplhmjeolnhobhfjggp.
The script uses OpenSSL::PKey::RSA.new(pkey).public_key to calculate the public key for the given private key. It then adds the key algorithm (a subject of which I understand not much more than what is described on Wikipedia) and takes the SHA256 hash in hexadecimal form. Of this hash it takes the first 32 characters, corresponding to the first 128 bits of the SHA256 hash.
Finally we need to shift from 0-9a-f to a-p. Technically this means that the extension id is not in hexadecimal format, but it is still in base 16. hash.unpack("C*") gives an array of ASCII character codes for the string. Each character code in the original 0-9a-f format is mapped to the corresponding code in the a-p format. We can exploit the ASCII table for this. Digits are character codes 48 to 57, while lowercase characters start at 97. Therefore, we can simply add 49 (the difference between 97 and 48) to each number and, since we’re shifting by ten characters, 10 to the letters a-f. Finally we pack("C*") the character codes back into the extension id.
I’ll be writing more about Chrome Extensions here, and am giving a talk on the subject at the Scandinavian Web Developer Conference at the end of May in Stockholm. I’ll also give a brief Chrome Extensions Primer at the Øresund JavaScript Meetup in Malmö this Wednesday.
Computed style values for clip:rect() are different in WebKit compared to Gecko and Opera. Given this test code:
<div id="test" style="clip:rect(0, 50px, 50px, 0);width:100px;height:100px;position:absolute;background:red"></div>
<script>
window.onload = function(){
alert(window.getComputedStyle(document.getElementById("test"), null).clip);
};
</script>
You get:
- WebKit:
rect(0px 50px 50px 0px) - Opera:
rect(0px, 50px, 50px, 0px) - Gecko:
rect(0px, 50px, 50px, 0px)
Nothing I couldn’t fix in the regular expression I was using to split the values, but an odd difference nonetheless.
Being a Dojo hacker I’ve been wanting to use Dojo in combination with Node for a while now. So on a recent flight to London I added support for the Node environment to Dojo. I cleaned it up yesterday and – after figuring out Git – the code is now available on my Dojo fork.
Dojo is available for other environments than browsers, although the pre-compiled Dojo Base code you might have downloaded will only work in a browser. Other supported environments are Firefox extensions, the Rhino JavaScript engine and the Spidermonkey JavaScript engine. And now, Node.
Dojo is able to support multiple environments by dynamically bootstrapping itself for the given environment. In the non-compiled code, the dojo.js file detects its environment and loads the appropriate hostenv_*.js file. The biggest task of the various hostenv files is to fix the loader code to ensure dojo.require() works. Depending on the environment the file might also add some other features, for example the Rhino environment adds an implementation for setTimeout.
Right now I’m detecting the Node environment by seeing if the process object exists in the global scope. This object provides various utility methods to Node and is, as far as I know, unique to just the Node environment.
We briefly need to interrupt this explanation by discussing how we invoke Dojo within Node. As far as I can tell Dojo needs to exist in the global scope, and the only way to accomplish this is by loading the Dojo code when Node starts. Therefore you’d run node dojo.js to kick things off.
Based on this invocation we can use the path to dojo.js to calculate the root location of the Dojo files. I use process.ARGV[1].replace(/[^\/]+\.js$/, "") to do this. The primary reason for calculating the root is because the Dojo bootstrapping code uses the root location to load the host environment file. Technically I could have left it empty but that’s a bit silly.
Finally we use require() to load further bootstrapping code, the loader, the host environment and Dojo Base. As a special trick for the Node environment, dojo.js invokes dojo._loadInit() to signal that the environment is ready for use. In browsers this function would be invoked on DOM ready, but of course that’s not possible in Node.
The Node host environment file (hostenv_node.js) patches the loader so we can use dojo.require() in combination with Node’s module loading support. I’ve also added support for passing djConfig properties to Dojo. Simply pass the JSON object to the --djConfig argument: node dojo.js --djConfig='{isDebug:true}'.
Because we have to start Node with the dojo.js file we need a way to load additional JavaScript code into Node. This is accomplished by passing the -s or --script argument, which should point to a JavaScript file to be loaded into Node with require().
Currently the Node environment is largely untested, and it is quite probable that there are parts of Dojo that are depending on other features that haven’t yet been ported. I did try implementing the XMLHttpRequest object however, as a wrapper around Node’s HTTP libraries, to make it easier to execute HTTP requests through dojo.xhr().
Check out the code at Github. Some examples are also available. See the documentation on usage.
Last month I travelled to Stockholm to give a lecture at Södertörn University. Entitled Building Installations in Five Days (and a bit) it discussed the main takeaways from the Hacker camps run by Mediamatic at PICNIC:
- Simplify, simplify, simplify, because there’s only five days and because it’s about the real value of the experience.
- Constraints are freeing, they limit what you can attempt, so you can actually build your installation.
- Have a Plan B, so you focus on what you can do in Plan A, and never even need Plan B.
- Share your shit, shared brainstorming, shared problem solving, shared code.
- Find a mentor, for experienced outside feedback.
- Get a support team, so you don’t have to run to the hardware store yourself.
These takeaways are illustrated by actual projects undertaken in the past three years, with a focus on the ikSpin, a frisbee game created by Eelco Wagenaar and myself.
Via Paul Irish a bunch of tips on optimizing your HTML structure. From the obvious in unobtrusive coding (no onclick="…" attributes) to not specifying method="get" on form elements, 22 tips on optimizing your site’s HTML.
