PostMessage: примеры (JAVASCRIPT)

Руководство по postMessage для обмена данными между окнами
Раздел: Web Workers, Коммуникация
postMessage(message (any), targetOrigin (string), transfer (Array)): void

Функция postMessage: основы

Метод window.postMessage() обеспечивает безопасный механизм асинхронного обмена данными между окнами, фреймами или воркерами, имеющими разное происхождение (origin). Он используется, когда необходимо организовать взаимодействие между документами, загруженными с разных доменов, протоколов или портов, что запрещено политикой одинакового происхождения (Same-Origin Policy).

Синтаксис метода: targetWindow.postMessage(message, targetOrigin, [transfer]).

  • message: Данные для отправки. Они сериализуются с использованием алгоритма «структурированного клонирования». Можно передавать разнообразные объекты: строки, числа, объекты, массивы, но не функции или DOM-элементы.
  • targetOrigin: Строка, указывающая источник целевого окна. Сообщение будет доставлено только если источник окна-получателя совпадает с указанным. Можно использовать "*" для отправки любому источнику или конкретный origin (например, "https://example.com").
  • transfer (необязательный): Массив передаваемых объектов (например, MessagePort, ArrayBuffer), права собственности на которые переходят к целевому контексту. После передачи отправитель больше не может их использовать.

Метод не возвращает значения. Ответная связь организуется через обработчик события message у окна-получателя. Событие содержит поля: data (полученное сообщение), origin (источник отправителя), source (ссылка на объект window отправителя).

Простые примеры использования

Отправка сообщения из iframe в родительское окно:

// Код внутри iframe
window.parent.postMessage('Привет, родитель!', 'https://parent-domain.com');
// В родительском окне срабатывает событие 'message' с data = 'Привет, родитель!'

Отправка объекта с указанием конкретного origin:

// Окно-отправитель
const data = { user: 'Иван', action: 'update' };
childWindow.postMessage(data, 'https://trusted-child.com');
// Окно 'childWindow' получит объект { user: 'Иван', action: 'update' }

Использование transfer для передачи ArrayBuffer:

// Создание и передача буфера
let buffer = new ArrayBuffer(16);
worker.postMessage(buffer, [buffer]);
console.log(buffer.byteLength); // 0
// Буфер передан воркеру, в исходном контексте он очищен.

Альтернативы в JavaScript

  • CustomEvent + dispatchEvent: Используется для передачи событий внутри одного окна или между компонентами одного origin. Не подходит для кросс-доменного взаимодействия.
  • WebSockets (WebSocket API): Обеспечивают двустороннюю связь по сети. Применяются для реального времени, но требуют серверной части и более сложны в настройке.
  • MessageChannel и MessagePort: Создают прямой канал связи между двумя контекстами. Удобны для организации прямого диалога, например, между основным потоком и iframe или воркером.
  • Broadcast Channel API: Позволяет отправлять сообщения всем контекстам (окнам, воркерам) одного origin. Проще для широковещательной рассылки, чем множественные вызовы postMessage.
  • SharedWorker: Общий воркер, к которому могут подключаться несколько контекстов одного origin для обмена данными.

Метод postMessage предпочтителен для безопасного кросс-доменного взаимодействия, особенно с iframe и popup-окнами.

Аналоги в других языках

Python (multiprocessing / asyncio): Для межпроцессного взаимодействия (IPC) используются очереди (Queue), каналы (Pipe) или разделяемая память.

# Пример с multiprocessing.Queue
from multiprocessing import Process, Queue
def worker(q):
    q.put(['data', 42])
if __name__ == '__main__':
    q = Queue()
    p = Process(target=worker, args=(q,))
    p.start()
    print(q.get())  # ['data', 42]
    p.join()
# Результат: ['data', 42]

PHP: Нет прямой аналогии в контексте веб-скриптов. Для связи между процессами используются расширения PCNTL, SysV IPC или очереди сообщений.

C: Межпроцессное взаимодействие организуется через системные вызовы: каналы (pipe), очереди сообщений (msgget, msgsnd), сокеты или разделяемую память (shmget).

