64 votes

Détecter l'échec du chargement du contenu d'une iframe

Je peux détecter le moment où le contenu d'une iframe a été chargé en utilisant la fonction charge événement. Malheureusement, pour mes objectifs, il y a deux problèmes avec cela :

  • Si une erreur survient lors du chargement de la page (404/500, etc.), l'événement load n'est jamais déclenché.
  • Si le chargement de certaines images ou d'autres dépendances a échoué, l'événement de chargement est déclenché comme d'habitude.

Existe-t-il un moyen de déterminer de manière fiable si l'une des erreurs ci-dessus s'est produite ?

J'écris une application semi-web semi-desktop basée sur Mozilla/XULRunner, donc les solutions qui ne fonctionnent que dans Mozilla sont les bienvenues.

13voto

splattne Points 48126

Si vous avez le contrôle de la page iframe (et que les pages sont sur le même nom de domaine), la stratégie pourrait être la suivante :

  • Dans le document parent, initialiser une variable var iFrameLoaded = false;
  • Lorsque le document iframe est chargé, définissez cette variable dans le parent comme suit true appeler la fonction d'un parent à partir du document iframe (setIFrameLoaded(); par exemple).
  • vérifier le iFrameLoaded en utilisant l'option timer (réglez la minuterie sur votre limite de temps préférée) - si le drapeau est toujours faux, vous pouvez dire que l'iframe n'a pas été chargé régulièrement.

J'espère que cela vous aidera.

7 votes

1) Pourquoi prendre la peine d'ajouter du JavaScript à la page iframe ? Je pourrais tout aussi bien utiliser setTimeout en conjonction avec l'événement de chargement. 2) Cela ne permet pas de détecter de manière fiable si le chargement échoue ou pourquoi, mais seulement s'il réussit dans un délai donné. 3) Il ne détecte pas le cas où les dépendances de la page ne se chargent pas.

2 votes

Des ressources différentes se chargeront dans des délais différents. Cette imprévisibilité va tuer votre approche Daniel Cassidy, puisque vous devrez prévoir un délai d'attente raisonnablement long. En attendant, les erreurs de chargement de courte durée resteront à l'écran jusqu'à ce que le temporisateur suppose que le chargement de la ressource a échoué et traite l'erreur.

12voto

C'est une réponse très tardive, mais je vais la laisser à quelqu'un qui en a besoin.

Tâche : charger le contenu d'origine croisée d'une iframe, émettre onLoaded sur le succès et onError sur l'erreur de chargement .

C'est la solution la plus indépendante de l'origine des navigateurs que j'ai pu développer. Mais tout d'abord, je vais vous parler brièvement des autres approches que j'ai eues et pourquoi elles sont mauvaises.

1. iframe C'était un petit choc pour moi, cette iframe a seulement onload et il est appelé au chargement et à l'erreur, aucun moyen de savoir si c'est une erreur ou non.

2. performance.getEntriesByType('resource') . Cette méthode renvoie les ressources chargées. Cela ressemble à ce dont nous avons besoin. Mais quelle honte, firefox ajoute toujours la ressource dans le tableau des ressources, qu'elle soit chargée ou non. Pas moyen de savoir par Resource l'instance a été un succès. Comme d'habitude. Au fait, cette méthode ne fonctionne pas dans ios<11.

3. script J'ai essayé de charger le html en utilisant <script> tag. Emet onload y onerror correctement, malheureusement, seulement dans Chrome.

Et quand j'étais prêt à abandonner, mon collègue plus âgé m'a parlé de la balise html4. <object> . C'est comme <iframe> sauf qu'il dispose de solutions de repli lorsque le contenu n'est pas chargé. Cela ressemble à ce dont nous avons besoin ! Malheureusement, ce n'est pas aussi simple qu'il n'y paraît.

CODE SECTION

var obj = document.createElement('object');

// we need to specify a callback (i will mention why later)
obj.innerHTML = '<div style="height:5px"><div/>'; // fallback

obj.style.display = 'block'; // so height=5px will work
obj.style.visibility = 'hidden'; // to hide before loaded

obj.data = src;

Après cela, nous pouvons définir certains attributs pour <object> comme nous avions voulu le faire avec iframe . La seule différence, c'est que nous devons utiliser <params> ne sont pas des attributs, mais leurs noms et leurs valeurs sont identiques.

for (var prop in params) {
    if (params.hasOwnProperty(prop)) {
        var param = document.createElement('param');
        param.name = prop;
        param.value = params[prop];

        obj.appendChild(param);
    }
}

Maintenant, la partie difficile. Comme beaucoup d'éléments similaires, <object> n'a pas de spécifications pour les rappels, de sorte que chaque navigateur se comporte différemment.

  • Chrome . Sur erreur et sur charge émet load événement.
  • Firefox . Emet load y error correctement.
  • Safari . N'émet rien....

On dirait que ce n'est pas différent de iframe , getEntriesByType , script .... Mais, nous avons le fallback du navigateur natif ! Donc, parce que nous définissons directement le fallback (innerHtml), nous pouvons savoir si <object> est chargé ou non

function isReallyLoaded(obj) {
    return obj.offsetHeight !== 5; // fallback height
}

/**
 * Chrome calls always, Firefox on load
 */
obj.onload = function() {
    isReallyLoaded(obj) ? onLoaded() : onError();
};

/**
 * Firefox on error
 */
obj.onerror = function() {
    onError();
};

Mais que faire avec Safari ? Le bon vieux setTimeout .

var interval = function() {
    if (isLoaded) { // some flag
        return;
    }

    if (hasResult(obj)) {
        if (isReallyLoaded(obj)) {
            onLoaded();
        } else {
            onError();
        }
    }

    setTimeout(interval, 100);
};

