web-dev-qa-db-fra.com

Comment puis-je accéder aux éléments DOM dans un iFrame

J'écris un plugin jQuery qui doit pouvoir fonctionner contre des éléments DOM dans un iFrame. Je teste cela localement en ce moment (c'est-à-dire que l'URL est le fichier: //.../example.html) et dans Chrome je continue à appuyer sur "SecurityError: Impossible de lire le 'contentDocument' propriété de 'HTMLIFrameElement': Bloqué un cadre avec Origin "null" d'accéder à un cadre cross-Origin. "et dans Safari je reçois juste un document vide.

Étant donné que le fichier parent et le fichier de l'iFrame sortent de mon disque local (en développement) et sortiront du même serveur (en production), j'aurais pensé que je ne serais pas soumis aux problèmes d'origine croisée .

Existe-t-il un moyen de convaincre le navigateur que mes fichiers locaux appartiennent en fait au même domaine?

<à côté> Fait intéressant dans Safari, en utilisant directement la console, je peux taper $("iframe").get(0).contentDocument.find("ol") et il trouve heureusement ma liste. Dans Chrome cette même ligne renvoie l'erreur de sécurité comme si elle était en cours d'exécution. </aside>

Mise à jour

Sur la base des suggestions ci-dessous, j'ai mis en marche un simple serveur Web local pour tester cela et je ne reçois plus l'erreur cross-Origin - yay - mais je n'ai pas non plus de contenu.

Mon Javascript ressemble

$(document).ready(function(){
  var myFrame = $("iframe"),
      myDocument = $(myFrame.get(0).contentDocument),
      myElements;
  myDocument.ready(function(){
    myElements = myDocument.find("ul, ol");
    console.debug("success - iFrame", myFrame, "document", myDocument, "elements", myElements);
  });
});

Le myDocument.ready est là juste pour s'assurer que le document de l'iFrame est prêt - en réalité, cela ne fait aucune différence.

Je me retrouve toujours avec myElements vide. ([] en safari ou jQuery.fn.init[0] dans Chrome)

Mais si je tape manuellement ceci dans la console:

$($("iframe").get(0).contentDocument).find("ol, ul")

Je reçois mes listes comme prévu. C'est désormais le cas dans Safari et Chrome.

Donc ma question devient: pourquoi mon script ne peut-il pas voir les éléments DOM alors que le même code lorsqu'il est entré directement dans la console du navigateur peut facilement voir les éléments DOM?

19
Dave Sag

