Archive for the 'Web Development' Category

Dec 22 2007

High Performance Ajax Applications - Video Presentation

Published by Julien Lecomte under Web Development

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!

7 responses so far

Dec 12 2007

The Problem With innerHTML

Published by Julien Lecomte under Web Development

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.

30 responses so far

Dec 05 2007

Adding Back Button and Bookmarking Support to Your DHTML Slide Show

Published by Julien Lecomte under Web Development

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="http://yui.yahooapis.com/2.4.0/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="http://yui.yahooapis.com/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!

6 responses so far

Nov 26 2007

Introducing CrossFrame, a Safe Communication Mechanism Across Documents and Across Domains

Published by Julien Lecomte under Web Development

The mashup problem

According to my coworker Douglas Crockford, Mashups are the most interesting advancement in software development in decades. They are also unsafe in the current generation of browsers. Lately, Douglas has been spending some time convincing the main browser vendors that mashups need to be made safe, wrote a proposal, and even mentioned Google Gears as a potential solution to the problem. While fixing the browser is the right thing to do, web developers are confronted with this problem today, and cannot afford to wait 5 years for a definitive solution.

Existing solutions to the mashup problem

One way mashups (or widgets, badges and gadgets, take your pick…) can be made safe is by sandboxing them in an IFrame pointing to another domain (Note: another way would be to run the untrusted code through ADsafe, and provide some safe API to do useful things on the page) The problem is that the Same Origin Policy isolates them so completely that they are then unable to cooperate with the page containing them or with each other. Several hacks have been exploited to achieve reasonably secure client-side cross-domain communication. The most popular ones use the URL fragment identifier or the Flash LocalConnection object.

Why the need for another technique?

CrossFrame is a variant of the URL fragment identifier mechanism. In the original technique, the containing page sets the URL fragment identifier of an embedded IFrame (usually via its src attribute), and the IFrame must poll to detect changes in the value of its location.hash property. This technique can be further built upon to allow for 2-way communications between an IFrame and its containing page, or between two distinct IFrames.

The original URL fragment identifier technique has many limitations, many of which can be worked around except maybe for the following:

  • It unnecessarily consumes CPU cycles by requiring the receiver to poll.
  • It creates “fake” history entries on Safari and Opera.

How does CrossFrame work?

While CrossFrame also has limitations of its own, I find it to be a much cleaner and simpler approach. Here is how it works:

In order to communicate with the mashup hosted in domain Y, the page, hosted in domain X, dynamically creates a hidden IFrame and points it to a special proxy file hosted in domain Y, using the URL fragment identifier to convey the message (step 1) When the special proxy file is loaded in the hidden IFrame, it reads its URL fragment identifier and passes it to a globally accessible function defined in the IFrame hosting the mashup (step 2) using parent.frames['mashup'] to get to it. The same technique can also be used by the mashup to communicate with the page (the proxy will use parent.parent to get to the page) Finally, when all is said and done, the hidden IFrame is automatically removed from the DOM by the library.

This however cannot work on Opera, which does not allow us to query any property of a window pointing to a different domain (so getting parent.parent for example will throw an exception) CrossFrame takes care of this by using, on Opera only, the HTML 5 way of sending messages across frames and across domains.

How to use CrossFrame?

In order to use the CrossFrame library, place the proxy file (proxy.html, included in the downloadable archive) on your web server so you can receive CrossFrame notifications for that domain. Make sure that the proxy file gets cached properly by web browsers, for example using a .htacess file similar to this one:

<Files proxy.html>
    ExpiresActive on
    ExpiresDefault "access plus 1 year"
</Files>

Then, import the necessary code and its dependencies in your page:

<script type="text/javascript" src="http://yui.yahooapis.com/2.3.1/build/yahoo-dom-event/yahoo-dom-event.js"></script>
<script type="text/javascript" src="cross-frame.js"></script>

To receive messages, subscribe to the onMessage event:

YAHOO.util.CrossFrame.onMessageEvent.subscribe(
    function (type, args, obj) {
        var message = args[0];
        var domain = args[1];
        // Do something with the incoming message...
    }
);

To send a message, call YAHOO.util.CrossFrame.send():

