Cómo ejecutar una función a través de postMessage

Tengo este código dentro de un iframe:

window.addEventListener('message', function(e){

  if(e.data == 'test')
    console.log(e);

}, false);

y esto dentro del documento principal:

$('#the_iframe').get(0).contentWindow.postMessage('test', 'http://localhost/');

Entonces, el documento principal envía un mensaje de "prueba" al iframe y funciona.

Pero, ¿cómo puedo definir una función en el documento principal y, de alguna manera, enviar esta función a través de postMessage al iframe, que ejecutará la función localmente?

La función hace algunos cambios en el documento como este:

var func = function(){
  $("#some_div").addClass('sss');
}

(#some_div existe en el iframe, no en el documento principal)

preguntado el 12 de junio de 12 a las 22:06

Creo que hay un window.parent variable que debe ser accesible desde el iframe. La ventana principal también tendrá una parent pero creo que se referirá a sí mismo -

5 Respuestas

No hay nada que le impida pasar una función en forma de cadena como datos de eventos posteriores al mensaje. La implementación es trivial, para cualquier declaración de función como

function doSomething(){
    alert("hello world!");
}

Usted podría encodeURI su interpretación de cadena:

console.log(encodeURI(doSomething.toString()));
//function%20doSomething()%20%7B%0A%20%20%20%20alert(%22hello%20world!%22);%0A%7D

Luego se puede ejecutar como parte de un cierre, algo no demasiado imaginativo como

eval('('+decodeURI(strCallback)+')();');

Hay una prueba de concepto manipulada sin la arquitectura de marcos cruzados: veré si puedo armar una versión posterior al mensaje, pero no sería trivial hospedar con jsfiddle

Noticias

Como prometí, una maqueta completa que funciona (enlaces a continuación). con correcto event.origin controles esto sería suficientemente impenetrable, pero sé por el hecho de que nuestro equipo de seguridad nunca dejaría eval en producción así :)

Dada la opción, sugeriría que la funcionalidad se normalice en las dos páginas para que solo sea necesario pasar un mensaje paramétrico (es decir, pasar argumentos, no funciones); sin embargo, definitivamente hay algunos escenarios en los que este es un enfoque preferido.

Código padre:

document.domain = "fiddle.jshell.net";//sync the domains
window.addEventListener("message", receiveMessage, false);//set up the listener

function receiveMessage(e) {
    try {
        //attempt to deserialize function and execute as closure
        eval('(' + decodeURI(e.data) + ')();');
    } catch(e) {}
}

Código de iframe:

document.domain = "fiddle.jshell.net";//sync the domains
window.addEventListener("message", receiveMessage, false);//set up the listener

function receiveMessage(e) {
    //"reply" with a serialized function
    e.source.postMessage(serializeFunction(doSomething), "http://fiddle.jshell.net");
}

function serializeFunction(f) {
    return encodeURI(f.toString());
}

function doSomething() {
    alert("hello world!");
}

Maqueta de prototipo: código principal y código iframe.

Respondido el 19 de junio de 12 a las 23:06

¿eso realmente funciona? Quiero decir que puede obtener el código de cada función llamando a function.toString() ? - Alex

@Alex: sí, sirve para cualquier función que ha definido - o, más bien, cualquier función no escrita en código nativo (es decir, no puede ver la representación de cadena del código fuente para window.alert) - Oleg

solo tenga en cuenta que termina con el código de peor práctica (evaluaciones, funciones ejecutadas en contexto global, etc.) - Dziad Borowy

Realmente no puedes. Aunque el (borrador) especificación para postMessage habla de objetos estructurados, por ejemplo, objetos anidados y matrices, [...] valores de JavaScript (cadenas, números, fechas, etc.) y ciertos [...] objetos de datos como objetos File Blob, FileList y ArrayBuffer la mayoría de los navegadores solo permiten cadenas (incluido JSON, por supuesto). Lea mas en DND or dev.opera. Sin embargo, estoy bastante seguro de que no será posible enviar objetos de función, al menos no como cierres que preserven su alcance.

