Data URI leak in Safari (was: Memory Leak with HTML5 canvas)

Data URI leak in Safari (was: Memory Leak with HTML5 canvas)



I have created a webpage that receives base64 encoded bitmaps over a Websocket and then draws them to a canvas. It works perfectly. Except, the browser's (whether Firefox, Chrome, or Safari) memory usage increases with each image and never goes down. So, there must be a memory leak in my code or some other bug. If I comment out the call to context.drawImage, the memory leak does not occur (but then of course the image is never drawn). Below are snippets from my webpage. Any help is appreciated. Thanks!


// global variables
var canvas;
var context;

...

ws.onmessage = function(evt)

var received_msg = evt.data;
var display_image = new Image();
display_image.onload = function ()

context.drawImage(this, 0, 0);

display_image.src = 'data:image/bmp;base64,'+received_msg;


...

canvas=document.getElementById('ImageCanvas');
context=canvas.getContext('2d');

...

<canvas id="ImageCanvas" width="430" height="330"></canvas>



UPDATE 12/19/2011



I can work around this problem by dynamically creating/destroying the canvas every 100 images or so with createElement/appendChild and removeChild. After that, I have no more memory problems with Firefox and Chrome.



However, Safari still has a memory usage problem, but I think it is a different problem, unrelated to Canvas. There seems to be an issue with repeatedly changing the "src" of the image in Safari, as if it will never free this memory.


display_image.src = 'data:image/bmp;base64,'+received_msg;



This is the same problem described on the following site: http://waldheinz.de/2010/06/webkit-leaks-data-uris/



UPDATE 12/21/2011



I was hoping to get around this Safari problem by converting my received base64 string to a blob (with a "dataURItoBlob" function that I found on this site) and back to a URL with window.URL.createObjectURL, setting my image src to this URL, and then later freeing the memory by calling window.URL.revokeObjectURL. I got this all working, and Chrome and Firefox display the images correctly. Unfortunately, Safari does not appear to have support for BlobBuilder, so it is not a solution I can use. This is strange, since many places including the O'Reilly "Programming HTML5 Applications" book state that BlobBuilder is supported in Safari/WebKit Nightly Builds. I downloaded the latest Windows nightly build from http://nightly.webkit.org/ and ran WebKit.exe but BlobBuilder and WebKitBlobBuilder are still undefined.



UPDATE 01/03/2012