function hasResult(obj) {
    return obj.offsetHeight > 0;
}

Ouais.... pas si vite. Le truc c'est que, <object> quand les échecs ont un comportement non mentionné dans les spécifications :

  1. Essayer de charger (size=0)
  2. Échecs (taille = quelconque) vraiment
  3. Fallback (taille = comme dans innnerHtml)

Donc, le code a besoin d'une petite amélioration

var interval = function() {
    if (isLoaded) { // some flag
        return;
    }

    if (hasResult(obj)) {
        if (isReallyLoaded(obj)) {
            interval.count++;
            // needs less then 400ms to fallback
            interval.count > 4 && onLoadedResult(obj, onLoaded);
        } else {
            onErrorResult(obj, onError);
        }
    }

    setTimeout(interval, 100);
};
interval.count = 0;

setTimeout(interval, 100);

Bien, et pour commencer à charger

document.body.appendChild(obj);

C'est tout . J'ai essayé d'expliquer le code dans tous les détails, pour que ça n'ait pas l'air si stupide.

P.S. WebDev craint

0 votes

C'est une perte de temps d'essayer toutes les options qui existent, je dois dire que les navigateurs devraient comprendre que lorsqu'ils appliquent une sécurité et interdisent une fonctionnalité, ils devraient fournir une alternative pour faire ce travail de manière sécurisée.

0 votes

Il s'agit d'un problème très courant lorsque nous ouvrons un lien à l'intérieur d'un iframe et que la page n'est pas chargée en raison du problème XFRAME=sameorigin. Nous sommes impuissants à gérer ce cas et à montrer un message approprié et à créer une interface utilisateur similaire dans tous les navigateurs.

8voto

Chris Points 56

J'ai eu ce problème récemment et j'ai dû mettre en place une action Javascript de sondage sur la page mère (qui contient la balise IFRAME). Cette fonction JavaScript vérifie le contenu de l'IFRAME à la recherche d'éléments explicites qui ne devraient exister que dans une bonne réponse. Cela suppose bien sûr que vous n'ayez pas à vous soucier de la violation de la "politique de la même origine".

Au lieu de vérifier toutes les erreurs possibles qui pourraient être générées par les nombreuses ressources réseau différentes j'ai simplement vérifié le ou les éléments positifs constants qui, je le sais, devraient être dans une bonne réponse.

Après un temps prédéterminé et/ou un certain nombre de tentatives infructueuses pour détecter le ou les éléments attendus, le JavaScript modifie l'attribut SRC de l'IFRAME (pour demander à ma servlet) une page d'erreur conviviale au lieu d'afficher le message HTTP ERROR typique. Le JavaScript pourrait tout aussi bien modifier l'attribut SRC pour effectuer une demande entièrement différente.

function checkForContents(){
var contents=document.getElementById('myiframe').contentWindow.document
if(contents){
    alert('found contents of myiframe:' + contents);
    if(contents.documentElement){
        if(contents.documentElement.innerHTML){
            alert("Found contents: " +contents.documentElement.innerHTML);
            if(contents.documentElement.innerHTML.indexOf("FIND_ME") > -1){
                openMediumWindow("woot.html", "mypopup");
            }
        }
    }
}

}

2voto

immanis Points 50

Cela ne répond pas exactement à votre question, mais ma recherche d'une réponse m'a amené ici, donc je poste juste au cas où quelqu'un d'autre aurait une requête similaire à la mienne.

Il n'utilise pas tout à fait un événement de chargement, mais il peut détecter si un site web est accessible et appelable (s'il l'est, alors l'iFrame, en théorie, devrait se charger).

Au départ, je pensais faire un appel AJAX comme tout le monde, sauf que cela n'a pas fonctionné pour moi au départ, car j'avais utilisé jQuery. Cela fonctionne parfaitement si vous faites un XMLHttpRequest :

var url = http://url_to_test.com/
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status != 200) {
        console.log("iframe failed to load");
    }
};
xhttp.open("GET", url, true);
xhttp.send();

Editar:
Cette méthode fonctionne bien, sauf qu'elle a beaucoup de faux négatifs (elle capte beaucoup de choses qui s'afficheraient dans une iframe) à cause de la malice de l'origine croisée. La façon dont j'ai contourné ce problème était de faire une requête CURL/Web sur un serveur, puis de vérifier les en-têtes de la réponse pour savoir a) si le site web existe, et b) si les en-têtes ont été définis comme suit x-frame-options .

Ce n'est pas un problème si vous utilisez votre propre serveur web, car vous pouvez faire votre propre appel api pour cela.

Mon implémentation en node.js :

app.get('/iframetest',function(req,res){ //Call using /iframetest?url=url - needs to be stripped of http:// or https://
   var url = req.query.url; 
    var request = require('https').request({host: url}, function(response){ //This does an https request - require('http') if you want to do a http request
        var headers = response.headers;
        if (typeof headers["x-frame-options"] != 'undefined') {
            res.send(false); //Headers don't allow iframe
        } else {
            res.send(true); //Headers don't disallow iframe
        }
    });
    request.on('error',function(e){
       res.send(false); //website unavailable
    });
    request.end();
});

1voto

Neil Points 24938

Je pense que l'événement pageshow est déclenché pour les pages d'erreur. Ou si vous faites cela à partir de chrome, alors vous vérifiez la requête de votre écouteur de progrès pour voir si c'est un canal HTTP, auquel cas vous pouvez récupérer le code d'état.

Pour ce qui est des dépendances de la page, je pense que vous ne pouvez le faire qu'à partir de chrome en ajoutant un écouteur d'événement onerror de capture, et même dans ce cas, il ne trouvera les erreurs que dans les éléments, pas dans les arrière-plans CSS ou autres images.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X