Статьи

Учебник по коммуникационным интерфейсам HTML5

Вступление

Обмен веб-сообщениями — это способ обмена данными между документами в разных контекстах просмотра, при этом 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 <foo@example.com> 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. Также рекомендуется проверять, было ли ваше сообщение отправлено ожидаемым источником. Мы сделали оба в нашей демонстрации обмена сообщениями канала .

Учить больше

 

Эта статья распространяется под лицензией Creative Commons Attribution 3.0 Unported .

 
Фото «Послание в бутылке» любезно предоставлено Серхио Агирре .


Источник: http://dev.opera.com/articles/view/window-postmessage-messagechannel/