Как выполнить функцию через postMessage

у меня есть этот код внутри тега 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, который будет выполнять функцию локально?

функция делает некоторые изменения в документе, как это:

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

(#some_div существует в iframe, а не в Родительском документе)

5 ответов


нет ничего, что помешало бы вам передать строковую функцию в качестве данных события postmessage. Реализация тривиальна, для любого объявления функции, например

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 проверки это было бы достаточно inpenetrable, но я знаю, что наша команда безопасности никогда не позволит 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) {}
}

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

макет прототипа: Родительский код и iframe код.


вы не можете на самом деле. Хотя ... --2-->(проект) спец. по метод postMessage говорит о структурированные объекты, например, вложенные объекты и массивы, [...] Значения JavaScript (строки, числа, даты и т. д.) и [...] некоторые объекты данных, такие как File Blob, FileList и ArrayBuffer objects большинство браузеров разрешают только строки (включая JSON, конечно). Подробнее в MDN или dev.опера. Тем не менее, я уверен, что отправить функцию будет невозможно объекты, по крайней мере, не как замыкания, сохраняющие свой объем.

таким образом, вы закончите строку функции и eval() Это в iframe, если вы действительно хотите выполнить некоторый код из родительского окна. Тем не менее, я не вижу причин для любого приложения разрешать оценку произвольного кода (даже если из зарегистрированных доменов); было бы лучше построить API сообщений, который может получать (JSON-)строковые команды и вызывать свои собственные методы.


в этом случае я бы попробовать другой подход. Почему? Берги уже объяснила, почему это не сработает так, как ты хочешь.

вы можете определить (и переопределить свои функции) на родительской странице:

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

Это может быть неудобно, но это работает.


развитии следующий вопрос вы можете stringify функцию, используйте postMessage отправить тело функции, а затем использовать eval, чтобы выполнить его.

по существу, то, что вы делаете, - это маршалинг функции, чтобы ее можно было отправить в 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();
}

родители:

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 или apply на нем bind и так далее.

обратите внимание, однако, что область действия функции является динамической (т. е. любые нелокальные переменные в функции должны быть уже определены в окне iframe).

в вашем случае jquery $ переменная, которую вы используете в своей функции, нелокальна. Следовательно, iframe уже должен иметь jquery нагруженный. Он не будет использовать jquery $ переменная из родительского окна.


вы всегда можете отправить postMessage назад к iframe и пусть iframe обрабатывает сообщение. Конечно, вы должны рассчитывать, что он не будет выполнен до следующей команды в iframe.