
Введение
Всем привет! Меня зовут Максим Иванов. Сегодня я хотел бы провести небольшой исторический экскурс и объяснить, почему Document Picture-in-Picture — это не просто способ отображать видео в формате «картинка в картинке», а новое и любопытное API, которое потенциально может заменить привычный всем window.open.
Возможность выводить видео в режиме PiP появилась еще в сентябре 2018 года в Chrome 69. С тех пор прошло более семи лет активного тестирования и развития. В 2019 году подобный механизм появился в Safari, а к 2020-му — и в Firefox.
Теперь, когда почти каждый браузер умеет открывать видео в отдельном плавающем окне, возникает логичный вопрос: могут ли браузеры с той же легкостью открывать в отдельном окне интерактивный HTML-контент, а не только видео? И если да, то каким образом? Давайте поговорим об этом ниже.
Оглавление
1. window.open
Старый добрый window.open(...) все еще часто используемый и практически единственный кросс-браузерный способ в JavaScript открыть всплывающее окно или отдельную вкладку браузера. Когда вы только начинаете изучать JavaScript, вам сразу демонстрируют следующий код:
window.open('https://habr.com', '_blank'); // window.open(url, target)
Он позволяет прямо с вашей страницы открывать внешние ссылки:
1.1. Режимы открытия (navigation target browsing context)
Второй аргумент у функции window.open(url, target) принимает такие параметры как _blank , _top, _self, _parent, _unfencedTop, это не элементы интерфейса и не часть DOM, они просто определяют куда браузер загрузит страницу, а не как она будет выглядеть.
_self
Открывается страницу в том же окне (или вкладке браузера), где пользователь сейчас находится;
Это поведение по умолчанию (если не указано иное настройками браузера);
-
Пример:
window.open('https://habr.com'); // Эквивалентно обычному: // location.href = 'https://habr.com';
_blank
Открывает страницу в новой вкладке (или новом окне в зависимости от настроек браузера);
-
Самый частый случай использования, эквивалентно в HTML клику по ссылке:
<a href="https://habr.com" target="_blank">habr.com</a> Именно _blank часто блокируется блокировщиками всплывающих окон, если вызов не связан с кликом пользователя.
_parent
Открывает страницу в родительском фрейме, если документ находится внутри iframe;
Если фрейма нет — действует как _self.
_top
Открывает в самом верхнем окне, то есть заменяет всю структуру фреймов;
-
Это полезно, если вы хотите «вырваться» из вложенных цепочек iframe и вы не знаете на каком уровне сейчас, например при авторизации:
window.open('/dashboard', '_top');
_unfencedTop
Появилось вместе с технологией приватности - Fenced Frames API от Google
1.1.1 _blank — создание нового окна/вкладки
Почему именно _blank блокируется, а не _top, _self, _parent? На самом деле, логика тут простая. Браузеры блокируют не сам метод window.open(), а попытку создать новое окно без явного действия пользователя, да и то, только тогда, когда это может быть навязчивым.
_blank = создание нового окна.
Благодаря этому параметру мы создаем новый контекст браузера, то есть вкладку (отдельное окно). Это визуально и системно больше, чем просто смена URL. Раньше сайты злоупотребляли этим. При заходе на страницу открывались десятки popup-окон с рекламой. Каждое новое окно могло имитировать системные диалоги, делать фишинг или подменять адресную строку.
Период примерно с 1999 по 2004 год был временем хаоса, интернет тех лет буквально засыпан всплывающими окнами. Сайты открывали десятки window.open при загрузке или при уходе со страницы и даже по таймерам. Реклама, фишинг, «подарки дня» и все это через всплывающие окна. Пользователь мог закрыть одно окно и тут же открывалось три новых. Internet Explorer 6 SP2 (2004) и Firefox 1.0 (2004) стали первыми браузерами, где появилась базовая защит. То есть браузеры решили: «Если пользователь сам не кликал, то сайт не имеет права открывать новое окно». Safari и Opera внедрили то же самое чуть раньше, но именно Firefox и IE закрепили стандарт. Правило было простое, браузер должен проверить, если window.open() вызывается асинхронно, например, в setTimeout, onload, Promise, XHR, то окно не должно открываться, а если вызывается в обработчике клика или submit, то можно. С появлением HTML5 все браузерные движки закрепили это поведение в спецификациях и стандартах W3C. Как раз период с 2010 по 2020 был эпохой ужесточения контекста доверия. С развитием одностраничных приложений (SPA), браузеры уточнили понятие trusted user gesture, стало быть разрешены только синхронные вызовы в рамках цепочки клика, а любые асинхронные действия уже не считаются безопасными:
button.addEventListener('click', async () => {
await fetch('https://habr.com');
window.open('https://habr.com', '_blank'); // ❌ заблокируется
});
1.1.2. _top, _self, _parent - навигация в существующее окно
_top, _self, _parent — не создают нового окна, а работают с тем же, а значит эти варианты не нарушают UX и потому не блокируются popup blocker(ами). Они просто меняют содержимое уже существующей вкладки или фрейма. Браузеру не нужно блокировать это лишь потому, что пользователь все равно останется на этом же сайте (в текущем окне), таким образом нет риска засорения вкладками или всплывающими окнами.
Логика браузеров здесь такая, что такая запись идентична window.open('page.html', '_self') = location.href = 'page.html', а раз так, то зачем блокировать обычный переход?
Тут возникает вопрос. Почему браузеры так строго следят за window.open() с _blank, но позволяют странице (чужому коду) незаметно заменить саму себя через _top, _self, _parent или даже location.href?
Ответ прост: не потому, что это безопасно, а потому что иначе веб просто перестал бы работать! Браузеры не блокируют навигацию в текущем окне, потому что переход между страницами - это основная модель взаимодействия Web(а). Если бы браузер требовал user gesture для любого изменения location.href, сломались бы:
автоматические редиректы (HTTP 3xx, meta refresh, SPA-навигация);
OAuth-логины, где идет серия редиректов;
платежные сценарии, checkout-страницы и тому подобное.
То есть мы как бы доверяем сайту, раз уже загрузили его страницу. Это не значит, что там безопасно, ведь вредоносный сайт/скрипт может:
заменить текущую вкладку на фишинговый сайт, визуально похожий на исходный;
перехватить back-навигацию через history.pushState();
устроить redirect loop или bounce attack.
Браузеры действительно знают об этих трюках, но бороться с ними стали иначе, через политики доверия и песочницы, а не через блокировку переходов. Чтобы компенсировать уязвимость навигации, появились более точные меры:
-
Content Security Policy (CSP) — ограничивает, куда можно делать navigate-to, form-action , все это можно задать на стороне сервера:
Content-Security-Policy: navigate-to 'self' https://trusted.siteВы буквально запрещаете сайту менять window.location на что-то вне whitelist-списка. Cамый надежный способ, потому что браузер получает политику до рендеринга сайта. А вот уже менее надежный, но допустимый - это использовать meta-тег на вашей странице:
<meta http-equiv="Content-Security-Policy" content="default-src 'self'; style-src 'self' 'unsafe-inline'"> -
Iframe sandboxing — ваш фрейм на сайте можно изолировать так, чтобы он не мог перенаправлять родителя:
<iframe src="ad.html" sandbox="allow-scripts"></iframe>Без allow-top-navigation — фрейм не сможет использовать _top;
Same-Origin Policy — не дает подменить контент другого домена после редиректа;
Navigation API — позволит точнее контролировать, кто и как меняет навигацию, правда пока эта технология работает только в Chrome.
Почему браузеры не блокируют по UX-причине? Представьте, что вы заходите на сайт, а он делает window.location = '/login'. Это абсолютно нормальный сценарий и браузеру сложно понять, это «действие пользователя» или «это вредный редирект». Лучше дать странице работать, чем сломать половину интернета в таком случае.
1.1.3 _unfencedTop - экспериментальный режим
Для начала ответим, как ведет себя обычный iframe. Допустим мы написали внутри родительской страницы:
<iframe src="https://example.com"></iframe>
а уже внутри дочерней страницы (в нашем случае, example.com), вызывается такой код:
window.open('https://another-site.com', '...');
в такой ситуации, если не заблокировано политикой sandbox, CSP, или popup blocker(ом), появляется новое окно, и у открывшегося окна есть частичная связь с родителем:
window.opener указывает на страницу, которая открыла окно;
через window.opener.postMessage(...) можно общаться;
cookies, localStorage, sessionStorage и document.referrer работают, если политика разрешает;
URL прозрачен — браузер и сайты знают, кто кого вызвал.
Это называется двунаправленная связь между контекстами. Прекрасно работает для многих веб-приложений, но ужасно подходит для приватности пользователей, потому что можно отслеживать все что угодно, вплоть до цепочек переходов. Google стремился исправить эту ситуацию. Вся магия должна была лечь на Fenced Frame = изолированный фрейм.
Представьте, что fenced frame - это как контейнер в Docker, изолированная среда, у которой нет прямого доступа к хосту (родительской странице). Такой фрейм может выполнять код, рендерить контент, но ничего не знать, кто его запустил, а значит, не сможет шпионить за окружением.
Как предполагалось поведение с fencedframe
<fencedframe> по задумке тег не мог принимать обычные ссылки вроде:
<fencedframe src="https://habr.com"></fencedframe>
потому что это сразу бы раскрывало, что именно встроено, а вместо этого браузер должен был работать с URN (Uniform Resource Name) — это не URL, а уникальный идентификатор, например: urn:uuid:8e8f4d8c-4f22-46af-8d0d-02e61b2b3a7b.
Зачем нужен был URN? Чтобы не возникла утечка информации между страницей-хозяином и содержимым фрейма. Если бы <fencedframe> принимал обычный src="https://...", владелец страницы мог бы: логировать какие именно объявления показываются; подменять динамически параметры; отслеживать поведение пользователя. В рекламной модели Google хотел, чтобы браузер сам решал какую рекламу показать, какую ссылку использовать при клике и как не раскрыть информацию ни одной стороне.
Например, в вашем коде, вы генерировали бы URN заранее:
const config = new FencedFrameConfig({
src: 'https://habr.com',
});
const urn = await config.urn();
// затем добавляем fencedframe в DOM
document.body.innerHTML =
`<fencedframe src="${urn}"></fencedframe>`;
Да, внутри экспериментального fencedframe мы все также могли вызвать обычный:
window.open('https://another-site.com');
Но поведение этого вызова не соответствовало целям Privacy Sandbox.
Что происходит при обычном window.open('https://site.com') внутри fencedframe? Формально браузер мог открыть окно, но он обязан был это сделать как полностью анонимный переход: нет window.opener; нет referrer; нет связи между окнами; нет доступа к cookie страницы; нет передачи контекста размещения рекламы. Это означает, что снаружи это выглядело как пользователь сам ввел адрес вручную. Ни рекламный фрейм, ни сайт-родитель не узнают, что произошло.
В чем проблема такого обычного window.open?
Обычный window.open дает браузеру слишком мало информации, чтобы правильно отработать клик по рекламе: браузер не знает, что это именно рекламный клик; рекламная система не получает подтверждение, что клик произошел; нельзя использовать защищенные механизмы Privacy Sandbox. И самое главное, нельзя открыть окна по URN, так как window.open принимает только реальные URL(ы), а fencedframe не должен знать этого.
Поэтому Google предложил вариант с _unfencedTop. Этот параметр предлагался как специальный анонимный канал для кликов, который не раскрывал бы родителя; не предоставлял бы referrer, opener; не предоставлял бы никакой связи между окнами и позволял работать с URN:
window.open('urn:uuid:1234', '_unfencedTop');
_unfencedTop это похоже на идею с docker run --network=host, но безопасно.
Что произошло дальше?
Google официально решил свернуть большую часть Privacy Sandbox, включая как раз те технологии, ради которых существовал их придуманный fencedframe внедренный в Chrome 126.
Google признал, что многие технологии не взлетели из-за низкого внедрения, сложности, непонимания со стороны рынка, и, что важно, отсутствия поддержки другими браузерами (Safari, Firefox, даже Edge не поддержал идею).
Поэтому они решили закрыть все экспериментальный API:
Protected Audience;
Attribution Reporting API — измерение эффективности рекламы;
Topics API — альтернатива cookies для таргетинга;
Private Aggregation и Shared Storage — анонимная аналитика;
SelectURL, SDK Runtime, On-device Personalization — вспомогательные вещи для рекламы;
Privacy Sandbox на Android тоже сворачивается.
В реальности fencedframe больше не будет развиваться как стандарт и Chrome, вероятнее всего, в будущем полностью удалит его, поэтому про параметр _unfencedTop можно смело забывать.
1.2. Стилизация и настройки всплывающих окон

