webentwicklung-frage-antwort-db.com.de

Wie kann ich auf die DOM-Elemente in einem iFrame zugreifen?

Ich schreibe ein jQuery-Plugin, das für DOM-Elemente in einem iFrame ausgeführt werden muss. Ich teste dies gerade lokal (dh die URL ist file: //.../example.html) und in Chrome Ich tippe weiter auf "SecurityError: Fehler beim Lesen des 'contentDocument' Eigenschaft von 'HTMLIFrameElement': Blockierte einen Frame mit Origin "null" für den Zugriff auf einen Cross-Origin-Frame. "In Safari erhalte ich nur ein leeres Dokument.

Angesichts der Tatsache, dass sowohl die übergeordnete Datei als auch die iFrame-Datei von meiner lokalen Festplatte (in der Entwicklung) und von demselben Server (in der Produktion) stammen, hätte ich gedacht, dass ich nicht den ursprungsübergreifenden Problemen ausgesetzt wäre .

Kann ich den Browser davon überzeugen, dass sich meine lokalen Dateien tatsächlich in derselben Domäne befinden?

<aside> Interessanterweise kann ich in Safari über die Konsole $("iframe").get(0).contentDocument.find("ol") eingeben und finde meine Liste glücklich. In Chrome) löst dieselbe Zeile den Sicherheitsfehler so aus, als würde er ausgeführt. </ aside>

pdate

Basierend auf den folgenden Vorschlägen habe ich einen einfachen lokalen Webserver gestartet, um dies zu testen und erhalte jetzt nicht den Cross-Origin-Fehler, aber ich erhalte auch keinen Inhalt.

Mein Javascript sieht so aus

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

Das myDocument.ready soll nur sicherstellen, dass das iFrame-Dokument bereit ist - in Wirklichkeit macht es keinen Unterschied.

Ich ende immer damit, dass myElements leer ist. ([] auf Safari oder jQuery.fn.init[0] in Chrome)

Aber wenn ich dies manuell in die Konsole eingebe:

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

Ich erhalte meine Listen wie erwartet. Dies ist jetzt sowohl in Safari als auch in Chrome der Fall.

Meine Frage lautet also: Warum kann mein Skript die DOM-Elemente nicht sehen, aber derselbe Code, der direkt in die Konsole des Browsers eingegeben wird, kann die DOM-Elemente problemlos sehen?

19
Dave Sag

Chrome verfügt über Standardsicherheitsbeschränkungen, die es Ihnen nicht ermöglichen, von der Festplatte auf andere Fenster zuzugreifen, obwohl diese technisch denselben Ursprung haben. Es gibt ein Flag in Chrome), das diese Sicherheitsbeschränkung lockern kann (Befehlszeilenargument unter Windows ist das, woran ich mich erinnere), obwohl ich nicht empfehlen würde, dieses Flag für mehr als einen kurzen Test zu verwenden. In dieser Beitrag oder dieser Artikel finden Sie Informationen zum Befehlszeilenargument.

Wenn Sie die Dateien von einem Webserver (auch wenn es sich um einen lokalen Webserver handelt) anstelle Ihrer Festplatte ausführen, tritt dieses Problem nicht auf.

Sie können auch in anderen Browsern testen, die nicht so restriktiv sind.


Nachdem Sie die Frage in etwas anderes geändert haben, müssen Sie warten, bis ein Iframe-Fenster geladen ist, bevor Sie auf den Inhalt darin zugreifen können, und Sie können jQueries .ready() nicht für ein anderes Dokument (it) verwenden arbeitet nicht an einem anderen Dokument).

$(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!";
    });
});

Testseite hier .


EDIT: Nach weiteren Recherchen stellte ich fest, dass jQuery einen Teil dieser Arbeit für Sie erledigt und die jQuery-Lösung in allen gängigen Browsern zu funktionieren scheint :

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

Testseite hier .

Wenn Sie sich die jQuery-Implementierung dafür ansehen, müssen Sie lediglich einen load -Ereignis-Listener auf dem iFrame selbst einrichten.


Wenn Sie wissen möchten, welche Details für das Debuggen/Lösen der ersten oben beschriebenen Methode wichtig sind:

