Estoy escribiendo un Plugin de jQuery que necesita para ser capaz de ejecutar en contra de elementos del DOM dentro de un iFrame. Solo estoy probando este local ahora mismo (es decir, la url es file://…/example.html) y en Chrome me dejan de golpear «SecurityError: no se pudo leer el ‘contentDocument’ propiedad de ‘HTMLIFrameElement’: Bloqueado un marco con origen «null» de acceder a una cruz de origen frame.», y en Safari me acaba de llegar un documento vacío.

Dado que tanto el archivo principal y el iFrame del archivo están saliendo de mi disco local (en desarrollo) y llegará el mismo servidor (en producción) yo había pensado que me gustaría no estar sujeto a la cruz-origen de los problemas.

Es hay alguna manera de convencer al navegador que a mi los archivos son en realidad del mismo dominio?

<a un lado>Curiosamente, en Safari, el uso de la consola directamente, me puede escribir $("iframe").get(0).contentDocument.find("ol") y que felizmente se encuentra en mi lista. En Chrome esta misma línea tira el error de seguridad, como si se tratara de ser ejecutado.</a un lado>

Actualización

Basado en las sugerencias de abajo se me han disparado hasta un simple local de servidor web para probar esto y no estoy ahora recibiendo la cruz-origen de error – yay – pero tampoco estoy recibiendo 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);
  });
});

La myDocument.ready está allí sólo para asegurarse de que el iFrame del documento está listo – en realidad no hace ninguna diferencia.

Siempre termino con myElements vacías. ([] en safari o jQuery.fn.init[0] en Chrome)

Pero si yo escriba esto en la consola:

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

Puedo obtener mis listas de espera. Este es el caso tanto en Safari y Chrome.

Así que mi pregunta es: ¿por qué no puede mi script para ver los elementos del DOM, pero el mismo código, cuando entró directamente en el navegador de la consola de alegría ver a los elementos del DOM?

InformationsquelleAutor Dave Sag | 2014-07-07