Третий аргумент features у данной функции отвечает за возможность стилизации нового окна: отображение верхней панели с кнопками назад и вперед; размеры окна; включение или отключение скроллбара; включение или отключение ресайза.
Слово features в window.open может звучать немного странно, будто речь о фичах, а не о размерах нового окна или чего-то еще. Но это исторический артефакт из 90-х, когда фича в данном контексте переводиться как функциональность, а не новой возможности продукта. Когда Netscape Navigator и Internet Explorer только внедряли window.open , примерно в 1995 году, API назывался «open a new window with features». То есть под features понимались возможности интерфейса окна:
“menubar, toolbar, scrollbars — these are window features that can be turned on or off.”
Тогда браузеры активно экспериментировали с тем, что можно было настроить: адресная строка, меню, навигация, бордеры, даже отображение кнопки "закрыть". Все это было описано в документации браузеров тех лет, например, Netscape 3.0 (1996):
The features argument is a comma-separated list of window features such as width, height, toolbar, location, directories, status, menubar, scrollbars, resizable.
Именно тогда третий аргумент имел название features string, но позже в HTML 4 и DOM Level 0 его стали называть features parameter. Наверное, его могли бы назвать options, но он пережил годы стандартизации и остался в API ради совместимости.
Popup окна (всплывающие окна)
Когда мы пишем вот так, то мы гарантировано открываем новую вкладку:
window.open('https://habr.com', '_blank');
Но стоит прописать третий параметр, как тут же браузер поймет, что мы намекаем ему о том, что хотим открыть именно popup окно.
window.open('https://habr.com', '_blank', 'width=400,height=400');
Правда браузер не обязан выполнять наши намеки буквально, он может вместо этого открыть вкладку и делает так очень часто. Кроме width и height можно передавать целый набор настроек. Как вы уже поняли, параметры - это строка вида ключ=значение, разделенная запятыми без пробелов. Не все браузеры поддерживают все, но общая идея едина.
Параметр |
Описание |
Поддержка |
Геометрия | ||
width, height |
Размеры окна в пикселях |
Все браузеры |
left, top |
Позиция окна на экране |
Все браузеры |
screenX, screenY |
Идентично left, top |
Все браузеры |
innerWidth, innerHeight |
Размер области контента |
Иногда поддерживается, но непредсказуемо |
Интерфейс браузера и поведение | ||
menubar |
Показывает или прячет браузера |
Не поддерживается |
status=no |
Панель статуса (status bar), внизу окна раньше была строка состояния |
Не поддерживается |
toolbar=no |
Скрывает верхнюю панель кнопок назад, вперед, обновить |
Не поддерживается |
location=no |
Скрывает адресную строку |
Не поддерживается |
scrollbars=no |
Разрешает прокрутку страницы |
Иногда поддерживается, но непредсказуемо |
resizable=no |
Разрешает изменять размер |
Иногда поддерживается, но непредсказуемо |
directories=no |
В 90-х так называлась панель закладок |
Не поддерживается |
noopener=yes |
Отключает связь между открывшим и открытым окном (window.opener = null). |
Все браузеры |
noreferrer=yes |
Не передает Referer-заголовков и автоматически включает noopener. |
Все браузеры |
fullscreen=yes |
Запускает в полноэкранном режиме |
Не поддерживается |
popup=yes |
Подсказывает браузеру, что это всплывающее окно, не вкладка |
Все браузеры |
alwaysRaised=yes |
Окно всегда сверху |
Не поддерживается |
alwaysLowered=yes |
Держать окно на заднем плане |
Не поддерживается |
Полный зоопарк свойств
width
height
left
top
screenX
screenY
outerWidth
outerHeight
innerWidth
innerHeight
scrollbars
resizable
menubar
toolbar
location
status
directories
personalbar
titlebar
dependent
alwaysRaised
alwaysLowered
hotkeys
fullscreen
channelmode
minimizable
modal
noopener
noreferrer
popup
dialogheight
dialogwidth
Почему современные браузеры почти все это ломают и не поддерживают?
Самое главное, из-за безопасности. Фишинговые popup(ы) делали окна похожими на системные программы, имитируя даже интерфейс браузера, что могло привести к непредсказуемым последствиям. Также из-за плохого UX, ведь UI браузера должен оставаться узнаваемым, а на деле мы могли забыть, что это окно было открыто из браузера. К тому же, есть современное мнение общества, что вкладки полностью заменили popup(окна). В наши дни, более удобно открывать страницы исключительно в новой вкладке, где эти параметры уже не имеют смысла.
Таким образом, сейчас существует очень короткий набор параметров, которые гарантированно работают во всех современных браузерах (Chrome, Firefox, Safari, Edge), а именно width, height, left, top, noopener, noreferrer, popup. Они распознаются всеми браузерными движками и дают предсказуемый эффект независимо от браузера. Остальные параметры либо частично живы, либо уже мертвы по соображениям безопасности и игнорируются современными браузерами.

