When working with the DOM .nodeName property there are two hard-and-fast rules that most people abide by:
The node names of HTML elements are always uppercase, even if they're explicitly created using lowercase characters. <html> will result in a .nodeName === "HTML" (see the HTML 5 draft).
The node names of XML elements are always in the original case, as specified when they're created. <data> will result in a .nodeName === "data", <DATA> will result in a .nodeName === "DATA".
Knowing these rules can be useful because it allows you to optimize your code. If you know that you're in an HTML document you can avoid having to upper/lowercase your .nodeName checks and you can just always assume that you're dealing with a .nodeName that's uppercase. This results in faster selectors for Internet Explorer and other minor optimizations.
However recently I've been running across two cases that've been especially problematic and have bucked the trend.
Importing Nodes from XML
The first is for browsers that support the adoptNode/importNode DOM methods. These methods allow you to move (or clone) a node from one DOM document to another. In this way you can move an XML node from an XML document and insert it into an HTML document. Normally this shouldn't matter much but, as it turns out, the original .nodeName case sensitivity is preserved from the original XML-ness of the node.
Thus if you have a lowercase XML element (<data>) and you use adoptNode or importNode to bring it into your HTML document the result will be .nodeName === "data" -- which completely bucks the trend for "all HTML element's node names are always uppercase." I consider this to be a bug, considering that the DOM element is now in an HTML document, not in an XML document, and should behave as such.
Unknown HTML 5 Elements
The second bit of weirdness comes from people attempting to use the new elements from HTML 5 in browsers that don't support it. Most browsers behave perfectly well when using some of the new HTML 5 elements (in that they don't freak out and support some level of styling). For Internet Explorer you must use the HTML 5 Shim technique - this will give unknown HTML 5 elements the ability to be styled and hold contents (such as a <section> element).
However there is an additional gotcha: When Internet Explorer encounters an element that it doesn't recognize it leaves the .nodeName in its original case. Thus if you have a <section> element in your HTML page the result will be .nodeName === "section" -- which directly contradicts the normal case sensitivity of the .nodeName property in HTML documents.
To try and understand all of this I made a bunch of test cases using a number of doctypes and document styles.
I ran the following tests in IE 6, IE 7, IE 8, Firefox 3.5, Safari 4.0.3, Chrome 3.0.195, and Opera 10.10. Additionally I tested against .tagName in addition to .nodeName and found no discernible difference (you can run your own .tagName tests by appending a ?tagName to any test URL like so.)
Note: The HTML 5, XHTML (served as HTML), and no-doctype pages all behaved identically to each other in every browser - thus I'm just going to not display the XHTML (as HTML) and no-doctype results as there wouldn't be anything interesting to show.
Firefox, Safari, and Chrome all yielded the same results here: Bringing in elements from an external document maintains the case sensitive nature of the .nodeName property - which is unexpected.
<div>
<DIV>
<section>
<SECTION>
HTML
DIV
DIV
SECTION
SECTION
HTML createElement
DIV
DIV
SECTION
SECTION
innerHTML
DIV
DIV
SECTION
SECTION
XML
div
DIV
section
SECTION
XML createElement
div
DIV
section
SECTION
HTML via importNode
div
DIV
section
SECTION
HTML via adoptNode
div
DIV
section
SECTION
Internet Explorer fails in a different manner. To start, Internet Explorer doesn't support importNode or adoptNode so those particular tests simply don't run. However we can confirm that the case sensitivity of the unknown HTML 5 element is maintained in HTML, even though it shouldn't be.
<div>
<DIV>
<section>
<SECTION>
HTML
DIV
DIV
section
SECTION
HTML createElement
DIV
DIV
section
SECTION
innerHTML
DIV
DIV
section
SECTION
XML
div
DIV
section
SECTION
XML createElement
div
DIV
section
SECTION
HTML via importNode
Error: Object doesn't support this property or method
HTML via adoptNode
Error: Object doesn't support this property or method
Opera ups the ante one further: Since it attempts to simultaneous follow web standards, and implement Internet Explorer's weird quirks, it both fails the importNode/adoptNode and the HTML 5 unknown element cases.
Nearly every browser that supported showing this page (Firefox, Safari, Opera, Chrome) displayed the same, expected, results:
<div>
<DIV>
<section>
<SECTION>
HTML
div
DIV
section
SECTION
HTML createElement
div
DIV
section
SECTION
innerHTML
div
DIV
section
SECTION
XML
div
DIV
section
SECTION
XML createElement
div
DIV
section
SECTION
HTML via importNode
div
DIV
section
SECTION
HTML via adoptNode
div
DIV
section
SECTION
An XHTML page served properly is just an XML document - thus the case of elements is sensitive (as to be expected).
... except in Opera. Opera apparently will treat div elements case insensitively, when injected using .innerHTML, even if it's being served within an XHTML document.
<div>
<DIV>
<section>
<SECTION>
HTML
div
DIV
section
SECTION
HTML createElement
div
DIV
section
SECTION
innerHTML
DIV
DIV
section
SECTION
XML
div
DIV
section
SECTION
XML createElement
div
DIV
section
SECTION
HTML via importNode
div
DIV
section
SECTION
HTML via adoptNode
div
DIV
section
SECTION
Update: XHTML as XML Tests
Based upon some suggestions in the comments I've run some additional tests. Namely I tested the loading of an XML document that has the correct XHTML namespace attached to it (specifically I used the same XHTML test page that I used for the other tests, just appending a .xml extension instead of .xhtml). The results are rather interesting - and promising, at least. (Note: Internet Explorer continues to fail as it doesn't have an adoptNode/importNode method.)
Firefox continues to fail the importing of XML nodes, even when they're coming from an XML document:
<div>
<DIV>
<section>
<SECTION>
HTML
DIV
DIV
SECTION
SECTION
HTML createElement
DIV
DIV
SECTION
SECTION
innerHTML
DIV
DIV
SECTION
SECTION
XML
div
DIV
section
SECTION
XML createElement
div
DIV
section
SECTION
HTML via importNode
div
DIV
section
SECTION
HTML via adoptNode
div
DIV
section
SECTION
XML (XHTML)
div
DIV
section
SECTION
XHTML via importNode
div
DIV
section
SECTION
As does Opera:
<div>
<DIV>
<section>
<SECTION>
HTML
DIV
DIV
section
SECTION
HTML createElement
DIV
DIV
section
SECTION
innerHTML
DIV
DIV
section
SECTION
XML
div
DIV
section
SECTION
XML createElement
div
DIV
section
SECTION
HTML via importNode
div
DIV
section
SECTION
HTML via adoptNode
div
DIV
section
SECTION
XML (XHTML)
div
DIV
section
SECTION
XHTML via importNode
div
DIV
section
SECTION
BUT both Safari and Chrome PASS on the importing of XHTML nodes, coming from an XML document:
<div>
<DIV>
<section>
<SECTION>
HTML
DIV
DIV
SECTION
SECTION
HTML createElement
DIV
DIV
SECTION
SECTION
innerHTML
DIV
DIV
SECTION
SECTION
XML
div
DIV
section
SECTION
XML createElement
div
DIV
section
SECTION
HTML via importNode
div
DIV
section
SECTION
HTML via adoptNode
div
DIV
section
SECTION
XML (XHTML)
div
DIV
section
SECTION
XHTML via importNode
DIV
DIV
SECTION
SECTION
This, in particular, is great news. It means that, at least, one browser understands the concept of loading in external (X)HTML into an HTML document and having it continue to work. It's unfortunate that it doesn't work in all browsers, though.
Conclusion
What can we learn from all of this? Unfortunately it appears as if we can't really trust our "trusted" rules about .nodeName case sensitivity for HTML documents. XML documents are completely safe and work as expected. XHTML (served with the correct mimetype) documents are nearly safe, save for the one bizarre Opera bug.
How will this change the code that we write? In short we can no longer trust the case insensitive nature of HTML documents - we need to assume that BOTH HTML and XML documents will be serving their content in a case sensitive nature - especially as more people start to adopt HTML 5 elements in their pages and expect some level of support in older browsers. This means that a number of selectors and DOM methods will take a performance hit as we can no longer take a case insensitive shortcut in our codebases.
There are a few outstanding jQuery tickets that are the result of these issues cropping up and now that I know the reasoning behind why they're happening I can now strip out all the case-insensitive performance improvements from the codebase - which is really quite unfortunate but at least it'll behave more consistently. I continue to stand by thesis from my earlier talk about the DOM: The DOM is a mess and every DOM method and property is broken in some way, in some browser.
This past weekend was the 2009 jQuery Conference here in Boston. It was an incredible event - 300 people attended and a ton of discussion, collaboration, and learning happened.
Nearly the entire jQuery project team had the opportunity to meet for two days prior to the conference and hash a number of things out - figuring out most of the planning for the upcoming year. The core dev team also had the opportunity to meet and work for two days just after the conference. We're much closer to shipping 1.3.3 now (which is likely to become 1.4, with all the new features that're being added).
(Left to Right: Mike Hostetler (Infrastructure), John Resig (Core), Paul Bakaus (UI), Brandon Aaron (Core), Richard D. Worth (UI), and Scott Jehl (UI and Design team)
A full list of the presentations that were given can be found on the events site. All the presentations that I gave can be found below.
I've been slowly working on a new JavaScript book that covers many of the specifics behind how and why JavaScript libraries are designed they way that they are - titled Secrets of the JavaScript Ninja. I'm still working on the book - I have four chapters left to write - and am hoping to have it completed this year.
Incidentally Manning Publishing is running a special (today only!) offering the ebook version of Secrets of the JavaScript Ninja for 50% off the normal price, if you use the coupon code pop0901. (Note: I've heard that it may also work for the ebook + paperback version of the book.)
A number of excerpts from the book can be found in the site archive. My Learning Advanced JavaScript interactive tutorial is also based upon the contents of the book.
Note: The book is not yet complete, nor has it gone through any proofreading or technical editing - there will be mistakes. Also, yes, I'm aware that the cover of the book has a samurai on it, not a ninja - I'm working with the publisher to rectify this.
I've also been asked if people should buy Pro JavaScript Techniques (my Apress book from 2006) or my upcoming one. I should note that the upcoming one is much more advanced than my previous one - covering much more complicated topics and more about cross-browser development. If you're looking for a good introductory book to JavaScript and the DOM I strongly recommend Nicholas Zakas' Professional JavaScript Developers book.
TestSwarm, the project that I've been working on over the past 6 months, or so, is now open to the public. Mozilla has been very gracious, allowing me to work on this project exclusively. At the beginning of April I moved from my old position as a JavaScript Evangelist on the Mozilla Evangelism team to that of a JavaScript Tool Developer on the new Developer Tools team (whose other major project is Bespin).
For more information on Test Swarm I've written up a detailed explanation of what Test Swarm provides and where it fits into the landscape of JavaScript developer tools.
TestSwarm ended up being a very challenging project to get to an alpha state (and probably will be even more challenging to get to a final release state). Dealing with cross-browser incompatibilities, cross-domain test suite execution, and asynchronous, distributed, client execution has been more than enough to make for a surprisingly difficult project. It's mostly written in PHP and uses MySQL as a back end (allowing it to run in virtually any environment). Patches will absolutely be appreciated.
This project has been a long time coming now, the first inklings started back in 2007. Some of us on the jQuery team were discussing ways to distribute the test suite load to multiple browsers in an automated fashion. Andy Kent came along and proposed a participatory application for testing visual code (such as jQuery UI). We worked on that code base for a while but it didn't get off the ground. Eventually I decided to re-tackle the problem early on in 2009. Even in its rough alpha state we've already been able to make great use of TestSwarm. For example, here's a view of jQuery commits run in TestSwarm:
The vertical axis is SVN commits to jQuery (newer commits at the top), the horizontal axis are all the different browsers that we target. Using TestSwarm we've been able to easily spot regressions and fix them with a minimum amount of hassle (especially since all the results are logged).
And this is only the beginning. There are so many different directions in which Test Swarm can be taken. For example:
A pastebin-like service where you can drop in code and see the results come back, from many browsers, in real-time.
IDE integration for sending minor changes out for quick testing.
Manual testing of user interface code. Pushing manual tests, with instructions, to users for them to walk through.
Distributing tests to any number of browsers, rather than a specific sub-set. (You could use this to embed a tiny iframe in your site to collect test results from a small sampling of our users.)
The ability to drive and test browser code or extensions.
And the list goes on. I'm definitely curious to see what directions the community is interested in driving the code base. I've gotten it to a level where it's particularly useful for me and the jQuery team - where should we go from here?
Web Workers are, undoubtedly, the coolest new feature to arrive in the latest version of web browsers. Web Workers allow you to run JavaScript in parallel on a web page, without blocking the user interface.
Normally in order to achieve any sort of computation using JavaScript you would need to break your jobs up into tiny chunks and split their execution apart using timers. This is both slow and unimpressive (since you can't actually run anything in parallel - more information on this in How JavaScript Timers Work).
With our current situation in mind, let's dig in to Web Workers.
Web Workers
The Web Worker recommendation is partially based off of the prior work done by the Gears team on their WorkerPool Module. The idea has since grown and been tweaked to become a full recommendation.
A 'worker' is a script that will be loaded and executed in the background. Web Workers provide a way to do this seamlessly, for example:
new Worker("worker.js");
The above will load the script, located at 'worker.js', and execute it in the background.
There are some HUGE stipulations, though:
Workers don't have access to the DOM. No document, getElementById, etc. (The notable exceptions are setTimeout, setInterval, and XMLHttpRequest.)
Workers don't have direct access to the 'parent' page.
With these points in mind the big question should be: How do you actually use a worker and what is it useful for?
You use a worker by communicating with it using messages. All browsers support passing in a string message (Firefox 3.5 also supports passing in JSON-compatible objects). This message will be communicated to the worker (the worker can also communicate messages back to the parent page). This is the extent to which communication can occur.
The message passing is done using the postMessage API, working like this:
var worker = new Worker("worker.js");
// Watch for messages from the worker
worker.onmessage = function(e){ // The message from the client:
e.data };
worker.postMessage("start");
The Client:
onmessage = function(e){ if( e.data === "start"){ // Do some computation
done() } };
function done(){ // Send back the results to the parent page
postMessage("done"); }
This particular message-passing limitation is in place for a number of reasons: It keeps the child worker running securely (since it can't, blatantly, affect a parent script) and it keeps the parent page thread-safe (having the DOM be thread safe would be a logistical nightmare for browser developers).
Right now Web Workers are implemented by Firefox 3.5 and Safari 4. They've also landed in the latest Chromium nightlies. Most people would balk when hearing this (only two released browsers!) but this shouldn't be a concern. Workers allow you to take a normal piece of computation and highly parallelize it. In this way you can easily have two versions of a script (one that runs in older browsers and one that runs in a worker, if it's available). Newer browsers will just run that much faster.
Some interesting demos have already been created that utilize this new API.
RayTracing
This demo makes use of Canvas to draw out a rendered scene. You'll note that when you turn on the workers the scene is drawn in pieces. This is working by telling a worker to compute a slice of pixels. The worker responds with an array of colors to draw on the Canvas and the parent page changes the canvas. (Note that the worker itself doesn't manipulate the canvas.)
Movement Tracking
(Requires Firefox 3.5. About the demo.) This one uses a number of technologies: The video element, the canvas element, and drawing video frames to a canvas. All of the motion detection it taking place in the background worker (so that the video rendering isn't blocked).
Simulated Annealing
This demo attempts to draw outlines around a series of randomly-placed points using simulated annealing (More information). It also includes an animated PNG (works in Firefox 3.5) that continues to spin even while all the processing is occurring in the background.
Computing with JavaScript Web Workers
The other day Engine Yard started an interesting contest (which is probably over, by the time that you're reading this).
The premise is that they would give you a phrase, which you would take the SHA1 of, and try to find another SHA1-ed string that has the smallest possible hamming distance from the original.
The phrase was posted the other day and developers have been furiously working to find a string that yields a low value.
The current leader is using a series of dedicated GPUs crunching out results at a pace of a couple hundred million per second. Considering the rate at which they're progressing any other implementation will have a hard time catching up.
Of greater interest to me were two pure-JavaScript (1, 2) entrants into the competition - they both run completely in the browser and utilize the user's JavaScript engine to find results. While neither of them have a prayer of overcoming the GPU-powered monsters dominating the pack, they do serve as an interesting realm for exploration.
Reading through the source to both implementations they both utilize nearly-identical tactics for computing results: They execute a batch of results broken up by a timer. I've played around with them in different browsers and have been able to get around 1000-1500 matches/second. Unfortunately they both peg the CPU pretty hard and even with the timer splitting they manage to bog down the user interface.
This sounds like a perfect opportunity to use Web Workers!
I took the Ray C Morgan implementation, stripped out all the UI components and timers, and pushed it in to worker (through which 4 of them are run in parallel). (I submit results back to the original implementation, just in case a good result is found.)
// Build a worker var worker = new Worker("worker.js");
// Listen for incoming messages
worker.onmessage = function(e){ var parts = e.data.split(" ");
// We're getting the rate at which computations are done if( parts[0] === "rate"){
rates[i] = parseInt(parts[1]);
// Total the rates from all the workers var total = 0; for(var j = 0; j < rates.length; j++ ){
total += rates[j]; }
num.innerHTML = total;
// We've found a new best score, send it to the server }elseif( parts[0] === "found"){ var img = document.createElement("img");
img.src = "http://www.raycmorgan.com/new-best?phrase=" +
escape(parts.slice(1).join(" "));
document.body.appendChild( img );
// A new personal best score was found }elseif( parts[0] === "mybest"){ var tmp = parseInt(parts[1]); if( tmp < mybest ){
mybest = tmp;
best.innerHTML = mybest; } } };
To start, we're constructing the worker and listening for any incoming messages. There are three types of messages that can come from the worker: "rate" (a 'ping' from the worker notifying the parent how quickly it's running), "found" (sent back when a new high scoring phrase has been found by the client), and "mybest" (sent when the worker gets a new personal-best high score).
Additionally we can see the initialization data sent to the client in worker.postMessage. Unfortunately we have to pass the data in using a string in order to have it work in all browsers (only Firefox 3.5 supports the ability to pass in a raw JavaScript object).
Looking at the contents of the worker we can see some more, interesting, logic.
// ... snip ...
// New Personal Best Found if(distance < myBest){
myBest = distance;
postMessage("mybest " + myBest); }
// New All-time Best Found if(distance < best){
best = distance;
postMessage("found " + phrase); }
// ... snip ...
// Report Rate Back to Parent function stats(){ var nowDiff = (new Date()).getTime() - startTime; var perSec = Math.floor(processed/nowDiff*1000);
postMessage("rate " + perSec ); }
// ... snip ...
// Get the incoming information from the parent
onmessage = function(e){ var parts = e.data.split(" ");
data = { sha: parts[0], words: parts[1].split(","), best: parts[2]};
start(); };
The two 'distance' checks take place deep in the computation logic. After a new match has been found it is compared against the existing high scores. If this a sufficiently good-enough the result is sent back to the parent page using postMessage.
The 'stats' function is called periodically, which then reports back the current rate of processing to the parent page.
The 'onmessage' callback listens for the initialization data to come from the parent page - and once it's been received begins processing.
--
In all I found this project to be a lot of fun - a relatively minor amount of code yielded 2-3x faster computation power. If you're doing any computation with JavaScript you should definitely opt to use Web Workers if they're available - the result is both faster and a better experience for the end user.
Ever since I saw the Bit.ly JavaScript API I've been wanting to build a simple script for tracking the number of people visiting a blog post from Twitter. This past weekend I built a little script for doing just that - and in a completely unobtrusive manner.
The script itself is completely standalone (no dependencies) and can be included in any page relatively painlessly. Additionally, since it's just HTML, CSS, and JavaScript, it's completely themeable and customizable to the style of your site. Before explaining how to use it, some demos:
(Note: You should place and use a copy from your own site, in case mine ever goes down.)
(Note 2: You DO NOT need a bit.ly account in order to use this script. A working one is provided for you by default.)
Step 2: Add a class of 'retweet' to any anchor that you wish to turn into a Retweet button - or add a link with classes of 'retweet' and 'self' to add a retweet button for the current page.
Retweet link for the current page: (See example at the end of this blog post.)
Wordpress: If you're using Wordpress you could sculpt a custom button like so, placed in your single.php theme file (although, the above link types should be more than sufficient for most cases):
The script has been tested in Internet Explorer 6-8, Opera 9-10, Safari 3.2-4, Chrome 2, and Firefox 3-3.5. Please write a comment if you encounter any problems.
Configuration and Themeing
There are a few options for the Retweet button that you can use. If you're going to change them you can do so by changing the following properties after you've already loaded the retweet.js file. However, I highly recommend that you download a copy of the retweet.js file, make the changes in the file itself, and simply load that result instead.
RetweetJS.link_text (Default: "Retweet")
You can change this to another value and change the text in all of the Retweet buttons. (Good for handling different languages.)
RetweetJS.count_text (Default: "clicks")
Right now only "clicks" are supported but if you specify "none" it will show no count (and load much faster, as a result).
RetweetJS.prefix (Default: "")
Setting to "RT @jeresig " will add it to the front of the tweets.
RetweetJS.styling (Default: The full CSS used for styling the retweet button.)
You'll probably want to either tweak the CSS from inside the file itself or completely overwrite it and include your own styling elsewhere. The markup for the button is surprisingly simple:
These default to a dummy account ('retweetjs') that can be used indefinitely. However if you wish to keep track of your own links and add them to your own Bit.ly account then please add in your Bit.ly username and API key (which can be found on your Bit.ly account page). These values should be changed within the retweet.js file itself.
Some of the major differences between this script and those are:
Completely Unobtrusive You can place the script in the <head> of your document instead of inline. The other scripts rely upon document.write() and write out iframes into your document.
Speed Retweet.js works completely asynchronously, loading data and updating the rendering as it comes in. This means that the buttons won't block your page while they're loading (unlike the other buttons).
Pure HTML/CSS The result is just pure HTML styled with CSS - you can customize it however you wish, no images are required.
Number of clicks, not retweets. The number of retweets may be interesting to some but it's a poor indicator of actual traffic. Instead, the number of clicks coming in is shown instead (a much more useful number).
You control the data All tracking works directly through Bit.ly, not a third party - this means that all the clicks and traffic can be stored straight in your bit.ly account, not some third parties.
100% Open Source This script is released under the MIT license and is completely open to modification and redistribution. The full source is available on Github.
If you have any questions regarding the script please feel free to post them her in the comments. If you have any tweaks for the script, please apply them against the source repository. Enjoy!
I'm in the process of working on, and improving, test suite support in TestSwarm (an upcoming project of mine). However, there isn't a lot of information on which unit testing frameworks developers actually use to test their code (whereas there is more information on which JavaScript libraries are used).
It will be of great help to me if you could quickly fill out the question below. I will release the results of the survey as soon as possible. Thanks!
The poll is now closed. I've received 1853 responses and plan on writing a detailed blog post on the results. Thanks everyone!
The video from my talk at JSConf has been posted. Thanks to Chris for organizing the conference and the excellent quality of the video.
The description from the JSConf site summarizes the talk well:
John Resig presents his mystery topic, which is actually three topics that strike his interest. First up is measuring performance and a quick introduction to benchmarking (and its positives and negatives). This is followed by JavaScript Games which he unveils some super cool hidden functionality (cheat codes++) on the jQuery web site. This is followed up by the introduction of John's distributed continuous test framework platform, Test Swarm. It is jam packed with Nirvana and goodness so be sure to watch both parts.