Как общаться между iframe и самой страницей?

👨‍💻 Frontend Developer 🟠 Может встретиться 🎚️ Средний
#JavaScript #Browser #Security

Краткий ответ

Есть два варианта:

  • Один и тот же origin: прямой доступ (parent, top, iframe.contentWindow).
  • Разные origin: только window.postMessage + обработка события message.

Минимальные примеры:

// Родитель → iframe (разные origin)
iframeEl.contentWindow.postMessage({ type: 'PING' }, 'https://child.example');
// iframe → родитель
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://parent.example') return;
  if (e.data?.type === 'PING') e.source?.postMessage({ type: 'PONG' }, e.origin);
});
// Один и тот же origin: из iframe
parent.doSomething?.();

— Всегда проверяйте event.origin и ставьте точный targetOrigin (не *).

Полный ответ

Модель взаимодействия

  • Один и тот же origin:

    • Доступ к родителю из iframe: parent/top.
    • Доступ к окну iframe из родителя: iframeEl.contentWindow.
    • Удобно вызывать заранее согласованные методы, без доступа к DOM.
  • Разные origin:

    • Используем postMessage и событие message.
    • Обязательно указываем точный targetOrigin и валидируем event.origin.
// Родитель → iframe
iframeEl.addEventListener('load', () => {
  iframeEl.contentWindow.postMessage({ type: 'INIT' }, 'https://child.example');
});
// iframe: приём
window.addEventListener('message', (e) => {
  if (e.origin !== 'https://parent.example') return;
  if (e.data?.type === 'INIT') e.source?.postMessage({ ok: true }, e.origin);
});

Безопасность и надёжность

  • Валидируйте event.origin, тип и схему данных.
  • Не используйте targetOrigin: "*", указывайте точный origin.
  • Ставьте обработчики и снимайте их при уничтожении компонента.
  • Соглашайте простой протокол сообщений: type, payload, requestId.
<!-- Ограничения iframe -->
<iframe src="https://child.example"
        sandbox="allow-scripts allow-same-origin"
        referrerpolicy="no-referrer"></iframe>

Дополнительно: MessageChannel

Для двунаправленного канала с портами:

const { port1, port2 } = new MessageChannel();
iframeEl.contentWindow.postMessage({ type: 'CHANNEL' }, 'https://child.example', [port2]);
port1.onmessage = (e) => { /* обработка */ };

Частые ошибки

  • Сообщение отправляется до load iframe.
  • Нет проверки origin или используется *.
  • Большие сообщения и сложные объекты (держите payload компактным).
  • Случайный доступ к DOM через cross-origin (недоступно, используйте postMessage).

Резюме

  • Один и тот же origin → прямой вызов методов между окнами.
  • Разные origin → postMessage + строгая проверка origin и протокола.

Дополнительные каналы (same-origin)

  • window.parent/window.frames:
// Прямой доступ
parent.doSomething?.(); // из&nbsp;iframe
iframeEl.contentWindow.doTask?.(); // из&nbsp;родителя
  • localStorage + storage:
// Отправка
localStorage.setItem('command', 'REFRESH');
// Приём в&nbsp;другом окне/iframe того&nbsp;же origin
window.addEventListener('storage', (e) => { if (e.key === 'command') {/* ... */} });
  • BroadcastChannel:
const channel = new BroadcastChannel('ui-sync');
channel.onmessage = (e) => {/* ... */};
channel.postMessage({ type: 'READY' });

Между вкладками и popup

  • Используйте BroadcastChannel или localStorage+storage.
  • Для разных origin — только postMessage.

Безопасность (напоминание)

  • Проверяйте event.origin, валидируйте type/payload.
  • Указывайте точный targetOrigin (не *).
  • Ограничайте iframe sandbox и права.