Лично я помню те времена, когда на Windows 98 или Me я ползал по интернету и случайно мог открыть какую-то нехорошую ссылку. Затем у меня могло появиться окно с экраном блокировки и шантажа, что ваш компьютер заражен, переведите деньги по СМС для разблокировки. Тогда я еще не знал, что это были просто браузерные окна на весь экран с отключенными возможностями закрыть окно, теперь я знаю. Думаю, современной молодежи уже не понять эти чувства страха за отцовский ПК и слава богу.
Работа с внутренним DOM всплывающего окна
Может мы и не можем управлять адресной строкой, открывать всплывающие окна на весь экран, зато мы можем управлять CSS и JS кодом этого окна. Если окно открывается в рамках нашего домена, разумеется. Итак, мы можем вставить туда HTML и стилизовать как захотим, например:
const popup = window.open('', '_blank', 'width=400,height=300');
popup.document.write(`
<style>
body { background: #222; color: white; font-family: sans-serif; }
</style>
<h1>Hello, styled world!</h1>
`);
Результат
Если скопируете себе в DevTools этот код и запустите, то увидите следующее:

Напомню, что window.open может вернуть не только объект Window, поэтому для типобезопасности обязательно делайте проверку, что вам вернулся не null. Так как вы не всегда можете гарантировать в какой среде запускается ваш код, велик шанс, что его могут запустить в iframe с отключенными правами:
const popup = window.open('', '_blank', 'width=400,height=300');
if (!popup) {
alert('Popup заблокирован, разрешите всплывающие окна для этой страницы');
}
popup?.document.write(`
<style>
body { background: #222; color: white; font-family: sans-serif; }
</style>
<h1>Hello, styled world!</h1>
`);
1.3. Для чего нужны всплы��ающие окна на window.open?
Прежде, чем ответить на этот вопрос, давайте заглянем в прошлое.
Веб 1995–2005 (Web 1.0)
Браузер воспринимался как страница с текстом и парой картинок, возможно немного JS-анимаций, да-да CSS анимаций тогда не было. То, что сегодня называют веб-приложением, тогда называлось простым и понятным русским языком "сайт с динамическими эффектами". Сегодня у нас есть Figma, Notion, VS Code в браузере, а тогда такого даже представить было нельзя.
Использование window.alert(), window.confirm(), window.prompt() было дело привычным на каждом сайте той эпохи. Это было очень брутально, я бы сказал, но зато не было никаких затемнений экрана, свистящих анимаций, z-index битв и прочих прелестей. Поэтому стоящий рядом window.open() в том же ряду был "модалкой" тех лет.
Базовый метод window.open давал то, чего тогда не было в HTML и CSS, а именно иметь отдельное окно, что означало иметь отдельный DOM и CSS контекст, свою область прокрутки. К тому же всплывающие окна (popup, попап) реально ощущались отдельными "приложениями" в браузере. Что гармонировало с нативными программами, так как выглядело это все визуально убедительно, особенно для интерфейсов на Windows той эпохи. Многие сайты делали свои интерфейсы чаще всего с использованием window.open.

