Iframes, Onload und document.domain

In dieser neuen Web 2.0-Mashup-Welt, zu der das Internet geworden ist, wurde viel Wert auf die Verwendung von Iframes gelegt, um Inhalte von Drittanbietern in eine Seite einzubetten. Iframes bieten ein gewisses Maß an Sicherheit, da der JavaScript-Zugriff auf den Domänennamen beschränkt ist, sodass ein Iframe, der Inhalte von einer anderen Website enthält, nicht auf JavaScript auf der enthaltenden Seite zugreifen kann. Diese domänenübergreifende Einschränkung gilt in beide Richtungen, da die enthaltende Seite auch keinen programmatischen Zugriff auf den Iframe hat. In jeder Hinsicht sind die enthaltende Seite und die Iframe-Seite von der Kommunikation abgeschnitten (was zur Cross-Document-Messaging-API in HTML5 geführt hat). Der fehlende Teil der Faszination in den meisten Diskussionen über iFrames ist der Besitz von JavaScript-Objekten.

Iframes und Eigentum

Das Iframe-Element selbst, <iframe> , gehört der enthaltenden Seite, und Sie können damit als Element arbeiten (Attribute abrufen/festlegen, seinen Stil manipulieren, es im DOM verschieben usw.). Die window Objekt, das den Iframe-Inhalt darstellt, ist die Eigenschaft der Seite, die in den Iframe geladen wurde. Damit die enthaltende Seite sinnvoll auf das Fensterobjekt des Iframes zugreifen kann, müssen die Domains der enthaltenden Seite und der Iframe-Seite identisch sein (Details).

Wenn die Domains übereinstimmen, kann die enthaltende Seite auf window zugreifen Objekt für den Iframe. Das iframe-Elementobjekt hat eine Eigenschaft namens contentDocument der den document des Iframes enthält Objekt, sodass Sie den parentWindow verwenden können -Eigenschaft, um den window abzurufen Objekt. Dies ist die Standardmethode, um den window des Iframes abzurufen -Objekt und wird von den meisten Browsern unterstützt. Internet Explorer vor Version 8 unterstützte diese Eigenschaft nicht, daher mussten Sie den proprietären contentWindow verwenden Eigentum. Beispiel:

function getIframeWindow(iframeElement){
    return iframeElement.contentWindow || iframeElement.contentDocument.parentWindow;
}

Zusätzlich kann das Fensterobjekt der enthaltenden Seite mit window.parent aus dem Iframe abgerufen werden Eigentum. Die Iframe-Seite kann auch einen Verweis auf das Iframe-Element abrufen, in dem sie sich befindet, indem sie den window.frameElement verwendet Eigentum. Dies überschreitet die Eigentumsgrenze, da der iFrame der enthaltenden Seite gehört, aber direkt über window des iframe zugänglich ist Objekt.

Verwendung des Onloads des iframe-Elements

Der Versuch festzustellen, wann ein Iframe geladen wird, ist aufgrund der Eigentumsprobleme im Zusammenhang mit Iframes eine interessante Aufgabe. Browser, die nicht Internet Explorer sind, machen etwas sehr Nützliches:Sie stellen einen load bereit Ereignis für das iframe-Element damit Sie wissen, wann ein Iframe geladen wurde, unabhängig vom Inhalt. Da das iframe-Element der enthaltenden Seite gehört, müssen Sie sich keine Gedanken über domänenübergreifende Einschränkungen machen. Ein Iframe, der lokale Inhalte lädt, kann genauso gut überwacht werden wie ein Iframe, der fremde Inhalte lädt (Experiment). Beispielcode:

var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";
iframe.onload = function(){
    alert("Iframe is now loaded.");
};
document.body.appendChild(iframe);

Dies funktioniert in allen Browsern außer Internet Explorer (sogar Version 8!). Ich hatte gehofft, dass vielleicht mit dem attachEvent() -Methode funktionieren würde, aber leider unterstützt Internet Explorer den load einfach nicht Ereignis auf einem Iframe-Element. Ziemlich enttäuschend.

Using the onload des iframe-Fensters

Es schien, dass der Internet Explorer meinen Tag vereiteln würde … wieder einmal. Dann erinnerte ich mich daran, dass ich mir keine Gedanken über fremde Inhalte in einem Iframe mache. In meinem speziellen Fall hatte ich es mit Inhalten derselben Domain zu tun. Da die Cross-Domain-Beschränkung nicht galt, konnte ich auf den window des Iframes zugreifen Objekt direkt und weisen Sie einen onload zu Event-Handler. Beispiel:

var iframe = document.createElement("iframe"),
    iframeWindow;
