Monthly Archives: December

High Performance Ajax Applications - Video Presentation

Video snapshot

A few days ago, I gave a talk at Yahoo! about High Performance Ajax Applications. Eric Miraglia, from the YUI team, and Ricky Montalvo, from the Yahoo! Developer Network, were kind enough to shoot the video, edit it, and put it on the YUI Blog. In this talk, I cover the following topics:

  • Developing for high performance
  • High performance page load
  • High performance JavaScript
  • High performance DHTML
  • High performance layout and CSS
  • High performance Ajax
  • Performance measurement tools

Follow along by downloading the PowerPoint slides, or by looking at the slides on Slideshare. I’m looking forward to reading your comments and answering your questions in the comments section of this blog!

week by week pregnancy videos | bad credit personal loan | pet carrier purse for small dogs

The Problem With innerHTML

The innerHTML property is extremely popular because it provides a simple way to completely replace the contents of an HTML element. Another way to do that is to use the DOM Level 2 API (removeChild, createElement, appendChild) but using innerHTML is by far the easiest and most efficient way to modify the DOM tree. However, innerHTML has few problems of its own that you need to be aware of:

  • Improper handling of the innerHTML property can enable script-injection attacks on Internet Explorer when the HTML string contains a script tag marked as deffered: <script defer>...<script>
  • Setting innerHTML will destroy existing HTML elements that have event handlers attached to them, potentially creating a memory leak on some browsers.

There are a few other minor drawbacks worth mentioning:

  • You don’t get back a reference to the element(s) you just created, forcing you to add code to retrieve those references manually (using the DOM APIs…)
  • You can’t set the innerHTML property on all HTML elements on all browsers (for instance, Internet Explorer won’t let you set the innerHTML property of a table row element)

I am more concerned with the security and memory issues associated with using the innerHTML property. Obviously, this problem is nothing new, and very bright people have already figured out ways to work around some of these problems.

Douglas Crockford wrote a purge function that takes care of breaking some circular references caused by attaching event handlers to HTML elements, allowing the garbage collector to release all the memory associated with these HTML elements.

Removing the script tags from the HTML string is not as easy as it seems. A regular expression should do the trick, although it’s hard to know whether it covers all possible cases. Here is the one I came up with:

/<script[^>]*>[\S\s]*?<\/script[^>]*>/ig

Now, let’s put these two techniques together in a single setInnerHTML function (Update: Thanks to those who commented on this article. I fixed the errors/holes you mentioned, and also decided to bind the setInnerHTML function to YAHOO.util.Dom)

YAHOO.util.Dom.setInnerHTML = function (el, html) {
    el = YAHOO.util.Dom.get(el);
    if (!el || typeof html !== 'string') {
        return null;
    }
    // Break circular references.
    (function (o) {
        var a = o.attributes, i, l, n, c;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                n = a[i].name;
                if (typeof o[n] === 'function') {
                    o[n] = null;
                }
            }
        }
        a = o.childNodes;
        if (a) {
            l = a.length;
            for (i = 0; i < l; i += 1) {
                c = o.childNodes[i];
                // Purge child nodes.
                arguments.callee(c);
                // Removes all listeners attached to the element via YUI's addListener.
                YAHOO.util.Event.purgeElement(c);
            }
        }
    })(el);
    // Remove scripts from HTML string, and set innerHTML property
    el.innerHTML = html.replace(/<script[^>]*>[\S\s]*?<\/script[^>]*>/ig, "");
    // Return a reference to the first child
    return el.firstChild;
};

Voila! Let me know if there is anything else that should be part of this function, or if I missed anything obvious in the regular expression.

Update: There are obviously many more ways to inject malicious code in a web page. The setInnerHTML function barely normalizes the <script> tag execution behavior across all A-grade browsers. If you are going to inject HTML code that cannot be trusted, make sure you sanitize it first on the server side. There are many libraries available for this.

Update: IE8 has a new toStaticHTML function attached to the window object that removes any potentially executable content from an HTML string!

Adding Back Button and Bookmarking Support to Your DHTML Slide Show

YUI 2.4.0, which was just released today, comes with a minor update to its history library. To celebrate this new release, I thought I would write a short article demonstrating how to use the YUI Browser History Manager to add back button and bookmarking support to a DHTML slide show.

Let’s start with a slightly modified version of Christian Heilmann’s maintainable, unobstrusive DHTML slide show. First, import the YUI Browser History Manager code and its dependencies:

<script type="text/javascript" src=".../2.4.0/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src=".../2.4.0/build/history/history-min.js"></script>

Then, add the necessary static markup to the page:

<iframe id="yui-history-iframe" src="img/aston-martin.jpg"></iframe>
<input id="yui-history-field" type="hidden">

Note that the asset loaded in the IFrame does not have to be an HTML document (here, we load the first visible image in the slide show) This trick is useful to avoid an additional server round-trip, which would degrade the performance of your site.

Don’t forget to hide the IFrame by adding the following style declaration:

#yui-history-iframe {
  position:absolute;
  top:0; left:0;
  width:1px; height:1px;
  visibility:hidden;
}

Our application is composed of only one module, the slide show, which we will refer to using the identifier “slideshow”. The state of the “slideshow” module will encode the 0-based index of the currently visible slide. The next step is to figure out the initial state of our slide show module:

initialState = YAHOO.util.History.getBookmarkedState("slideshow") || "0";

The “slideshow” module can now be registered with the Browser History Manager, passing in the onStateChange callback, which will be executed when the state of the “slideshow” module changes:

YAHOO.util.History.register("slideshow", initialState, function (state) {
    showSlide(parseInt(state));
});

The initialization routine (initSlideShow) needs to be slightly modified to add a history entry instead of just showing the next slide when the user hits the “previous” or “next” links:

function initSlideShow () {
    currentSlideIndex = parseInt(YAHOO.util.History.getCurrentState("slideshow"));
    slides = YAHOO.util.Dom.get("slides").getElementsByTagName("li");
    YAHOO.util.Dom.addClass(slides[currentSlideIndex], "current");
    YAHOO.util.Event.addListener(["prev", "next"], "click", function (evt) {
        YAHOO.util.Event.stopEvent(evt);
        var newSlideIndex = this.id === "next" ?
            currentSlideIndex + 1 :
            currentSlideIndex - 1;
        if (newSlideIndex >= slides.length) {
            newSlideIndex = 0;
        } else if (newSlideIndex < 0) {
            newSlideIndex = slides.length - 1;
        }
        YAHOO.util.History.navigate("slideshow", newSlideIndex.toString());
    });
}

Call initSlideShow when the Browser History Manager is ready:

YAHOO.util.History.onReady(function () {
    initSlideShow();
});

Finally, initialize the Browser History Manager:

YAHOO.util.History.initialize("yui-history-field", "yui-history-iframe");

The final version is available here. Cheers!