YAHOO.util.CrossFrame.send("http://www.y.com/proxy.html",
                           "frames['mashup']",
                           "message");

Here is a demo showing the CrossFrame library in action.

Limitations

The CrossFrame library does not support chunking (i.e. the ability to pass a large message in several smaller chunks) so the size of the messages that may be sent is limited by the maximum length of a URL (which varies across browsers…) However, it is not impossible to implement (for more information on chunking, you may want to look at Dojo’s XHR IFrame proxy implementation, which I believe supports chunking)

Also, the user may experience a short delay the first time a message gets sent to a specific domain. This is due to the server round trip necessary to download the proxy file. However, this can easily be mitigated by preloading the proxy file for that specific domain.

Conclusion: The dangers of temporary solutions

There is a danger associated with this kind of “hack”. First of all, browser vendors may decide to change their security policies and mimic Opera’s behavior for example. If this happens, CrossFrame will stop working for those browsers. Furthermore, I do not recommend using hacks because they slow down the rate of innovation on the web (it makes the task of developing web browsers even more complicated than it already is, and also makes your application less maintainable) Therefore, as paradoxical as it may seem, I do not recommend using CrossFrame (or any of those ugly hacks for that matter)

7 responses so far

Nov 11 2007

Getting Rid of the Navigation Click Sound on IE

Published by Julien Lecomte under Web Development

Internet Explorer plays a little click sound when the location of a page changes. This is a great usability feature as it lets the user know that something is happening. However, this little click sound can become really annoying with web applications that make extensive use of iframes. For example, imagine an application using several iframes to sandbox untrusted third party ads. Refreshing the content of the iframes (by changing their src attribute) makes the application sound like an automatic rifle. Scary, huh? Well, I recently found out about a very clever trick that can make this click sound go away.

Replace:

iframe.src = "...";

by:

var newIFrame = document.createElement("iframe");
newIFrame.src = "...";
iframe.parentNode.replaceChild(newIframe, iframe);

The secret is to set the iframe src attribute before appending it to the document… However, this simple approach exhibits a very unpleasant flickering. The following snippet shows how to get rid of the flickering as well:

function setIFrameSrc(iframe, src) {
    var el;
    iframe = YAHOO.util.Dom.get(iframe);
    if (YAHOO.env.ua.ie) {
        // Create a new hidden iframe.
        el = iframe.cloneNode(true);
        el.style.position = "absolute";
        el.style.visibility = "hidden";
        // keep the original iframe id unique!
        el.id = "";
        // Listen for the onload event.
        YAHOO.util.Event.addListener(el, "load", function () {
            // First, remove the event listener or the old iframe
            // we intend to discard will not be freed...
            YAHOO.util.Event.removeListener(this, "load", arguments.callee);
            // Show the iframe.
            this.style.position = "";
            this.style.visibility = "";
            // Replace the old iframe with the new one.
            iframe.parentNode.replaceChild(this, iframe);
            // Reset the iframe id.
            this.id = iframe.id;
        });
        // Set its src first...
        el.src = src;
        // ...and then append it to the body of the document.
        document.body.appendChild(el);
    } else {
        iframe.src = src;
    }
}

This example demonstrates this technique (turn the volume up, and open it up with Internet Explorer) Note: the navigation sound you hear when using Internet Explorer (or Windows Explorer) can be configured via the Sounds dialog accessible from the Control Panel.

Update: I forgot to mention that, by using this trick, you will break the back / forward navigation buttons. Therefore, this trick should only be used for a non-navigational purpose only.

6 responses so far

Oct 25 2007

Running CPU Intensive JavaScript Computations in a Web Browser

Published by Julien Lecomte under Web Development

The pattern discussed below is a well known pattern that has been used for 10 years. The goal of this article is to present this pattern under a new light, and most importantly to discuss ways of reducing its overhead.

The biggest deterrent for running CPU intensive computations in a web browser is the fact that the entire browser user interface is frozen while a JavaScript thread is running. This means that under no circumstance should a script ever take more than 300 msec (at most) to complete. Breaking this rule inevitably leads to bad user experience.

