14

I have a web application which dynamically loads in data over a long period of time. Within the data there are links to images which are then rendered in the browser.

e.g.

var object = { 
    Name: ko.observable("Foo"), 
    Ref: ko.observable("Bar"), 
    ImageUrl: ko.observable("http://.....")           
}

I am using Knockoutjs 's template binding to render the data on the screen.

<img data-bind="attr: { src: imageUrl }" />         

So every time the object changes via an Ajax call, the Knockoutjs template is re-rendered with the data, and the images change.

After a long period of time, these images build up and will eat up more memory. Modern browsers seem to cope better, but the problem is mainly with IE8 (we do not support < IE8). Even in modern browsers the memory will eventually get so high that the browser freezes.

See this screen shot for an example of the image resources building up.

enter image description here

I decided to see what would happen if instead of using an <img /> tag, use a <iframe />.

So my code now looks like

<iframe data-bind="attr: { src: imageUrl }"></iframe>

What happens now is that the frame is created, but as soon as the imageUrl changes, the frame simply updates and does not create additional resources.

enter image description here

enter image description here

So if I want to keep the browser memory usage down, I can use this <iframe /> technique, but I don't like it. It forces me to make many other changes to the application, plus I need to use iframes!

I have run various tests now to see how much memory is used up using both techniques, and over the same period of time the memory will increase from 81,000k to 200,000k (with <img />) compared to 81,000k to 98,000k ( with <iframe />)

Question

Is there a better way to manage image resources within a browser? Is there a way to properly dispose of this image? I have searched the web for an answer, but so far I have not found anything.

Edit

At the very basic level. I have tried to remove an image via the jQuery method remove(), but the image resource is never removed. See this fiddle for a very basic example. http://jsfiddle.net/ezb9e/

code:

html

<img src="http://www.fillmurray.com/200/300" />

JS

$(function(){   
    setTimeout(function(){
        $('img').remove();
        $('body').append($('<img />', { attr: { src: 'http://www.fillmurray.com/300/200' }}));
    }, 3000);    
});
Tim B James
  • 19,595
  • 4
  • 68
  • 96
  • Might it be possible to use these hooks: http://knockoutjs.com/documentation/template-binding.html#note_3_using_afterrender_afteradd_and_beforeremove to manually remove the image from the DOM yourself, before the new one is added? That should stop the problem. – Nik Mar 24 '13 at 11:07
  • I have already tried to remove the image first, and then add a new one, but the image resources always build up. See this fiddle for an example. http://jsfiddle.net/ezb9e/ – Tim B James Mar 24 '13 at 11:18
  • Are the image urls always changing when you refresh the data, or do they stay relatively constant? – Paul Manzotti Mar 24 '13 at 17:37
  • As a sanity check, if you use images of a half the size, does the memory climb at half the rate? – Nik Mar 24 '13 at 17:42
  • @PaulManzotti The image URLs are always different. Same domain, different images. – Tim B James Mar 25 '13 at 09:02
  • @Nik Yeah I ran a test which had twice as many images displaying and the memory usage was double. – Tim B James Mar 25 '13 at 09:02
  • I take it that my suggestion of using a custom binding didn't fix it then? – Paul Manzotti Jul 22 '14 at 10:56
  • Duplicate of [Unloading Resources on HTML with JavaScript](http://stackoverflow.com/questions/7488866/unloading-resources-on-html-with-javascript)? – Jeff Mercado Jul 23 '14 at 06:12

1 Answers1

5

I would try using a custom binding, and create and destroy the images in that. I had a similar problem last year with an SVG, and that's what I had to do.

ko.bindingHandlers.createImage = {
    update: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
    // Use something unique to identify this image object
    var imageName = viewModel.Name();
    var parent = bindingContext.$parent;

    var imageObject = parent.Images[imageName];

    if (imageObject) {
        $(element).empty()
        imageObject = null;
    }

    imageObject = $(element).append('<img src="' + viewModel.imgSrc() + '" />')[0];
    parent.Images[imageName] = imageObject;
    }
};

Here's recreating your original problem:

http://jsfiddle.net/manzanotti/ezb9e/5/

And here's my version:

http://jsfiddle.net/manzanotti/ezb9e/13/

Memory goes up initially, but it gets garbage collected every now and again, so it never gets out of control. This is tested in IE9 and Chrome.

Update Hmmm, I'm not now convinced that this completely fixes the problem in IE8. I've run the fiddle in sIEve, and the memory is going up still in that, though as sIEve adds hooks into DOM nodes, it could be a result of running it in sIEve. Chrome is definitely fine though, and IE9 seems, at the very least, to be a lot better, if not completely fixed.

Paul Manzotti
  • 5,027
  • 19
  • 27
  • Unfortunately, it looks like IE8 just has a pretty bad memory leak in regard to image creation. A proposed solution (that seems to work for only some people), is to *turn on* "Enable SmartScreen Filter" in Advanced Internet Options. Quite the atrocious workaround, but may be worth it if you really need the IE8 support and don't want to use iframes. Source: http://com.hemiola.com/2009/11/23/memory-leaks-in-ie8/#comment-880 – snickle Jul 23 '14 at 20:59