Ok, I finally fixed this by decoding the base64-encoded data URI string with atob() and then creating a pixel data array and writing it to the canvas with putImageData (see http://beej.us/blog/2010/02/html5s-canvas-part-ii-pixel-manipulation/). Doing it this way (as opposed to constantly modifying an image's "src" and calling drawImage in the onload function), I no longer see a memory leak in Safari or any browser.






what happens if you add a clearRect call before drawing the image, or if you use the reset trick of setting the width to itself? (from stackoverflow.com/questions/2142535/…)

– Mikeb
Dec 16 '11 at 19:24






I have tried context.clearRect(0, 0, canvas.width, canvas.height); before drawImage but the memory leak still occurs.

– bglaudel
Dec 16 '11 at 19:33







Are you sure it isn't just storing the image just in case you go back to the page so it can just load it from memory? Might be a runtime optimization.

– Andrew Rasmussen
Dec 16 '11 at 21:04






2018 : Safari v11 --> data uri still leaks like CRAZY ("I would note put this in prod" kind of crazy). Firefox Quantum --> leaks a little bit. Chrome --> totally fine.

– Guillaume Le Mière
Apr 17 '18 at 5:10





4 Answers
4



Without actual working code we can only speculate as to why.



If you're sending the same image over and over you're making a new image every time. This is bad. You'd want to do something like this:


var images = ; // a map of all the images

ws.onmessage = function(evt)

var received_msg = evt.data;
var display_image;
var src = 'data:image/bmp;base64,'+received_msg;
// We've got two distinct scenarios here for images coming over the line:
if (images[src] !== undefined)
// Image has come over before and therefore already been created,
// so don't make a new one!
display_image = images[src];
display_image.onload = function ()
context.drawImage(this, 0, 0);

else
// Never before seen image, make a new Image()
display_image = new Image();
display_image.onload = function ()
context.drawImage(this, 0, 0);

display_image.src = src;
images[src] = display_image; // save it for reuse




There are more efficient ways to write that (I'm duplicating onload code for instance, and I am not checking to see if an image is already complete). I'll leave those parts up to you though, you get the idea.






Every image is unique.

– bglaudel
Dec 16 '11 at 19:35






If every image is unique, and memory usage increases as you make each new HTMLImageElement (new Image()), that's just normal!

– Simon Sarris
Dec 16 '11 at 19:37


new Image()






Then why does memory usage stay level if I comment out the call to drawImage?

– bglaudel
Dec 16 '11 at 19:45






Right now, in your code, every single time drawImage is called, new Image() is also called. Unless you are only calling drawImage X times where X is the number of unique images, you have a huge problem because you are making more new Image()s than you want. How many images are there, and how many times is new Image() getting called?

– Simon Sarris
Dec 16 '11 at 19:51


drawImage


new Image()


drawImage


new Image()


new Image()






There are an infinite number of unique images, as many as my Websocket server wants to send. My point is, when I forgo calling drawImage (by commenting it out), it appears that the memory allocated by new Image() is freed after some period, probably by garbage collection. But when I leave in the call to drawImage that memory is never freed.

– bglaudel
Dec 16 '11 at 19:59



I don't believe this is a bug. The problem seems to be that the images are stacked on top of each other. So to clear up the memory, you need to use clearRect() to clear your canvas before drawing the new image in it.



ctx.clearRect(0, 0, canvas.width, canvas.height);



How to clear your canvas matters






I noticed that for some reason the canvas height seems to be wrong in my case. Using the image height as the "canvas.height" in the code sample seems to do the trick.

– Stitch10925
Apr 24 '12 at 15:47



you're probably drawing the image a lot more times than you are expecting to. try adding a counter and output the number to an alert or to a div in the page to see how many times the image is being drawn.



That's very interesting. This is worth reporting as a bug to the various browser vendors (my feeling is that it shouldn't happen). You might responses along the lines of "Don't do that, instead do such and such" but at least then you'll know the right answer and have an interesting thing to write up for a blog post (more people will definitely run into this issue).



One thing to try is unsetting the image src (and onload handler) right after the call to drawImage. It might not free up all the memory but it might get most of it back.



If that doesn't work, you could always create a pool of image objects and re-use them once they have drawn to the canvas. That's a hassle because you'll have to track the state of those objects and also set your pool to an appropriate size (or make it grow/shrink based on traffic).



Please report back your results. I'm very interested because I use a similar technique for one of the tightPNG encoding in noVNC (and I'm sure others will be interested too).



Thanks for contributing an answer to Stack Overflow!



But avoid



To learn more, see our tips on writing great answers.



Required, but never shown



Required, but never shown




By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Popular posts from this blog

𛂒𛀶,𛀽𛀑𛂀𛃧𛂓𛀙𛃆𛃑𛃷𛂟𛁡𛀢𛀟𛁤𛂽𛁕𛁪𛂟𛂯,𛁞𛂧𛀴𛁄𛁠𛁼𛂿𛀤 𛂘,𛁺𛂾𛃭𛃭𛃵𛀺,𛂣𛃍𛂖𛃶 𛀸𛃀𛂖𛁶𛁏𛁚 𛂢𛂞 𛁰𛂆𛀔,𛁸𛀽𛁓𛃋𛂇𛃧𛀧𛃣𛂐𛃇,𛂂𛃻𛃲𛁬𛃞𛀧𛃃𛀅 𛂭𛁠𛁡𛃇𛀷𛃓𛁥,𛁙𛁘𛁞𛃸𛁸𛃣𛁜,𛂛,𛃿,𛁯𛂘𛂌𛃛𛁱𛃌𛂈𛂇 𛁊𛃲,𛀕𛃴𛀜 𛀶𛂆𛀶𛃟𛂉𛀣,𛂐𛁞𛁾 𛁷𛂑𛁳𛂯𛀬𛃅,𛃶𛁼

Edmonton

Crossroads (UK TV series)