UX как на десктопе
Во многом в те года сайты все больше хотели походить на десктопные приложения. Поэтому применение popup считалось вполне привычным UX. Разработчики сайтов, применяли их, как правило, для редактирования профиля, открытия картинки в полном размере, отображения настроек сайта, чаще всего это были чаты переписок. Так ваш сайт мог быть больше похож на таких гигантов как ICQ2go, Hotmail, Yahoo Mail.
1.4. Когда всплывающие окна стали плохим UX?
Как вы понимаете, ни один современный сайт в интернете уже не открывает настройки, профиль или корзину с вашими товарами в popup окне. И этому тоже есть разумные объяснения ходу событий прошлых лет.
Как складывалась эволюция Web 2.0?

В 2004-2005 году Gmail запускает полноценное web-приложение, а благодаря статье Джесси Джеймса Гарретта, AJAX становится публичным и начинается новая эра. Google делает значительные инвестиции в развитие данного подхода к написанию своих сайтов. По первому времени их называли AJAX-сайтами, а позже устоялось понятие веб-приложение.
В их приложениях появляются прорывные технологии smooth drag, realtime updates, и в те же годы Google анонсирует YouTube. Таким образом, Web 2.0 как культурный перелом превратил статичные страницы сайтов в приложения. И как следствие, использовать popup окна на "каждый чих" просто перестало быть необходимостью. К этому моменту уже подтянулись JavaScript и CSS с новыми возможностями, где можно было делать еще более красивые, интерактивные и безопасные варианты интерфейсов с UI чем-то похожими на "модалки".
jQuery UI Dialog
Сначала разработчики научились делать первые окна на div-элементах с абсолютным позиционированием, затемняя задний фон, использовать CSS тени и кастомные кнопки в своих псевдо-всплываюших окнах. Современному разработчику код тех лет может показаться кошмаром, поэтому любой повторяющийся JavaScript код было принято выносить в библиотеки и переиспользовать. Тогда не было даже централизованного npm-хранилища, поэтому скрипты везде блуждали по интернету и подключались на сайты прямо по http-протоколу со сторонних ресурсов. Мне довелось работать в те годы с Mootools.js, но в последствии самую большую известность приобрел jQuery. Его использовали абсолютно все, Google, Facebook, Youtube и прочие гиганты. Дизайн API библиотеки казался максимально лаконичным и привлекательным, понятным и доступным. Именно jQuery UI как библиотека, которая инкапсулировала в себе монотонный бойлерплейт код принес моду на диалоги в веб-приложениях:
$("#dialog").dialog();
Всего одна строчка, и на выходе вы получаете: draggable окна, resizable окна, overlay, stack-order, анимации. Это и был настоящий конец для топорного window.open как UI-механизма при написании веб-приложений.
2007–2012

