Вступление
Обмен веб-сообщениями — это способ обмена данными между документами в разных контекстах просмотра, при этом DOM не подвергается вредоносным сценариям разных источников. В отличие от других форм межсайтового взаимодействия (междоменный XMLHttpRequest или динамическая вставка сценария), веб-обмен сообщениями никогда не подвергает DOM непосредственному воздействию.
Например, вы хотите отправить данные со своей страницы в объявление, содержащееся в iframe, размещенном на стороннем сервере. Если родительский документ пытается прочитать переменную внутри iframe или наоборот, браузеры сгенерируют исключение безопасности. С помощью веб-сообщений мы можем обойти это, передав эти данные как событие сообщения.
Первоначально Автор Тиффани Браун
Когда мы говорим о веб-обмене сообщениями, мы на самом деле говорим о двух немного разных системах: обмен сообщениями между документами и обмен сообщениями по каналам . Обмен сообщениями между документами часто называют его синтаксисом как window.postMessage (), а обмен сообщениями по каналам также называют MessageChannel. Наряду с отправляемыми сервером событиями и веб-сокетами , обмен документами и сообщениями между каналами являются важной частью «набора» коммуникационных интерфейсов HTML5.
Веб-сообщения поддерживаются Opera, Chrome и Safari, хотя Safari ≤ 5.1.2 содержит ошибку . Internet Explorer 8+ частично поддерживает обмен сообщениями между документами: в настоящее время он работает с фреймами, но не с новыми окнами. Internet Explorer 10, однако, будет поддерживать MessageChannel. В настоящее время Firefox поддерживает обмен сообщениями между документами, но не MessageChannel.
Сообщения о событиях
Прежде чем мы перейдем к мелочам веб-сообщений, давайте взглянем на объект события сообщения. Перекрестный обмен документами, обмен сообщениями по каналам, отправленные сервером события и веб-сокеты — все это инициирует события сообщений, поэтому понимание этого полезно. События сообщений, определенные интерфейсом MessageEvent , содержат пять атрибутов только для чтения:
-
dataСодержит произвольную строку данных, отправленных исходным скриптом.
-
originСтрока, содержащая схему исходного документа, имя домена и порт (например:
https: //domain.example: 80 )
-
lastEventIdСтрока, содержащая уникальный идентификатор для текущего события сообщения.
-
источникСсылка на окно исходного документа. Точнее, это
объект WindowProxy .
-
портыМассив, содержащий любые
объекты MessagePort, отправленные с сообщением.
В случае событий обмена сообщениями между документами и обмена сообщениями в канале значение lastEventId всегда является пустой строкой; lastEventId применяется к отправленным сервером событиям. Если с сообщением не отправлено ни одного порта, значением атрибута ports будет массив, длина которого равна нулю.
MessageEvent наследует от интерфейса событий DOM и разделяет его свойства. Однако события сообщений не всплывают, не могут быть отменены и не имеют действия по умолчанию.
Обмен документами между документами
Теперь мы рассмотрели события сообщений. Давайте продолжим, разбираясь с междисциплинарными сообщениями.
Отправка кросс-документа сообщения
Чтобы отправить кросс-документное сообщение, нам нужно создать новый контекст просмотра — либо создав новое окно, либо сославшись на iframe. Затем мы можем отправить ему сообщение с помощью метода postMessage (). Для обмена сообщениями между документами postMessage () требует двух аргументов.
- сообщение : сообщение для отправки.
- targetOrigin : источник, на который будет отправлено сообщение.
Необязательный третий аргумент, передача , используются для канала передачи сообщений , которые мы dicuss позже в этой статье.
Параметр сообщения не ограничивается строками. Структурированные объекты, объекты данных (такие как File и ArrayBuffer) или массивы также могут быть отправлены в виде сообщений. Имейте в виду, однако, что Internet Explorer 8 и 9 и Firefox версии 3.6 и ниже поддерживают только строки.
TargetOrigin является источником получения документа. Браузеры не будут отправлять сообщение, если источник получающего контекста просмотра не совпадает с тем, который указан в targetOrigin. Вы можете обойти это ограничение, используя символ подстановки *. Однако это может привести к утечке информации, поэтому лучше установить конкретный целевой источник.
Вы также можете ограничить отправку сообщений одним и тем же источником, установив аргумент targetOrigin в /. Однако на момент публикации только Opera поддерживает это.
В приведенном ниже примере мы отправим сообщение из родительского документа в документ, содержащийся в iframe. Несмотря на то, что наши документы имеют одинаковое происхождение, для кросс-браузерной совместимости мы установим значение targetOrigin на http://dev.opera.com вместо /. Если бы наш документ находился в другом домене, мы могли бы отправить сообщение, используя его источник в качестве цели.
Обратите внимание, что в источниках нет косой черты.
var iframe = document.querySelector('iframe'); var button = document.querySelector('button'); var clickHandler = function(){ // iframe.contentWindow refers to the iframe's window object. iframe.contentWindow.postMessage('The message to send.','http://dev.opera.com'); } button.addEventListener('click',clickHandler,false);
Получение кросс-документа сообщения
Конечно, отправка события — это только половина процесса. Нам также необходимо обработать эти события в принимающем документе. Как обсуждалось выше, каждый раз, когда вызывается postMessage (), в принимающем документе отправляется событие сообщения .
Затем мы можем прослушать событие сообщения, как показано ниже:
var messageEventHandler = function(event){ // check that the origin is one we want. if(event.origin == 'http://dev.opera.com'){ alert(event.data); } } window.addEventListener('message', messageEventHandler,false);
Посмотрите рабочий пример отправки и получения сообщений в этой демонстрации обмена сообщениями между документами .
Обнаружение, когда готовый документ готов
В приведенных выше примерах window.postMessage () вызывается внутри обработчика событий, который требует взаимодействия с пользователем. Для простой демонстрации это нормально. Однако лучший способ справиться с этим в реальном мире — убедиться, что сценарии в целевом контексте просмотра успели настроить прослушиватели и что они готовы получать наши сообщения. Чтобы проверить это, мы можем отправить событие сообщения нашему родительскому документу при загрузке нового документа.
Давайте посмотрим на пример кода. В этом примере мы собираемся открыть новое окно. Когда документ в этом окне загружается, он публикует сообщение в открывшемся окне. Предположим также, что в нашей разметке есть элемент button, который позволяет открывать новое окно.
var clickHandler, messageHandler, button; button = document.querySelector('button'); clickHandler = function(){ window.open('otherpage.html','newwin','width=500,height=500'); } button.addEventListener('click',clickHandler,false); messageHandler = function(event){ if(event.origin == 'http://foo.example'){ event.source.postMessage('This is the message.','http://foo.example'); } } window.addEventListener('message',messageHandler, false);
Когда наша кнопка нажата, функция clickHandler откроет новое окно, и наш прослушиватель дефлектора функции messageHandler прослушает сообщение из открытого окна. Обратите внимание, что event.source — это объект WindowProxy, который представляет наше открытое окно.
В нашем открытом окне мы будем прослушивать событие DOMContentLoaded — см. Ниже. Когда он запущен, он будет использовать window.postMessage (), чтобы «уведомить» открывающий документ о том, что он готов к получению сообщений (см. Демонстрацию уведомления window.postMessage () ).
var loadHandler = function(event){ event.currentTarget.opener.postMessage('ready','http://foo.example'); } window.addEventListener('DOMContentLoaded', loadHandler, false);
Канал обмена сообщениями
Канальный обмен сообщениями обеспечивает прямую двустороннюю связь между контекстами просмотра. Как и при обмене сообщениями между документами, DOM не предоставляется напрямую. Вместо этого на каждом конце нашей трубы есть порт ; данные, отправленные с одного порта, становятся входными данными для другого (и наоборот).
Обмен сообщениями по каналам особенно полезен для связи между несколькими источниками. Рассмотрим следующий сценарий. У нас есть документ по адресу http: //socialsite.example, содержащий контент из http: //games.example, встроенный в один iframe, и контент из http: //addressbook.example в другой.
Теперь предположим, что мы хотим отправить сообщение с сайта нашей адресной книги на наш игровой сайт. Мы могли бы использовать социальный сайт в качестве прокси. Это, однако, означает, что адресная книга получает тот же уровень доверия, что и социальный сайт. Наш социальный сайт должен либо доверять каждому запросу, либо фильтровать их для нас.
Однако при обмене сообщениями по каналам http: //addressbook.example и http: //games.example могут взаимодействовать напрямую.
Объекты MessageChannel и MessagePort
Когда мы создаем объект MessageChannel, мы действительно создаем два взаимосвязанных порта. Один порт остается открытым на нашей отправляющей стороне. Другой пересылается в другой контекст просмотра.
Каждый порт является объектом MessagePort с тремя доступными методами.
- postMessage (): отправляет сообщение через канал.
- start (): Начинает отправку сообщений, полученных по порту.
- close (): закрывает и деактивирует порт.
Объекты MessagePort также имеют атрибут события onmessage, который можно использовать для определения функции обработчика событий вместо добавления прослушивателя событий.
Отправка портов и сообщений
Давайте рассмотрим пример общения с канальным обменом сообщениями. Мы будем использовать сценарий, аналогичный описанному выше: документ, содержащий два фрейма. Мы будем отправлять сообщения от одного iframe другому, используя объект MessageChannel и порты.
Все документы в приведенных выше примерах имеют одинаковое происхождение. Однако этот процесс одинаков для связи между источниками.
В нашем первом iframe мы сделаем следующее.
- Создайте новый объект MessageChannel.
- Перенесите один порт MessageChannel в наш родительский документ, где он будет перенаправлен в наш другой iframe.
- Определите прослушиватель событий для нашего оставшегося порта для обработки сообщения, отправленного с другого нашего iframe
- Откройте наш порт, чтобы мы могли получать сообщения.
Мы также обернем все в функцию, которая будет вызвана, когда DOM будет готов.
var loadHandler = function(){ var mc, portMessageHandler; mc = new MessageChannel(); // Send a port to our parent document. window.parent.postMessage('documentAHasLoaded','http://foo.example',[mc.port2]); // Define our message event handler. portMessageHandler = function(portMsgEvent){ alert( portMsgEvent.data ); } // Set up our port event listener. mc.port1.addEventListener('message', portMessageHandler, false); // Open the port mc.port1.start(); } window.addEventListener('DOMContentLoaded', loadHandler, false);
Теперь в нашем родительском документе мы будем слушать это входящее сообщение и связанный порт. Когда он будет получен, мы отправим сообщение нашему второму iframe и перенаправим наш порт с этим сообщением.
var loadHandler = function(){ var iframes, messageHandler; iframes = window.frames; // Define our message handler. messageHandler = function(messageEvent){ if( messageEvent.ports.length > 0 ){ // transfer the port to iframe[1] iframes[1].postMessage('portopen','http://foo.example',messageEvent.ports); } } // Listen for the message from iframe[0] window.addEventListener('message',messageHandler,false); } window.addEventListener('DOMContentLoaded',loadHandler,false);
Наконец, во втором iframe мы можем обработать сообщение из родительского документа и отправить сообщение в порт. Сообщение, отправленное с этого порта, будет обработано функцией portMsgHandler в нашем первом документе.
var loadHandler(){ // Define our message handler function var messageHandler = function(messageEvent){ // Our form submission handler var formHandler = function(){ var msg = 'add <[email protected]> to game circle.'; messageEvent.ports[0].postMessage(msg); } document.forms[0].addEventListener('submit',formHandler,false); } window.addEventListener('message',messageHandler,false); } window.addEventListener('DOMContentLoaded',loadHandler,false);
Приведенные выше примеры обмена сообщениями в канале были немного упрощены для удобства чтения. На практике вы всегда должны проверять, поддерживается ли MessageChannel. Также рекомендуется проверять, было ли ваше сообщение отправлено ожидаемым источником. Мы сделали оба в нашей демонстрации обмена сообщениями канала .
Учить больше
- Спецификация HTML5 Web Messaging от World Wide Web Consortium
- HTML5 Web Messaging : слайды Майка Тейлора на Slideshare
Эта статья распространяется под лицензией Creative Commons Attribution 3.0 Unported .
Фото «Послание в бутылке» любезно предоставлено Серхио Агирре .
Источник: http://dev.opera.com/articles/view/window-postmessage-messagechannel/