from - iframe javascript
¿Cómo puedo acceder a los elementos DOM dentro de un iFrame? (2)
Estoy escribiendo un complemento jQuery que necesita poder ejecutar contra los elementos DOM dentro de un iFrame. Solo estoy probando esto localmente ahora (es decir, url es file: //.../example.html) y en Chrome sigo presionando "SecurityError: no se pudo leer la propiedad ''contentDocument'' de ''HTMLIFrameElement'': Blocked a frame con origen "nulo" de acceder a un marco de origen cruzado ". y en Safari acabo de obtener un documento vacío.
Dado que tanto el archivo principal como el archivo del iFrame provienen de mi disco local (en desarrollo) y procederán del mismo servidor (en producción), pensé que no estaría sujeto a los problemas de origen cruzado. .
¿Hay alguna manera de convencer al navegador de que mis archivos locales son en realidad del mismo dominio?
<aside> Curiosamente en Safari, al usar la consola directamente, puedo escribir $("iframe").get(0).contentDocument.find("ol")
y encuentra felizmente mi lista. En Chrome, esta misma línea arroja el error de seguridad como si se estuviera ejecutando. </ aside>
Actualizar
En base a las sugerencias a continuación, he activado un servidor web local simple para probar esto y ahora no obtengo el error de origen cruzado, pero tampoco recibo ningún contenido.
Mi Javascript parece
$(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);
});
});
El myDocument.ready
está allí solo para garantizar que el documento de iFrame esté listo; en realidad, no importa.
Siempre termino con myElements
estando vacío. ( []
en safari o jQuery.fn.init[0]
en Chrome)
Pero si lo escribo manualmente en la consola:
$($("iframe").get(0).contentDocument).find("ol, ul")
Obtengo mis listas como esperaba. Este es ahora el caso tanto en Safari como en Chrome.
Entonces mi pregunta es: ¿por qué mi guión no puede ver los elementos DOM pero el mismo código cuando se ingresa directamente en la consola del navegador puede ver felizmente los elementos DOM?
Dado que tanto el archivo principal como el archivo del iFrame provienen de mi disco local (en desarrollo) y procederán del mismo servidor (en producción), pensé que no estaría sujeto a los problemas de origen cruzado. .
"Abra este documento HTML perfectamente inofensivo que he adjuntado a este correo electrónico". Hay buenas razones para que los navegadores apliquen seguridad entre dominios a los archivos locales.
¿Hay alguna manera de convencer al navegador de que mis archivos locales son en realidad del mismo dominio?
Instala un servidor web. Prueba a través de http://localhost
. Como beneficio adicional, obtenga todos los demás beneficios de un servidor HTTP (como poder usar URI relativos que comiencen con /
y desarrollen con el código del lado del servidor).
Chrome tiene restricciones de seguridad predeterminadas que no le permiten acceder a otras ventanas desde el disco duro aunque técnicamente son del mismo origen. Hay una bandera en Chrome que puede relajar esa restricción de seguridad (el argumento de línea de comando en Windows es lo que recuerdo), aunque no recomendaría correr con esa bandera para más que una prueba rápida. Consulte esta publicación o este artículo para obtener información sobre el argumento de la línea de comando.
Si ejecuta los archivos fuera de un servidor web (incluso si es un servidor web local) en lugar de su disco duro, no tendrá este problema.
O bien, podría probar en otros navegadores que no sean tan restrictivos.
Ahora que ha cambiado la pregunta a algo diferente, tiene que esperar a que se cargue una ventana de iframe para poder acceder al contenido y no puede usar .ready()
jQuery en un documento diferente (no lo hace). trabajar en otro documento).
$(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!";
});
});
Página de prueba aquí .
EDITAR: Tras realizar más investigaciones, descubrí que jQuery hará algo de este trabajo para usted así y la solución jQuery parece funcionar en todos los principales navegadores:
$(document).ready(function() {
$("#testFrame").load(function() {
var doc = this.contentDocument || this.contentWindow.document;
var target = doc.getElementById("target");
target.innerHTML = "Found It!";
});
});
Página de prueba aquí .
Al ver la implementación de jQuery para esto, todo lo que realmente está haciendo es configurar un oyente de evento de load
en el propio iFrame.
Si desea conocer los detalles esenciales para la depuración / solución del primer método anterior:
En mi intento de resolver este problema, descubrí algunas cosas bastante extrañas (en Chrome) con iFrames. Cuando miras por primera vez en la ventana del iframe, hay un documento y dice que readyState === "complete"
modo que creas que se ha terminado de cargar, pero está mintiendo. El documento real y la etiqueta <body>
actual del documento que se está cargando a través de la URL en el iframe en realidad NO están ahí todavía. Lo probé poniendo un atributo personalizado en <body data-test="hello">
y comprobando ese atributo personalizado. Lo y he aquí. Aunque document.readyState === "complete"
, ese atributo personalizado no está en la etiqueta <body>
. Entonces, concluyo (al menos en Chrome) que el iFrame inicialmente tiene un documento y un cuerpo ficticio y vacío que no son los reales que estarán en su lugar una vez que la URL se carga en el iFrame. Esto hace que todo este proceso de detección esté listo para ser bastante confuso (me costó horas averiguarlo). De hecho, si configuro un temporizador de intervalo y sondeo iWindow.document.body.getAttribute("data-test")
, lo veré como undefined
repetidamente y finalmente aparecerá con el valor correcto y todo esto con document.readyState === "complete"
que significa que está mintiendo por completo.
Creo que lo que sucede es que el iFrame comienza con un documento y un cuerpo ficticio y vacío que luego se reemplaza DESPUÉS de que el contenido comience a cargarse. Por otro lado, la window
iFrame es la ventana real. Por lo tanto, las únicas formas en que he encontrado que realmente espero que se cargue el contenido son para supervisar el evento de load
en la window
iFrame window
ya que eso no parece mentir. Si sabía que había contenido específico que estaba esperando, también puede sondear hasta que ese contenido esté disponible. Pero, incluso entonces, debe tener cuidado porque no puede obtener el iframe.contentWindow.document
demasiado pronto porque será el documento incorrecto si lo recupera demasiado pronto. Todo está bastante roto. No puedo encontrar ninguna forma de utilizar DOMContentLoaded
desde fuera del documento de iFrame en sí porque no tiene forma de saber si el document
actual está en su lugar para poder adjuntar el controlador de eventos. Entonces ... me conformé con el evento de load
en la ventana de iFrame que parece funcionar.
Si realmente controla el código en el iFrame, puede desencadenar el evento más fácilmente desde el mismo iFrame, ya sea usando jQuery con $(document).ready()
en el código iFrame con su propia versión de jQuery o llamando a un Funciona en la ventana primaria desde un script ubicado después de su elemento objetivo (asegurando así que el elemento objetivo esté cargado y listo).
Editar más
Después de un montón más de investigación y pruebas, esta es una función que le dirá cuándo un iFrame golpea el evento DOMContentLoaded
lugar de esperar el evento de load
(que puede tomar más tiempo con imágenes y hojas de estilo).
// 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();
}
Esto simplemente se llama así:
// 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!";
});