Iframe, onload a document.domain

V tomto novém mashupovém světě Webu 2.0, kterým se internet stal, byl kladen velký důraz na použití prvků iframe pro vkládání obsahu třetích stran na stránku. Prvky iframe poskytují určitou úroveň zabezpečení, protože přístup JavaScript je omezen názvem domény, takže prvek iframe obsahující obsah z jiného webu nemá přístup k JavaScriptu na stránce obsahující. Toto omezení mezi doménami platí oběma směry, protože stránka obsahující také nemá žádný programový přístup k prvku iframe. Obsahující stránka a stránka s prvky iframe jsou ve všech ohledech odříznuty od komunikace (což vedlo k rozhraní API pro zasílání zpráv napříč dokumenty v HTML5). Chybějící kousek intriky ve většině diskusí o prvcích iframe je vlastnictví objektu JavaScript.

Prvky iframe a vlastnictví

Samotný prvek iframe, <iframe> , je vlastněna obsahující stránkou, takže na ní můžete pracovat jako na prvku (získání/nastavení atributů, manipulace s jejím stylem, přesouvání v DOM atd.). window objekt představující obsah prvku iframe je vlastnost stránky, která byla načtena do prvku iframe. Aby obsahová stránka mohla nějakým smysluplným způsobem přistupovat k objektu okna iframe, doména obsahující stránky a stránky iframe musí být stejné (podrobnosti).

Když se domény shodují, obsahuje stránka přístup k window objekt pro iframe. Objekt prvku iframe má vlastnost nazvanou contentDocument který obsahuje prvek iframe document objekt, takže můžete použít parentWindow vlastnost k načtení window objekt. Toto je standardní způsob, jak načíst window prvku iframe objekt a je podporován většinou prohlížečů. Internet Explorer před verzí 8 tuto vlastnost nepodporoval, a proto jste museli použít vlastní contentWindow vlastnictví. Příklad:

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

Kromě toho lze objekt okna obsahující stránku načíst z prvku iframe pomocí window.parent vlastnictví. Stránka iframe může také načíst odkaz na prvek iframe, ve kterém se nachází, pomocí window.frameElement vlastnictví. To překračuje hranici vlastnictví, protože prvek iframe vlastní stránka obsahující, ale je přímo přístupný z window prvku iframe. objekt.

Použití načítání prvku iframe

Zjistit, kdy je prvek iframe načten, je zajímavý úkol kvůli problémům s vlastnictvím prvků iframe. Prohlížeče, které nejsou Internet Explorer, dělají něco velmi užitečného:odhalují load událost pro prvek iframe abyste mohli být informováni o načtení prvku iframe bez ohledu na jeho obsah. Vzhledem k tomu, že prvek iframe vlastní stránka, která ho obsahuje, nemusíte se nikdy obávat omezení mezi doménami. Prvek iframe načítající místní obsah lze sledovat stejně dobře jako prvek iframe načítající cizí obsah (experiment). Příklad kódu:

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

Toto funguje ve všech prohlížečích kromě Internet Exploreru (dokonce i verze 8!). Doufal jsem, že možná pomocí attachEvent() metoda by fungovala, ale bohužel, Internet Explorer prostě nepodporuje load událost v prvku iframe. Docela zklamání.

Použití načtení okna iframe

Vypadalo to, že Internet Explorer mi zkazí den...opět. Pak jsem si vzpomněl, že se nebojím cizího obsahu v prvku iframe. V mém konkrétním případě jsem řešil obsah ze stejné domény. Protože se nevztahovalo omezení mezi doménami, měl jsem přístup k window prvku iframe objekt přímo a přiřadit onload obsluha události. Příklad:

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

Zajímavou částí tohoto přístupu je, že musíte přiřadit obslužnou rutinu události po prvek iframe byl přidán na stránku. Předtím je prvek iframe window objekt neexistuje, a proto nemůžete přiřadit obsluhu události. Tento přístup funguje v prohlížečích Internet Explorer a Firefox pouze pro stránky stejné domény. Jiné prohlížeče zatím nevytvořily window objekt a tak vyvolá chybu (experiment).

Zadejte document.domain

Smířil jsem se s použitím jedné metody detekce načítání iframe pro Internet Explorer a další pro každý jiný prohlížeč, takže jsem pokračoval ve svém úkolu. Dále jsem musel nastavit document.domain na obsahující stránce, protože jsem měl několik různých subdomén, ze kterých jsem potřeboval načíst prvky iframe. Pokud používáte různé subdomény, nastavte document.domain do kořenového adresáře názvu hostitele umožňuje prvkům iframe komunikovat se svými rodiči a mezi sebou navzájem. Pokud bych například musel načíst stránku iframe z www2.nczonline.net , která je technicky považována za jinou doménu a nebyla by povolena. Pokud však nastavím document.domain na „nczonline.net“ na stránce obsahující i na stránce iframe mohou tyto dva komunikovat. Stačí jediný řádek kódu, ideálně umístěný v horní části stránky:

document.domain = "nczonline.net";

Tím se vyrovná rozdíl mezi doménami a vše bude fungovat, jako by obě stránky byly ze stejné domény. Nebo jsem si to alespoň myslel.

