(Fix) Memory Leaks: Ajax page replacement

Resolving memory issues with HTML replacement and Ajax

In Illyriad we do very large numbers of ajax requests using jQuery over a players session. Some of these are pure data requests, but many of them are navigational HTML page replacements.

A simple replacement seems to leak memory in various browsers:

[code lang=”js”]
$(‘#ElementToReplaceContents’).html(htmlToReplaceWith);
[/code]

Even calling the jQuery function .empty() before the replacement doesn’t seem to help in all cases. To fix this we have created a clean up function which is a common or garden “overkill” variety which seems to do the trick. Usage below:

[code lang=”js”]
function ChangeLocationSucess(data, textStatus, XHR) {
if (XHR.status == 200) {
var div = $(‘#ElementToReplaceContents’);
DoUnload(); // More on this further down
div.RemoveChildrenFromDom(); // Clean up call
div.html(data); // HTML replacement
}

[/code]

Clearing the memory from the existing HTML is done with the clean up code below:

[code lang=”js” title=”Added: RemoveChildrenFromDom jQuery plugin”]
(function( $ ){
$.fn.RemoveChildrenFromDom = function (i) {
if (!this) return;
this.find(‘input[type="submit"]’).unbind(); // Unwire submit buttons
this.children()
.empty() // jQuery empty of children
.each(function (index, domEle) {
try { domEle.innerHTML = ""; } catch (e) {} // HTML child element clear
});
this.empty(); // jQuery Empty
try { this.get().innerHTML = ""; } catch (e) {} // HTML element clear
};
})( jQuery );
[/code]

Some pages have extra resources cached and extra elements with wired up events. For these we have introduced a DoUnload function which also gets called before the replacement. This runs through the list of on page clean up functions and calls them one by one:

[code lang=”js” title=”Extra clean up system”]
var unloadFuncs = [];
function DoUnload() {
while (unloadFuncs.length > 0) {
var f = unloadFuncs.pop();
f();
f = null;
}
}
[/code]

We create these unload functions on the page that has any extra clean up to do by using the following code; which adds clean up functions to the unloadFuncs array above. This code looks like the code below:

[code lang=”js” title=”On page clean up registration”]
var unload = function() {
OnPageCachedResources.clear(); // Clear array of cached resources
OnPageCachedResources = null;
$("#OnPageElementWithWiredUpEvents")
.unbind()
.undelegate(); // Remove attached events
}
unloadFuncs.push(unload);
[/code]

Obviously this varies from page to page and most of our pages don’t need it – at which point the DoUnload function just returns with out doing work.

This coupled with the Fix for IE and JQuery 1.4 Ajax deal with most of the regular unexpected leaks.

(As an aside: When replacing HTML with Ajax, do not wire up events using onclick=”” etc in the HTML or this will also leak. Use event binding from script – a third party library like jQuery, Prototype or Dojo will make this very straight forward)

Integration: Chrome Web Store

Publishing your web app on the Chrome Web Store is so straight forward, not doing so almost seems foolish!

Create a zip file containing a folder “chrome_app” and inside that two files:

  1. 128×128 png icon file
  2. manifest.json file
The manifest we use for Illyriad looks similar to this (make sure the icon field matches the name and case of the icon in the zip):

[code lang=”javascript” title=”manifest.json”]
{
"name": "Illyriad Test",
"description": "Real-time massively-multiplayer…",
"version": "1.0",
"app": {
"urls": [
"https://www.illyriad.co.uk/",
"https://uk1.illyriad.co.uk/",
"https://img3.illyriad.co.uk/",
"https://img4.illyriad.co.uk/",
"https://img5.illyriad.co.uk/",
"https://img6.illyriad.co.uk/"
],
"launch": {
"web_url": "https://www.illyriad.co.uk/loginOAuth.asp"
}
},
"icons": {
"128": "Logo-128.png"
},
"permissions": [
"unlimitedStorage",
"notifications"
]
}
[/code]

Your launch web_url should be either your login page, or if not required the main app page. If you do require a login page it’s also advisable to accept Google OpenId, at which point you can bypass the login page for CWS users who have a Google account and have approved you; but more on this in a later post.

To publish your app and access the developer dashboard a one-off developer registration fee of US$5.00 is required to verify your account, and presumably to cut down on spammers.

In the CWS developer dashboard click the Add new item button, which will take you to the upload app page:

Choose the zip file you created earlier and upload it:

Once you have uploaded your zip you will be taken to the Edit item page. First thing to do is upload the Icon you used in the zip file (unless you want a different icon in the Web Store to the one to be displayed in Chrome)

Now you’ll want to marketing materials. You can choose at the bottom of the page “Save draft and return to dashboard” if you don’t have these ready yet, so you can return to editing later. The items you’ll want are:

  1. A detailed description (Focus on explaining what the item does and why users should install it)
  2. Some screenshots (400 x 275 pixels or proportionally larger)
  3. (Optional) A YouTube video
  4. (Optional) A background image to show on your app page
  5. (Optional) 2 promotional images

After adding these on the edit item page, choose a category to publish in and then click “Preview changes” to see what your item will look like. If you like it hit publish, else return to editing. At this point you are published! Easy!

There is lots more you can do, of course, you can publish to test users first, use a verified website, ¬†change your pricing – your apps don’t have to be free, you can change the pricing and also sell apps from the Web Store – it is a store after all! But this should get you on your way…

(Fix) Memory Leaks: IE and JQuery 1.4 Ajax

While testing Illyriad we found that over time the browsers IE7 and IE8 leak memory on Ajax calls using JQuery 1.4.

If you make a lot of ajax calls without changing page this can build up quite quickly and start causing issues. This is caused by the onreadystatechange event not being detached and IE not garbage-collecting the associated memory.

To fix this, locate these lines in the source JQuery file; already kindly annotated with “Stop memory leaks”:

[code lang=”js” firstline=”6019″ title=”Original jquery-1.4.4.js”]
// Stop memory leaks
if ( s.async ) {
xhr = null;
}
[/code]

Add in these extra lines to change them to the following (we detach the abort event also for good measure):

[code lang=”js” firstline=”6019″ title=”Modified jquery-1.4.4.js”]
// Stop memory leaks
if ( s.async ) {
try {
xhr.onreadystatechange = null;
xhr.abort = null;
} catch (ex) { };
xhr = null;
}
[/code]

Don’t forget to minify your jquery file before including it. [We prefer using UglifyJS]

Also rename it slightly so users with cached versions pick up the new file

e.g. jquery-1.4.4a-min.js

(Fix) Javascript: Avoiding “console is undefined”

Testing if a function or object is defined can sometimes cause as many errors as not doing so. What to do?

When developing JavaScript using either Firebug in Firefox or Chrome’s¬†built-in console¬†you will probably at some point be using console.log either for debugging messages or for exception information.

However, when you come to run this code in Internet Explorer, it throws an exception “console is undefined”:

[code lang=”javascript”]
try {
… // code throwing error
} catch (e) {
console.log(e); // throws script error "console is undefined"
}
[/code]

“A-ha” you think; “easily¬†solved will just test if its defined before use”. However things are not so straight forward:

[code lang=”javascript”]
try {
… // code throwing error
} catch (e) {
if (console && console.log) // throws script error "console is undefined"
console.log(e);
}
[/code]

Even though now you are testing to see if it exists before use, you still get:

Console is undefined

While this would normally work for attributes of objects, globally scoped variables work differently. Luckily, all variables in global scope will be declared as attributes on the global window variable. This enables you to test for existence on the window.

Here we’ve wrapped the test up in a LogException function to ease changing of logging methodologies:

[code lang=”javascript”]
function LogException(e) {
if (window.console // check for window.console not console
&& window.console.log) window.console.log(e);
// Other logging here
}

try {
… // code throwing error
} catch (e) {
LogException(e); // works fine
}
[/code]

Note: Its isn’t exclusive to Internet Explorer, it just most visible there; so checking existence of globally scoped functions and variables against window rather than just by name is good practice!