Bei meinem Versuch, dieses Problem zu lösen, habe ich einige ziemlich bizarre Dinge (in Chrome) mit iFrames entdeckt. Wenn Sie zum ersten Mal in das Fenster des iframe schauen, gibt es ein Dokument, das besagt, dass es readyState === "complete" Ist, so dass Sie glauben, es sei geladen, aber es lügt. Das aktuelle Dokument und das aktuelle <body> - Tag aus dem Dokument, das über die URL in den iframe geladen wird, sind noch NICHT vorhanden. Ich habe dies bewiesen, indem ich dem <body data-test="hello"> Ein benutzerdefiniertes Attribut hinzugefügt und nach diesem benutzerdefinierten Attribut gesucht habe. Siehe da. Obwohl document.readyState === "complete", Befindet sich dieses benutzerdefinierte Attribut nicht im Tag <body>. Daher komme ich (zumindest in Chrome) zu dem Schluss, dass der iFrame anfangs ein Dummy und ein leeres Dokument und einen leeren Textkörper enthält, die nicht die tatsächlichen sind, die vorhanden sind, sobald die URL in den iFrame geladen wird. Dies macht den gesamten Prozess des Erkennens verwirrend (es hat mich Stunden gekostet, das herauszufinden). Wenn ich einen Intervall-Timer und eine Abfrage iWindow.document.body.getAttribute("data-test") einstelle, wird dies wiederholt als undefined angezeigt und schließlich mit dem korrekten Wert und dies alles mit document.readyState === "complete" Was bedeutet, dass es komplett lügt.

Ich denke, was los ist, ist, dass der iFrame mit einem Dummy und einem leeren Dokument und Body beginnt, die dann ersetzt werden, nachdem der Inhalt geladen wird. Auf der anderen Seite ist der iFrame window das eigentliche Fenster. Die einzige Möglichkeit, auf das Laden des Inhalts zu warten, besteht darin, das Ereignis load auf dem iFrame window zu überwachen, da dies nicht zu lügen scheint. Wenn Sie wussten, dass es einen bestimmten Inhalt gibt, auf den Sie gewartet haben, können Sie auch abfragen, bis dieser Inhalt verfügbar ist. Aber auch dann müssen Sie vorsichtig sein, da Sie den iframe.contentWindow.document Nicht zu früh abrufen können, da es sich um das falsche Dokument handelt, wenn Sie es zu früh abrufen. Das Ganze ist ziemlich kaputt. Ich finde keine Möglichkeit, DOMContentLoaded außerhalb des iFrame-Dokuments selbst zu verwenden, da Sie nicht wissen können, ob das tatsächliche document -Objekt vorhanden ist, damit Sie den Ereignishandler daran anhängen können . Also habe ich mich für das Ereignis load im iFrame-Fenster entschieden, das anscheinend funktioniert.


Wenn Sie den Code im iFrame tatsächlich steuern, können Sie das Ereignis einfacher vom iFrame selbst auslösen, indem Sie entweder jQuery mit $(document).ready() im iFrame-Code mit seiner eigenen Version von jQuery verwenden oder eine Funktion aufrufen im übergeordneten Fenster aus einem Skript, das sich hinter Ihrem Zielelement befindet (um sicherzustellen, dass das Zielelement geladen und bereit ist).


Weitere Bearbeitung

Nach ein paar weiteren Recherchen und Tests finden Sie hier eine Funktion, die Sie darüber informiert, wann ein iFrame das Ereignis DOMContentLoaded trifft, anstatt auf das Ereignis load zu warten (was bei Bildern und Stylesheets länger dauern kann). .

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

Das nennt man einfach so:

// 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

Angesichts der Tatsache, dass sowohl die übergeordnete Datei als auch die iFrame-Datei von meiner lokalen Festplatte (in der Entwicklung) und von demselben Server (in der Produktion) stammen, hätte ich gedacht, dass ich nicht den ursprungsübergreifenden Problemen ausgesetzt wäre .

Msgstr "Bitte öffne dieses vollkommen harmlose HTML - Dokument, das ich an diese E - Mail angehängt habe." Es gibt gute Gründe für Browser, domänenübergreifende Sicherheit auf lokale Dateien anzuwenden.

Kann ich den Browser davon überzeugen, dass sich meine lokalen Dateien tatsächlich in derselben Domäne befinden?

Installieren Sie einen Webserver. Testen Sie mit http://localhost. Als Bonus erhalten Sie alle anderen Vorteile eines HTTP-Servers (z. B. die Möglichkeit, relative URIs zu verwenden, die mit einem / Beginnen und mit serverseitigem Code entwickelt werden).

1
Quentin