Why is a Safari page breaking iOS rendering?
I know the title is not that explanatory but here is the story: I am developing a browser game, mostly using JavaScript and the Mapbox library.
Everything works well on desktop, Android and iOS but one problem appears on iOS: after letting the game run for a few minutes the phone suddenly begins to have graphic artifacts and display most of the text scrambled.
Here are some photos of what the phone begins too look like:
My question is: what exactly in my code can cause this? A memory leak? (LE: it turned out to actually be a memory leak)
The real question is: How comes that you can almost brick the entire phone by simply browsing a web page? Shouldn't Safari stop this, or at least the iOS ?
This is not a problem with this specific device, as this problem can be reproduced on different iPhone devices. (I'm not so sure about different iOS versions).
How I can reproduce the error:
- Open the game (inside Safari).
- Let it run for 3-4 minutes.
- Slide down the notification center and everything goes crazy.
I have added a YouTube video showing how I can reproduce the error (on my iPhone 5C).
It seems that the issue first appears in the notification center (if you swipe down the menu from the top).
As for now, this problem seems to only occur oniPhone 5C
iOS 9.2.1 (13D15). It also occurs on the new iOS 9.3 version.
In order to fix this issue I have to:
- Close the Safari application (in which the game tab is open).
- Lock the phone. After unlocking it everything is back to normal.
Some details about the game itself:
- The game shows a Mapbox map and some units over it (markers).
- A Node.js server runs at 1 tick/second and after each tick the updated game state is sent to the browser through Socket.io.
- Every time the browser receives the game state it updates the markers accordingly.
- *The game might also update markers if you zoom in or out or if you select them.
EDIT2:
Found the memory leak (as expected). After fixing this leak (check for undefined
_icon) the issue no longer occurs. This means, that somewhere along those lines the Safari/iOS bug is triggered.
Here is what exactly was being called each tick, for each unit that was clustered (was hidden and grouped with others inside a MarkerCluster):
var $icon = $(marker._icon); // marker._icon is undefined because of the clustering
$icon.html('');
$icon.append($('<img class="markerIcon" src="' + options.iconUrl + '" />'));
var iconX = 10;
var iconY = -10;
var iconOffset = 0;
for(var v in this.icons) {
this.icons[v].css('z-index', + $icon.css('z-index') + 1);
this.icons[v].css('transform', 'translate3d(' + iconX + 'px,'
+ (iconY + iconOffset) + 'px,' + '0px)');
iconOffset += 20;
this.icons[v].appendTo($icon);
}
// Fire rate icons
this.attackRateCircle = $('<div class="circle"></div>');
this.attackRateCircle.circleProgress({
value: 0,
size: 16,
fill: { color: "#b5deff" },
emptyFill: 'rgba(0, 0, 0, 0.5)',
startAngle: -Math.PI / 2,
thickness: 4,
animation: false,
});
this.attackRateCircle.hide();
// Create and display the healthbar
this.healthBar = $('<div>').addClass('healthBar ');
this.healthBar.css('z-index', $icon.css('z-index'));
this.healthBarFill = $('<span class="fill">');
this.healthBar.append(this.healthBarFill);
$icon.append(this.healthBar);
$icon.append(this.attackRateCircle);
And this is the icons
array:
this.icons = {
attack_order: $('<img src="img/attack.png" class="status_icon">'),
attack: $('<img src="img/damage.png" class="status_icon icon_damage">'),
hit: $('<img src="img/hit.png" class="status_icon icon_hit">'),
};
circleProgress
call is from this library: https://github.com/kottenator/jquery-circle-progress
DEMO
Yay, I have been able to create a jsFiddle that reproduces the bug: https://jsfiddle.net/cte55cz7/14/ Open on Safari on iPhone 5C and wait a couple of minutes. On iPhone 6 and iPad mini the page crashes (as expected due to the memory leak)
Here's the same code in a HasteBin, for anyone who doesn't want to run it.
This memory leaks is probably due to how 'WebKit’s JS Engine' works [safari webkit-javascript llvm]
and really looks like to be a virtual memory buffer-overflow, having a direct impact on the remaining RAM (shared and used also by iOS to store User Interface graphical elements)
Relatively to the piece of code: "[...]finding jQuery memory leaks is easy. Check the size of $.cache. If it’s too large, inspect it and see which entries stay and why. [...]" (http://javascript.info/tutorial/memory-leaks)
Let me expect that it is relative to this for loop :
for(var v in this.icons) {
this.icons[v].css('z-index', + $icon.css('z-index') + 1);
this.icons[v].css('transform', 'translate3d(' + iconX + 'px,'
+ (iconY + iconOffset) + 'px,' + '0px)');
iconOffset += 20;
this.icons[v].appendTo($icon);
}
Assuming inspection is done, and also assuming the fact that you find the entries, you may want to clean the data manually with removeData() or you may use first $elem.detach() and then put $(elem).remove() in setTimeout.