Bookmark with del.icio.us submit Performance%20Calendar digg.com reddit

Performance Calendar

Each day this month, a web performance expert is posting an article on Performance Calendar. There is some really fantastic content here.

My contribution is Faster Ads with HTML5.

Bookmark with del.icio.us submit HTML5%20History%20is%20the%20Future digg.com reddit

HTML5 History is the Future

interface History {
  ...
  void pushState(in any data, in DOMString title, in optional DOMString url);
  void replaceState(in any data, in DOMString title, in optional DOMString url);
};
interface HTMLBodyElement : HTMLElement {
  ...
  attribute Function onpopstate;
};

The new interface provides push/pop semantics that, until now, we've emulated using JavaScript libraries like Really Simple History.

Web Apps and History

Web applications need to interact with the browser history to support bookmarkable and "back-buttonable" states. This is a pretty common feature of Ajaxy applications. For example, visit http://www.viewru.com/#Bonobo and browse or search for your favorite musician. The application uses an Ajaxy, single-window-context model to keep the music playing while you browse, but supports bookmarks and the back-button because users expect these features to work in their browser.

Prior to the HTML5 History additions, a solution to this problem involved these two parts:

// Store a representation of the state in the location hash
function setState(state) {
  window.location.hash = state;
}
// Detect a change to location.hash
var lastState;
window.setTimeout(function() {
  var state = window.location.hash;
  if (lastState != state) {
    lastState = state;
    handleState(state);
  }
}, 100);

The solution is less than ideal for several reasons:

Sluggishness
  • Executing a timeout function every 100ms won't make your app any faster.
  • 100ms delay in responding to back button actions.

Browser compatibility
  • The solution above doesn't work, for example, in IE 6-7, which won't add a history event on a hash change. Really Simple History gets around this by adding an iframe and issuing a call to your server for every state change. Webkit browsers present different challenges.

Not a stack
  • Suppose your application transitions from A->B->C. Can it transition directly back to A?

The HTML5 version looks like this:

// Push the state onto the history stack
function setState(state) {
  window.history.pushState(state, '');
}
// Respond to a popstate event.
window.onpopstate = function(e) {
  handleState(e.state);
};

Here, state may be a string representation of the current application state, but it can also be an object. Note that it will be deep-copied on each pushState() operation.

Storing application state in the hash is a hack

You may point out that the new hashchange event in HTML5 addresses concerns about sluggishness, but why use location.hash at all? Storing application state in the location hash presents some real problems and limitations. Here's a short list:

  • Content isn't crawlable: Most crawlers do not execute JavaScript or traverse dynamic fragment ids. If you want users to find your site and content via Search Engines, this just won't do.
  • Audible "click" in IE: For some reason, IE makes a sound every time you update the location. Imagine if your application updated the location on every mousemove event!
  • HTTP-Referer is inaccurate or missing data: The fragment ID isn't exposed in the referrer.
  • Handling bookmarks requires extra round trip:
    • Fetch myApp.html
    • Parse myApp.html#myState (server can't see myState)
    • Fetch myState
  • Legacy (and non-JS) browsers effectively have different URI schemes and can't share bookmarks and links.

HTML5 History and URLs

Perhaps the most interesting feature of the new pushState() and replaceState() interfaces is the optional url argument. For the first time, web developers are able to change the effective URL to represent a change in application state without navigating away from a window context. It's very powerful. The location bar, bookmark service, and HTTP-Referer all reflect the new URL. This feature addresses many of the issues I mentioned above:

  • Content is crawlable: A developer may express all crawlable states of their application as valid URLs.
  • HTTP-Referer is meaningful: The referrer can reflect the latest state of the application and show or hide any information to/from 3rd parties, at the developer's discretion.
  • Bookmarks can be handled in a single fetch: Since application state can be expressed in a URL, it's possible to service a request for the application and state in one request/response.
  • Can use a single URI scheme to cover new and old browsers: Since information is encoded in URLs instead of the location hash, modern and legacy users can share bookmarks and links.
  • Audible "click" in IE? Okay, I don't know about this one, but it makes sense that programmatic operations on window.history would not have an audible side effect, right?

Availability and further reading

You can take these new features for a spin in Firefox nightlies. For more info, see:

Bookmark with del.icio.us submit It%27s%20pronounced%20%22vyoo-roo%22 digg.com reddit

It's pronounced "vyoo-roo"

I added support for Firefox, Safari and Internet Explorer to viewru. It also now works on Chrome 3 in addition to Chrome 4. On non-Chrome browsers, the user experience has been simplified so that it's reasonably performant. So browse and listen away with your browser of choice.

Also, nearly everyone that's asked me about viewru doesn't know how to pronounce it. I pronounce it "vyoo-roo" (like "guru" with a "view").
Bookmark with del.icio.us submit Progressive%20XMLHttpRequest digg.com reddit