// Упрощенный пример сокетов (TCP)
// Сервер принимает сообщение
char buffer[256];
read(newsockfd, buffer, 255);
printf("Получено: %s\n", buffer);
// Результат зависит от отправленных данных.

Ключевое отличие JavaScript postMessage - его специализация на безопасной коммуникации между контекстами браузера с разными источниками, что не является типичной задачей для серверных языков.

Типичные ошибки

1. Отсутствие проверки origin в обработчике. Это критическая уязвимость безопасности.

// ОШИБКА: Небезопасный обработчик
window.addEventListener('message', (event) => {
    document.getElementById('content').innerHTML = event.data; // Уязвимость!
});
// Любой сайт во вкладке может внедрить произвольный HTML.
// ПРАВИЛЬНО: Всегда проверять origin
window.addEventListener('message', (event) => {
    if (event.origin !== 'https://trusted-site.com') return;
    // Обработка данных
});

2. Использование '*' для targetOrigin в производственной среде. Это допустимо только для отладки или закрытых систем.

3. Отправка конфиденциальных данных до того, как целевое окно будет готово. Нужно дождаться события load или обменяться служебными сообщениями о готовности.

4. Циклическая передача данных. Если два окна отвечают друг другу автоматически без условий, это приводит к бесконечному циклу.

Изменения в спецификации

С момента введения в HTML5, спецификация метода postMessage оставалась относительно стабильной. Основные уточнения касались алгоритма структурированного клонирования, который был расширен для поддержки большего количества типов объектов (например, File, Blob, FileList, ImageData).

Важным дополнением стал необязательный третий аргумент transfer для эффективной передачи владения объектами, поддерживающими transferable interface (например, ArrayBuffer, MessagePort).

Также были уточнены правила обработки события message для различных контекстов исполнения (окна, воркеры, SharedWorker). Сейчас метод является частью Living Standard HTML.

Расширенные примеры

Организация двустороннего канала связи между iframe и родительским окном с проверкой origin и обработкой ошибок:

Пример javascript
// Родительское окно
const iframe = document.getElementById('myFrame');
const channel = {}

// Отправка команды в iframe
function sendToFrame(cmd) {
    iframe.contentWindow.postMessage({ command: cmd }, 'https://iframe-domain.com');
}

// Прием и ответ
window.addEventListener('message', (event) => {
    if (event.origin !== 'https://iframe-domain.com') return;
    if (event.data.status === 'ready') {
        channel.frameReady = true;
        sendToFrame('loadData');
    }
    if (event.data.result) {
        console.log('Данные получены:', event.data.result);
    }
});
// В iframe будет получена команда 'loadData', после обработки может быть отправлен ответ.

Использование с Service Worker для передачи данных между вкладками:

Пример javascript
// Скрипт страницы
if (navigator.serviceWorker.controller) {
    navigator.serviceWorker.controller.postMessage({
        type: 'SYNC_TABS',
        payload: { tabId: 1 }
    });
}

// В Service Worker (sw.js)
self.addEventListener('message', (event) => {
    if (event.data.type === 'SYNC_TABS') {
        // Рассылка другим клиентам
        self.clients.matchAll().then(clients => {
            clients.forEach(client => {
                if (client.id !== event.source.id) {
                    client.postMessage(event.data);
                }
            });
        });
    }
});
// Все другие вкладки, контролируемые этим Service Worker, получат сообщение.

Передача экземпляра MessagePort для создания прямого частного канала:

Пример javascript
// Создание канала
const channel = new MessageChannel();

// Отправка порта во воркер через postMessage
worker.postMessage(
    { type: 'SETUP_PORT', port: channel.port1 },
    [channel.port1]
);

// Использование порта port2 для отправки сообщений
channel.port2.onmessage = (event) => {
    console.log('От воркера:', event.data);
};
channel.port2.postMessage('Начало работы');
// Воркер получит порт и сможет установить прямую связь через него.

JS postMessage function comments

En
PostMessage Sends a message to the worker or main thread