Если меня просили добавить на сайт карусель изображений с полном экранным отображением картинки, видео с YouTube или просто отобразить Google-карту в модальном окне, мне больше не нужен был уже window.open, поголовно все тогда устанавливали себе Lightbox, FancyBox или Colorbox плагины, которые были написаны на jQuery и всех все устраивало.
2011–2012

Twitter наступает на хвост jQuery UI, и создает свой UI kit с базой компонентов, и вот у нас появляются красивые модальные окна на Bootstrap. В общем, уже давно стало понятно, что старые window.alert(), window.prompt(), window.confirm(), window.open() API все меньше и меньше нужны в реальных веб-приложениях.
1.5. Используется ли window.open сейчас?
В Web 3.0+ window.open уже почти не используют, однако, он все еще живее всех живых, поскольку достаточно важен в специфичных задачах, не связанных с UI.
OAuth авторизация
Google Login, GitHub OAuth, Discord OAuth почти всегда используют window.open() для OAuth popup flow.

Основная причина - сохранение контекста приложения. При использовании всплывающего окна. Пользователю не нужно покидать текущую страницу, проходить процесс аутентификации на стороннем сайте, а затем снова ожидать полной загрузки основного сайта с которого и был перенаправлен. Короче говоря, это обеспечивает более плавный и непрерывный UX за счет бесшовной передачи данных через window.postMessage(). После завершения аутентификации во всплывающем окне, дочернее окно может безопасно обмениваться данными, например, передать токен авторизации в родительское окно. К тому же, всплывающее окно — это узнаваемый UX-паттерн для входа через сторонние сервисы, к которому пользователи уже давно привыкли.
Print Preview
Например, вы хотите распечатать какую-то область страницы. Допустим у вас WYSIWYG приложение на подобии Google Docs, где скорее всего вашим пользователям при редактировании контента нужно сохранить его PDF или попросту распечатать. В этом вам также может пригодится window.open.
const win = window.open('', '_blank');
win?.document.write('<h1>Мой текст, который хочу распечатать</h1>');
win?.print();
win?.close();
Ваша задача только подготовить содержимое страницы с использованием соответствующих стилей для печати посредством @media print в CSS.
Enterprise решения (ERP UI)
Корпоративные системы обычно открывают документы и отчеты точно также через window.open так, чтобы ощущалось будто вы открыли отдельное служебное окно, где возможна минимальная навигация, но при этом там была кнопка с PDF выгрузкой, собственной шапкой сайта, логотипом и кнопкой для печати.
В реальной жизни это могло выглядеть так:
window.open('/report/print?id=192', '_blank', 'width=400,height=900');

