web-dev-qa-db-fra.com

Pourquoi une page Safari brise-t-elle le rendu iOS?

Je sais que le titre n'est pas si explicatif, mais voici l'histoire: je développe un jeu par navigateur, principalement en utilisant JavaScript et la bibliothèque Mapbox.

Tout fonctionne bien sur le bureau, Android et iOS mais un problème apparaît sur iOS: après avoir laissé le jeu fonctionner pendant quelques minutes, le téléphone commence soudainement à avoir des artefacts graphiques et à afficher la plupart du texte brouillé.

Voici quelques photos de ce à quoi ressemble trop le téléphone: enter image description hereenter image description hereenter image description here

Ma question est: qu'est-ce qui exactement dans mon code peut provoquer cela? Une fuite de mémoire? ( [~ # ~] le [~ # ~] : il s’agit en fait d’une fuite de mémoire)
La vraie question est: comment se fait-il que vous puissiez presque brique l'ensemble du téléphone en parcourant simplement une page Web? Safari ne devrait-il pas arrêter cela, ou du moins iOS?

Ce n'est pas un problème avec cet appareil spécifique, car ce problème peut être reproduit sur différents appareils iPhone. (Je ne suis pas sûr des différentes versions d'iOS).

Comment je peux reproduire l'erreur:

  1. Ouvrez le jeu (dans Safari).
  2. Laissez-le fonctionner pendant 3-4 minutes.
  3. Faites glisser le centre de notification et tout devient fou.
    J'ai ajouté un vidéo YouTube montrant comment je peux reproduire l'erreur (sur mon iPhone 5C).
    Il semble que le problème apparaisse d'abord dans le centre de notifications (si vous faites glisser le menu vers le haut).
    Pour l'instant, ce problème ne semble se produire que sur iPhone 5C iOS 9.2.1 (13D15). Cela se produit également sur la nouvelle version iOS 9.3.

Afin de résoudre ce problème, je dois:

  1. Fermez l'application Safari (dans laquelle l'onglet de jeu est ouvert).
  2. Verrouillez le téléphone. Après l'avoir déverrouillé, tout redevient normal.

Quelques détails sur le jeu lui-même:

  1. Le jeu montre une carte Mapbox et quelques unités dessus (marqueurs).
  2. Un serveur Node.js fonctionne à 1 tick/seconde et après chaque tick, l'état du jeu mis à jour est envoyé au navigateur via Socket.io.
  3. Chaque fois que le navigateur reçoit l'état du jeu, il met à jour les marqueurs en conséquence.
  4. * Le jeu peut également mettre à jour les marqueurs si vous effectuez un zoom avant ou arrière ou si vous les sélectionnez.

EDIT2: Trouvé la fuite de mémoire (comme prévu). Après avoir corrigé cette fuite (recherchez undefined _icon), le problème ne se produit plus. Cela signifie que quelque part dans ce sens, le bug Safari/iOS est déclenché.

Voici ce que l'on appelait exactement chaque tick, pour chaque unité qui était en cluster (était cachée et groupée avec d'autres à l'intérieur d'un 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);

Et voici le tableau icons:

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 l'appel provient de cette bibliothèque: https://github.com/kottenator/jquery-circle-progress

[~ # ~] démo [~ # ~]

Oui, j'ai pu créer un jsFiddle qui reproduit le bug: https://jsfiddle.net/cte55cz7/14/ Ouvrez sur Safari sur iPhone 5C et attendez quelques minutes. Sur iPhone 6 et iPad mini, la page se bloque (comme prévu en raison de la fuite de mémoire)

Voici le même code dans un HasteBin, pour tous ceux qui ne veulent pas l'exécuter.

79
Cristy

Cette fuite de mémoire est probablement due au fonctionnement du "moteur JS de WebKit" [safari webkit-javascript llvm]

et ressemble vraiment à un débordement de mémoire virtuelle, ayant un impact direct sur le RAM (partagé et également utilisé par iOS pour stocker les éléments graphiques de l'interface utilisateur)

Relativement au morceau de code: "[...] trouver des fuites de mémoire jQuery est facile. Vérifiez la taille de $ .cache. S'il est trop grand, inspectez-le et voyez quelles entrées restent et pourquoi. [...]" ( http://javascript.info/tutorial/memory-leaks )

Je m'attends à ce que cela soit relatif à cela pour la boucle :

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);
}

En supposant que l'inspection est terminée, et en supposant également que vous trouviez les entrées, vous pouvez nettoyer les données manuellement avec removeData () ou vous pouvez utiliser d'abord $ elem.detach () puis mettez $ (elem) .remove () dans setTimeout.

1
A STEFANI