Как выполнить функцию через 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.