关于javascript:如何通过postMessage执行函数

How to execute a function through postMessage

我在iframe中有以下代码:

1
2
3
4
5
6
window.addEventListener('message', function(e){

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

}, false);

并在父文档中:

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

因此,父文档会向iframe发送"测试"消息,并且它可以正常工作。

但是,如何在父文档中定义函数,以及如何通过postMessage将该函数发送到iframe,iframe将在本地执行该函数?

该函数对文档进行如下更改:

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

(#some_div存在于iframe中,而不是父文档中)


没有什么可以阻止您将字符串化函数作为消息传递后事件数据传递的。对于任何函数声明,实现都是微不足道的

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

您可以encodeURI它的字符串解释:

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

然后可以将其作为闭包的一部分来执行-像这样的想象力不足

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

没有跨框架体系结构,这是一个经过提倡的概念证明-我将看看是否可以组合一个postMessage版本,但是托管w / jsfiddle并不是一件容易的事

更新资料

如所承诺的,一个完整的模型可以工作(下面的链接)。使用正确的event.origin检查将是完全不可渗透的,但是我知道我们的安全团队永远不会像这样让eval进入生产环境:)

给定该选项,我建议在两个页面上对功能进行标准化,以便仅需要传递参数消息(即传递参数而不传递函数);但是,肯定有一些方案是首选方案。

父代码:

1
2
3
4
5
6
7
8
9
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) {}
}

iframe代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
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!");
}

原型模型:父代码和iframe代码。


你真的不能。尽管postMessage的(草稿)规范讨论的是结构化对象,例如嵌套的对象和数组,JavaScript值(字符串,数字,日期等)和某些数据对象(例如File Blob,FileList和ArrayBuffer对象),大多数浏览器仅允许以下字符串(包括JSON)课程)。在MDN或dev.opera上了解更多信息。但是我非常确定,不可能发送函数对象,至少不能以闭包形式保存它们的范围。

因此,如果您真的想从父窗口执行一些代码,则将字符串化并在iframe中eval()结束。但是,我认为没有理由允许任何应用程序评估任意代码(即使来自注册域)也是如此。最好构建一个可以接收(JSON-)字符串命令并调用其自身方法的消息API。


在这种情况下,我会尝试其他方法。为什么? Bergi已经解释了为什么它无法按照您想要的方式工作。

您可以在父页面中定义(并重新定义函数):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
<html>                                                                  
<head>                                                                  
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js">
<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
    });
                                                               
</head>                                                                
<body>                                                                  
    <iframe id="frame" src="frame.htm"></iframe>
</body>                                                                
</html>

并在框架页面内调用函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
<html>                                                                  
<head>                                                                  
<script type="text/javascript" src="https://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js">
<script type="text/javascript">                                        
    $(function() {
        parent.fun();
    });
                                                               
</head>                                                                
<body>                                                                  
    I am a div (but inside frame)!
</body>                                                                
</html>

可能不方便,但可以。


扩展以下问题,您可以对函数进行字符串化,使用postMessage将函数主体发送过来,然后使用eval执行它。

本质上,您正在做的是将功能封送,以便可以将其发送到iframe,然后在另一端将其拆封。为此,请使用以下代码:

iframe:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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();
}

上级:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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");
}

在iframe和eval中获得函数主体后,就可以像正常函数一样使用它了。您可以将其分配给变量,将其传递,在其上使用callapply,在其上使用bind,依此类推。

但是请注意,该函数的范围是动态的(即,该函数中的所有非局部变量都必须已在您的iframe窗口中定义)。

在您的情况下,您在函数中使用的jquery $变量是非本地的。因此,iframe必须已经加载了jquery。它不会在父窗口中使用jquery $变量。


您始终可以将postMessage发送回iframe,并让iframe处理该消息。当然,您必须计数它不会在iframe中的下一个命令之前执行。