iframe.src = "simpleinner.htm";
document.body.appendChild(iframe);
iframeWindow = iframe.contentWindow || iframe.contentDocument.parentWindow;
iframeWindow.onload = function(){
    alert("Local iframe is now loaded.");
};

Das Interessante an diesem Ansatz ist, dass Sie den Event-Handler after zuweisen müssen Das Iframe-Element wurde der Seite hinzugefügt. Davor der window des iframe Objekt existiert nicht und Sie können den Ereignishandler nicht zuweisen. Dieser Ansatz funktioniert in Internet Explorer und Firefox nur für Seiten derselben Domain. Andere Browser haben den window noch nicht erstellt Objekt und werfen so einen Fehler (Experiment).

Dokument.domain eingeben

Ich hatte mich damit abgefunden, eine Methode zum Erkennen des Ladens eines Iframes für Internet Explorer und eine andere für jeden anderen Browser zu verwenden, also fuhr ich mit meiner Aufgabe fort. Als nächstes musste ich document.domain einstellen auf der enthaltenden Seite, weil ich ein paar verschiedene Subdomains hatte, von denen ich Iframes laden musste. Bei Verwendung unterschiedlicher Subdomains document.domain einstellen an den Stamm des Hostnamens ermöglicht es Iframes, mit ihren Eltern und untereinander zu kommunizieren. Zum Beispiel, wenn ich eine Iframe-Seite von www2.nczonline.net laden müsste , die technisch als eine andere Domain angesehen wird und nicht zulässig wäre. Wenn ich jedoch document.domain einstelle an „nczonline.net“ sowohl auf der enthaltenden Seite als auch auf der Iframe-Seite, dürfen die beiden kommunizieren. Eine einzige Codezeile, idealerweise oben auf der Seite platziert, genügt:

document.domain = "nczonline.net";

Dadurch wird der Domänenunterschied ausgeglichen und alles funktioniert so, als ob beide Seiten von derselben Domäne stammten. Dachte ich jedenfalls.

Das Problem bei diesem Ansatz besteht darin, dass der Iframe vor dem Laden immer noch als Eigentum der Domain betrachtet wird, wie es in seinem src spezifisch ist Attribut. Ein relativer Pfad wird automatisch der Domain vorangestellt, von der die enthaltende Seite geladen wurde (www.nczonline.net ) gegenüber demjenigen, der document.domain zugewiesen ist . Das bedeutet einen Vergleich von wnczonline.net bis www.nczonline.net schlägt die Prüfung auf dieselbe Domäne fehl und verursacht einen JavaScript-Fehler, wenn Sie versuchen, auf window des iframe zuzugreifen Objekt (Experiment). Die zugehörige Domain der Iframe-Seite wird erst geändert, wenn sie geladen und der JavaScript-Befehl zum Ändern der Domain ausgeführt wurde. Sobald die iframe-Seite geladen wurde, funktioniert jedoch alles einwandfrei. Aber woher wissen Sie, wann die Iframe-Seite geladen wurde?

Umkehrung des Prozesses

Nachdem ich immer noch keine browserübergreifende Lösung gefunden habe, um festzustellen, wann ein Iframe geladen wurde, beschloss ich, meine Meinung umzukehren. Anstatt dass die enthaltende Seite fragt, wann der iframe geladen wird, was wäre, wenn der iframe der enthaltenden Seite mitteilt, dass er geladen wurde? Wenn die Iframe-Seite auf ihren eigenen load lauschte Ereignis und teilte dann der enthaltenden Seite mit, wann das auftrat, das sollte das Problem lösen. Ich wollte, dass dies so einfach ist wie das Zuweisen eines Event-Handlers, also kam ich auf die folgende Idee:Ich würde dem iframe-Element eine Methode zuweisen. Dann ruft die Iframe-Seite diese Methode auf, wenn sie geladen wurde. Die Methode muss dem Element zugewiesen werden und nicht dem window des Iframes Objekt, da letzteres nicht in allen Browsern rechtzeitig vorhanden ist. Das Ergebnis sah so aus:

var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";
iframe._myMethod = function(){
    alert("Local iframe is now loaded.");
};
document.body.appendChild(iframe);

Dieser Code hat eine Methode namens _myMethod() zugewiesen auf das iframe-Element. Die Seite, die in den Iframe geladen wird, fügt dann diesen Code hinzu:

window.onload = function(){
    window.frameElement._myMethod();
}

Denn dieser Code wird nach der Zuweisung an document.domain ausgeführt , gibt es keine Sicherheitseinschränkungen, über die Sie sich Sorgen machen müssen. Dies funktioniert hervorragend für alle Ressourcen, die denselben Root-Hostnamen verwenden (Experiment). Es funktioniert in allen Browsern, genau das, wonach ich gesucht habe, aber das Problem, zu erkennen, wann eine fremde Ressource in einen Iframe geladen wurde, hat mich immer noch genervt.