2 Comentarios

  1. 28

    Chrome ha predeterminado restricciones de seguridad que no permite el acceso a otros windows desde el disco duro, incluso a pesar de que, técnicamente, el mismo origen. Hay una bandera en Chrome que puede relajarse que la restricción de seguridad (argumento de línea de comandos en Windows, es lo que recuerdo), aunque yo no recomendaría correr con esa bandera por más de una prueba rápida. Ver este post o este artículo para información sobre el argumento de línea de comandos.

    Si ejecuta los archivos de un servidor web (incluso si se trata de un servidor web local) en lugar de su disco duro, usted no tendrá este problema.

    O, usted podría poner a prueba en otros navegadores que no son tan restrictivas.


    Ahora que ha cambiado el asunto en algo diferente, usted tiene que esperar un iframe ventana para cargar antes de que pueda acceder a los contenidos en ella y usted no puede usar jQuery .ready() en un documento diferente (no funciona 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í.


    EDICIÓN: más allá de la investigación, he encontrado que jQuery hará parte de este trabajo para usted como esta y el jQuery solución que parece funcionar en todos los navegadores principales:

    $(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í.

    Mirando en la implementación de jQuery para esto, todo lo que realmente está haciendo es establecer un load detector de eventos en el propio iFrame.


    Si quieres saber los detalles nitty gritty que entró en depuración/problemas el primer método anterior:

    En mi tratando de resolver este problema, he descubierto algunas bastante extrañas cosas (en Chrome) con iFrames. La primera vez que mire en la ventana del iframe, hay un documento que dice que su readyState === "complete" de tal manera que usted piensa que se termine de cargar, pero es mentira. El documento actual y real <body> etiqueta en el documento que se está cargando a través de la URL en el iframe NO está realmente allí todavía. He probado poniendo un atributo personalizado en el <body data-test="hello"> y la comprobación de que el atributo personalizado. He aquí. Aunque document.readyState === "complete", ese atributo personalizado no está allí en el <body> etiqueta. Así, llego a la conclusión (al menos en Chrome) que el iFrame tiene inicialmente un maniquí y documento vacío y el cuerpo en el que no son los reales que estarán en el lugar una vez que la URL se carga en el iFrame. Esto hace que todo este proceso de detectar cuando está listo para ser bastante confuso (que me costó horas de calcular esto). De hecho, si puedo configurar un temporizador de intervalos y de la encuesta de iWindow.document.body.getAttribute("data-test"), voy a ver que muestran como undefined en repetidas ocasiones y, finalmente, se mostrará con el valor correcto y todo esto con document.readyState === "complete" lo que significa que es completamente mentira.

    Creo que lo que pasa es que el iFrame se inicia con un maniquí y documento vacío y el cuerpo que luego es reemplazado DESPUÉS de que el contenido se inicia la carga. Por otro lado, el iFrame window es la ventana real. Así, la única manera que he encontrado para realmente espere a que el contenido cargado a supervisar el load evento en el iFrame window como que no se parece mentira. Si usted sabía que había un contenido específico que estaba esperando, usted también podría encuesta hasta que el contenido estaba disponible. Pero, incluso entonces usted tiene que tener cuidado porque no se puede recuperar la iframe.contentWindow.document demasiado pronto porque va a ser el documento equivocado si tomase demasiado pronto. La cosa es bastante roto. No puedo encontrar alguna forma de usar DOMContentLoaded desde fuera del iFrame documento en sí mismo, porque no hay forma de saber el real document objeto está en su lugar para que usted puede adjuntar el manejador del evento. Así que … que se establecieron para el load evento en el iFrame de la ventana de que no parece funcionar.


    Si realmente controlan el código del iFrame, a continuación, puede activar el evento más fácilmente desde el propio iFrame, ya sea mediante el uso de jQuery con $(document).ready() en el código iFrame con su propia versión de jQuery o llamar a una función en la ventana principal de una secuencia de comandos encuentra después de su elemento objetivo (asegurando así el elemento objetivo está cargado y listo).


    Editar Más

    Después de un montón más de investigación y pruebas, he aquí una función que le dirá cuando un iFrame golpea el DOMContentLoaded evento en lugar de esperar a que el load evento (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 se llama simplemente 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!";
    });
    • Justo lo suficiente. Se ejecuta en Mac y no puedo controlar lo que mis usuarios deben ejecutar (aún en pruebas).
    • Ejecutando un servidor web local hizo Chrome y Safari comportamiento consistente, pero no se resuelve el problema. ver la actualización a la pregunta anterior.
    • He añadido en mi respuesta a la dirección de su nuevo tema.
    • Gracias por la detallada y la respuesta del paciente. En mi situación, mi iFrame en realidad es cargado dentro de una Extensión de Chrome, pero el contenido de secuencias de comandos se ejecuta fuera de ese iFrame, por lo que sí tengo acceso a el contenido del iFrame, pero yo no era capaz de encontrar una manera de ejecutar el código dentro de ese iFrame. Desde entonces he trabajado en eso, pero se plantean algunas cuestiones más complejas para mí con el inter-frame de paso de mensajes. Voy a cavar en su respuesta más cuidadosamente hoy y saber cómo me fue. De nuevo, gracias.
    • Después de más investigación, he desarrollado un iFrameReady() función (ahora incluido en mi respuesta) que le notifique cuando un iFrame hits DOMContentLoaded sin esperar hasta que todas las imágenes y hojas de estilo son cargados (que es lo que el load caso para el iFrame.
    • Muchas gracias. He implementado tu sugerencia, pero por desgracia mi myElements matriz es todavía terminar como [] – aunque, como se mencionó en mi pregunta, si puedo ejecutar el mismo código en la consola de los elementos están ahí, y que por supuesto se muestran en el navegador. También probé la votación de los elementos en sí, pero que nunca aparecen tan lejos como el código se refiere. Consulte github.com/davesag/listJuggler/blob/scriptless-iframe/example/… para mi de código fuente; tal vez me he perdido algo realmente obvio.
    • ¿Tienes tu página web, trabajando en línea, un lugar donde puedo directamente de depuración yo mismo se ejecuta en un navegador? Traté de su concepto en mi propia página y parece que funciona, pero al parecer algo más es un problema en su página en particular.
    • Gracias @jfriend00 – si se tira de la fuente de la ‘scriptless-iframe» rama en github.com/davesag/listJuggler/tree/scriptless-iframe hay un Nodo simple basado en servidor web incluido. Sólo npm install, entonces grunt para generar el código de Coffeescript y, a continuación, node webServer.js para ejecutarlo. La página es accesible en http://localhost:8080/example/iframeExternallyReferencedExample.html
    • lo siento, pero no voy a instalar un montón de cosas en mi propio sistema para intentar reproducir el problema. Tenía la esperanza de que podría ofrecer una manera más sencilla de reproducir y depurar.
    • Sí, justo lo suficiente. Me había alojado los ejemplos en GitHub – consulte davesag.github.io/listJuggler/ejemplo/…
    • Ahh sí, un problema es que en Chrome y Firefox no se puede recuperar el documento hasta DESPUÉS de iFrameReady() pide su devolución de llamada. El código fue ir a buscar myFrame.contents() antes. Todo esto es explicado en mi respuesta, pero es debido a que usted recibe un documento temporal que no va a terminar siendo el documento verdadero y por lo tanto usted nunca encontrar cualquier contenido en este temporal y documento vacío. iFrameReady() pasa el documento actual como this cuando llama a la devolución de llamada para que usted probablemente debería hacer su búsqueda en el iFrame dentro de la devolución de llamada con $(this).find(...).
    • Para su INFORMACIÓN, usted también tiene una aplicación de error en su aplicación de iFrameReady(). La variable fired se declara a ser local en el ready() función, cuando debería estar a un nivel superior. Esto permite que la devolución de llamada para ser llamado varias veces para el mismo iFrame (que su implementación está haciendo), que no es cómo se debe trabajar.
    • Bien gracias – que es clavado. Saludos D
    • Yo quiero votar hasta la cara frente a esta respuesta. Muchas gracias por la adición de este.
    • Cada vez que veo a 1ms de tiempo de espera en la respuesta a la pregunta respecto a los acontecimientos, puedo grabar un pequeño cachorro con napalm. Así que ten cuidado con eso.
    • generalmente yo estaría de acuerdo con usted, pero este es un caso donde iFrames en algunos navegadores tienen un extraño procedimiento de inicio donde ellos comienzan con un espacio en blanco «sobre:» el documento y cuando, en ese estado, no se puede asignar controladores de eventos debido a que los controladores de eventos que se borrarán cuando se empieza a cargar el documento actual. Así, el código de «encuestas» hasta parece que el documento real de comenzar a cargar y, a continuación, instala los controladores de eventos y paradas de votación. El corto setTimeout() es un bucle de sondeo. Si hubo un caso de uso, que sería preferible seguro, pero no parece ser.

  2. 1

    Dado que tanto el archivo principal y el iFrame del archivo están saliendo de mi disco local (en desarrollo) y llegará el mismo servidor (en producción) yo había pensado que me gustaría no estar sujeto a la cruz-origen de los problemas.

    «Por favor abre este perfectamente inofensivo documento HTML que he adjuntado a este correo electrónico.» Hay buenas razones para que los navegadores para aplicar cruz-dominio de seguridad de archivos locales.

    Es hay alguna manera de convencer al navegador que a mi los archivos son en realidad del mismo dominio?

    Instalar un servidor web. Prueba a través de la http://localhost. Como un bono, obtener todos los otros beneficios de un servidor HTTP (tales como ser capaz de utilizar URIs relativas que empezar con un / y desarrollar con el lado del servidor de código).

    • Gracias – me he quedado un servidor web local y ahora Chrome y Safari comportan de la misma forma, aunque el problema no se ha resuelto todavía. ver la actualización anterior.
    • Eso es un asunto completamente diferente. Es probablemente debido a que el DOM del documento de los padres está preparado mucho tiempo antes de que el documento en el marco de las cargas. El uso más adecuado de eventos (como la carga).

Dejar respuesta

Please enter your comment!
Please enter your name here