Browsing Context, WindowProxy, Window

от автора

Каждый Frontend-разработчик знает, что такое объект Window. С самими объектом, вроде бы, все понятно. Но при детальном рассмотрении оказывается, что браузер никогда не отдает этот важнейший глобальный объект напрямую. В этой статье я предлагаю разобраться в спецификации HTML и в том, как именно ведет себя браузер в части глобального контекста.

Начать стоит с того, что в документе может быть несколько глобальных объектов Window (например, iframe на странице). Более того, самих документов тоже может быть несколько (например, открытые табы браузера). Между документами и их объектами Window возможна некоторая связанность посредством frame.contentWindowself.openerwindow.open() и пр. Согласно спецификации, каждый браузерный документ, будь то таб, iframe или что-то еще, является так называемым navigable. В свою очередь, кажды navigable в спецификации HTML называется browsing context. Сам же browsing context имеет ассоциированные объекты WindowProxy и Window. Когда мы переключаем контекст, объект Window меняется на соответствующий, а вот WindowProxy всегда один и тот же.

Дело в том, что WindowProxy является как бы универсальной оберткой-прокси для множества Window. Он прозрачно отдает все доступные свойства Window, однако, сам Window может время от времени меняться. Так же, в отличие от Window, являющимся обычным объектом (ordinary object), WindowProxy является необычным (exotic object). Напомню, обычными считаются все классические объекты в JavaScript, которые мы можем свободно создавать. Необычными считаются те объекты, поведение которых может иметь некую скрытую логику, к таким объектам относят, например ArrayStringArguments и др.

WindowProxy, будучи exotic, имеет слот [[Window]], а так же ряд скрытых методов: GetPrototypeOfSetPrototypeOfIsExtensiblePreventExtensionsGetOwnProperty, которые призваны обеспечить работу объекта, как прокси. 

Зачем же нужны такие сложности, не проще ли просто отдавать Window как есть? Ответ прост, основная причина — политика безопасности. Мы знаем, что политика same-origin запрещает доступ к коду одного origin из другого. Т.е. нельзя с домена a.com получить доступ к коду b.com. Однако, например, если b.com был размещен внутри a.com через iframe, есть некоторые послабления в политике и доступ к коду все получить можно, что потенциально создает дыры в системе безопасности.

Представим следующий код

Что делает этот код

// вешаем onload колбэк на iframe, который сработает при // загрузке/перезагрузке фрейма frame.onload = function onFrameLoad() {   // после загрузки фрейма сохраним ссылку на contentWindow   cWindow = frame.contentWindow;      // а так же, создадим глобальную переменную a в контексте Window   cWindow.a = "a";      // Выведем результат на экран.   //   // Функция ниже сравнивает ссылку на сохраненный contentWindow   // c реальным contentWindow:   //   // cWindow === frame.contentWindow   //   // а так же, выводит значение глобальной переменной a из Window   //   // const printResult = (id) => {   //   const isEqual = cWindow === frame.contentWindow;   //   const value = cWindow.a;   //   //   document.getElementById(id).innerHTML = contentWindow is equal: ${isEqual};\nvalue: ${value};   // }   printResult("result-1");      // далее повесим новый колбэк на onload этого же iframe   frame.onload = function () {     // и еще раз выведем результат после перезгрузки фрейма     printResult("result-2");   } }

Мы видим, что после первой загрузки фрейма ссылка cWindow идентична frame.contentWindow, а глобальная переменная window.a = 'a', как и ожидалось.

После второй загрузки фрейма видим, что cWindow по прежнему идентична frame.contentWindow, но window.aуже не существует в этом контексте. С точки зрения политики безопасности — это правильно, iframe не должен иметь возможности мутировать родительский глобальный объект. Эта и другие проблемы безопасности в дочерних контекстах были предметом головной боли и детального обсуждения Бобби Холлей, Борисом Жбарски, Яном Хиксона, Адамом Варта и Эн ван Кестерен на протяжении многих лет, которое завершилось пул-реквестом «Define security around Window, WindowProxy, and Location properly«.

Но как такое оказалось возможным технически? Ведь ссылка на контекст не изменилась? Как в одном и том же контексте переменная может существовать и не существовать одновременно?

Всему виной WindowProxy. Как я уже говорил, работая в объектом Window, мы, на самом деле, всегда обращаемся к WindowProxy, который обеспечивает изоляцию Window внутри browsing context. При этом ссылка на сам WindowProxy остается перманентной. Именно поэтому cWindow === frame.contentWindow всегда возвращает true, обе ссылки — ссылки на WindowProxy. Однако, перезагрузив фрейм мы создали второй контекст Window, который будет существовать внутри WindowProxyManager. Обращаясь к глобальной переменной, WindowProxy определяет, из какого контекста происходит обращение и выставляет нужный Windowв слот [[Window]].


Эту и другие мои статьи, так же, читайте в моем канале

RU: https://t.me/frontend_almanac_ru
EN: https://t.me/frontend_almanac


ссылка на оригинал статьи https://habr.com/ru/articles/779252/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *