javascript 如何通过 postMessage 执行函数

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11005223/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-26 11:46:50  来源:igfitidea点击:

How to execute a function through postMessage

javascriptjquerypostmessage

提问by Alex

I have this code inside a iframe:

我在 iframe 中有此代码:

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

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

}, false);

and this inside the parent document:

这在父文档中:

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

So the parent document sends a "test" message to the iframe and it works.

所以父文档向 iframe 发送一个“测试”消息并且它可以工作。

But how can I define a function in the parent document, and somehow send this function through postMessage to the iframe, which will execute the function locally?

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

The function does some changes to the document like this:

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

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

(#some_divexists in the iframe, not the parent document)

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

回答by o.v.

There's nothing that would prevent you from passing a stringified function as postmessage event data. Implementation is trivial, for any function declaration like

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

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

You could encodeURIits string interpretation:

您可以encodeURI对其字符串解释:

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

It can then be executed as part of a closure - something not overly imaginative like

然后它可以作为闭包的一部分执行 - 一些不太富有想象力的东西,比如

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

There's a fiddle'd proof of conceptwithoutthe cross-frame architecture - I'll see if I can put together a postMessage version, but it would be non-trivial to host w/jsfiddle

有一个没有跨框架架构的小提琴概念证明- 我会看看我是否可以将 postMessage 版本放在一起,但是托管 w/jsfiddle 是很重要的

Update

更新

As promised, a full mockup that works (links below). With correct event.originchecks this would be sufficiently inpenetrable, but I know for the fact that our security team would never let evalinto production like this :)

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

Given the option I'd suggest the functionality be normalized across the two pages so that only a parametric message would need to be passed (i.e. pass arguments not functions); however there are definitely a few scenarios where this is a preferred approach.

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

Parent code:

父代码:

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

框架代码:

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!");
}

Prototype mockup: parent codeand iframe code.

原型模型:父代码iframe 代码

回答by Bergi

You can't really. Although the (draft) spec for postMessagetalks about structured objects, e.g. nested objects and arrays, [...] JavaScript values (strings, numbers, Dates, etc) and [...] certain data objects such as File Blob, FileList, and ArrayBuffer objectsmost browsers only allow strings (including JSON, of course). Read more at MDNor dev.opera. Yet I'm quite sure that it won't be possible to send function objects, at least not as closures preserving their scope.

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

So you'll end in stringifying the function and eval()it in the iframe, if you really want to execute some code from the parent window. However, I can see no reason for any application to allow evaluation of arbitrary code (even if from registered domains); it would be better to build an message API which can receive (JSON-)string commands and invoke its own methods.

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

回答by Zbigniew

In this case I'd try different approach. Why? Bergi already explained why it won't work the way you want it.

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

You can define (and redefine your functions) in parent page:

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

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

and call your function from within frame-page:

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

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

It may be inconvenient, but it works.

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

回答by Aadit M Shah

Expanding upon the following questionyou can stringify a function, use postMessageto send the function body over, and then use evalto execute it.

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

Essentially what you are doing is marshalling the function so that it can be sent to the iframe and then unmarshalling it on the other end. To do so use the following code:

本质上,您正在做的是对函数进行编组,以便将其发送到 iframe,然后在另一端对其进行解组。为此,请使用以下代码:

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();
}

Parent:

家长:

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");
}

Once you get the function body in your iframe and evalit you can use it like a normal function. You can assign it to a variable, pass it around, use callor applyon it, bindit, and so on.

一旦你在你的 iframe 中获得了函数体,你eval就可以像普通函数一样使用它。您可以将它分配给一个变量、传递它、使用callapply使用它、bind它等等。

Note however that the scope of the function is dynamic (i.e. any non-local variables in the function must be already defined in your iframe window).

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

In your case the jquery $variable which you are using in your function is non-local. Hence the iframe must already have jquery loaded. It won't use the jquery $variable from the parent window.

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

回答by Aiphee

You can always send postMessageback to the iframe and let iframe handle the message. Of course you must count that it wont get executed before next command in iframe.

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