Furthermore, in web browsers, JavaScript threads have a limited amount of time to complete (there can be either a static time limit — that’s the case of Mozilla based browsers — or some other limit such as a maximum number of elementary operations — that’s the case of Internet Explorer) If a script takes too long to complete, the user is presented with a dialog asking whether that script should be terminated.

Google Gears provides the ability to run CPU intensive JavaScript code without the two aforementioned limitations. However, you cannot usually rely on the presence of Gears (in the future, I would like to see a solution like the Gears WorkerPool API as part of the standard browser API)

Fortunately, the setTimeout method of the global object allows us to execute code on a delay, giving the browser a chance to handle events and update the user interface, even if the timeout value passed to setTimeout is 0. This allows us to cut a long running process into smaller units of work, and chain them according to the following pattern:

function doSomething (callbackFn [, additional arguments]) {
    // Initialize a few things here...
    (function () {
        // Do a little bit of work here...
        if (termination condition) {
            // We are done
            callbackFn();
        } else {
            // Process next chunk
            setTimeout(arguments.callee, 0);
        }
    })();
}

This pattern can also be slightly modified to accept a progress callback instead of a completion callback. This is especially useful when using a progress bar:

function doSomething (progressFn [, additional arguments]) {
    // Initialize a few things here...
    (function () {
        // Do a little bit of work here...
        if (continuation condition) {
            // Inform the application of the progress
            progressFn(value, total);
            // Process next chunk
            setTimeout(arguments.callee, 0);
        }
    })();
}

This example demonstrates the sorting of a fairly large array using this pattern.

Notes:

  1. This pattern has a lot of overhead i.e. the total amount of time required to complete a task can be far greater than the time it would take to run the same task uninterrupted.
  2. The shorter each cycle, the greater the overhead, the more reactive the user interface, the greater the overall time required to complete the task.
  3. If you can be sure that each iteration of your algorithm is of very short duration — say 10 msec, you may want to group several iterations within a single cycle to reduce the overhead. The decision whether to start the next cycle or continue with more iterations can be made based on how long the current cycle has been running. This example demonstrates this technique. Although it uses the same sorting algorithm as the example above, notice how much faster it is, while still keeping the user interface perfectly reactive.
  4. Never pass a string to setTimeout! If you do, the browser needs to do an implicit eval every time the code is executed, which adds an incredible amount of completely unnecessary overhead.
  5. If you manipulate global data, make sure that access to that data is synchronized since it could also be modified by another JavaScript thread running between two cycles of your task.

Finally, consider running this kind of task on the server (though you’ll have to deal with serialization / deserialization and network latency, especially if the data set is large) Having to run CPU intensive computations on the client might be a sign of a deeper, more serious architectural problem with your application.

5 responses so far

Oct 06 2007

The Birth Of Web 3.0

Published by Julien Lecomte under Web Development

Is Web 3.0 yet another buzz word, or is it a real turnaround in our industry?

Web 1.0 was the good old web of the 1990s. In those times, all client-side changes were the result of a server round-trip. The Internet was ramping up in popularity.

Web 2.0 has been a little more than just a technological evolution. The staple of Web 2.0 has been the emergence of social media (Internet users creating most of the content), powered by mature technologies (DHTML, Ajax) on somewhat stable web browsers.

Web 3.0 is not a revolution either. It is yet another technological evolution destined to provide users with an even better experience, both online and offline. Web 3.0 will lead to the blurring of that artificial wall between the web browser and the desktop, providing a full — but secure — integration with devices and services exposed by the operating system.

Web 3.0 is just starting. Look around you and you’ll see that Web 3.0 technologies are slowly cropping up everywhere on the web. Google Gears, one of the first Web 3.0 technologies, allows you to build web applications that can work offline. Thanks to Google Gears, applications such as Remember The Milk, an online to-do list and task management system, can now work offline. The Adobe Flash player already allows application developers limited access to the webcam and the microphone. Soon, we’ll also be able to drag and drop files from the desktop to a web browser (see this Java Upload Applet for an example using the Java technology)

Another aspect of Web 3.0 is the use of stunning graphics, smooth animations, high definition audio and video, 3D, etc. and all of this inside a web browser!