Progressive XMLHttpRequest

Perhaps it's subtle, but the draft spec for XMLHttpRequest calls for support for progressive response handling:

4.7.6 The responseText attribute

The responseText attribute must return the result of running these steps:

1. If the state is not LOADING or DONE return the empty string and terminate these steps.

2. Return the text response entity body.

To rephrase for my purposes, responseText should return the intermediate contents of the response when an XMLHttpRequest is interrogated during the LOADING state. It'll take a little work to handle these partial responses as valid script, but let's first address browser support. Firefox and Webkit browsers already support this behavior if you set the Content-Type header of your response correctly. IE8 throws an exception when responseText is accessed before readyState reaches COMPLETE.

I ran a modified version of the streaming response tests I used in my last post to verify progressive XHR handling. The server returns several chunks in 100ms intervals that include script that indicates how much of the response was received before it is first handled by the browser.

  Bytes Buffered
Configuration Firefox 3.5 Chrome 3.0 IE 8
Tranfer-Encoding: chunked 111 536 N/A
Content-Type: text/html
Tranfer-Encoding: chunked
111 N/A N/A
Content-Type: text/plain
Tranfer-Encoding: chunked
111 85 N/A
Content-Type: application/x-javascript
Tranfer-Encoding: chunked
111 111 N/A

For Webkit browsers, it's critical to specify a Content-Type of "text/plain" or "application/x-javascript" when returning script content to an XHR for progressive handling. Seems reasonable, but it's easy to neglect. In my testing, I didn't see any change in behavior in the presence of a "charset" param.

Note that Microsoft's documentation for XMLHttpRequest now refers the to draft specification. I'm hopeful that we'll be seeing support for progressive responses soon.

Now, since we'll be interpreting partial response content as executable script, we'll need to do something to ensure that each chunk we evaluate terminates on a complete expression. For this test, I added delimiters between valid blocks of source:

window.aFunction();
// -- //
window.bFunction();
// -- //

Where //--// is the delimiter. When outputting using chunked transfer encoding, you might organize code so that a delimiter is present at the end of each chunk boundary. On each readyState change, if the state is LOADING or DONE, I call a function to read the new content, identify a safe place to trim it, and append it to a buffer.

var index = 0;
var buffer = '';
var DELIMITER = '//--//';

function handlePartialResponse(request) {
  var i = request.responseText.lastIndexOf(DELIMITER);
  if (i > index) {
    i += DELIMITER.length;
    var newChunk = request.responseText.substr(index, (i - index));
    buffer += newChunk;
    index = i;
    flushBuffer();
  }
}

Finally, we evaluate the contents of the buffer. It's not necessary to remove the delimiter, since it's a valid JavaScript comment.

function flushBuffer() {
  window.eval(buffer);
  buffer = '';
}

What would you use this for? Consider this technique for the response channel in your next Comet app or any time you're able to deliver part of a script response while doing expensive server side work to produce the rest.
Bookmark with del.icio.us submit Performance%20Implications%20of%20%22charset%22. digg.com reddit

Performance Implications of "charset".

A while back, I wrote a post documenting the progressive response handling behavior of different browsers. I was specifically interested in how many bytes must be received or how much time must pass before a browser begins to parse content. My friend and colleague, Bryan McQuade (a co-author of Google PageSpeed), recently pointed out that character encoding detection is the source of buffering delay in response handling. Specifically, in the absence of a "charset" param in the Content-Type header, a browser may buffer a large quantity of content while looking for a <meta> tag declaring the content encoding.

I set up a new round of tests to identify the impact that content encoding declarations have on progressive response handling in different browsers. As in previous tests, the server returns several chunks, in 100ms intervals, each containing script that indicates how much of the response has been received.

  Bytes Buffered
Configuration Firefox 3.5 Chrome 3.0 IE 8
Tranfer-Encoding: chunked 1134 1056 300
Content-Type: text/html
Tranfer-Encoding: chunked
204 1056 341
Content-Type: text/html
Tranfer-Encoding: chunked
...
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
166 204 218
Content-Type: text/html; charset=utf-8
Tranfer-Encoding: chunked
204 280 300

Note that the test doesn't account for content in the document <head>, so the numbers for the <meta> configuration are artificially short by ~70b.

It's clear that for Chrome and Firefox, indicating the charset has a measurable impact on performance. Now, is it more desirable to declare the charset in the Content-Type header or a <meta> tag? Darin Fisher suggests that placing it in the response header is more performant: The charset impacts how the response is parsed, so when the browser encounters the <meta> tag, it must reprocess everything it's handled so far. If you're forced to use the <meta>, place it as near as possible to the top of the document to reduce the amount of throwaway work done by the browser.
Search
Google+   Facebook   LinkedIn