Verwendung des onreadystatechange des iframe

Ich habe mich entschieden, die Iframe-Implementierung von Internet Explorer etwas genauer zu untersuchen. Es war klar, dass man dem onload etwas zuweist Die Eigenschaft hat nicht den gewünschten Effekt erzielt, aber ich dachte mir, dass es etwas anderes Ähnliches geben muss. Ich habe versucht, den Event-Handler mit attachEvent() anzuhängen , was auch nicht funktioniert hat. Okay, offensichtlich gab es keine Unterstützung für das Ladeereignis auf dem Iframe. Was ist mit etwas anderem?

Da erinnerte ich mich an den bizarren readystatechange von IE Ereignis, das es auf Dokumenten hat. Das ist natürlich komplett anders als der readystatechange Ereignis ausgelöst am XMLHttpRequest Objekte. Ich habe mich gefragt, ob das iframe-Element dieses Ereignis ebenfalls unterstützen könnte, und wie sich herausstellt, tut es das. Das iframe-Element unterstützt den readyState -Eigenschaft, die auf „interaktiv“ und dann auf „vollständig“ geändert wird, wenn der Inhalt des Iframes geladen wurde. Und weil das auf dem iframe-Element steht und nicht auf dem iframe window Objekt gibt es keine Bedenken hinsichtlich domänenübergreifender Einschränkungen (Experiment). Der letzte Code, den ich erhalten habe, sieht folgendermaßen aus:

var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";

if (navigator.userAgent.indexOf("MSIE") > -1 && !window.opera){
    iframe.onreadystatechange = function(){
        if (iframe.readyState == "complete"){
            alert("Local iframe is now loaded.");
        }
    };
} else {
    iframe.onload = function(){
        alert("Local iframe is now loaded.");
    };
}

document.body.appendChild(iframe);

Die Überprüfung, ob der Browser IE ist oder nicht, ist ein bisschen chaotisch. Ich hätte lieber auf die Existenz von iframe.readyState geprüft , dies löst jedoch einen Fehler aus, wenn Sie versuchen, auf die Eigenschaft zuzugreifen, bevor Sie den Iframe zum Dokument hinzufügen. Ich habe überlegt, die Existenz von document.readyState zu verwenden um festzustellen, ob readystatechange verwendet werden soll , jedoch unterstützen die meisten anderen Browser diese Eigenschaft jetzt, sodass dies nicht gut genug ist. Bei YUI würde ich einfach Y.UA.ie verwenden um dies festzustellen (Sie können die für Sie am besten geeignete Methode verwenden).

Unterstützung für verstecktes Onload von IE

Kurz nach dem Posten dieses Blogs kommentierte Christopher dies mit attachEvent () auf dem iframe-Element funktioniert im IE. Ich hätte schwören können, dass ich das schon einmal versucht habe, aber aufgrund seiner Aufforderung habe ich ein weiteres Experiment auf die Beine gestellt. Wie sich herausstellt, hat er völlig recht. Ich musste die MSDN-Dokumentation durchsuchen, um schließlich eine Umwegreferenz zu finden, aber tatsächlich ist sie da. Dies führte zu einem abschließenden Code-Snippet von:

var iframe = document.createElement("iframe");
iframe.src = "simpleinner.htm";

if (iframe.attachEvent){
    iframe.attachEvent("onload", function(){
        alert("Local iframe is now loaded.");
    });
} else {
    iframe.onload = function(){
        alert("Local iframe is now loaded.");
    };
}

document.body.appendChild(iframe);

Dieser Code funktioniert auch in allen Browsern und vermeidet mögliche Probleme im Zusammenhang mit dem Timing von readystatechange Ereignis gegen load Veranstaltung.

Zusammenfassung

Nach einiger Recherche scheint es möglich zu sein, festzustellen, wann ein Iframe in allen Browsern geladen wurde, unabhängig von der Herkunft der Iframe-Seite. Dadurch wird die Überwachung und Fehlerbehandlung von Iframe-Inhalten viel einfacher zu verwalten. Ich bin dankbar, dass alle Browseranbieter den Vorteil gesehen haben, diese Ereignisse zum iframe-Element selbst hinzuzufügen, anstatt sich auf den iframe window zu verlassen widersprechen oder erwarten, dass es uns normalerweise egal ist, ob ein Iframe geladen wurde oder nicht.

**Aktualisierung (15. September 2009):**Abschnitt über attachEvent() hinzugefügt basierend auf Christophers Kommentar.