renovations
This commit is contained in:
parent
11f2d7352f
commit
bd1d5e4305
23 changed files with 804 additions and 948 deletions
|
@ -4,13 +4,11 @@
|
|||
|
||||
Нужно написать функцию, которая показывает подсказку при *наведении* на элемент, но не при *быстром проходе* над ним.
|
||||
|
||||
То есть, если посетитель именно навёл курсор мыши на элемент и почти остановился -- подсказку показать, а если быстро провёл над ним, то не надо (зачем излишнее мигание?).
|
||||
То есть, если посетитель именно навёл курсор мыши на элемент и почти остановился -- подсказку показать, а если быстро провёл над ним, то не надо, зачем излишнее мигание?
|
||||
|
||||
Технически -- можно измерять скорость движения мыши над элементом, если она маленькая, то считаем, что это "наведение на элемент" (показать подсказку), если большая -- "быстрый проход мимо элемента" (не показывать).
|
||||
|
||||
Задача -- сделать универсальный код, который отслеживает "наведение на элемент".
|
||||
|
||||
Пусть это будет объект `new HoverIntent(options)`, который при создании принимает `options`:
|
||||
Реализуйте это через универсальный объект `new HoverIntent(options)`, с параметрами `options`:
|
||||
<ul>
|
||||
<li>`elem` -- элемент, наведение на который нужно отслеживать.</li>
|
||||
<li>`over` -- функция-обработчик наведения на элемент.</li>
|
||||
|
@ -19,10 +17,6 @@
|
|||
|
||||
Пример использования такого объекта для подсказки:
|
||||
```js
|
||||
function HoverIntent(options) {
|
||||
//... ваш код ...
|
||||
}
|
||||
|
||||
// образец подсказки
|
||||
var tooltip = document.createElement('div');
|
||||
tooltip.className = "tooltip";
|
||||
|
@ -48,6 +42,6 @@ new HoverIntent({
|
|||
|
||||
Если провести мышкой над "часиками" быстро, то ничего не будет, а если медленно или остановиться на них, то появится подсказка.
|
||||
|
||||
Обратите внимание -- подсказка не "мигает"" при проходе мыши внутри "часиков", по подэлементам.
|
||||
Обратите внимание -- подсказка не "мигает" при проходе мыши внутри "часиков", по подэлементам.
|
||||
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
window.open("http://ya.ru");
|
||||
```
|
||||
|
||||
...При запуске откроется новое окно с данным URL.
|
||||
...При запуске откроется новое окно с указанным URL.
|
||||
|
||||
Большинство браузеров по умолчанию создают новую вкладку вместо отдельного окна, но чуть далее мы увидим, что можно и "заказать" именно окно.
|
||||
|
||||
|
@ -25,37 +25,12 @@ window.open("http://ya.ru");
|
|||
|
||||
Как же браузер понимает -- посетитель вызвал открытие окна или нет?
|
||||
|
||||
**Для этого при работе скрипта он хранит внутренний "флаг", который говорит -- инициировал посетитель выполнение или нет.**
|
||||
Для этого при работе скрипта он хранит внутренний "флаг", который говорит -- инициировал посетитель выполнение или нет. Например, при клике на кнопку весь код, который выполнится в результате, включая вложенные вызовы, будет иметь флаг "инициировано посетителем" и попапы при этом разрешены.
|
||||
|
||||
Например, при клике на кнопку весь код, который выполнится в результате, включая вложенные вызовы, будет иметь флаг "инициировано посетителем" и попапы при этом разрешены.
|
||||
А если код был на странице и выполнился автоматически при её загрузке -- у него этого флага не будет. Попапы будут заблокированы.
|
||||
|
||||
А если код был на странице и выполнился автоматически при её загрузке -- у него этого флага не будет. Попапы будут заблокированы..
|
||||
|
||||
Здесь есть ещё ряд тонких моментов. Например: обработчик `onclick` вызвал `setTimeout(func, timeout)`. В результате код `func` выполнится уже после того, как событие `onclick` обработано. Следует ли передать ему флаг "инициировано посетителем" или нет?
|
||||
|
||||
Возможны разные точки зрения, например, IE блокирует такие окна (видимо, проще было реализовать), а Firefox и Chrome/Safari передают флаг, если таймаут меньше секунды. А если больше -- то не передают ;)
|
||||
|
||||
Вот это окно откроется в Firefox и Chrome (но не в IE):
|
||||
|
||||
```js
|
||||
//+ run
|
||||
setTimeout(function() {
|
||||
window.open("http://ya.ru");
|
||||
}, *!*500*/!*);
|
||||
```
|
||||
|
||||
...А это -- будет заблокировано:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
setTimeout(function() {
|
||||
window.open("http://ya.ru");
|
||||
}, *!*2500*/!*);
|
||||
```
|
||||
|
||||
**Таким образом, современные браузеры расширяют область "действий посетителя" и блокируют попапы не всегда.**
|
||||
|
||||
## Синтаксис window.open
|
||||
## Полный синтаксис window.open
|
||||
|
||||
Полный синтаксис:
|
||||
|
||||
|
@ -63,17 +38,19 @@ setTimeout(function() {
|
|||
win = window.open(url, name, params)
|
||||
```
|
||||
|
||||
Функция возвращает ссылку но объект `window` нового окна, либо `null`, если окно было заблокировано браузером.
|
||||
|
||||
Параметры:
|
||||
|
||||
<dl>
|
||||
<dt>`url`</dt>
|
||||
<dd>URL для загрузки в новое окно.</dd>
|
||||
<dt>`name`</dt>
|
||||
<dd>Имя нового окна. Может быть использовано в параметре `target` в формах. Если позднее вызвать `window.open()` с тем же именем, то некоторые браузеры (Firefox) заменяют существующее окно на новосозданное. А вот IE при этом откроет новое окно.</dd>
|
||||
<dd>Имя нового окна. Может быть использовано в параметре `target` в формах. Если позднее вызвать `window.open()` с тем же именем, то браузеры (кроме IE) заменяют существующее окно на новое.</dd>
|
||||
<dt>`params`</dt>
|
||||
<dd>Строка с конфигурацией для нового окна. Состоит из параметров, перечисленных через запятую. Пробелов в ней быть не должно.</dd>
|
||||
</dl>
|
||||
|
||||
**Можно легко узнать, было ли окно заблокировано. При этом результат `window.open` равен `null`.**
|
||||
|
||||
Значения параметров `params`.
|
||||
|
||||
<ol>
|
||||
|
@ -108,32 +85,14 @@ win = window.open(url, name, params)
|
|||
<li>Еще есть небольшое количество не кросс-браузерных свойств, которые обычно не используются. Вы можете узнать о них в документации, например MDN: [window.open](https://developer.mozilla.org/en/DOM/window.open).</li>
|
||||
</ol>
|
||||
|
||||
Откроем окно с минимальным набором функций, чтобы посмотреть, какие из них браузер позволяет отключить:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var params = 'scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no' +
|
||||
'width=0,height=0,left=-1000,top=-1000';
|
||||
window.open('/', 'test', params);
|
||||
```
|
||||
|
||||
Здесь большинство параметров отключено и окно позиционированно за пределами экрана. Запустите, чтобы увидеть, что произойдет на самом деле.
|
||||
|
||||
Теперь давайте добавим корректные параметры позиционирования: нормальную ширину, высоту и координаты левого верхнего угла.
|
||||
|
||||
Этот пример браузер отобразит "как заказано":
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var params = 'scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no' +
|
||||
'width=100,height=100,left=100,top=100';
|
||||
window.open('/', 'test', params);
|
||||
```
|
||||
[warn]
|
||||
Браузер подходит к этим параметрам интеллектуально. Он может проигнорировать их часть или даже все, они скорее являются "пожеланиями", нежели "требованиями".
|
||||
[/warn]
|
||||
|
||||
Важные моменты:
|
||||
|
||||
<ul>
|
||||
<li>Если при вызове `open` третий параметр отсутствует, то используются параметры по умолчанию. Обычно при этом будет открыто не окно, а вкладка.</li>
|
||||
<li>Если при вызове `open` указан только первый параметр, параметр отсутствует, то используются параметры по умолчанию. Обычно при этом будет открыто не окно, а вкладка, что зачастую более удобно.</li>
|
||||
<li>Если указана строка с параметрами, но некоторые `yes/no` параметры отсутствуют, то браузер выставляет их в `no`. Поэтому убедитесь, что все нужные вам параметры выставлены в `yes`.</li>
|
||||
<li>Когда не указан `top/left`, то браузер откроет окно с небольшим смещением относительно левого верхнего угла последнего открытого окна.</li>
|
||||
<li>Если не указаны `width/height`, новое окно будет такого же размера, как последнее открытое.</li>
|
||||
|
@ -143,12 +102,23 @@ window.open('/', 'test', params);
|
|||
|
||||
Вызов `window.open` возвращает ссылку на новое окно. Она может быть использована для манипуляции свойствами окна, изменения URL, доступа к его переменным и т.п.
|
||||
|
||||
В примере ниже содержимое нового окна модифицируется после загрузки.
|
||||
В примере ниже мы заполняем новое окно содержимым целиком из JavaScript:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var newWin = window.open("about:blank", "hello", "width=200,height=200");
|
||||
|
||||
newWin.document.write("Привет, мир!");
|
||||
```
|
||||
|
||||
А здесь модифицируем содержимое после загрузки:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var newWin = window.open('/', 'example', 'width=600,height=400');
|
||||
newWin.focus();
|
||||
|
||||
alert(newWin.location.href); // (*) about:blank, загрузка ещё не началась
|
||||
|
||||
newWin.onload = function() {
|
||||
|
||||
// создать div в документе нового окна
|
||||
|
@ -156,33 +126,101 @@ newWin.onload = function() {
|
|||
div.innerHTML = 'Добро пожаловать!'
|
||||
div.style.fontSize = '30px'
|
||||
|
||||
*!*
|
||||
var body = newWin.document.body;
|
||||
*/!*
|
||||
// вставить первым элементом в новое body
|
||||
body.insertBefore(div, body.firstChild);
|
||||
// вставить первым элементом в body нового окна
|
||||
body.insertBefore(div, newWin.document.body.firstChild);
|
||||
}
|
||||
```
|
||||
|
||||
**Сразу после `window.open` новое окно ещё не загружено.**
|
||||
Обратим внимание: сразу после `window.open` новое окно ещё не загружено. Это демонстрирует `alert` в строке `(*)`. Поэтому в примере выше окно модифицируется при `onload`. Можно было и поставить обработчик на `DOMContentLoaded` для `newWin.document`.
|
||||
|
||||
**Связь между окнами -- двухсторонняя.**
|
||||
|
||||
Родительское окно получает ссылку на новое через `window.open`, а дочернее -- ссылку на родителя `window.opener`.
|
||||
|
||||
Оно тоже может его модифицировать.
|
||||
|
||||
Если запустить пример ниже, то новое окно заменит содержимое текущего на `'Test'`:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var newWin = window.open("about:blank", "hello", "width=200,height=200");
|
||||
|
||||
newWin.document.write(
|
||||
"<script>*!*window.opener.document.body.innerHTML = 'Test'*/!*</script>"
|
||||
);
|
||||
```
|
||||
|
||||
Поэтому в примере выше изменения начинаются при `onload`. Можно использовать и событие `DOMContentLoaded`.
|
||||
|
||||
[warn header="Same Origin Policy -- защита проверкой протокол-сайт-порт"]
|
||||
**Большинство действий, особенно получение содержимого окна и его переменных, возможны лишь в том случае, если URL нового окна происходит из того же источника (англ. - *"Same Origin"*), т.е. совпадают домен, протокол и порт.**
|
||||
Большинство действий, особенно получение содержимого окна и его переменных, возможны лишь в том случае, если URL нового окна происходит из того же источника (англ. - *"Same Origin"*), т.е. совпадают домен, протокол и порт.
|
||||
|
||||
Иначе говоря, если новое окно содержит документ с того же сайта.
|
||||
|
||||
Исключение: замена адреса. Поменять (но не прочитать) `win.location = ...` можно всегда, даже если окно с другого домена.
|
||||
|
||||
Больше информации об этом -- в главе [](/same-origin-policy).
|
||||
Больше информации об этом будет позже, в главе [](/same-origin-policy).
|
||||
[/warn]
|
||||
|
||||
## Связь между новым и родительским окном
|
||||
|
||||
Связь между окнами -- двухсторонняя. Родительское окно получает ссылку из `window.open`, а дочернее -- специальное свойство `window.opener`.
|
||||
## События
|
||||
|
||||
Наиболее важные события при работе с окном браузера:
|
||||
|
||||
<ul>
|
||||
<li>`onresize` -- событие изменения размера окна.</li>
|
||||
<li>`onscroll` -- событие при прокрутке окна.</li>
|
||||
<li>`onload` -- полностью загрузилась страница со всеми ресурсами.</li>
|
||||
<li>`onfocus/onblur` -- получение/потеря фокуса.</li>
|
||||
</ul>
|
||||
|
||||
## Методы и свойства
|
||||
|
||||
<dl>
|
||||
<dt>`window.closed`
|
||||
<dd>Свойство `window.closed` равно `true`, если окно закрыто. Может быть использовано, чтобы проверить, закрыл ли посетитель попап.</dd>
|
||||
<dt>`window.close()`</dt>
|
||||
<dd>Закрывает попап без предупреждений и уведомлений. Вообще, метод `close()` можно вызвать для любого окна, в том числе, текущего. Но если окно открыто не с помощью `window.open()`, то браузер может проигнорировать вызов `close` или запросить подтверждение.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Перемещение и изменение размеров окна
|
||||
|
||||
Существует несколько методов для перемещения/изменения размеров окна.
|
||||
|
||||
<dl>
|
||||
<dt>`win.moveBy(x,y)`</dt>
|
||||
<dd>Перемещает окно относительно текущего положения на `x` пикселей вправо и `y` пикселей вниз. Допускаются отрицательные значения.</dd>
|
||||
<dt>`win.moveTo(x,y)`</dt>
|
||||
<dd>Передвигает окно в заданную координатами `x` и `y` точку экрана монитора.</dd>
|
||||
<dt>`win.resizeBy(width,height)`</dt>
|
||||
<dd>Изменяет размер окна на заданную величину `width/height` (ширина/высота). Допускаются отрицательные значения.</dd>
|
||||
<dt>`win.resizeTo(width,height)`</dt>
|
||||
<dd>Изменяет размер окна на заданное значение.</dd>
|
||||
</dl>
|
||||
|
||||
[warn]
|
||||
Чтобы предотвратить использование этих методов с плохими целями, браузеры часто блокируют их выполнение. Как правило, они работают, если окно `win` открыто вызовом [window.open](https://developer.mozilla.org/en-US/docs/Web/API/window.open) из JavaScript текущей страницы и в нём нет дополнительных вкладок.
|
||||
[/warn]
|
||||
|
||||
[warn header="Ни свернуть ни развернуть"]
|
||||
Заметим, что JavaScript не может ни свернуть ни развернуть ни "максимизировать" (Windows) окно.
|
||||
|
||||
Эти функции операционной системы от Frontend-разработчиков скрыты. Вызовы, описанные выше, в случае свёрнутого или максимизированного окна не работают.
|
||||
[/warn]
|
||||
|
||||
## Прокрутка окна
|
||||
|
||||
Прокрутка окна требуется, пожалуй, чаще всего. Мы уже говорили о ней в главе [](/metrics-window):
|
||||
|
||||
<dl>
|
||||
<dt>`win.scrollBy(x,y)`</dt>
|
||||
<dd>
|
||||
Прокрутка окна на заданное число пикселей вперед или назад. Допускаются отрицательные значения.</dd>
|
||||
<dt>`win.scrollTo(x,y)`</dt>
|
||||
<dd>Прокручивает окно к заданным координатам.</dd>
|
||||
<dt>`elem.scrollIntoView(top)`</dt>
|
||||
<dd>Этот метод прокрутки вызывается на элементе. При этом окно прокручивается так, чтобы элемент был полностью видим. Если параметр `top` равен `true` или не задан, то верх элемента совпадает с верхом окна. Если он равен `false`, то окно прокручивается так, чтобы нижний край элемента совпал с нижним краем окна.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
Оно равно `null`, если открывшего окна нет.
|
||||
|
||||
## Итого
|
||||
|
||||
|
@ -195,11 +233,9 @@ newWin.onload = function() {
|
|||
<li>Окна могут общаться между собой как угодно, если они из одного источника. Иначе действуют жёсткие ограничения безопасности.</li>
|
||||
</ul>
|
||||
|
||||
Всплывающие окна используются нечасто. Ведь загрузить новую информацию можно динамически, с помощью технологии AJAX, а показать -- в элементе `DIV`, расположенным над страницей (`z-index`). Ещё одна альтернатива -- тег `IFRAME`.
|
||||
Всплывающие окна используются нечасто. Ведь загрузить новую информацию можно динамически, с помощью технологии AJAX, а показать -- в элементе `<div>`, расположенным над страницей (`z-index`). Ещё одна альтернатива -- тег `<iframe>`.
|
||||
|
||||
Но в некоторых случаях всплывающие окна бывают очень даже полезны. Например, отдельное окно сервиса онлайн-консультаций. Посетитель может ходить по сайту в основном окне, а общаться в чате -- во вспомогательном.
|
||||
|
||||
Если вы хотите использовать всплывающее окно, предупредите посетителя об этом, так же и при использовании `target="_blank"` в ссылках или формах. Иконка открывающегося окошка на ссылке поможет посетителю понять, что происходит и не потерять оба окна из поля зрения.
|
||||
|
||||
|
||||
<p style="text-align:right">Спасибо Марату Шагиеву за помощь в выкладке русскоязычного варианта этой главы.</p>
|
160
3-more/5-frames-and-windows/2-iframes/article.md
Normal file
160
3-more/5-frames-and-windows/2-iframes/article.md
Normal file
|
@ -0,0 +1,160 @@
|
|||
# Окно внутри ифрейма
|
||||
|
||||
Элемент `iframe` является обычным узлом DOM, как и любой другой. Существенное отличие -- в том, что с ним связан объект `window` внутреннего окна. Он доступен по ссылке `iframe.contentWindow`.
|
||||
|
||||
[cut]
|
||||
|
||||
Таким образом, `iframe.contentWindow.document` будет внутренним документом, `iframe.contentWindow.document.body` -- его `<body>` и так далее.
|
||||
|
||||
[smart header="Когда-то..."]
|
||||
В старых браузерах использовались другие свойства, такие как `iframe.contentDocument` и даже `iframe.document`, но они давно не нужны.
|
||||
[/smart]
|
||||
|
||||
## Переход внутрь ифрейма
|
||||
|
||||
В примере ниже JavaScript получает документ внутри ифрейма и модифицирует его:
|
||||
|
||||
```html
|
||||
<!--+ run height=100 -->
|
||||
<iframe src="javascript:'тест'" style="height:60px"></iframe>
|
||||
|
||||
<script>
|
||||
var iframe = document.getElementsByTagName('iframe')[0];
|
||||
*!*
|
||||
var iframeDoc = iframe.contentWindow.document;
|
||||
*/!*
|
||||
iframeDoc.body.style.backgroundColor = 'green';
|
||||
</script>
|
||||
```
|
||||
|
||||
[smart header="src='javascript:...'"]
|
||||
Атрибут `src` может использовать протокол `javascript:...`. При этом код выполняется и его результат будет содержимым ифрейма. Этот способ описан в стандарте и поддерживается всеми браузерами.
|
||||
|
||||
Атрибут `src` является обязательным, и его отсутствие может привести к проблемам, вплоть до игнорирования ифрейма браузером.
|
||||
|
||||
Чтобы ничего не загружать в ифрейм, можно указать `src="javascript:''"`.
|
||||
[/smart]
|
||||
|
||||
## Кросс-доменность: ограничение доступа к окну
|
||||
|
||||
Элемент `<iframe>` является "двуличным". С одной стороны, это обычный узел DOM, с другой -- внутри находится окно, которое может иметь совершенно другой URL, содержать независимый документ из другого источника.
|
||||
|
||||
Внешний документ имеет полный доступ к `<iframe>` как к DOM-узлу. А вот к окну -- если они с одного источника.
|
||||
|
||||
Это приводит к забавным последствиям. Например, мы можем повесить обработчик `iframe.onload`, чтобы узнать о загрузке `<iframe>`, а вот на `iframe.contentWindow.onload` -- лишь в случае, если окно с того же источника.
|
||||
|
||||
```html
|
||||
<!--+ run height=120 -->
|
||||
<iframe src="http://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`.
|
||||
|
||||
Есть два способа доступа:
|
||||
<ol>
|
||||
<li>`window.frames[0]` -- доступ по номеру.</li>
|
||||
<li>`window.frames.iframeName` -- доступ по `name` ифрейма.</li>
|
||||
</ol>
|
||||
|
||||
Обратим внимание: в коллекции хранится именно окно (`contentWindow`), а не DOM-элемент.
|
||||
|
||||
Демонстрация всех способов доступа к окну:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<iframe src="javascript:''" style="height:80px" name="i"></iframe>
|
||||
|
||||
<script>
|
||||
var iframeTag = document.body.children[0];
|
||||
|
||||
var iframeWindow = iframe.contentWindow; // окно из тега
|
||||
|
||||
alert(frames[0] === iframeWindow); // true, окно из коллекции frames
|
||||
alert(frames.i == iframeWindow); // true, окно из frames по имени
|
||||
</script>
|
||||
```
|
||||
|
||||
Внутри ифрейма могут быть свои вложенные ифреймы. Всё это вместе образует иерархию.
|
||||
|
||||
Ссылки для навигации по ней:
|
||||
|
||||
<ul>
|
||||
<li>`window.frames` -- коллекция "детей" (вложенных ифреймов)</li>
|
||||
<li>`window.parent` -- содержит ссылку на родительское окно, позволяет обратиться к нему из ифрейма.
|
||||
|
||||
Всегда верно:
|
||||
|
||||
```js
|
||||
// (из окна со фреймом)
|
||||
window.frames[0].parent === window; // true
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>`window.top` -- содержит ссылку на самое верхнее окно (вершину иерархии).
|
||||
|
||||
Всегда верно (в предположении, что вложенные фреймы существуют):
|
||||
|
||||
```js
|
||||
window.frames[0].frames[0].frames[0].top === window
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
**Свойство `top` позволяет легко проверить, во фрейме ли находится текущий документ:**
|
||||
|
||||
```js
|
||||
//+ run
|
||||
if (window == top) {
|
||||
alert('Этот скрипт является окном верхнего уровня в браузере');
|
||||
} else {
|
||||
alert('Этот скрипт исполняется во фрейме!');
|
||||
}
|
||||
```
|
||||
|
||||
## Песочница sandbox
|
||||
|
||||
Атрибут `sandbox` позволяет построить "песочницу" вокруг ифрейма, запретив ему выполнять ряд действий.
|
||||
|
||||
Наличие атрибута `sandbox`:
|
||||
<ul>
|
||||
<li>Заставляет браузер считать ифрейм загруженным с другого источника, так что он и внешнее окно больше не могут обращаться к переменным друг друга.</li>
|
||||
<li>Отключает формы и скрипты в ифрейме.</li>
|
||||
<li>Запрещает менять `parent.location` из ифрейма.</li>
|
||||
</ul>
|
||||
|
||||
Пример ниже загружает в `<iframe sandbox>` документ с JavaScript и формой. Ни то ни другое не сработает:
|
||||
|
||||
[codetabs src="sandbox"]
|
||||
|
||||
Если у атрибута `sandbox` нет значения, то браузер применяет максимум ограничений.
|
||||
|
||||
Атрибут `sandbox` может содержать через пробел список ограничений, которые не нужны:
|
||||
<dl>
|
||||
<dt>allow-same-origin</dt>
|
||||
<dd>Браузер может не считать документ в ифрейме пришедшим с другого же домена. Если ифрейм *и так* с другого домена, то ничего не меняется.</dd>
|
||||
<dt>allow-top-navigation</dt>
|
||||
<dd>Разрешает ифрейму менять `parent.location`.</dd>
|
||||
<dt>allow-forms</dt>
|
||||
<dd>Разрешает отправлять формы из `iframe`.</dd>
|
||||
<dt>allow-scripts</dt>
|
||||
<dd>Разрешает выполнение скриптов из ифрейма. Но скриптам, всё же, будет запрещено открывать попапы.</dd>
|
||||
</dl>
|
||||
|
||||
[smart]
|
||||
Цель атрибута `sandbox` -- наложить дополнительные ограничения. Он не может снять уже существующие, в частности, убрать ограничения безопасности, если ифрейм с другого источника.
|
||||
[/smart]
|
|
@ -0,0 +1,11 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<iframe sandbox src="sandboxed.html"></iframe>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,18 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
alert(1);
|
||||
</script>
|
||||
|
||||
<form action="http://google.ru">
|
||||
<input type="text">
|
||||
<input type="submit" value="Отправить форму на http://google.ru">
|
||||
</form>
|
||||
|
||||
</body>
|
||||
</html>
|
137
3-more/5-frames-and-windows/3-same-origin-policy/article.md
Normal file
137
3-more/5-frames-and-windows/3-same-origin-policy/article.md
Normal file
|
@ -0,0 +1,137 @@
|
|||
# Кросс-доменные ограничения и их обход
|
||||
|
||||
Ограничение "Same Origin" ("тот же источник") ограничивает доступ окон и фреймов друг к другу, а также влияет на AJAX-запросы к серверу.
|
||||
|
||||
Причина, по которой оно существует -- безопасность. Если есть два окна, в одном из которых `vasya-pupkin.com`, а в другом `gmail.com`, то мы бы не хотели, чтобы скрипт из первого мог читать нашу почту.
|
||||
|
||||
Сама концепция проста, но есть много важных исключений и особенностей, которые нужно знать для полного понимания этого правила.
|
||||
[cut]
|
||||
|
||||
## Концепция Same Origin [#same-origin]
|
||||
|
||||
Два URL считаются имеющим один источник ("same origin"), если у них одинаковый протокол, домен и порт.
|
||||
|
||||
Эти URL имеют один источник:
|
||||
<ul>
|
||||
<li>`http://site.com`</li>
|
||||
<li>`http://site.com`/</li>
|
||||
<li>`http://site.com/my/page.html`</li>
|
||||
</ul>
|
||||
|
||||
А вот эти -- все из других источников:
|
||||
<ul>
|
||||
<li>http://<span style="color:red;font-weight:bold">www.</span>site.com (другой домен)</li>
|
||||
<li>http://site.<span style="color:red;font-weight:bold">org</span> (другой домен)</li>
|
||||
<li>http<span style="color:red; font-weight:bold">s</span>://site.com (другой протокол)</li>
|
||||
<li>http://site.com<span style="color:red; font-weight:bold">:8080</span> (другой порт)</li>
|
||||
</ul>
|
||||
|
||||
Существует ряд исключений, позволяющих-таки окнам с разных доменов обмениваться информацией, но прямой вызов методов друг друга и чтение свойств запрещены.
|
||||
|
||||
## В действии
|
||||
|
||||
Если одно окно попытается обратиться к другому, то браузер проверит, из одного ли они источника. Если нет -- доступ будет запрещён.
|
||||
|
||||
Например:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<iframe src="http://example.com"></iframe>
|
||||
|
||||
<script>
|
||||
var iframe = document.body.children[0];
|
||||
|
||||
iframe.onload = function() {
|
||||
try {
|
||||
alert(iframe.contentWindow.document);
|
||||
} catch(e) {
|
||||
alert("Ошибка: " + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
</script>
|
||||
```
|
||||
|
||||
Пример выше выведет ошибку.
|
||||
|
||||
## Исключение: запись в location
|
||||
|
||||
Окна могут менять `location` друг друга, даже если они из разных источников.
|
||||
|
||||
Причём *читать* свойства `location` нельзя, одно окно не имеет право знать, на каком URL пользователь в другом. А вот *запись* браузеры считают безопасной.
|
||||
|
||||
Например, открыв на `javascript.ru` iframe с `http://example.com`, из этого ифрейма нельзя будет прочитать URL, а вот поменять его -- запросто:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<iframe src="http://example.com"></iframe>
|
||||
|
||||
<script>
|
||||
var iframe = document.body.children[0];
|
||||
|
||||
iframe.onload = function() {
|
||||
try {
|
||||
// не сработает (чтение)
|
||||
alert(iframe.contentWindow.location.href);
|
||||
} catch(e) {
|
||||
alert("Ошибка: " + e.message);
|
||||
}
|
||||
|
||||
// сработает (запись)
|
||||
iframe.contentWindow.location.href = 'http://wikipedia.org';
|
||||
|
||||
iframe.onload = null;
|
||||
}
|
||||
|
||||
</script>
|
||||
```
|
||||
|
||||
Если запустить код выше, то окно сначала начнёт загрузит `example.com`, а потом будет перенаправлено на `wikipedia.org`.
|
||||
|
||||
## Исключение: поддомен 3го уровня
|
||||
|
||||
Ещё одно важное исключение касается доменов третьего уровня.
|
||||
|
||||
Если несколько окон имеют общий домен второго уровня, к примеру `john.site.com`, `peter.site.com`, `site.com`, и присваивают в `document.domain` свой общий поддомен 2го уровня `site.com`, то все ограничения снимаются.
|
||||
|
||||
То есть, на всех этих сайтах должен быть код:
|
||||
```js
|
||||
document.domain = 'site.com';
|
||||
```
|
||||
|
||||
Тогда между ними не будет кросс-доменных ограничений.
|
||||
|
||||
Обратим внимание: свойство `document.domain` должно быть присвоено на всех окнах, участвующих в коммуникации. Выглядит абсурдно, но даже на документе с `site.com` нужно вызвать: `document.domain="site.com"`. Иначе не будет работать.
|
||||
|
||||
Таким образом разные подсайты в рамках одного общего проекта могут взаимодействовать без ограничений.
|
||||
|
||||
## Исключения в IE
|
||||
|
||||
В браузере Internet Explorer есть два своих, дополнительных исключения из Same Origin Policy.
|
||||
|
||||
<ol>
|
||||
<li>Порт не входит в понятие "источник" (origin).
|
||||
|
||||
Это означает, что окно с `http://site.com` может свободно общаться с `http://site.com:8080`.
|
||||
|
||||
Это иногда используют для общения серверов, использующих один IP-адрес. Но допустимо такое только в IE.</li>
|
||||
<li>Если сайт находится в зоне "Надёжные узлы", то в Internet Explorer ограничения к нему не применяются.
|
||||
|
||||
При этом подразумевается, что для этой зоны в параметрах "Безопасность" включена опция "Доступ к источникам данных за пределами домена".</li>
|
||||
</ol>
|
||||
|
||||
## Итого
|
||||
|
||||
Ограничение "одного источника" запрещает окнам и фреймам с разных источников вызывать методы друг друга и читать данные друг из друга.
|
||||
|
||||
При этом "из одного источника" означает "совпадают протокол, домен и порт".
|
||||
|
||||
У этого подхода ряд существенных исключений:
|
||||
|
||||
<ul>
|
||||
<li>Свойства `window.location.*` нельзя читать, но можно менять.</li>
|
||||
<li>Домены третьего уровня с общим наддоменом могут поменять `document.domain` на их общий домен второго уровня, и тогда они смогут взаимодействовать без ограничений.
|
||||
</li>
|
||||
<li>IE не включает порт в понятие источника. Кроме того, он позволяет снять ограничения для конкретного сайта включением в доверенную зону.</li>
|
||||
</ul>
|
||||
|
|
@ -5,13 +5,9 @@
|
|||
Он очень удобен, например, для взаимодействия внешних виджетов и сервисов, подключённых через ифрейм с основной страницей.
|
||||
[cut]
|
||||
|
||||
## Интерфейс
|
||||
## Отправитель: метод postMessage
|
||||
|
||||
Интерфейс состоит из двух частей.
|
||||
|
||||
### Отправитель: метод postMessage
|
||||
|
||||
Первая часть состоит из метода [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). Его вызывает окно, которое хочет отправить сообщение, в контексте окна-получателя.
|
||||
Первая часть интерфейса состоит из метода [postMessage](https://developer.mozilla.org/en-US/docs/Web/API/Window.postMessage). Его вызывает окно, которое хочет отправить сообщение, в контексте окна-получателя.
|
||||
|
||||
Проще говоря, если мы хотим отправить сообщение в окно `win`, то нужно вызвать `win.postMessage(data, targetOrigin)`.
|
||||
|
||||
|
@ -28,14 +24,25 @@
|
|||
Мы ведь не можем из JavaScript узнать, на каком именно URL находится другое окно. Но иногда хочется быть уверенным, что данные передаются в доверенный документ. Для этого и нужен этот параметр. Проверку осуществляет браузер. При указании `'*'` ограничений нет.</dd>
|
||||
</dl>
|
||||
|
||||
[warn header="В IE8 можно использовать `postMessage` только для ифреймов"]
|
||||
Например:
|
||||
```html
|
||||
<iframe src="http://target.com" name="target">
|
||||
|
||||
В браузере IE8, интерфейс `postMessage` работает только с ифреймами. Он не работает между табами и окнами.
|
||||
<script>
|
||||
var win = window.frames.target;
|
||||
win.postMessage("сообщение", "http://javascript.ru");
|
||||
</script>
|
||||
```
|
||||
|
||||
Это ошибка в данном конкретном браузере, в других -- всё в порядке.
|
||||
|
||||
[warn header="В IE11- можно использовать `postMessage` только для ифреймов"]
|
||||
|
||||
В браузере IE, интерфейс `postMessage` работает только с ифреймами. Он не работает между табами и окнами.
|
||||
|
||||
Это ошибка в данном конкретном браузере, в других -- всё в порядке. Детали по этой и связанным с ней ошибкам: [HTML5 Implementation Issues in IE8 and later](http://blogs.msdn.com/b/ieinternals/archive/2009/09/16/bugs-in-ie8-support-for-html5-postmessage-sessionstorage-and-localstorage.aspx).
|
||||
[/warn]
|
||||
|
||||
### Получатель: событие onmessage
|
||||
## Получатель: событие onmessage
|
||||
|
||||
Чтобы получить сообщение, окно должно поставить обработчик на событие `onmessage`.
|
||||
|
||||
|
@ -53,40 +60,28 @@
|
|||
|
||||
```js
|
||||
function listener(event) {
|
||||
if( event.origin != 'http://learn.javascript.ru') {
|
||||
// что-то прислали с чужого домена - проигнорируем..
|
||||
if( event.origin != 'http://javascript.ru') {
|
||||
// что-то прислали с неизвестного домена - проигнорируем..
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById("msg").innerHTML = "получено: " + event.data;
|
||||
alert("получено: " + event.data);
|
||||
}
|
||||
|
||||
if (window.addEventListener){
|
||||
window.addEventListener("message", listener);
|
||||
} else {
|
||||
// IE8
|
||||
window.attachEvent("onmessage", listener);
|
||||
}
|
||||
```
|
||||
|
||||
Этот код содержит ифрейм ниже, с именем `name="receiveFrame"`:
|
||||
|
||||
<iframe src="/files/tutorial/window/receive.html" style="width:100%; height: 60px; border:1px solid black" frameborder="0" name="receiveFrame"></iframe>
|
||||
|
||||
Запустите код для отправки ему сообщения:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
var win = frames.receiveFrame;
|
||||
win.postMessage("Привет!", "http://learn.javascript.ru");
|
||||
```
|
||||
|
||||
## Задержка
|
||||
|
||||
[smart header="Задержка отсутствуют"]
|
||||
Задержки между отправкой и получением нет, совсем.
|
||||
|
||||
Если для `setTimeout` стандарт предусматривает минимальную задержку 4мс, то для `postMessage` она равна 0мс.
|
||||
|
||||
Поэтому `postMessage` можно, в том числе, использовать как мгновенную альтернативу `setTimeout`.
|
||||
[/smart]
|
||||
|
||||
## Итого
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
# Focus, привлечение внимания к окну
|
||||
# Привлечение внимания к окну
|
||||
|
||||
Проверить, находится ли окно в фокусе, а также перевести внимание посетителя на него -- сложно.
|
||||
|
||||
|
@ -19,25 +19,22 @@
|
|||
setInterval(function() { window.focus() }, 1000);
|
||||
```
|
||||
|
||||
Что будет, если запустить этот код, и затем переключиться в другое окно или вкладку?
|
||||
|
||||
Можно подумать, что окно будет оказываться в фокусе раз в секунду. Но это не так.
|
||||
|
||||
Произойдет одно из трех:
|
||||
|
||||
<ol>
|
||||
<li>Окно развернется (при необходимости) и выйдет на передний план. Обычно это происходит, когда метод `window.focus()` вызывается для попапа, а активно сейчас -- главное окно.</li>
|
||||
<li>Заголовок окна начнет мигать. Чтобы увидеть это в действии -- откройте данную страницу в IE, запустите код и переключитесь на другое окно.</li>
|
||||
|
||||
Браузер попытается привлечь Ваше внимание миганием/мерцанием заголовка окна.</li>
|
||||
<li>Не наблюдается никакого видимого эффекта. Откройте эту страницу в Firefox в Windows, запустите код и сверните окно. Вызов `window.focus` не сработает. Также ведёт себя и Opera.</li>
|
||||
<li>Вообще никакого эффекта. Самый распространённый случай, если в окне много вкладок.</li>
|
||||
<li>Окно развернется (при необходимости) и выйдет на передний план. Обычно это происходит, когда метод `window.focus()` вызывается для попапа, а активно сейчас -- главное окно. То есть, в этом случае вызов сработает.</li>
|
||||
<li>Заголовок окна начнет мигать. Чтобы увидеть это в действии -- откройте данную страницу в IE, запустите код и переключитесь на другое окно. Браузер попытается привлечь Ваше внимание миганием/мерцанием заголовка окна.</li>
|
||||
</ol>
|
||||
|
||||
Как же, всё-таки, можно гарантированно развернуть окно и поместить его на первый план?
|
||||
|
||||
Если это Вам действительно необходимо -- есть только одно надежное решение: плагин к браузеру или подписанный Java-апплет, который может обратиться напрямую к функциям ОС. Написать такое вполне можно. Но есть и другие способы привлечь внимание к окну.
|
||||
## Мерцание заголовка
|
||||
|
||||
## Получение внимания -- для всплывающих окон
|
||||
|
||||
Один из лучших способов -- это сочетание `window.focus()` с мерцанием заголовка окна, как показано в примере ниже:
|
||||
В дополнение к `window.focus()` используют мерцание заголовка окна, как показано в примере ниже:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
|
@ -73,7 +70,7 @@ setInterval(function() { window.focus() }, 1000);
|
|||
<input type="button" onclick="getAttention(win)" value="getAttention(win)">
|
||||
```
|
||||
|
||||
Запустите код и сверните всплывющее окно. А затем -- нажмите кнопку с надписью "getAttention(win)". Браузер будет привлекать ваше внимание, как умеет ;)
|
||||
Запустите код и сверните всплывающее окно. А затем -- нажмите кнопку с надписью "getAttention(win)". Браузер будет привлекать ваше внимание, как умеет ;)
|
||||
|
||||
Обратите внимание: в коде есть проверка на `win.closed`. Попытка манипулирования закрытым окном вызовет исключение.
|
||||
|
||||
|
@ -149,7 +146,7 @@ setInterval(function() { window.focus() }, 1000);
|
|||
|
||||
Но этот получает фокус только если посетитель сфокусируется где-то в документе: щелкнет или сделает еще какое-то действие в документе, а не просто посмотрит на него и проведет над ним мышкой.
|
||||
|
||||
Впрочем, никто не мешает использовать сочетание всех описанных методов :)
|
||||
Впрочем, никто не мешает использовать сочетание всех описанных методов.
|
||||
|
||||
## Итого
|
||||
|
||||
|
@ -168,5 +165,3 @@ setInterval(function() { window.focus() }, 1000);
|
|||
|
||||
Поэтому для определения переключения на окно -- используйте его вместе с делегируемым `focus` на документе, а также `document.onmousemove`.</li>
|
||||
</ul>
|
||||
|
||||
<p style="text-align:right">Спасибо Марату Шагиеву за помощь в выкладке русскоязычного варианта этой главы.</p>
|
203
3-more/5-frames-and-windows/6-clickjacking/article.md
Normal file
203
3-more/5-frames-and-windows/6-clickjacking/article.md
Normal file
|
@ -0,0 +1,203 @@
|
|||
# Атака Clickjacking и защита от неё
|
||||
|
||||
Атака "кликджекинг" (англ. Clickjacking) позволяет хакеру выполнить клик на сайте-жертве *от имени посетителя*.
|
||||
|
||||
В русском языке встречается дословный перевод термина clickjacking: "угон клика". Так же применительно к clickjacking-атаке можно встретить термины "перекрытие iframe" и "подмена пользовательского интерфейса".
|
||||
|
||||
Кликджекингу подверглись в своё время Twitter, Facebook , PayPal, YouTube и многие другие сайты. Сейчас, конечно, они уже защищены.
|
||||
[cut]
|
||||
## Идея атаки
|
||||
|
||||
В целом идея очень проста.
|
||||
|
||||
Вот как выглядел "угон клика" пользователя, который зарегистрирован на Facebook:
|
||||
|
||||
<ol>
|
||||
<li>На вредоносной странице пользователю подсовывается безобидная ссылка (скажем, что-то скачать, "разбогатеть сейчас", посмотреть ролик или просто перейти по ссылке на интересный ресурс).</li>
|
||||
<li>Поверх этой заманчивой ссылки помещен прозрачный iframe со страницей facebook.com, так что кнопка "Like" находится чётко над ней.</li>
|
||||
<li>Кликая на ссылку, посетитель на самом деле нажимает на эту кнопку.</li>
|
||||
</ol>
|
||||
|
||||
## Демо
|
||||
|
||||
Вот пример вредоносной страницы (для наглядности `iframe` -- полупрозрачный):
|
||||
|
||||
```html
|
||||
<!--+ run height=120 -->
|
||||
<style>
|
||||
iframe { /* iframe с сайта-жертвы */
|
||||
width: 400px;
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
top:0; left:-20px;
|
||||
*!*
|
||||
opacity: 0.5; /* в реальности opacity:0 */
|
||||
*/!*
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>Нажмите, чтобы разбогатеть сейчас:</div>
|
||||
|
||||
<!-- URL в реальности - с другого домена (атакуемого сайта) -->
|
||||
*!*
|
||||
<iframe src="facebook.html"></iframe>
|
||||
|
||||
<button>Жми тут!</button>
|
||||
*/!*
|
||||
|
||||
<div>..И всё получится (хе-хе, у меня, злого хакера, получится)!</div>
|
||||
```
|
||||
|
||||
В действии:
|
||||
|
||||
[codetabs src="clickjacking-visible" height=200]
|
||||
|
||||
Так как `<iframe src="facebook.html">` полупрозрачный, то в примере выше легко видеть, как он перекрывает кнопку. При клике на "Жми тут" на самом деле происходит клик на `<iframe>` (на "Like").
|
||||
|
||||
В итоге, если посетитель авторизован на facebook (а в большинстве случаев так и есть), то facebook.com получает щелчок от имени посетителя.
|
||||
|
||||
На Twitter это была бы кнопка "Follow".
|
||||
|
||||
Тот же самый пример, но ближе к реальности, с `opacity:0` для `<iframe>`. Вообще незаметно, что на самом деле посетитель кликает на `<iframe>`:
|
||||
|
||||
[codetabs src="clickjacking" height=200]
|
||||
|
||||
Итак, все, что нужно для проведения атаки -- это правильно расположить iframe на вредоносной странице. В большинстве случаев это делается средствами HTML/CSS.
|
||||
|
||||
[smart header="С клавиатурой так не сделаешь"]
|
||||
Атака назвается "Clickjacking", то есть "угон клика", так как события клавиатуры "угнать" гораздо труднее.
|
||||
|
||||
Посетителя можно заставить сфокусироваться на `<input>` с невидимого `<iframe>`-жертвы, но текст в поле ввода также будет невидимым. Посетитель начнёт печатать, но, не увидев текст, прекратит свои действия.
|
||||
[/smart]
|
||||
|
||||
## Плохая защита
|
||||
|
||||
Самый старый метод защиты -- это код JavaScript, не позволяющий отобразить веб-страницу внутри фрейма ("framebusting", также его называют "framekilling" и "framebreaking").
|
||||
|
||||
Примерно такой:
|
||||
|
||||
```js
|
||||
if (top != window) {
|
||||
top.location = window.location;
|
||||
}
|
||||
```
|
||||
|
||||
То есть, если окно обнаруживает, что оно загружено во фрейме, то оно автоматически делает себя верхним.
|
||||
|
||||
Увы, в настоящий момент это уже не является сколько-нибудь надежной защитой. Есть несколько способов обхода framebusting. Давайте рассмотрим некоторые из них.
|
||||
|
||||
### Блокировка top-навигации.
|
||||
|
||||
Можно заблокировать переход, инициированный сменой `top.location`, в событии [onbeforeunload](#window.onbeforeunload).
|
||||
|
||||
Обработчик этого события ставится на внешней (хакерской) странице и, при попытке `iframe` поменять `top.location`, спросит посетителя, хочет он покинуть данную страницу. В большинстве браузеров хакер может спросить посетителя, используя своё сообщение.
|
||||
|
||||
```js
|
||||
window.onbeforeunload = function() {
|
||||
window.onbeforeunload = null;
|
||||
return "Хотите уйти с этой страницы, не узнав все её тайны (хе-хе)?";
|
||||
}
|
||||
```
|
||||
|
||||
Так что, скорее всего, посетитель ответит на такой странный вопрос отрицательно (он же не знает про ифрейм, видит только страницу, причины для ухода нет). А значит, ожидаемая смена `top.location` не произойдёт!
|
||||
|
||||
Пример в действии:
|
||||
|
||||
[codetabs src="top-location"]
|
||||
|
||||
### Атрибут sandbox
|
||||
|
||||
Современные браузеры поддерживают атрибут [sandbox](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox)
|
||||
|
||||
Он позволяет разрешить во фрейме скрипты `allow-scripts` и формы `allow-forms`, но запретить top-навигацию (не указать `allow-top-navigation`).
|
||||
|
||||
"Защищённый" `<iframe>` хакер может подключить, к примеру, так:
|
||||
|
||||
```html
|
||||
<iframe *!*sandbox="allow-scripts allow-forms"*/!* src="facebook.html"></iframe>
|
||||
```
|
||||
|
||||
Есть и другие приёмы для обхода этой простейшей защиты.
|
||||
|
||||
Firefox и старый IE могут активировать designMode на исходной странице, это также предотвращает framebusting, у IE есть нестандартный атрибут [security](https://msdn.microsoft.com/en-us/library/ie/ms534622.aspx) для ифреймов, который можно использовать с той же целью.
|
||||
|
||||
Как мы видим, эта защита не только не выдерживает реальной атаки, но и может скомпрометировать сайт (программист-то думает, что защитил его).
|
||||
|
||||
## Заголовок X-Frame-Options
|
||||
|
||||
Все современные браузеры поддерживают заголовок `X-Frame-Options`.
|
||||
|
||||
Он разрешает или запрещает отображение страницы, если она открыта во фрейме.
|
||||
|
||||
Браузеры игнорируют заголовок, если он определен в МЕТА тег. Таким образом, `<meta http-equiv="X-Frame-Options"...>` будет проигнорирован.
|
||||
|
||||
У заголовка может быть три значения:
|
||||
|
||||
<dl>
|
||||
<dt>SAMEORIGIN</dt>
|
||||
<dd>Рендеринг документа, при открытии во фрейме, производится только в том случае, когда верхний (top) документ -- с того же домена.</dd>
|
||||
<dt>DENY</dt>
|
||||
<dd>Рендеринг документа внутри фрейма запрещён.</dd>
|
||||
<dt>ALLOW-FROM domain</dt>
|
||||
<dd>Разрешает рендеринг, если внешний документ с данного домена (не поддерживается в Safari, Firefox).</dd>
|
||||
</dl>
|
||||
|
||||
К примеру, Twitter использует `X-Frame-Options: SAMEORIGIN`. Результат:
|
||||
|
||||
```html
|
||||
<iframe src="http://twitter.com"></iframe>
|
||||
```
|
||||
|
||||
<iframe src="http://twitter.com"></iframe>
|
||||
|
||||
В зависимости от браузера, `iframe` выше либо пустой, либо в нём находится сообщение о невозможности отобразить его (IE).
|
||||
|
||||
## Показ с отключённым функционалом
|
||||
|
||||
Заголовок `X-Frame-Options` имеет неприятный побочный эффект. Иногда поисковики, анонимайзеры или другие сайты хотели бы отобразить страницу в `iframe`, по вполне "легальным" причинам, но не могут.
|
||||
|
||||
Хорошо бы показывать их посетителям не пустой `iframe`, а нечто, что может быть более интересно.
|
||||
|
||||
Например, можно изначально "накрывать" документ `div` с `height:100%;width:100%`, который будет перехватывать все клики. И поставить на нём ссылку, ведующую на страницу в новом окне.
|
||||
|
||||
```html
|
||||
<style>
|
||||
#iframe-protector {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="iframe-protector">
|
||||
<a href="/" target="_blank">Перейти на сайт</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if (top.document.domain == document.domain) {
|
||||
убрать iframe-protector
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Если страница -- не во фрейме или домен совпадает, то посетитель не увидит его.
|
||||
|
||||
## Заключение
|
||||
|
||||
Атаку "Clickjacking" легко осуществить, если на сайте есть действие, активируемое с помощью одного клика.
|
||||
|
||||
Злоумышленник может осуществить атаку через целенаправленно на посетителей ресурса -- опубликовав ссылку на форуме, или "счастливой рассылкой". Существует масса вариантов.
|
||||
|
||||
С первого взгляда, она "неглубокая": всё, что можно сделать -- это один клик. С другой стороны, если хакер знает, что после клика появляется какой-то другой управляющий элемент, то он, хитрыми сообщениями, может заставить посетителя кликнуть и по нему. А это уже не один, а два клика.
|
||||
|
||||
Атака особенно опасна, поскольку, проектируя интерфейс сайта, обычно никто и не задумывается о том, что клик от имени юзера может сделать хакер. Точки уязвимости могут быть в совершенно непредсказуемых местах.
|
||||
|
||||
|
||||
<ul>
|
||||
<li>Рекомендуется использовать `X-Frame-Options` на страницах, заведомо не предназначеных для запуска во фрейме и на важнейших страницах (финансовые транзакции).</li>
|
||||
<li>Используйте перекрывающий `<div>`, если это допустимо вашим проектом и вы хотите разрешить безопасный показ документа во фреймах с любых доменов.</li>
|
||||
</ul>
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body style="margin:10px;padding:10px">
|
||||
|
||||
<input type="button" onclick="alert('Нажата кнопка Like с другого домена!')" value="I LIKE IT !">
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<style>
|
||||
iframe { /* iframe с сайта-жертвы */
|
||||
width: 400px;
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
top:0; left:-20px;
|
||||
opacity: 0.5;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>Нажмите, чтобы разбогатеть сейчас:</div>
|
||||
|
||||
<!-- URL, в реальности - с другого домена (атакуемого сайта) -->
|
||||
<iframe src="facebook.html"></iframe>
|
||||
|
||||
<button>Жми тут!</button>
|
||||
|
||||
<div>..И всё получится (хе-хе, у меня, злого хакера, получится)!</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,8 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body style="margin:10px;padding:10px">
|
||||
|
||||
<input type="button" onclick="alert('Нажата кнопка Like с другого домена!')" value="I LIKE IT !">
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,29 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<style>
|
||||
iframe { /* iframe с сайта-жертвы */
|
||||
width: 400px;
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
top:0; left:-20px;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>Нажмите, чтобы разбогатеть сейчас:</div>
|
||||
|
||||
<!-- URL, в реальности - с другого домена (атакуемого сайта) -->
|
||||
<iframe src="facebook.html"></iframe>
|
||||
|
||||
<button>Жми тут!</button>
|
||||
|
||||
<div>..И всё получится (хе-хе, у меня, злого хакера, получится)!</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<div>Меняет top.location на javascript.ru</div>
|
||||
|
||||
<script>
|
||||
top.location = 'http://javascript.ru';
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -0,0 +1,38 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
|
||||
<style>
|
||||
iframe { /* iframe с сайта-жертвы */
|
||||
width: 400px;
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
top:0; left:-20px;
|
||||
opacity: 0;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function attack() {
|
||||
|
||||
window.onbeforeunload = function() {
|
||||
window.onbeforeunload = null;
|
||||
return "Хотите уйти с этой страницы, не узнав все её тайны (хе-хе)?";
|
||||
};
|
||||
|
||||
document.body.insertAdjacentHTML('beforeend', '<iframe src="iframe.html">');
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<p>При нажатии на кнопку посетитель получит "странный" вопрос о том, не хочет ли уйти со страницы.</p>
|
||||
|
||||
<p>Наверно, он ответит "хочу остаться" и защита ифрейма будет провалена.</p>
|
||||
|
||||
<button onclick="attack()">Подключить "защищённый" iframe</button>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,114 +0,0 @@
|
|||
# Окно браузера: свойства и методы
|
||||
|
||||
Объект `window` выполняет две функции (или играет две роли):
|
||||
<ol>
|
||||
<li>Является глобальным объектом Javascript.</li>
|
||||
<li>Предоставляет интерфейс для работы с окном браузера.</li>
|
||||
</ol>
|
||||
|
||||
О глобальном объекте мы говорили в главе [](/closures). А здесь -- обратим внимание на то, что касается браузера.
|
||||
|
||||
[cut]
|
||||
|
||||
## События
|
||||
|
||||
Наиболее важные события при работе с окном браузера:
|
||||
<ul>
|
||||
<li>`onresize` -- событие изменения размера окна.</li>
|
||||
<li>`onscroll` -- событие при прокрутке окна.</li>
|
||||
<li>`onload` -- полностью загрузилась страница со всеми ресурсами.</li>
|
||||
</ul>
|
||||
|
||||
## Методы и свойства
|
||||
|
||||
<dl>
|
||||
<dt>`window.closed`
|
||||
<dd>Свойство `window.closed` равно `true`, если окно закрыто. Может быть использовано, чтобы проверить, закрыл ли посетитель попап.</dd>
|
||||
<dt>`window.close()`</dt>
|
||||
<dd>Закрывает попап без предупреждений и уведомлений. Вообще, метод `close()` можно вызвать для любого окна, в том числе, текущего. Но если окно открыто не с помощью `window.open()`, то браузер может проигнорировать вызов `close` или запросить подтверждение.
|
||||
|
||||
Нажмите кнопку ниже, для закрытия текущего окна.
|
||||
|
||||
<input type="button" onclick="window.close()" value="window.close()">
|
||||
|
||||
Попытайтесь это сделать в разных браузерах. На момент написания статьи, Firefox, Opera и Safari проигнорируют вызов `close()`, а IE запросит подтверждение.
|
||||
</dd>
|
||||
<dt>`window.title`</dt>
|
||||
<dd>Заголовок окна соостветствует содержимому элемента `TITLE` в `HEAD`.
|
||||
|
||||
**Забавно, но для *изменения* заголовка нужно использовать не это свойство, а `document.title`.**
|
||||
|
||||
Код, расположенный ниже, каждую секунду обновляет заголовок окна на текущее время:
|
||||
|
||||
```js
|
||||
//+ run
|
||||
setInterval(function() {
|
||||
document.title = new Date();
|
||||
}, 1000);
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Перемещение и изменение размеров окна
|
||||
|
||||
Существует несколько методов для перемещения/изменения размеров окна.
|
||||
|
||||
<dl>
|
||||
<dt>`win.moveBy(x,y)`</dt>
|
||||
<dd>Перемещает окно относительно текущего положения на `x` пикселей вправо и `y` пикселей вниз. Допускаются отрицательные значения.</dd>
|
||||
<dt>`win.moveTo(x,y)`</dt>
|
||||
<dd>Передвигает окно в заданную координатами `x` и `y` точку экрана монитора.</dd>
|
||||
<dt>`win.resizeBy(width,height)`</dt>
|
||||
<dd>Изменяет размер окна на заданную величину `width/height` (ширина/высота). Допускаются отрицательные значения.</dd>
|
||||
<dt>`win.resizeTo(width,height)`</dt>
|
||||
<dd>Изменяет размер окна на заданное значение.</dd>
|
||||
</dl>
|
||||
|
||||
Чтобы предотвратить использование этих методов с плохими целями, браузеры часто блокируют их выполнение. Как правило, они работают, если окно `win` открыто вызовом [window.open](https://developer.mozilla.org/en-US/docs/Web/API/window.open) из JavaScript текущей страницы и в нём нет дополнительных вкладок.
|
||||
|
||||
**JavaScript не может свернуть или максимизировать окно.**
|
||||
|
||||
Эти функции операционной системы от Frontend-разработчиков скрыты. Вызовы, описанные выше, в случае свёрнутого или максимизированного окна не работают.
|
||||
|
||||
## Прокрутка окна
|
||||
|
||||
Прокрутка окна требуется, пожалуй, чаще всего. Мы уже говорили о ней в главе [](/metrics-window):
|
||||
|
||||
<dl>
|
||||
<dt>`win.scrollBy(x,y)`
|
||||
Прокрутка окна на заданное число пикселей вперед или назад. Допускаются отрицательные значения.</dt>
|
||||
<dt>`win.scrollTo(x,y)`</dt>
|
||||
<dd>Прокручивает окно к заданным координатам.</dd>
|
||||
<dt>`elem.scrollIntoView(top)`</dt>
|
||||
<dd>Этот метод прокрутки вызывается на элементе. При этом окно прокручивается так, чтобы элемент был полностью видим.
|
||||
|
||||
Если параметр `top` равен `true` или не задан, то верх элемента совпадает с верхом окна. Если он равен `false`, то окно прокручивается так, чтобы нижний край элемента совпал с нижним краем окна.
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Итого
|
||||
|
||||
Что можно сделать с окном браузера:
|
||||
|
||||
<ul>
|
||||
<li>Открыть новое -- вызовом `open()`</li>
|
||||
<li>Проверить, закрыто ли всплывающее окно -- свойство `window.closed`</li>
|
||||
<li>Закрыть окно -- метод `window.close()`. Если окно открыто не через `open()`, то не сработает в Firefox, Opera и Safari).</li>
|
||||
<li>Поставить свой заголовок в `document.title`.</li>
|
||||
</ul>
|
||||
|
||||
Управление размером, положением, прокруткой окна:
|
||||
|
||||
<ul>
|
||||
<li>Переместить вправо-вниз -- `win.moveBy(x,y)`.</li>
|
||||
<li>Переместить в координату на экране монитора -- `win.moveTo(x,y)`.</li>
|
||||
<li>Изменить размер на данную величину -- `win.resizeBy(width, height)`.</li>
|
||||
<li>Выставить точный размер окна -- `win.resizeTo(width,height)`</li>
|
||||
<li>Прокрутить документ в окне на данную величину -- `win.scrollBy(x,y)`.</li>
|
||||
<li>Прокрутить документ в окне до точных координат (относительно документа) -- `win.scrollTo(x,y)`.</li>
|
||||
<li>Дополнительно для прокрутки можно использовать
|
||||
`elem.scrollIntoView(top)`, где top может быть true/false — прокрутка elem до совпадения нижней/верхней границы elem с нижней/верхней границей документа, так, чтобы elem был полностью видим. По умолчанию `true`.</li>
|
||||
</ul>
|
||||
|
||||
<p style="text-align:right">Спасибо Марату Шагиеву за помощь в выкладке русскоязычного варианта этой главы.</p>
|
|
@ -1,250 +0,0 @@
|
|||
# Общение с окном в ифрейме
|
||||
|
||||
Здесь предполагается, что мы прекрасно знаем, что такое ифрейм, и как подключить внешний документ с его использованием. Иначе говоря, HTML для нас не нов.
|
||||
|
||||
В этой главе мы рассмотрим способы работы с ними из JavaScript.
|
||||
|
||||
[cut]
|
||||
|
||||
## Ифрейм: тонкости с атрибутами
|
||||
|
||||
Хоть мы и знаем, что такое ифрейм, но позвольте заострить внимание на нескольких моментах, касающихся атрибутов.
|
||||
|
||||
<dl>
|
||||
<dt>Рамка: `frameborder="0"`</dt>
|
||||
<dd>Если хочется, чтобы в IE8- вокруг ифреймов не было рамок, то поставьте атрибут `frameborder="0"`.
|
||||
|
||||
В IE8- все ифреймы в примере ниже, кроме последнего, будут с рамкой:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<style> iframe { width: 100px; height:40px } </style>
|
||||
<ol>
|
||||
<li><iframe src="javascript:'тест'"></iframe></li>
|
||||
<li><iframe src="javascript:'тест'" style="border:0"></iframe></li>
|
||||
*!*
|
||||
<li><iframe src="javascript:'тест'" frameborder="0"></iframe></li>
|
||||
*/!*
|
||||
</ol>
|
||||
```
|
||||
|
||||
</dd>
|
||||
<dt>Пустой `src`</dt>
|
||||
<dd>Атрибут `src` может использовать протокол `javascript:...`. При этом код выполняется и его результат будет содержимым ифрейма. Этот способ описан в стандарте и поддерживается всеми браузерами.
|
||||
|
||||
Атрибут `src` является обязательным, и его отсутствие может привести к проблемам, вплоть до игнорирования ифрейма браузером.
|
||||
|
||||
**Чтобы ничего не загружать в ифрейм, укажите `src="javascript:false"`.**
|
||||
</dd>
|
||||
<dt>Атрибут `name` и создание ифрейма в IE7-</dt>
|
||||
<dd>В старых IE нельзя менять атрибут `name` после создания ифрейма:
|
||||
|
||||
```js
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.name = 'iName'; // в IE7- не сработает
|
||||
```
|
||||
|
||||
Поэтому, если нужна совместимость, создавайте ифреймы через `innerHTML`:
|
||||
|
||||
```js
|
||||
var tmp = document.createElement('div');
|
||||
tmp.innerHTML = '<iframe name="iName" src="javascript:false"></iframe>';
|
||||
|
||||
var iframe = tmp.firstChild;
|
||||
```
|
||||
|
||||
</dd>
|
||||
</dl>
|
||||
|
||||
## Ифрейм: доступ к document и window
|
||||
|
||||
Элемент `iframe` является обычным узлом DOM, как и любой другой. Существенное отличие -- в том, что с ним связан объект `window` внутреннего окна. Он доступен по ссылке `iframe.contentWindow`.
|
||||
|
||||
Таким образом, `iframe.contentWindow.document` будет внутренним документом.
|
||||
|
||||
[smart header="Когда-то..."]
|
||||
В старых браузерах использовались дополнительные свойства, такие как `iframe.contentDocument` и даже `iframe.document`, но они давно не нужны.
|
||||
[/smart]
|
||||
|
||||
В примере ниже JavaScript получает документ внутри ифрейма и модифицирует его:
|
||||
|
||||
```html
|
||||
<!--+ height="100" run -->
|
||||
<iframe src="javascript:'тест'" style="height:60px"></iframe>
|
||||
|
||||
<script>
|
||||
var iframe = document.getElementsByTagName('iframe')[0];
|
||||
*!*
|
||||
var iframeDoc = iframe.contentWindow.document;
|
||||
*/!*
|
||||
iframeDoc.body.style.backgroundColor = 'green';
|
||||
</script>
|
||||
```
|
||||
|
||||
[smart]
|
||||
Обратите внимание, HTML `'тест'` в примере выше -- невалидный, там нет `BODY`. Поэтому ифрейм будет отображаться в режиме совместимости.
|
||||
|
||||
Но браузер исправляет структуру и гарантирует, что после загрузки документа у него всегда есть `document.body` и ровно одно.
|
||||
[/smart]
|
||||
|
||||
В целях безопасности возможность доступа к документу в ифрейме ограничена. Если он с другого домена, на другом порту или протоколе, то доступ запрещён. Подробнее об этом ограничении и как его можно обойти -- далее, в главе [](/same-origin-policy).
|
||||
|
||||
## Иерархия window.frames
|
||||
|
||||
Альтернативный способ доступа к окну ифрейма -- это получить его из коллекции `window.frames`.
|
||||
|
||||
Есть два способа доступа:
|
||||
<ol>
|
||||
<li>`window.frames[0]` -- доступ по номеру.</li>
|
||||
<li>`window.frames.iframeName` -- доступ по `name` ифрейма.</li>
|
||||
</ol>
|
||||
|
||||
В коллекции хранится именно окно (`contentWindow`), а не тег.
|
||||
|
||||
Например:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<iframe src="javascript:''" style="height:80px" name="iframeName"></iframe>
|
||||
|
||||
<script>
|
||||
var iframe = document.body.children[0];
|
||||
var iframeWindow = iframe.contentWindow; // окно из тега
|
||||
|
||||
alert(frames[0] === iframeWindow); // true, окно из коллекции frames
|
||||
alert(frames.iframeName == iframeWindow); // true, окно из frames по имени
|
||||
</script>
|
||||
```
|
||||
|
||||
Внутри ифрейма могут быть свои вложенные ифреймы. Всё это вместе образует иерархию.
|
||||
|
||||
Ссылки для навигации по ней:
|
||||
|
||||
<ul>
|
||||
<li>**По детям:** `window.frames` -- коллекция "детей" (вложенных ифреймов)</li>
|
||||
<li>**На родителя:** `window.parent` -- содержит ссылку на родительское окно, позволяет обратиться к нему из ифрейма.
|
||||
|
||||
Всегда верно:
|
||||
|
||||
```js
|
||||
// (из окна со фреймом)
|
||||
window.frames[0].parent === window; // true
|
||||
```
|
||||
|
||||
</li>
|
||||
<li>**На корень:** `window.top` -- содержит ссылку на самое верхнее окно.
|
||||
|
||||
Всегда верно:
|
||||
|
||||
```js
|
||||
// (в предположении, что вложенные фреймы существуют)
|
||||
window.frames[0].frames[0].frames[0].top === window
|
||||
```
|
||||
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
**Свойство `top` позволяет легко проверить, во фрейме ли находится текущий документ:**
|
||||
|
||||
```js
|
||||
//+ run
|
||||
if (window == top) {
|
||||
alert('Этот скрипт является окном верхнего уровня в браузере');
|
||||
} else {
|
||||
alert('Этот скрипт исполняется во фрейме!');
|
||||
}
|
||||
```
|
||||
|
||||
## Событие onload
|
||||
|
||||
У ифрейма есть своё событие `onload`, которое не связано с `onload` основного окна.
|
||||
|
||||
Иными словами, `onload` основного окна не ждёт, пока ифрейм догрузится.
|
||||
|
||||
**Событие `onload` есть и на теге `iframe` и на его окне.**
|
||||
|
||||
Это важно, так как обработчик на теге более универсален.
|
||||
|
||||
**В случае, когда документ с другого домена, внешний документ сможет отследить его загрузку только по `onload` на теге.**
|
||||
|
||||
Например, попытаемся отследить загрузку ифрейма с сайта `http://vk.com/`:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<iframe src="http://vk.com/" name="vk" style="height:100px"></iframe>
|
||||
|
||||
<script>
|
||||
// поставить onload на элемент
|
||||
document.getElementsByTagName('iframe')[0].onload = function() {
|
||||
alert('Фрейм загрузился')
|
||||
}
|
||||
|
||||
// set onload on window
|
||||
frames.vk.onload = function() {
|
||||
alert('Окно фрейма загрузилось')
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Запустите пример выше. Вы увидите, что работает только `iframe.onload`. А если бы ифрейм был с того же домена, то сработали бы оба обработчика.
|
||||
|
||||
|
||||
## Атрибуты seamless и sandbox
|
||||
|
||||
В стандарте HTML5 для [iframe](http://www.w3.org/wiki/HTML/Elements/iframe) предусмотрены атрибуты `seamless` и `sandbox`.
|
||||
|
||||
Оба они на момент написания (конец 2012) поддерживаются в Chrome, в других браузерах поддержка тоже не за горами.
|
||||
|
||||
### seamless
|
||||
|
||||
Атрибут `seamless` полностью интегрирует ифрейм в документ, убирая рамку и применяя CSS-стили внешнего окна к содержимому ифрейма, как будто это обычный элемент.
|
||||
|
||||
То есть, если в основном документе есть стиль `p { font-weight: bold }`, то он повлияет и на содержимое ифрейма.
|
||||
|
||||
Для JavaScript он не важен.
|
||||
|
||||
### sandbox
|
||||
|
||||
Атрибут `sandbox` позволяет построить "песочницу" вокруг ифрейма, запретив ему выполнять ряд действий.
|
||||
|
||||
Наличие атрибута `sandbox`:
|
||||
<ul>
|
||||
<li>Заставляет браузер считать ифрейм загруженным с другого домена, так что он и внешнее окно больше не могут обращаться к переменным друг друга.</li>
|
||||
<li>Отключает формы и скрипты в ифрейме.</li>
|
||||
<li>Запрещает менять `parent.location` из ифрейма.</li>
|
||||
</ul>
|
||||
|
||||
Пример ниже загружает в такой ифрейм документ:
|
||||
|
||||
```html
|
||||
<!--+ run height=200 -->
|
||||
<iframe *!*sandbox*/!* src="/files/tutorial/window/sandboxed.html"></iframe>
|
||||
```
|
||||
|
||||
Содержимое файла `sandboxed.html`:
|
||||
|
||||
```html
|
||||
<script>
|
||||
alert(1);
|
||||
</script>
|
||||
|
||||
<form action="http://google.ru">
|
||||
<input type="submit" value="Отправить форму на http://google.ru">
|
||||
</form>
|
||||
```
|
||||
|
||||
Запустите пример выше для просмотра. Ни форма ни скрипты не сработают.
|
||||
|
||||
**Атрибут `sandbox` может содержать флаги через пробел, которые убирают ограничения:**
|
||||
<dl>
|
||||
<dt>allow-same-origin</dt>
|
||||
<dd>Браузер может не считать документ в ифрейме пришедшим с другого же домена. Если ифрейм *и так* с другого домена, то ничего не меняется.</dd>
|
||||
<dt>allow-top-navigation</dt>
|
||||
<dd>Разрешает ифрейму менять `parent.location`.</dd>
|
||||
<dt>allow-forms</dt>
|
||||
<dd>Разрешает отправлять формы из `iframe`.</dd>
|
||||
<dt>allow-scripts</dt>
|
||||
<dd>Разрешает выполнение скриптов из ифрейма. Но скриптам, всё же, будет запрещено открывать попапы.</dd>
|
||||
</dl>
|
||||
|
||||
Цель атрибута `sandbox` -- наложить дополнительные ограничения. Он не может снять уже существующие, в частности, убрать ограничения безопасности, если ифрейм с другого домена.
|
|
@ -1,163 +0,0 @@
|
|||
# Ограничение "Same Origin"
|
||||
|
||||
Ограничение "Same Origin" ("тот же источник") ограничивает доступ окон и фреймов друг к другу, а также влияет на AJAX-запросы к серверу.
|
||||
|
||||
Причина, по которой оно существует -- безопасность. Если есть два окна, в одном из которых `vasya-pupkin.com`, а в другом `gmail.com`, то мы бы не хотели, чтобы скрипт из первого мог читать нашу почту.
|
||||
|
||||
Сама концепция проста, но есть много важных исключений и особенностей, которые нужно знать для полного понимания этого правила.
|
||||
[cut]
|
||||
|
||||
## Same Origin [[#same-origin]
|
||||
|
||||
[summary]
|
||||
Два URL считаются имеющим один источник ("same origin"), если у них одинаковый протокол, домен и порт.
|
||||
[/summary]
|
||||
|
||||
Эти URL имеют один источник:
|
||||
<ul>
|
||||
<li>`http://site.com`</li>
|
||||
<li>`http://site.com`/</li>
|
||||
<li>`http://site.com/my/page.html`</li>
|
||||
</ul>
|
||||
|
||||
А вот эти -- все из других источников:
|
||||
<ul>
|
||||
<li>http://<span style="color:red">www.</span>site.com (другой домен)</li>
|
||||
<li>http://site.<span style="color:red">org</span> (другой домен)</li>
|
||||
<li>http<span style="color:red">s</span>://site.com (другой протокол)</li>
|
||||
<li>http://site.com<span style="color:red">:8080</span> (другой порт)</li>
|
||||
</ul>
|
||||
|
||||
Существует ряд исключений, позволяющих-таки окнам с разных доменов обмениваться информацией, но прямой вызов методов друг друга и чтение свойств запрещены.
|
||||
|
||||
## Пример ограничений
|
||||
|
||||
Если одно окно попытается обратиться к другому, то браузер проверит, из одного ли они источника. Если нет -- доступ будет запрещён.
|
||||
|
||||
Например:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<iframe src="http://vk.com" name="vk" style="height:100px"></iframe>
|
||||
|
||||
<script>
|
||||
var iframe = document.getElementsByName('vk')[0];
|
||||
iframe.onload = function() {
|
||||
try {
|
||||
alert(iframe.contentWindow.document);
|
||||
} catch(e) {
|
||||
alert("Ошибка: " + e.message);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
**При запуске примера выше Safari/Chrome могут вместо ошибки вывести `undefined`.**
|
||||
|
||||
Это их способ (некорректный) показать, что "чтение запрещено". Будем надеяться, исправят.
|
||||
|
||||
|
||||
|
||||
## Исключение: запись в location
|
||||
|
||||
Окно и вложенный в него `iframe` могут менять `location` друг друга, даже если они из разных источников.
|
||||
|
||||
Причём *читать* `location` нельзя, одно окно не имеет право знать, на каком URL пользователь в другом. А вот *запись* браузеры считают безопасной.
|
||||
|
||||
Например, открыв на `javascript.ru` ифрейм с `vk.com`, из этого ифрейма нельзя будет узнать URL, а вот поменять его -- запросто:
|
||||
|
||||
```html
|
||||
<!--+ run -->
|
||||
<iframe src="http://vk.com" name="vk"></iframe>
|
||||
|
||||
<script>
|
||||
var iframe = document.getElementsByName('vk')[0];
|
||||
|
||||
iframe.onload = function () {
|
||||
|
||||
try { // попытка чтения
|
||||
*!*
|
||||
alert(iframe.contentWindow.location.href); // неудачно
|
||||
*/!*
|
||||
} catch(e) {
|
||||
alert('Ошибка: ' + e.message);
|
||||
}
|
||||
|
||||
try { // запись в location будет успешной
|
||||
*!*
|
||||
iframe.contentWindow.location = 'http://wikipedia.org';
|
||||
*/!*
|
||||
alert('Перенаправили на http://wikipedia.org');
|
||||
}
|
||||
|
||||
};
|
||||
</script>
|
||||
```
|
||||
|
||||
## Исключение: поддомен 3го уровня
|
||||
|
||||
Ещё одно важное исключение касается доменов третьего уровня.
|
||||
|
||||
Например, у нас есть окно на `http://site.com` и два ифрейма: первый с источника `http://john.site.com`, а второй -- с `http://peter.site.com`.
|
||||
|
||||
**Если несколько окон присваивают в `document.domain` свой общий поддомен 2го уровня, то все ограничения снимаются.**
|
||||
|
||||
Важно:
|
||||
<ol>
|
||||
<li>Должен быть общий поддомен второго уровня.
|
||||
|
||||
Можно поставить `document.domain='site.com'` на странице с `my.site.com`, но нельзя это сделать на странице с `vaysa-pupkin.ru`.</li>
|
||||
<li>Свойство `document.domain` должно быть присвоено на всех окнах, участвующих в коммуникации, в том числе на том, которое и так с этого домена.
|
||||
|
||||
Выглядит абсурдно, но на документе с `site.com` нужно вызвать: `document.domain=document.domain`. Тогда будет работать.</li>
|
||||
</ol>
|
||||
|
||||
|
||||
## Исключение: порт в IE
|
||||
|
||||
В браузере Internet Explorer порт не входит в понятие "источник" (origin).
|
||||
|
||||
Это означает, что окно с `http://site.com` может свободно общаться с `http://site.com:8080`.
|
||||
|
||||
Это иногда используют для общения разных сервисов, использующих один IP-адрес. Но допустимо такое только в IE.
|
||||
|
||||
## Исключение: зона в IE
|
||||
|
||||
Если сайт находится в зоне "Надёжные узлы", то в Internet Explorer ограничения к нему не применяются.
|
||||
|
||||
При этом подразумевается, что для этой зоны в параметрах "Безопасность" включена опция "Доступ к источникам данных за пределами домена".
|
||||
|
||||
## Общение между окнами с разных источников
|
||||
|
||||
Если ни одно из исключений выше не подошло, то есть три основных способа, как окнам общаться между собой:
|
||||
|
||||
<ol>
|
||||
<li>Все современные браузеры, включая IE8+, поддерживают специальный интерфейс `postMessage` для общения между окнами с разных доменов. Мы рассмотрим его в отдельной главе [](/cross-window-messaging-with-postmessage).</li>
|
||||
<li>Одно окно может поменять другому `location.hash` -- часть пути после `#`. При этом не произойдёт смены `URL`, но другое окно, увидев это, может прочитать из хэша информацию и, в свою очередь, ответить.
|
||||
|
||||
Этот способ испольуется там, где требуется поддержка IE7-.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
В совокупности с этим можно использовать интерфейс `localStorage` для оповещении всех окон и вкладок о событии. При сохранении данных в `localStorage` генерируется событие `onstorage`, причём сразу на всех окнах, фреймах и табах с тем же доменом.
|
||||
|
||||
Кроме того, если вы используете `IFRAME` вместе с AJAX, то, возможно, вам пригодятся способы, описанные в главе [](/ajax-iframe-xdomain).
|
||||
|
||||
|
||||
|
||||
## Итого
|
||||
|
||||
Ограничение "одного источника" запрещает окнам и фреймам с разных источников вызывать методы друг друга и читать данные друг из друга.
|
||||
|
||||
При этом "из одного источника" означает "совпадают протокол, домен и порт".
|
||||
|
||||
Как ни странно, у этого подхода ряд существенных исключений:
|
||||
|
||||
<ul>
|
||||
<li>Свойство `window.location` нельзя читать, но можно менять.</li>
|
||||
<li>Домены третьего уровня с общим наддоменом могут поменять `document.domain` на их общий домен второго уровня, и тогда они смогут взаимодействовать без ограничений.
|
||||
</li>
|
||||
<li>IE не включает порт в понятие источника. Кроме того, он позволяет снять ограничения для конкретного сайта включением в доверенную зону.</li>
|
||||
</ul>
|
||||
|
||||
В современных браузерах (IE8+) кросс-доменное общение можно организовать через `location.hash` и [postMessage](/cross-window-messaging-with-postmessage). При этом IE8 не умеет передавать данные между окнами, но, если нужно, `localStorage` передаёт всё и везде :)
|
|
@ -1,250 +0,0 @@
|
|||
# Атака Clickjacking и защита от неё
|
||||
|
||||
Атака "кликджекинг" (англ. Clickjacking) позволяет хакеру выполнить клик на сайте-жертве *от имени посетителя*.
|
||||
|
||||
В русском языке встречается дословный перевод термина clickjacking: "угон клика". Так же применительно к clickjacking-атаке можно встретить термины "перекрытие iframe" и "подмена пользовательского интерфейса".
|
||||
|
||||
Кликджекингу подверглись в своё время Twitter, Facebook , PayPal, YouTube и многие другие сайты. Сейчас, конечно, они уже защищены.
|
||||
[cut]
|
||||
## Идея атаки
|
||||
|
||||
В целом идея очень проста.
|
||||
|
||||
Вот как выглядит «угон клика» пользователя, который зарегистрирован на facebook:
|
||||
|
||||
<ol>
|
||||
<li>На вредоносной странице пользователю подсовывается безобидная ссылка
|
||||
(скажем, что-то скачать, «разбогатеть сейчас», посмотреть ролик или просто перейти по ссылке на интересный ресурс).</li>
|
||||
<li>Поверх этой заманчивой ссылки помещен прозрачный iframe со страницей facebook.com, так что кнопка "Like" находится чётко над ней.
|
||||
|
||||
Кликая на ссылку, посетитель на самом деле нажимает на эту кнопку.
|
||||
</li>
|
||||
</ol>
|
||||
|
||||
## Пример
|
||||
|
||||
Вот пример (для наглядности `iframe` -- полупрозрачный):
|
||||
|
||||
```html
|
||||
<!--+ run height=120 -->
|
||||
<style>
|
||||
iframe { /* iframe с сайта-жертвы */
|
||||
width: 300px;
|
||||
height: 100px;
|
||||
position: absolute;
|
||||
top:0; left:-20px;
|
||||
filter: alpha(opacity=50); /* в рельности будет opacity=50 */
|
||||
opacity: 0.5;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>Нажмите, чтобы разбогатеть сейчас:</div>
|
||||
|
||||
<!-- URL в реальности - с другого домена (атакуемого сайта) -->
|
||||
<iframe src="https://js.cx/clickjacking/facebook.html"></iframe>
|
||||
|
||||
<a href="http://google.com">Нажми тут!</a>
|
||||
|
||||
<div>..И всё получится (хе-хе, у меня, злого хакера, получится)!</div>
|
||||
```
|
||||
|
||||
**При клике на ссылку на самом деле происходит клик на iframe (на «Like»).**
|
||||
|
||||
Если посетитель авторизован на facebook (а в большинстве случаев так и есть), то facebook.com получает щелчок от имени посетителя.
|
||||
|
||||
На Twitter это была бы кнопка «Follow».
|
||||
|
||||
Тот же самый код, но как это было бы в реальности, с `opacity:0` для iframe (щелкните, чтобы увидеть захваченную кнопку):
|
||||
|
||||
[iframe src="clickjacking" height=120 link edit]
|
||||
|
||||
Итак, все, что нужно для проведения атаки -- это правильно расположить iframe на вредоносной странице. В большинстве случаев это делается средствами HTML/CSS.
|
||||
|
||||
События клавиатуры захватить гораздо труднее, потому что, если `iframe` является невидимым, то текст в поле ввода также будет невидимым. Посетитель начнёт печатать, но, не увидев текст, прекратит свои действия.
|
||||
|
||||
## Защита и способы обхода.
|
||||
|
||||
Самый старый метод защиты -- это код JavaScript, не позволяющий отобразить веб-страницу внутри фрейма (*framebusting*, также его называют *framekilling* и *framebreaking*).
|
||||
|
||||
Добавьте к документу следующий код, если не хотите, чтобы документ загружался в iframe:
|
||||
|
||||
<script>
|
||||
if (top != window) {
|
||||
top.location = window.location;
|
||||
}
|
||||
</script>
|
||||
|
||||
То есть, если окно обнаруживает, что оно загружено во фрейме, то оно автоматически делает себя верхним.
|
||||
|
||||
В настоящий момент это уже не является сколько-нибудь надежной защитой.
|
||||
|
||||
Есть несколько способов обхода framebusting. Давайте рассмотрим некоторые из них.
|
||||
|
||||
### Блокировка top-навигации.
|
||||
|
||||
Можно заблокировать переход, инициированный сменой `top.location`, в событии [onbeforeunload](#window.onbeforeunload).
|
||||
|
||||
Обработчик этого события ставится на внешней (хакерской) странице и, при попытке `iframe` поменять `top.location`, спросит посетителя, хочет он покинуть данную страницу. В большинстве браузеров хакер может спросить посетителя, используя своё сообщение.
|
||||
|
||||
Так что, скорее всего, посетитель ответит на такой странный вопрос отрицательно (он же не знает про ифрейм, видит только страницу, причины для ухода нет).
|
||||
|
||||
В приведенном ниже примере представлен "защищенный" `iframe`:
|
||||
|
||||
```html
|
||||
<!--+ src="cj_location.html" play -->
|
||||
```
|
||||
|
||||
А вот -- хакерская страница с этим `iframe`, которая отменяет перезагрузку верхнего окна при помощи `onbeforeunload` (предполагается, что посетитель нажмёт «отмену»):
|
||||
|
||||
```html
|
||||
<script>
|
||||
window.onbeforeunload = function() {
|
||||
window.onbeforeunload = null;
|
||||
return "Хотите уйти с этой страницы, не узнав все её тайны (хе-хе)?";
|
||||
}
|
||||
</script>
|
||||
|
||||
<iframe src="/files/tutorial/window/cj_location.html" style="height:80px"></iframe>
|
||||
```
|
||||
|
||||
[Открыть в отдельном окне (сразу спросит)](/files/tutorial/window/cj_location_hack.html).
|
||||
|
||||
### security = "restricted"
|
||||
|
||||
В IE8 есть особый атрибут `security = "restricted"` -- отключающий исполнение скриптов во фрейме.
|
||||
|
||||
Пример использования:
|
||||
|
||||
```html
|
||||
<iframe security="restricted" src="/files/tutorial/window/cj_location.html" style="height:80px"></iframe>
|
||||
```
|
||||
|
||||
При этом клик во фрейме, если он не требует JavaScript, всё равно сработает, а вот защита -- нет.
|
||||
|
||||
### HTML5
|
||||
|
||||
Одна из возможностей HTML5 -- атрибут [sandbox](http://www.whatwg.org/specs/web-apps/current-work/multipage/the-iframe-element.html#attr-iframe-sandbox)
|
||||
|
||||
Он позволяет разрешить во фрейме скрипты `allow-scripts` и формы `allow-forms`, но запретить top-навигацию (не указать `allow-top-navigation`).
|
||||
|
||||
Выглядеть это будет так:
|
||||
|
||||
```html
|
||||
<iframe *!*sandbox="allow-scripts allow-forms"*/!* src="/files/tutorial/window/cj_location.html"></iframe>
|
||||
```
|
||||
|
||||
Сработает в браузерах, которые поддерживают `sandbox`, например -- в Chrome.
|
||||
|
||||
### Ещё приёмы
|
||||
|
||||
Есть и еще приёмы для обхода этой простейшей защиты.
|
||||
|
||||
Firefox и старый IE могут активировать designMode на исходной странице, это также предотвращает framebusting (спасибо за идею owasp.org, страница clickjacking).
|
||||
|
||||
Как мы видим, она не только не выдерживает реальной атаки, но и может скомпрометировать сайт (программист-то думает, что защитил его).
|
||||
|
||||
## Надежная защита от Clickjacking
|
||||
|
||||
### Заголовок X-Frame-Options
|
||||
|
||||
Все современные браузеры поддерживают заголовок `X-Frame-Options`.
|
||||
|
||||
Он разрешает или запрещает отображение страницы, если она открыта во фрейме.
|
||||
|
||||
Браузеры игнорируют заголовок, если он определен в МЕТА тег. Таким образом, `<meta http-equiv="X-Frame-Options"...>` будет проигнорирован.
|
||||
|
||||
У заголовка может быть три значения:
|
||||
|
||||
<dl>
|
||||
<dt>SAMEORIGIN</dt>
|
||||
<dd>Рендеринг документа, при открытии во фрейме, производится только в том случае, когда верхний (top) документ -- с того же домена.</dd>
|
||||
<dt>DENY</dt>
|
||||
<dd>Рендеринг документа внутри фрейма запрещён.</dd>
|
||||
<dt>ALLOW-FROM domain</dt>
|
||||
<dd>Разрешает рендеринг, если внешний документ с данного домена (не поддерживается в Safari, Firefox).</dd>
|
||||
</dl>
|
||||
|
||||
К примеру, Twitter использует `X-Frame-Options: SAMEORIGIN`. Результат:
|
||||
|
||||
```html
|
||||
<iframe src="http://twitter.com"></iframe>
|
||||
```
|
||||
|
||||
<iframe src="http://twitter.com"></iframe>
|
||||
|
||||
В зависимости от браузера, `iframe` выше либо пустой, либо в нём находится сообщение о невозможности отобразить его (IE).
|
||||
|
||||
### Приостановка показа документа
|
||||
|
||||
Если нужно поддерживать старые браузеры (IE7-), то можно и просто отменить показ документа:
|
||||
|
||||
```html
|
||||
<head>
|
||||
<style> body { display : none } </style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script>
|
||||
if (self == top) {
|
||||
document.getElementsByTagName('body').style.display = "block";
|
||||
} else {
|
||||
top.location = self.location;
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
[warn header="Почему не `document.body`?"]
|
||||
В приведенном выше примере, мы используем `document.getElementsByTagName`, вместо `document.body`, потому что это способ получения `BODY` работает во всех браузерах, когда документ еще не готов.
|
||||
[/warn]
|
||||
|
||||
Обратите внимание -- изначально документ скрыт. Мало ли, вдруг JavaScript во фрейме отключен -- защита сработает.
|
||||
|
||||
Он будет показан только в том случае, если заведомо всё в порядке: не отключён JavaScript и страница не во фрейме.
|
||||
|
||||
### Показ с отключённым функционалом
|
||||
|
||||
Заголовок `X-Frame-Options` имеет неприятный побочный эффект. Иногда поисковики, анонимайзеры или другие сайты хотели бы отобразить страницу в `iframe`, по вполне "легальным" причинам, но не могут.
|
||||
|
||||
Хорошо бы показывать их посетителям не пустой `iframe`, а нечто, что может быть более интересно.
|
||||
|
||||
Например, можно изначально "накрывать" документ `div` с `height:100%;width:100%`, который будет перехватывать все клики. И поставить на нём ссылку, ведующую на страницу в новом окне.
|
||||
|
||||
```html
|
||||
<style>
|
||||
#iframe-protector {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 0;
|
||||
z-index: 99999999;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div id="iframe-protector">
|
||||
<a href="/" target="_blank">Перейти на сайт</a>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
if (top.document.domain == document.domain) {
|
||||
убрать iframe-protector
|
||||
}
|
||||
</script>
|
||||
```
|
||||
|
||||
Если страница -- не во фрейме или домен совпадает, то посетитель не увидит его.
|
||||
|
||||
## Заключение
|
||||
|
||||
Атаку "Clickjacking" легко осуществить, если на сайте есть действие, активируемое с помощью одного клика.
|
||||
|
||||
Злоумышленник может осуществить атаку через целенаправленно на посетителей ресурса -- опубликовав ссылку на форуме, или «счастливой рассылкой». Существует масса вариантов.
|
||||
|
||||
<ul>
|
||||
<li>Рекомендуется использовать X-Frame-Options на страницах, заведомо не предназначеных для запуска во фрейме и на важнейших страницах (финансовые транзакции).</li>
|
||||
<li>Используйте защиту через framebusting для защиты IE7-, т.к. там не поддерживается X-Frame-Options.</li>
|
||||
<li>Используйте перекрывающий DIV, если это допустимо вашим проектом и вы хотите разрешить безопасный показ документа во фреймах с любых доменов.</li>
|
||||
</ul>
|
||||
|
||||
<div style="text-align:right; font-style:italic">При участии Марата Шагиева</div>
|
|
@ -1,14 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<div>Меняет top.location на google.com</div>
|
||||
|
||||
<script>
|
||||
top.location = 'http://google.com';
|
||||
</script>
|
||||
|
||||
<input type="button" value="Like" onclick="alert('Кнопка сработала')">
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,25 +0,0 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<body>
|
||||
<style>
|
||||
iframe { /* iframe с сайта-жертвы */
|
||||
width:300px;
|
||||
height:100px;
|
||||
position:absolute;
|
||||
top:0; left:-20px;
|
||||
filter:alpha(opacity=0);
|
||||
opacity:0;
|
||||
z-index: 1;
|
||||
}
|
||||
</style>
|
||||
|
||||
<div>Нажмите, чтобы разбогатеть сейчас:</div>
|
||||
|
||||
<iframe src="/files/tutorial/window/facebook.html"></iframe>
|
||||
|
||||
<a href="http://google.com">Нажми тут!</a>
|
||||
|
||||
<div>..И ваша жизнь удалась!</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,9 +1,5 @@
|
|||
# Регулярные выражения [в работе]
|
||||
|
||||
Регулярные выражения -- мощный способ поиска и замены строк, который используется в самых разных языках, включая, конечно, JavaScript.
|
||||
Регулярные выражения -- мощный способ поиска и замены для строк.
|
||||
|
||||
У меня здесь для вас две новости.
|
||||
<ol>
|
||||
<li>Первая плохая -- в JavaScript они на редкость убогие, хуже большинства существующих языков.</li>
|
||||
<li>Вторая хорошая -- зато нам будет проще их изучить.</li>
|
||||
</ol>
|
||||
В JavaScript они поддерживаются в простом варианте, менее мощном, чем в большинстве других языков. Но зато нам будет проще их изучить.
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue