12

我在 iframe 中有这段代码:

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

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

}, false);

这在父文档中:

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

因此,父文档向 iframe 发送一条“测试”消息并且它可以工作。

但是如何在父文档中定义一个函数,并以某种方式通过 postMessage 将该函数发送到 iframe,iframe 将在本地执行该函数?

该函数对文档进行一些更改,如下所示:

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

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

4

5 回答 5

16

没有什么可以阻止您将字符串化函数作为消息后事件数据传递。对于任何函数声明,如

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

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

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

然后它可以作为闭包的一部分执行——不要过于想象

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

在没有跨框架架构的情况下有一个小提琴的概念证明 - 我会看看我是否可以组合一个 postMessage 版本,但托管 w/jsfiddle 并非易事

更新

正如所承诺的,一个完整的模型可以工作(下面的链接)。通过正确的event.origin检查,这将是非常难以穿透的,但我知道我们的安全团队永远不会eval像这样投入生产:)

给定选项,我建议在两个页面之间对功能进行规范化,以便只需要传递参数消息(即传递参数而不是函数);但是,在某些情况下,这是首选方法。

父代码:

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) {}
}

框架代码:

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 代码

于 2012-06-18T04:12:16.173 回答
10

你真的不能。尽管postMessage 规范(草案)讨论了结构化对象,例如嵌套对象和数组,[...] JavaScript 值(字符串、数字、日期等)和 [...] 某些数据对象,例如 File Blob、FileList , 和 ArrayBuffer 对象大多数浏览器只允许字符串(当然包括 JSON)。在MDNdev.opera上阅读更多内容。但是我很确定不可能发送函数对象,至少不能作为保留其范围的闭包。

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

于 2012-06-12T23:31:17.830 回答
2

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

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

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

并从框架页面中调用您的函数:

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

这可能不方便,但它有效。

于 2012-06-17T13:28:43.850 回答
1

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

本质上,您所做的是对函数进行编组,以便将其发送到 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();
}

家长:

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你就可以像使用普通函数一样使用它。您可以将它分配给一个变量,传递它,使用它,call它等等。applybind

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

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

于 2012-06-18T05:36:56.180 回答
0

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

于 2016-03-29T07:37:01.330 回答