Así que terminará en stringificando la función y eval() en el iframe, si realmente desea ejecutar algún código desde la ventana principal. Sin embargo, no veo ninguna razón para que ninguna aplicación permita la evaluación de código arbitrario (incluso si proviene de dominios registrados); sería mejor crear una API de mensajes que pueda recibir comandos de cadena (JSON-) e invocar sus propios métodos.

Respondido el 18 de junio de 12 a las 17:06

En este caso, probaría un enfoque diferente. ¿Por qué? Bergi ya explicó por qué no funcionará como tú quieres.

Puede definir (y redefinir sus funciones) en la página principal:

<html>                                                                  
<head>                                                                  
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> 
<script type="text/javascript">                                        
    var fun = function() { alert('I am a function!'); };

    $(function() {
        // use this function to change div background color
        fun = function() { 
            // get desired div
            var theDiv = $('#frame').contents().find('#some_div');
            theDiv.css('background', '#ff0000');
        };

        // or override it, if you want to change div font color
        fun = function() { 
            var theDiv = $('#frame').contents().find('#some_div');
            theDiv.css('color', '#ff0000');
        };

        // in this example second (font color changing) function will be executed
    });
</script>                                                               
</head>                                                                 
<body>                                                                  
    <iframe id="frame" src="frame.htm"></iframe>
</body>                                                                 
</html>

y llame a su función desde dentro de la página de marco:

<html>                                                                  
<head>                                                                  
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> 
<script type="text/javascript">                                         
    $(function() {
        parent.fun();
    });
</script>                                                               
</head>                                                                 
<body>                                                                  
    <div id="some_div">I am a div (but inside frame)!</div>
</body>                                                                 
</html>

Puede ser un inconveniente, pero funciona.

Respondido el 17 de junio de 12 a las 14:06

Ampliando la siguiente pregunta puedes encadenar una función, usa postMessage para enviar el cuerpo de la función y luego usar eval para ejecutarlo

Esencialmente, lo que está haciendo es ordenar la función para que pueda enviarse al iframe y luego desarmarla en el otro extremo. Para ello utiliza el siguiente código:

Iframe:

window.addEventListener("message", function (event) {
    var data = JSON.parse(event.data);
    var callback = window[data.callback];
    var value = data.value;

    if (data.type === "function")
        value = eval(value);

    var callback = window[data.callback];

    if (typeof callback === "function")
        callback(value);
}, false);

function callFunction(funct) {
    funct();
}

Padres:

var iframe = $("#the_iframe").get(0).contentWindow;

postMessage(function () {
    $("#some_div").addClass("sss");
}, "callFunction");

function postMessage(value, callback) {
    var type = typeof value;

    if (type === "function")
        value = String(value);

    iframe.postMessage({
        type: type,
        value: value,
        callback: callback
    }, "http://localhost");
}

Una vez que obtenga el cuerpo de la función en su iframe y eval se puede utilizar como una función normal. Puede asignarlo a una variable, pasarlo, usar call or apply en eso, bind eso, y así sucesivamente.

Sin embargo, tenga en cuenta que el alcance de la función es dinámico (es decir, cualquier variable no local en la función ya debe estar definida en su ventana iframe).

En tu caso el jquery $ La variable que está utilizando en su función no es local. Por lo tanto, el iframe ya debe tener jquery cargado. No usará el jquery $ variable de la ventana principal.

contestado el 23 de mayo de 17 a las 12:05

Es 2015, no debería sugerir usar eval en producción. - Yaguiz

@Yagiz Ese es un argumento estúpido. Es como decir: "Es 2015, no deberías animar a la gente a atarse los cordones de los zapatos". - Aadit M Shah

Siempre puedes enviar postMessage regrese al iframe y deje que iframe maneje el mensaje. Por supuesto, debe tener en cuenta que no se ejecutará antes del próximo comando en iframe.

respondido 29 mar '16, 08:03

No es la respuesta que estás buscando? Examinar otras preguntas etiquetadas or haz tu propia pregunta.