Problém s tímto přístupem je v tom, že před načtením prvku iframe je stále považován za vlastněný doménou, jak je uvedeno v jeho src atribut. Relativní cesta automaticky připojí před doménu, ze které byla stránka načtena (www.nczonline.net ) oproti tomu přiřazenému k document.domain . To znamená srovnání wnczonline.net na www.nczonline.net selže při kontrole stejné domény a způsobí chybu JavaScriptu, když se pokusíte o přístup k prvku iframe window objekt (experiment). Na stránce iframe se přidružená doména nezmění, dokud nebude načtena a nebude proveden příkaz JavaScript ke změně domény. Po načtení stránky iframe však vše funguje dobře. Jak ale poznáte, že byla stránka iframe načtena?

Obrácení procesu

Protože jsem stále nenarazil na řešení pro různé prohlížeče, jak určit, kdy se načetl prvek iframe, rozhodl jsem se obrátit své myšlení. Namísto toho, aby se obsahující stránka zeptala, kdy je prvek iframe načten, co když iframe sdělil obsahující stránce, že byl načten? Pokud stránka iframe naslouchá vlastnímu load a poté řekl obsahující stránce, když k tomu došlo, že by to mělo problém vyřešit. Chtěl jsem, aby to bylo stejně jednoduché jako přiřazení obsluhy události, a tak jsem přišel s následujícím nápadem:přiřadím metodu prvku iframe. Poté stránka iframe zavolá tuto metodu, když se načte. Metoda musí být přiřazena prvku, nikoli window prvku iframe objekt, protože posledně jmenovaný neexistuje ve všech prohlížečích v dostatečném předstihu. Výsledek vypadal takto:

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

Tento kód přiřadil metodu nazvanou _myMethod() na prvek iframe. Stránka, která se načítá do prvku iframe, pak přidá tento kód:

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

Protože tento kód je spuštěn po přiřazení k document.domain , neexistují žádná bezpečnostní omezení, kterých byste se měli obávat. To funguje skvěle pro všechny zdroje, které sdílejí stejný kořenový název hostitele (experiment). Funguje ve všech prohlížečích, což je přesně to, co jsem hledal, ale problém s detekcí, kdy byl cizí zdroj načten do prvku iframe, mě stále otravoval.

Použití onreadystatechange prvku iframe

Rozhodl jsem se trochu více prozkoumat implementaci iframe aplikace Internet Explorer. Bylo jasné, že přiřadit něco k onload vlastnost nepřinesla požadovaný efekt, ale napadlo mě, že musí existovat něco podobného. Pokusil jsem se připojit obsluhu události pomocí attachEvent() , což také nefungovalo. Dobře, jasně, že na prvku iframe nebyla žádná podpora pro událost zatížení. A co něco jiného?

Tehdy jsem si vzpomněl na bizarní readystatechange IE pokud má na dokladech. To je samozřejmě úplně jiné než readystatechange událost spuštěna na XMLHttpRequest objektů. Napadlo mě, zda by prvek iframe mohl podporovat i tuto událost, a jak se ukázalo, podporuje. Prvek iframe podporuje readyState vlastnost, která se po načtení obsahu prvku iframe změní na „interaktivní“ a poté na „dokončit“. A protože je to na prvku iframe a ne na prvku iframe window objekt, neexistují žádné obavy z omezení mezi doménami (experiment). Konečný kód, se kterým jsem skončil, je v tomto smyslu:

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

Kontrola, zda je prohlížeč IE nebo ne, je trochu chaotická. Raději bych zkontroloval existenci iframe.readyState to však vyvolá chybu, když se pokusíte o přístup k vlastnosti před přidáním prvku iframe do dokumentu. Zvažoval jsem použití existence document.readyState určit, zda použít readystatechange tuto vlastnost však nyní podporuje většina ostatních prohlížečů, takže to není dost dobrý determinant. S YUI bych použil pouze Y.UA.ie abyste to určili (můžete použít metodu, která vám nejlépe vyhovuje).

podpora skrytého načítání IE

Krátce po zveřejnění tohoto blogu to Christopher poznamenal pomocí attachEvent () na prvku iframe funguje v IE. Mohl jsem přísahat, že jsem to zkoušel už dříve, ale kvůli jeho nabádání jsem vybičoval další experiment. Jak se ukázalo, má naprostou pravdu. Musel jsem se prohrabat dokumentací MSDN, abych nakonec našel odkaz na kruhový objezd, ale určitě tam je. To vedlo ke konečnému fragmentu kódu:

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

Tento kód také funguje ve všech prohlížečích a předchází potenciálním problémům s načasováním readystatechange událost versus load událost.

Shrnutí

Po dlouhém zkoumání se zdá, že je možné určit, kdy se prvek iframe načetl ve všech prohlížečích bez ohledu na původ stránky iframe. Díky tomu je mnohem snazší spravovat monitorování a zpracování chyb obsahu iframe. Jsem vděčný, že všichni dodavatelé prohlížečů viděli výhodu přidání těchto událostí do samotného prvku iframe namísto spoléhání se na prvek iframe window objekt nebo očekáváme, že je nám obvykle jedno, zda byl prvek iframe načten nebo ne.

**Aktualizace (15. září 2009):**Přidána sekce o attachEvent() na základě Christopherova komentáře.