Но на сегодняшний день, использование window.open в enterprise решениях для открытия отчетов в отдельных страницах рассматривается как устаревший подход, который только приводит к проблемам связанным с UX и блокировщиками всплывающих окон.
1.6. Особенности на iOS/Android
У window.open на мобильных устройствах есть целый набор ограничений, которые делают его гораздо менее надежным, чем в десктопном браузере.
⚠️ Всегда открывается как новая вкладка
Мобильные браузеры не умеют создавать отдельные всплывающие окна, управлять их позиционированием и кастомизацией, поэтому такая запись:
window.open(url, '_blank', 'width=400,height=300');
гарантировано всегда новая вкладка, а не всплывающее окно.
⚠️ window.open часто возвращает null
На мобильных устройствах window.open всегда блокируется если:
вызван не по клику (не напрямую от touchend);
вызван внутри async/await;
вызван из setTimeout;
вызван из какой-либо Promise цепочки;
вызван в тот момент, когда потерян прямой user gesture (после event loop tick).
⚠️ iOS Safari не позволяет взаимодействовать с окном сразу
const win = windo.open(...);
win.document.write(...);
win.postMessage(...);
Часто возникает ситуация, когда document.write или postMessage просто не работает.
⚠️ postMessage ненадежен на мобильных устройствах
Если пользователь начинает переключать вкладки в своем мобильном браузере, например, тот же Safari для минимизации энергопотребления может попросту начать замораживать неактивные страницы. Из чего следует, что события postMessage могут теряться, а порядок сообщений становиться не консистентным, потому что страница была просто выгружена из памяти.
⚠️ Проблемы с OAuth авторизациями
const win = window.open(...); // win === null в 70–90% случаев
Основное окно открывает новую вкладку, неожиданно там возвращается null, происходит OAuth redirect, который не может отправить postMessage , по итогу приложение зависает. Google/Facebook и многие другие сами рекомендуют не полагаться на popup-flow на мобильных устройствах, а использовать редиректы (redirect-based OAuth flow) в ту же вкладку с дополнительным state/query-параметрами.
⚠️ window.open в iframe из мобильного браузера ведет себя непредсказуемо
Во всяком случае, на iOS доподлинно известно, что в iframe sandbox почти всегда блокирует открытие вкладки, да даже без sandbox атрибутов иногда открытие попросту игнорируется. Довольно часто iframe блокируется встроенными политиками безопасности, а некоторые cookie и Storage API не доступны. А самое важно OAuth запрещает авторизацию внутри iframe.
⚠️ Часть API не работает на мобильных устройствах
Открытые окна можно закрыть только пользователю вручную, то есть window.close() просто игнорируется. Также window.opener вовсе недоступен.
2. Document Picture-in-Picture API