At first, Web 3.0 features will be available using plugins (Google Gears, Java, Flash, Silverlight, ActiveX and Firefox extensions, etc.) But slowly, we may start seeing browser vendors integrating them into their browsers, followed by some level of standardization. The HTML 5 Working Draft seems to be going in the right direction.

These are exciting times for web front-end engineers! The risk of fragmentation, inevitable with such ground-breaking technologies, will hopefully be mitigated in the short term by the use of JavaScript toolkits. The Dojo Toolkit, for example, has already started making Web 3.0 features available (see dojo.gfx and the Dojo Offline Toolkit) Hopefully, all the other major frameworks will follow suite so we can all start building cool new applications that wow our users!

7 responses so far

Sep 28 2007

YUI Compressor Version 2.2.1 Now Available

Published by Julien Lecomte under Web Development

I implemented a few enhancement requests and fixed a bug in this new version of the YUI Compressor. Let me know if you encounter any issue with it.

Update (9/27/07): YUI Compressor version 2.2.2 is now available. It fixes a lot of bugs that have been reported recently. By the way, I really appreciate all the bug reports, so keep them coming!

Update (9/28/07): New bugs have been reported and fixed in version 2.2.3 now available for download (check out the CHANGELOG file in the download page) And keep these bug reports coming!

Update (10/1/07): A few more minor bugs have been fixed in version 2.2.4. Thanks for the bug reports!

Download version 2.2.4 of the YUI Compressor

27 responses so far

Sep 20 2007

Trimming comments in HTML documents using Apache Ant

Published by Julien Lecomte under Web Development

This short article, explaining how to trim unnecessary code (comments, empty lines) from HTML documents, is a follow-up to an article published a couple of weeks ago on this blog: Building Web Applications With Apache Ant. Basically, the idea is to use Ant’s optional replaceregexp task as shown below:

<target name="-trim.html.comments">

    <fileset id="html.fileset"
        dir="${build.dir}"
        includes="**/*.jsp, **/*.php, **/*.html"/>

    <!-- HTML Comments -->
    <replaceregexp replace="" flags="g"
        match="\&lt;![ \r\n\t]*(--([^\-]|[\r\n]|-[^\-])*--[ \r\n\t]*)\&gt;">
        <fileset refid="html.fileset"/>
    </replaceregexp>

    <!-- Empty lines -->
    <replaceregexp match="^\s+[\r\n]" replace="" flags="mg">
        <fileset refid="html.fileset"/>
    </replaceregexp>

</target>

Update: Use this code very carefully as it is dangerous territory (Thanks to my co-worker Ryan Grove for pointing out some of the shortcomings)

6 responses so far

Sep 18 2007

YUI Compressor Version 2.2 Now Available

Published by Julien Lecomte under Web Development

This new version of the YUI Compressor supports stdin and stdout. This means that you can now call the YUI Compressor using the following command line:

java -jar yuicompressor-2.2.jar --type js < input.js > output.js

You can still use the following syntax as well:

java -jar yuicompressor-2.2.jar -o output.js input.js

This has three main consequences:

  1. All informational and error messages are now printed to stderr.
  2. If no input file is specified, the YUI Compressor defaults to stdin. In that case, you must specify the --type option because the YUI compressor has no way of knowing whether it should invoke the JavaScript or CSS compressor (there is no file extension to look at)
  3. If no output file is specified, the YUI Compressor defaults to stdout (in prior versions, it used to create a file named after the input file, and appended the -min suffix)

The other main feature brought by this new version of the YUI Compressor is the support for JScript conditional comments:

/*@cc_on
   /*@if (@_win32)
      document.write("OS is 32-bit, browser is IE.");
   @else @*/
      document.write("Browser is not 32 bit IE.");
   /*@end
@*/

Note that the presence of a conditional comment inside a function (i.e. not in the global scope) will reduce the level of compression for the same reason the use of eval or with reduces the level of compression (conditional comments, which do not get parsed, may refer to local variables, which get obfuscated) In any case, the use of Internet Explorer’s conditional comments is to be avoided.

Finally, a few improvements have been made to the CSS compressor.

Download version 2.2 of the YUI Compressor

11 responses so far

Next »