Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
168 lines (109 sloc) 9.82 KB

Общение между окнами и фреймами

Элемент iframe является обычным узлом DOM, как и любой другой. Существенное отличие -- в том, что с ним связан объект window внутреннего окна. Он доступен по ссылке iframe.contentWindow.

Таким образом, iframe.contentWindow.document будет внутренним документом, iframe.contentWindow.document.body -- его <body> и так далее.

В старых браузерах использовались другие свойства, такие как `iframe.contentDocument` и даже `iframe.document`, но они давно не нужны.

Переход внутрь ифрейма

В примере ниже JavaScript получает документ внутри ифрейма и модифицирует его:

<iframe src="javascript:'тест'" style="height:60px"></iframe>

<script>
  var iframe = document.getElementsByTagName('iframe')[0];

  var iframeDoc = iframe.contentWindow.document;

  if (iframeDoc.readyState == 'complete') {
    iframeDoc.body.style.backgroundColor = 'green';
  }
  iframe.onload = function() {
    var iframeDoc2 = iframe.contentWindow.document;
    iframeDoc2.body.style.backgroundColor = 'orange';
  }
</script>
Атрибут `src` может использовать протокол `javascript`, как указано выше: `src="javascript:код"`. При этом код выполняется и его результат будет содержимым ифрейма. Этот способ описан в стандарте и поддерживается всеми браузерами.

Атрибут `src` является обязательным, и его отсутствие может привести к проблемам, вплоть до игнорирования ифрейма браузером. Чтобы ничего не загружать в ифрейм, можно указать пустую строку: `src="javascript:''"` или специальную страницу: `src="about:blank"`.

В некоторых браузерах (Chrome) пример выше покажет iframe зелёным. А в некоторых (Firefox) -- оранжевым.

Дело в том, что, когда iframe только создан, документ в нём обычно ещё не загружен.

При обычных значениях iframe src="...", которые указывают на HTML-страницу (даже если она уже в кеше), это всегда так. Документ, который в iframe на момент срабатывания скрипта iframeDoc -- временный, он будет заменён на новый очень скоро. И работать надо уже с новым документом iframeDoc2 -- например, по событию iframe.onload.

В случае с javascript-протоколом, по идее, ифрейм уже загружен, и тогда onload у него уже не будет. Но здесь мнения браузеров расходятся, некоторые (Firefox) всё равно "подгрузят" документ позже. Поэтому факт "готовности" документа в скрипте проверяется через iframeDoc.readyState.

Ещё раз заметим, что при обычных URL в качестве src нужно работать не с начальным документом, а с тем, который появится позже.

Кросс-доменность: ограничение доступа к окну

Элемент <iframe> является "двуличным". С одной стороны, это обычный узел DOM, с другой -- внутри находится окно, которое может иметь совершенно другой URL, содержать независимый документ из другого источника.

Внешний документ имеет полный доступ к <iframe> как к DOM-узлу. А вот к окну -- если они с одного источника.

Это приводит к забавным последствиям. Например, чтобы узнать об окончании загрузки <iframe>, мы можем повесить обработчик iframe.onload. По сути, это то же самое что iframe.contentWindow.onload, но его мы можем поставить лишь в случае, если окно с того же источника.

<iframe src="https://example.com" style="height:100px"></iframe>

<script>
  var iframe = document.getElementsByTagName('iframe')[0];

  // сработает
  iframe.onload = function() {
    alert( "iframe onload" );
  };

  // не сработает
  iframe.contentWindow.onload = function() {
    alert( "contentWindow onload" );
  };
</script>

Если бы в примере выше <iframe src> был с текущего сайта, то оба обработчика сработали бы.

Иерархия window.frames

Альтернативный способ доступа к окну ифрейма -- это получить его из коллекции window.frames.

Есть два способа доступа:

  1. window.frames[0] -- доступ по номеру.
  2. window.frames.iframeName -- доступ по name ифрейма.

Обратим внимание: в коллекции хранится именно окно (contentWindow), а не DOM-элемент.

Демонстрация всех способов доступа к окну:

<iframe src="javascript:''" style="height:80px" name="i"></iframe>

<script>
  var iframeTag = document.body.children[0];

  var iframeWindow = iframeTag.contentWindow; // окно из тега

  alert( frames[0] === iframeWindow ); // true, окно из коллекции frames
  alert( frames.i == iframeWindow ); // true, окно из frames по имени
</script>

Внутри ифрейма могут быть свои вложенные ифреймы. Всё это вместе образует иерархию.

Ссылки для навигации по ней:

  • window.frames -- коллекция "детей" (вложенных ифреймов)

  • window.parent -- содержит ссылку на родительское окно, позволяет обратиться к нему из ифрейма.

    Всегда верно:

    // (из окна со фреймом)
    window.frames[0].parent === window; // true
  • window.top -- содержит ссылку на самое верхнее окно (вершину иерархии).

    Всегда верно (в предположении, что вложенные фреймы существуют):

    window.frames[0].frames[0].frames[0].top === window

Свойство top позволяет легко проверить, во фрейме ли находится текущий документ:

if (window == top) {
  alert( 'Этот скрипт является окном верхнего уровня в браузере' );
} else {
  alert( 'Этот скрипт исполняется во фрейме!' );
}

Песочница sandbox

Атрибут sandbox позволяет построить "песочницу" вокруг ифрейма, запретив ему выполнять ряд действий.

Наличие атрибута sandbox:

  • Заставляет браузер считать ифрейм загруженным с другого источника, так что он и внешнее окно больше не могут обращаться к переменным друг друга.
  • Отключает формы и скрипты в ифрейме.
  • Запрещает менять parent.location из ифрейма.

Пример ниже загружает в <iframe sandbox> документ с JavaScript и формой. Ни то ни другое не сработает:

[codetabs src="sandbox"]

Если у атрибута sandbox нет значения, то браузер применяет максимум ограничений.

Атрибут sandbox может содержать через пробел список ограничений, которые не нужны:

allow-same-origin : Браузер будет считать документ в ифрейме пришедшим с другого домена и накладывать соответствующие ограничения на работу с ним. Если ифрейм и так с другого домена, то ничего не меняется.

allow-top-navigation : Разрешает ифрейму менять parent.location.

allow-forms : Разрешает отправлять формы из iframe.

allow-scripts : Разрешает выполнение скриптов из ифрейма. Но скриптам, всё же, будет запрещено открывать попапы.

Цель атрибута `sandbox` -- наложить дополнительные ограничения. Он не может снять уже существующие, в частности, убрать ограничения безопасности, если ифрейм с другого источника.