Document PiP API - это экспериментальное решение от Google Chrome, позволяющее вывести любой HTML в маленькое плавающее окно поверх всех других окон ОС.
const pip = await documentPictureInPicture.requestWindow({
width: 400,
height: 300
});
pip.document.body.innerHTML = `
<div style="padding: 20px; background: #f0f0f0;">
<h2>? PiP Window!</h2>
<p>Прощай window.open()</p>
</div>
`;
Результат
Если скопируете себе в DevTools этот код и запустите, то увидите следующее:

2.1. В чем преимущества и недостатки перед window.open?
window.open
❌ Может быть заблокирован блокировщиками окон;
❌ Легко потерять окно среди других, если уйдет фокус, неудобный UX;
❌ Всплывающее окно живет независимо от родительской вкладки;
❌ Не всегда имеем возможность контролировать DOM, метод может вернуть null;
✅ Можно вызвать из iframe, можно вызвать даже из sandbox-iframe;
✅ Может загрузить OAuth, банковскую авторизацию, PDF, любой URL;
✅ Работает везде, корпоративные терминалы на IE, встроенные WebView.
✅ Иногда можно вызывать даже после promise/await, если событие пользовательское.
Document PiP
✅ Не блокируется;
✅ Всегда поверх (system-level floating) окон других приложений, удобный UX;
✅ Автоматически закрывается, когда закрывается родительская вкладка-окно;
✅ Полный контроль над HTML, CSS;
❌ Запрещен во вложенном контексте: iframe, sandbox-iframe, popup внутри popup;
❌ Рендерит только DOM с того же origin вкладки;
❌ Работает только в Chrome Desktop (начиная с 116), в других браузерах отсутствует;
❌ Обязан быть вызван напрямую из клика.
2.2. Где лучше всего подходит?
Какие это могут быть веб-приложения? Наверняка это те, которым нужно второе маленькое окно, будь это отдельный экран, панель инструментов в стиле фотошопа, мини-плеер, виджет статуса или любое вспомогательное пространство, которое живет независимо от основной вкладки, но остается частью приложения.