Chrome a des restrictions de sécurité par défaut qui ne vous permettent pas d'accéder à d'autres fenêtres à partir du disque dur même si elles sont techniquement de la même origine. Il y a un indicateur dans Chrome qui peut assouplir cette restriction de sécurité (l'argument de la ligne de commande sur Windows est ce dont je me souviens), bien que je ne recommanderais pas de courir avec cet indicateur pour plus qu'un test rapide. Voir cet article ou cet article pour plus d'informations sur l'argument de la ligne de commande.

Si vous exécutez les fichiers sur un serveur Web (même s'il s'agit d'un serveur Web local) au lieu de votre disque dur, vous n'aurez pas ce problème.

Ou, vous pouvez tester dans d'autres navigateurs qui ne sont pas aussi restrictifs.


Maintenant que vous avez changé la question en quelque chose de différent, vous devez attendre qu'une fenêtre iframe se charge avant de pouvoir y accéder et vous ne pouvez pas utiliser la .ready() de jQuery sur un autre document (il ne fonctionne pas sur un autre document).

$(document).ready(function() {
    // get the iframe in my documnet
    var iframe = document.getElementById("testFrame");
    // get the window associated with that iframe
    var iWindow = iframe.contentWindow;

    // wait for the window to load before accessing the content
    iWindow.addEventListener("load", function() {
        // get the document from the window
        var doc = iframe.contentDocument || iframe.contentWindow.document;

        // find the target in the iframe content
        var target = doc.getElementById("target");
        target.innerHTML = "Found It!";
    });
});

Page de test ici .


EDIT: Après de nouvelles recherches, j'ai trouvé que jQuery fera une partie de ce travail pour vous comme ça et la solution jQuery semble fonctionner dans tous les principaux navigateurs :

$(document).ready(function() {
    $("#testFrame").load(function() {
        var doc = this.contentDocument || this.contentWindow.document;
        var target = doc.getElementById("target");
        target.innerHTML = "Found It!";
    });
});

Page de test ici .

En regardant l'implémentation jQuery pour cela, tout ce qu'il fait est de configurer un écouteur d'événement load sur l'iFrame lui-même.


Si vous voulez connaître les petits détails qui ont été utilisés pour déboguer/résoudre la première méthode ci-dessus:

En essayant de résoudre ce problème, j'ai découvert des choses assez bizarres (dans Chrome) avec les iFrames. Lorsque vous regardez pour la première fois dans la fenêtre de l'iframe, il y a un document et il dit que son readyState === "complete" Tel que vous pensez que le chargement est terminé, mais qu'il ment. Le document réel et la balise <body> Réelle du document qui est chargé via l'URL dans l'iframe n'est PAS encore là. J'ai prouvé cela en mettant un attribut personnalisé sur le <body data-test="hello"> Et en vérifiant cet attribut personnalisé. Et voilà. Même si document.readyState === "complete", Cet attribut personnalisé n'est pas là sur la balise <body>. Donc, je conclus (au moins dans Chrome) que l'iFrame contient initialement un document et un corps factices et vides qui ne sont pas ceux qui seront en place une fois l'URL chargée dans l'iFrame. Cela rend tout ce processus de détection quand il est prêt à être assez déroutant (cela m'a coûté des heures à le comprendre). En fait, si je règle une minuterie d'intervalle et interroge iWindow.document.body.getAttribute("data-test"), je le verrai s'afficher comme undefined à plusieurs reprises, puis finalement il apparaîtra avec la valeur correcte et tout cela avec document.readyState === "complete" Ce qui signifie qu'il ment complètement.

Je pense que ce qui se passe, c'est que l'iFrame commence avec un document et un corps factices et vides qui sont ensuite remplacés APRÈS le chargement du contenu. D'un autre côté, l'iFrame window est la vraie fenêtre. Donc, les seules façons que j'ai trouvées d'attendre que le contenu soit chargé sont de surveiller l'événement load sur l'iFrame window car cela ne semble pas mentir. Si vous saviez qu'il y avait du contenu spécifique que vous attendiez, vous pouvez également interroger jusqu'à ce que ce contenu soit disponible. Mais, même alors, vous devez être prudent car vous ne pouvez pas récupérer le iframe.contentWindow.document Trop tôt car ce sera le mauvais document si vous le récupérez trop tôt. Le tout est assez cassé. Je ne trouve aucun moyen d'utiliser DOMContentLoaded depuis l'extérieur du document iFrame lui-même car vous n'avez aucun moyen de savoir que l'objet document réel est en place pour vous pouvez y attacher le gestionnaire d'événements . Alors ... je me suis contenté de l'événement load sur la fenêtre iFrame qui semble fonctionner.


Si vous contrôlez réellement le code dans l'iFrame, vous pouvez déclencher l'événement plus facilement à partir de l'iFrame lui-même, soit en utilisant jQuery avec $(document).ready() dans le code iFrame avec sa propre version de jQuery ou en appelant une fonction dans la fenêtre parent à partir d'un script situé après votre élément cible (garantissant ainsi que l'élément cible est chargé et prêt).


Édition supplémentaire

Après un tas de recherches et de tests, voici une fonction qui vous dira quand une iFrame frappe l'événement DOMContentLoaded plutôt que d'attendre l'événement load (qui peut prendre plus de temps avec les images et les feuilles de style) .

// This function ONLY works for iFrames of the same Origin as their parent
function iFrameReady(iFrame, fn) {
    var timer;
    var fired = false;

    function ready() {
        if (!fired) {
            fired = true;
            clearTimeout(timer);
            fn.call(this);
        }
    }

    function readyState() {
        if (this.readyState === "complete") {
            ready.call(this);
        }
    }

    // cross platform event handler for compatibility with older IE versions
    function addEvent(elem, event, fn) {
        if (elem.addEventListener) {
            return elem.addEventListener(event, fn);
        } else {
            return elem.attachEvent("on" + event, function () {
                return fn.call(elem, window.event);
            });
        }
    }

    // use iFrame load as a backup - though the other events should occur first
    addEvent(iFrame, "load", function () {
        ready.call(iFrame.contentDocument || iFrame.contentWindow.document);
    });

    function checkLoaded() {
        var doc = iFrame.contentDocument || iFrame.contentWindow.document;
        // We can tell if there is a dummy document installed because the dummy document
        // will have an URL that starts with "about:".  The real document will not have that URL
        if (doc.URL.indexOf("about:") !== 0) {
            if (doc.readyState === "complete") {
                ready.call(doc);
            } else {
                // set event listener for DOMContentLoaded on the new document
                addEvent(doc, "DOMContentLoaded", ready);
                addEvent(doc, "readystatechange", readyState);
            }
        } else {
            // still same old original document, so keep looking for content or new document
            timer = setTimeout(checkLoaded, 1);
        }
    }
    checkLoaded();
}

Cela s'appelle simplement comme ceci:

// call this when you know the iFrame has been loaded
// in jQuery, you would put this in a $(document).ready()
iFrameReady(document.getElementById("testFrame"), function() {
    var target = this.getElementById("target");
    target.innerHTML = "Found It!";
});
28
jfriend00

Étant donné que le fichier parent et le fichier de l'iFrame sortent de mon disque local (en développement) et sortiront du même serveur (en production), j'aurais pensé que je ne serais pas soumis aux problèmes d'origine croisée .

"Veuillez ouvrir ce parfaitement inoffensif document HTML que j'ai joint à cet e-mail." Il existe de bonnes raisons pour que les navigateurs appliquent une sécurité interdomaine aux fichiers locaux.

Existe-t-il un moyen de convaincre le navigateur que mes fichiers locaux appartiennent en fait au même domaine?

Installez un serveur Web. Testez par http://localhost. En prime, bénéficiez de tous les autres avantages d'un serveur HTTP (comme la possibilité d'utiliser des URI relatifs qui commencent par un / et développer avec le code côté serveur).

1
Quentin