22 Jan
2010

Memory Leaks In jQuery Dialogs

1 Comment Posted in Development

The Problem

I've spent the last week optimizing a lot of our JavaScript code at work. We rely heavily on jQuery there (a great framework!) and it's partner in crime jQuery UI for some of our UI widgets. Part of my optimizing including tracking down some of the memory leaks we've been seeing on the site with extended use, especially in IE (surprise!).

After tracking down quite a few closures and fixing those up, I was still left with 10 meg leaks here and there. After a bit I tracked it down to every time we used a dialog from jQuery UI's library.

Our helper function was pretty simple -- allow the code to specify the title and the text, and optionally provide a callback function that'll be called with what they clicked to close the dialog. The dialog would pop up on the screen and gray out the background forcing the user to interact with the alert box. I was concerned because we rely on modals quite a bit, especially in the edit pages. A 10meg leak per dialog shown would quickly add up to 100,200,300+megs leaked over the course of a normal session.

I removed a lot of the surrounding code, making sure I wasn't leaking memory in my own code. I was left with a pretty simple use of the dialog use:

function AlertDialog(dialogTitle, dialogText, callbackFunction) {
    // dynamically generate and add a div so we can create the pop-up
    $('body').append("
" + dialogText + "
"); // define/configure the modal pop-up $("#alertDialog").dialog({ draggable: false, resizable: false, modal: true, buttons: { 'OK': function() { if (callbackFunction != undefined) { callbackFunction('OK'); } //remove the default beforeclose event and trigger the close $("#alertDialog").unbind('dialogbeforeclose').dialog('close'); } } }).bind('dialogbeforeclose', function(event, ui) { // Close (X) button was clicked; NOT the OK button if (callbackFunction != undefined) { callbackFunction('cancel'); } }).bind('dialogclose', function() { $('#alertDialog').dialog("destroy").remove(); }).dialog("open"); }

Despite all my efforts I couldn't get the leak to stop. Simply opening the alert would cause it, so I figured it wasn't my callback function. Frustrated I changed the modal option to false. Bam, no more memory leak in IE! How could this be? So I tried playing around with the appearance of the modal in their CSS file. I tried setting the background property to a GIF instead of a PNG, since IE is terrible with PNGs, with no luck. I tried removing the opacity filter in the CSS with no luck too. Turns out it's just how jQuery UI generates the gray background behind the dialog that causes the leak.

The Solution

The solution was to never use the modal: true setting in our use of the dialog boxes. I instead wrote two helper methods that would be used to gray out the UI and block interaction with anything but the alert.

HTML:
CSS:
#BlockUI 
{
    position: fixed;
    display: none;
    top: 0;
    bottom: 0;
    left: 0;
    right: 0;
    z-index: 600;
    background: url(/Resources/grayout.gif);
}
JavaScript:
var blockCount = 0;

function BlockUI() {
    blockCount++;
    $('#BlockUI').show();
}

function UnBlockUI() {
    blockCount--;
    if (blockCount <= 0) {
        blockCount = 0;
        $('#BlockUI').hide();
    }
}

With this I had a common method for triggering a grayed-out background effect. The GIF I used was a checker-board pattern one, alternating between a light gray color and a transparent pixel. One could use a solid PNG with it's alpha set to something around 50%, but I wanted IE6 compatibility. The difference between the two is minimal.

With that in place I just needed to update the dialog to trigger the block and unblock calls. JQuery UI's dialog lets you specify a function for the "open" option which will be called when the dialog opens.

function AlertDialog(dialogTitle, dialogText, callbackFunction) {
    // dynamically generate and add a div so we can create the pop-up
    $('body').append("
" + dialogText + "
"); // define/configure the modal pop-up $("#alertDialog").dialog({ draggable: false, resizable: false, modal: false, open: function() { BlockUI(); }, buttons: { 'OK': function() { if (callbackFunction != undefined) { callbackFunction('OK'); } //remove the default beforeclose event and trigger the close $("#alertDialog").unbind('dialogbeforeclose').dialog('close'); } } }).bind('dialogbeforeclose', function(event, ui) { // Close (X) button was clicked; NOT the OK button if (callbackFunction != undefined) { callbackFunction('cancel'); } }).bind('dialogclose', function() { $('#alertDialog').dialog("destroy").remove(); UnBlockUI(); }).dialog("open"); }

And with that the memory leak was fixed. It's not ideal to have to rely on custom-code to get around the leak, but until jQuery UI is fixed to avoid this it's the best work around to make sure IE doesn't bloat like crazy when you use modals.

Next Entry
The Great Wall »

Previous Entry
« Assassin's Creed 2

1 Comment

Paul Marangoni said:

Thank you so very much for this!!!!

Leave A Comment

(Won’t give it out, promise)
(Markdown enabled)