Самая очевидная сфера применения - видеозвонки, WebRTC-приложения в стиле Google Meet и прочие. PiP окно может показывать мини-поток вашего видео или видео собеседника.
Редакторы и IDE-подобные веб-инструменты. PiP окно идеально вписывается в такие сценарии как панель логов или консоль ошибок, отладчик, мини-превью документа или макета.
Учебные платформы и тренажеры. Это могло бы быть окно подсказок, отображения формул, дополнительного материала или мини-видео инструкций, где пользователь работает в основной вкладке, а PiP окно показывает только вспомогательный контент.
Корпоративные и CRM-системы. Многие системы могут только выиграть от такого. Например, индикатора статуса задачи, прогресса загрузки отчета, таймера з��онка, напоминания о SLA, подсказки от AI-ассистентов.
Как можем себе представить, все ограничивается только нашей фантазией и технической поддержкой того, где это должно работать.
3. Заключение

На мой субъективный взгляд, Document PiP по-прежнему выглядит немного сыроватым. Тем не менее, если вы создаете собственный аналог Google Meet или любое другое приложение со вспомогательными панелями и видео-интерфейсами, это API может придать вашему продукту более естественное и современное поведение по сравнению с традиционными веб-механиками.
Во всех остальных сценариях — будь то OAuth, работа с PDF и печатью, сложные микрофронтенды с цепочками iframe или ситуации, когда просто нужно открыть новую вкладку — надежным инструментом по-прежнему остается window.open. При этом ничто не мешает вашему приложению сочетать разные подходы и использовать каждый API там, где его сильные стороны проявляются лучше всего.
Список дополнительных материалов
1) Window: open() method
2) Use window.open but block use of window.opener
3) Every known way to get references to windows, in javascript
4) Understanding window.open() Behavior on iOS Safari
5) Unlock exciting use cases with the Document Picture-in-Picture API
6) Getting Started with the Document Picture-in-Picture API
7) Document Picture-in-Picture Specification
8) The Picture-in-Picture API
9) Guide to use the Document Picture in Picture API
10) Picture-in-Picture for any Element, not just <video>