I'm trying to build an image gallery in Safari that mimics the iPad photo app. It works perfectly, except that once I load more than 6MB or so worth of images either by adding them to the DOM or creating new Image objects, new images either stop loading or the browser crashes. This problem is widespread enough (with everyone else hitting up against the same limit) that I've ruled out my Javascript code as the culprit.
Given that you can stream much more than a few MB in a element or through the in-browser media player, this limit seems unnecessary, and there should be some kind of workaround available. Perhaps by freeing up memory or something else.
I also came across this reference for UIWebView.
"JavaScript allocations are also limited to 10 MB. Safari raises an exception if you exceed this limit on the total memory allocation for JavaScript."
Which matches what I'm seeing fairly well. Is it possible to deallocate objects in Javascript, or does Safari/UIWebView keep a running total and never lets go? Alternately, is there any workaround to load in data another way that doesn't eat up this 10MB?
I'm running in a similar issue in Chrome too, developing an extension that loads images in the same page (the popup, actually) replacing old images with new ones. The memory used by the old images (removed from the DOM) is never freed, consuming all the PC memory in a short time. Have tried various tricks with CSS, without success. Using hardware with less memory than a PC, like the iPad, this problem arises earlier, naturally.
I encountered an out of memory with Javascript on the iPad when we were trying to refresh an image very often, like every couple of seconds. It was a bug to refresh that often, but Safari crashed out to the home screen. Once I got the refresh timing under control, the web app functioned fine. It seemed as if the Javascript engine couldn't keep up with garbage collection quickly enough to discard all the old images.
The 6.5MB(iPad) / 10MB(iPhone) download limits are calculated based on the number of image elements used to set an image through its src property. Mobile safari doesn't seem to differentiate images loaded from cache or via the network. It also doesn't matter whether the image is injected into the dom or not.
The second part to the solution is that mobile safari seems to be able to load an unlimited number of images via the "background-image" css property.
This proof of concept uses a pool of precacher's which set the background-image properties once successfully downloaded. I know that it's not optimal and doesn't return the used Image downloader to the pool but i'm sure you get the idea :)
The idea is adapted from Rob Laplaca's original canvas workaround http://roblaplaca.com/blog/2010/05/05/ipad-safari-image-limit-workaround/
Update: I think there's an even easier way to do this, depending on your application. Instead of having multiple images, if you simply have one
<img>
element orImage
object (or maybe two, like a 'this' image and a 'next' image if you need animations or transitions) and simply update the.src
,.width
,.height
and so on, you should never get near the 10MB limit. If you wanted to do a carousel application, you'd have to use smaller placeholders first. You might find this technique might be easier to implement.I think I may actually have found a work-around to this.
Basically, you'll need to do some deeper image management and explicitly shrink any image you don't need. You'd normally do this by using
document.removeChild(divMyImageContainer)
or$("myimagecontainer").empty()
or what have you, but on Mobile Safari this does absolutely nothing; the browser simply never deallocates the memory.Instead, you need to update the image itself so it takes up very little memory; and you can do that by changing the image's
src
attribute. The quickest way I know of to do that is to use a data URL. So instead of saying this:...say this instead:
Below is a test to demonstrate it working. In my tests, my large 750KB image would eventually kill the browser and halt all JS exectution. But after resetting
src
, I"ve been able to load in instances of the image over 170 times. An explanation of how the code works is below as well.This code was written to test my solution, so you'll have to figure out how to apply it to your own code. The code comes in three parts, which I will explain below, but the only really important part is
imgStoredImage.src = strNullImage;
loadNextImage()
simply loads a new image and callsshrinkImages()
. It also assigns anonload
event which is used to begin the process of loading another image (bug: I should be clearing this event later, but I'm not).waitAndReload()
is only here to allow the image time to show up on the screen. Mobile Safari is pretty slow and displaying big images, so it needs time after the image has loaded to paint the screen.shrinkImages()
goes through all previously loaded images (except the active one) and changes the.src
to the dataurl address.I'm using a file-folder image for the dataurl here (it was the first dataurl image I could find). I'm using it simply so you can see the script working. You'll probably want to use a transparent gif instead, so use this data url string instead:
data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==
I filed a bug with jQuery as jQuery trys to handle memory leaks...so I'd consider this a bug. Hopefully the team can come up with some concise and clever way of handling this problem in Mobile Safari soon.
http://dev.jquery.com/ticket/6944#preview
I was unable to find a solution for this. Here are a couple of methods I tried, and all of them failed:
Simply changed the background of a DIV using
div.style.backgroundImage = "url("+base64+")"
Changed the
.src
of an image usingimg.src = base64
Removed the old and added the new image using
removeChild( document.getElementById("img") ); document.body.appendChild( newImg )
The same as above but with a random height on the new image
Removing and adding the image as a HTML5 canvas object. Also doesn't work, since a new
Image();
has to be created, see *On launch, created a new
Image()
object, let's call it container. Displayed the image as<canvas>
, every time the image changed, I would change container's.src
and redraw the canvas usingctx.drawImage( container, 0,0 )
.The sames as the previous, but without actually redrawing the canvas. Simply changing the
Image()
object'ssrc
uses up memory.A strange thing I noticed: The bug occurs even if the image isn't displayed! For example, when doing this:
Every 5 seconds, and nothing else, no loading or displaying the image, of course wrapped up in an object, also crashes the memory after some time!