javascript:监听来自特定 iframe 的 postMessage 事件

问题描述 投票:0回答:8

我在一个页面中有多个 iframe。现在,我的页面有一个

message
事件监听器,它从所有 iframe 获取消息。我有一个解决方法可以知道消息来自哪个 iframe。

我想分别为每个 iframe 制作事件侦听器。这可能吗?

javascript html iframe postmessage
8个回答
106
投票

您必须监听

message
对象的全局
window
事件,但您可以使用
source
MessageEvent
属性过滤源 iframe。

示例:

const childWindow = document.getElementById('test-frame').contentWindow;
window.addEventListener('message', message => {
    if (message.source !== childWindow) {
        return; // Skip message in this event listener
    }

    // ...
});

11
投票

如果每个 iframe 的

src
属性是唯一的,那么你可以尝试以下操作:

对于孩子:

function sendHeight() {
  // sends height to parent iframe
  var height = $('#app').height();
  window.parent.postMessage({
    'height': height,
    'location': window.location.href
  }, "*");
}

$(window).on('resize', function() {
  sendHeight();
}).resize();

在家长身上:

$(window).on("message", function(e) {
    var data = e.originalEvent.data;
    $('iframe[src^="' + data.location + '"]').css('height', data.height + 'px');
});

子级使用

postMessage()
将其高度和 URL 发送到 iframe 父级。然后父级监听该事件,使用该 URL 获取 iframe 并为其设置高度。


9
投票

其实你可以。为每个 iframe 添加唯一的名称属性。 iframe 名称被传递到 contentWindow。所以 iframe 里面的 window.name 是 iframe 的名称,你可以轻松地在 post 消息中发送它。


4
投票

您可以使用

e.originalEvent.origin
来识别 iframe。

在 iframe 子节点上:

window.parent.postMessage({
  'msg': 'works!'
}, "*");

在 iframe 父级上:

Javascript

window.addEventListener('message', function(e) {
  console.log(e.origin); // outputs "http://www.example.com/"
  console.log(e.data.msg); // outputs "works!"
  if (e.origin === 'https://example1.com') {
    // do something
  } else if (e.origin === 'https://example2.com'){
    // do something else
  }
}, false);

jQuery

$(window).on('message', function(e) {
  ...
}, false);

因此

origin
包含发射
postMessage()
的协议和域。它不包括 URI。该技术假设所有 iframe 都有一个唯一的域。


1
投票

不,这是不可能的。您能做的最好的事情就是拥有一个处理程序,根据消息发送者的来源将收到的消息路由到帮助处理程序。


1
投票

检测消息来源的一种方法是检查哪个 iframe 获得焦点,或者对于我的特定场景,哪个 iframe 可见。


0
投票

我实现了一个 iframe 代理。 iframe 与 iframe 中(嵌套它们)。 每个 iFrame 代理都会创建自己独特的 Id。 从子 iframe 发送到父级的每条消息都会获得 iframe 代理的添加字段。 然后,在父级中,您将来自 iframeproxy 的每条消息路由到其专用处理程序。 这个机制完美的分离了iframe


0
投票

我想分别为每个 iframe 制作事件侦听器。这可能吗?

如果您愿意为每个框架提供专用的MessageChannel

,这是
很有可能

如果您的代码库开始变得更加复杂,并且性能稍微提高,那么这样做还将使您的程序不易受到某些 CSRF 攻击,因为您只需将主要的真实性检查放在 1 个位置(确保通道是安全建立的),而不必将主要的真实性检查编织到子进程的通用堡垒消息处理程序中。

async function openSecureChannel_a(exactWindow) {
    const { port1, port2 } = new MessageChannel();
    const { port1: meta_port1, port2: meta_port2 } = new MessageChannel();
    await new Promise((resolve, reject) => {
        meta_port1.onmessage = ((event) => {
            switch (event.data.command) {
                case 'ok':
                    resolve();
                    break;
                default:
                    reject(new Error('Unexpected response.', { cause: event.data }));
                    break;
            }
        });
        exactWindow.postMessage({
            command: 'openSecureChannel',
            port: port2,
            meta_port: meta_port2
        }, { transfer: [port2, meta_port2], targetOrigin: '*' });
        setTimeout(() => reject(new DOMException('The operation timed out.', 'TimeoutError')), 2718.2818284590453);
    });
    return port1;
}


async function openSecureChannel_b(expectedOrigin) {
    if (expectedOrigin === undefined)
        expectedOrigin = location.origin;
    return await new Promise((resolve, reject) => {
        window.addEventListener('message', ((event) => {
            if (event.origin !== expectedOrigin && expectedOrigin !== '*')
                return;
            switch (event.data.command) {
                case 'openSecureChannel':
                    if (event.data.port instanceof MessagePort) {
                        event.data.meta_port.postMessage({
                            command: 'ok'
                        });
                        resolve(event.data.port);
                    } else {
                        event.data.meta_port.postMessage({
                            command: 'error',
                            message: `Expected type MessagePort, got type ${Object.getPrototypeOf(event.data.port).constructor.name}.`
                        });
                    }
            }
        }));
        setTimeout(() => reject(new DOMException('The operation timed out.', 'TimeoutError')), 2718.2818284590453);
    });
}
<p>Demo:</p>

<script type="module">
let frame = document.createElement('iframe');
frame.src = `data:text/html,%3Cscript type="module"%3E
${openSecureChannel_b.toString().trim()};
const securePort_inIFrame = await openSecureChannel_b();
securePort_inIFrame.onmessage = ((event) => {
    const p = document.createElement('p');
    const s = \`Got message from parent: \$\{JSON.stringify(event.data)\}\`;
    p.appendChild(document.createTextNode(s));
    document.body.appendChild(p);
});
securePort_inIFrame.postMessage("Oh, by the way, these channels are bidirectional!");

%3C/script%3E`;
document.body.appendChild(frame);
await new Promise((resolve) => frame.addEventListener('load', () => resolve(), { once: true }));
const securePort_inParent = await openSecureChannel_a(frame.contentWindow);
securePort_inParent.onmessage = ((event) => {
    const p = document.createElement('p');
    const s = `Got message from child: ${JSON.stringify(event.data)}`;
    p.appendChild(document.createTextNode(s));
    document.body.appendChild(p);
});
securePort_inParent.postMessage("HELLO FROM THE PARENT!");
securePort_inParent.postMessage("This messagePort can be re-used!");
</script>

© www.soinside.com 2019 - 2024. All rights reserved.