Tuesday, September 22nd, 2009

Gmail Mobile team talks Latency and Code Loading

Category: Google, Mobile, Tip

<p>Bikin Chiu of the Gmail Mobile team picks up the HTML5 series with a piece on reducing startup latency.

It starts off by talking about lazily loading code via the old favorites of adding a <script> to the DOM, or XHR+eval, but then it gets beyond the typical and discusses the nuance of mobile + offline caching. The hack that is used, is another oldie…. which is to hide the script and eval it later:

For an HTML 5 application that takes advantage of the application cache to reduce startup latency and to serve the application offline, there are a few caveats one should be aware of. Mobile networks have decent bandwidth, but poor round trip latency, so listing each module as a separate resource in the manifest incurs quite a bit of extra startup latency when the application cache is empty. Also, if one of the module resources fails to be downloaded by the application cache (e.g. disconnected from network), additional error handling code needs to be written to handle such a case. Finally, applications today have no control when the application cache decides to download the resources in the manifest (such a feature is not defined in the current specification of the draft standard). Typically, resources are downloaded once the main page is loaded, but that’s not an ideal time since that’s when the application requests user data.

To work-around these caveats, we found a trick that allows you to bundle all of your modules into a single resource without having to parse any of the JavaScript. Of course, with this strategy, there is greater latency with the initial download of the single resource (since it has all your JavaScript modules), but once the resource is stored in the browser’s application cache, this issue becomes much less of a factor.

To combine all modules into a single resource, we wrote each module into a separate script tag and hid the code inside a comment block (/* */). When the resource first loads, none of the code is parsed since it is commented out. To load a module, find the DOM element for the corresponding script tag, strip out the comment block, and eval() the code. If the web app supports XHTML, this trick is even more elegant as the modules can be hidden inside a CDATA tag instead of a script tag. An added bonus is the ability to lazy load your modules synchronously since there’s no longer a need to fetch the modules asynchronously over the network.

On an iPhone 2.2 device, 200k of JavaScript held within a block comment adds 240ms during page load, whereas 200k of JavaScript that is parsed during page load added 2600 ms. That’s more than a 10x reduction in startup latency by eliminating 200k of unneeded JavaScript during page load! Take a look at the code sample below to see how this is done.

  1. <script id="lazy">
  2. // Make sure you strip out (or replace) comment blocks in your JavaScript first.
  3. /*
  4. JavaScript of lazy module
  5. */
  6. </script>
  7.  
  8. <script>
  9.   function lazyLoad() {
  10.     var lazyElement = document.getElementById('lazy');
  11.     var lazyElementBody = lazyElement.innerHTML;
  12.     var jsCode = stripOutCommentBlock(lazyElementBody);
  13.     eval(jsCode);
  14.   }
  15. </script>

In the future, we hope that the HTML5 standard will allow more control over when the application cache should download resources in the manifest, since using comments to pass along code is not elegant but worked nicely for us.

Related Content:

Posted by Dion Almaer at 6:09 am
9 Comments

+++--
3.2 rating from 21 votes

9 Comments »

Comments feed TrackBack URI

Wouldn’t it be easier to just use some kind of custom type attribute on the script tag so the browser doesn’t parse it?

Comment by PeteB — September 22, 2009

I never use eval, it’s very slow on FF. Instead do:

new Function(jsCode)();

Comment by Stakka — September 22, 2009

makes sense, but I wonder if we could make this a bit more standard suggesting, as example, namespaces in ids in order to include incrementally if necessary.

(function(w){

// require
function require() {
for(var
i = 0, length = arguments.length,
n = w, s, split;
i < length; ++i
){
split = arguments[i].split(".");
do {
if(!n[s = split.shift()])
eval(
document
.getElementById(s)
.innerHTML
.replace(
/^[\r\n]+|\*\/\s*$/g,
""
)
)()
;
n = n[s];
} while(split.length);
};
};

// global scope ...
var eval = w.Function;
})(this);

In this example we could use
require(“jQuery.plugin.whatEver”); whenever we need the jQuery plugin whatever and the evaluation will be for jQuery, present so skipped, jQuery.plugin, hopefully present as object, and the script node with id jQuery.plugin.whatEver with
/* this is my whatEver plugin
… plugin code …
*/

In few words, this is a good hint, but if everybody will start to implement a different lazy evaluator we’ll have soon portability and conflicts problems (as is right now with certain libraries)

Comment by WebReflection — September 22, 2009

@Stakka Firebug shows evaluated stuff in a dedicated panel, wrapping native eval, that is why it could result that slower. Try to disable Firebug but I agree in any case ’cause for this stuff we do not want internal scope conflicts, but possibly a global one.

Comment by WebReflection — September 22, 2009

Getting round the problem that the browser waits for evaluation of script elements which are in the document head?

Put the script element at the bottom of the body.

Comment by ExtAnimal — September 22, 2009

@Stakka

new Function(jsCode)();

Thanks for the tip :)

Comment by Darkimmortal — September 22, 2009

>> I never use eval, it’s very slow on FF
@Stakka – unless you still use older FF, eval is faster.
http://weblogs.asp.net/yuanjian/archive/2009/03/22/json-performance-comparison-of-eval-new-function-and-json.aspx

Comment by Les — September 22, 2009

@WebReflection they want to include things that may not actually be executed as well, so that they can preload their entire app into the browser’s offline cache. The scripts should probably be at the end of the page regardless.

Comment by j5 — September 22, 2009

I find that the <XMP style=”display:none”> tag is perfect for putting HTML, javascript, or CSS text into a page and not have it be processed.

The <XMP> tag is not a normal tag. Instead it acts at the lexing stage and is a meta-quote that quotes everything as text until the </XMP> is reached. Kind of like ”’ in Python, or the perl <<’EOL’ i.e. everything inside the XMP becomes text *even* if there is HTML inside.

Comment by morrisj — October 7, 2009

Leave a comment

You must be logged in to post a comment.