2026 г.

Как устроен браузер

Проект ЦИТадель

Браузер одновременно является сетевым клиентом, средой исполнения JavaScript, движком разметки и границей безопасности между сайтами. Чтобы понимать задержки интерфейса и ограничения веб-приложений, полезно проследить путь от URL до кадра: как загружаются ресурсы, какие стадии превращают документ в изображение, когда выполняются задачи JavaScript и как процессы браузера изолируют недоверенный код. Общие правила задаются стандартами веб-платформы, но распределение работы по потокам и процессам, устройство JIT-компилятора и детали рендеринга различаются между движками. В практикуме эти механизмы исследуются через инструменты разработчика.

1. Путь запроса: от URL до документа

После разбора URL браузер определяет политику запроса, проверяет доступные кэши и при необходимости разрешает имя, устанавливает соединение и отправляет HTTP-запрос. Сетевые стадии подробно разобраны в статьях «TLS: как устроено защищённое соединение» и «HTTP/2 и HTTP/3». Здесь важны три особенности клиента:

  • Кэш не всегда означает отсутствие сети. Свежий сохранённый ответ может быть использован без обращения к серверу. Устаревший ответ обычно требует проверки условным запросом и может получить 304 без повторной передачи тела. Решение зависит от директив ответа и режима загрузки. Service Worker способен перехватить запрос и обратиться к Cache Storage, отдельному от HTTP-кэша. Правила свежести и проверки HTTP-кэша задаёт RFC 9111.
  • Ресурсы обнаруживаются постепенно. Основной HTML можно разбирать по мере поступления. Парсер и механизм предварительного просмотра разметки находят стили, скрипты, шрифты и изображения, после чего планировщик назначает запросам приоритеты. Приоритет является сигналом, а не гарантией точного порядка на сервере или в сети; современная схема HTTP описана в RFC 9218.
  • Часть работы выполняется вне главного потока документа. Сеть, декодирование ресурсов и обслуживание кэшей могут находиться в отдельных потоках или процессах. Конкретная архитектура остаётся свойством браузера.

2. Конвейер рендеринга: от текста к пикселям

Путь удобно описывать пятью логическими стадиями. Это не обязательная последовательность полного пересчёта: браузер обрабатывает данные постепенно, переиспользует прежние результаты и может выполнять часть работы параллельно.

  1. Парсинг. Поток HTML преобразуется в DOM по детально заданным правилам, включая восстановление после многих синтаксических ошибок. CSS разбирается в представление таблиц стилей. Обычный встроенный или внешний скрипт без специальных атрибутов может остановить HTML-парсер; defer откладывает исполнение до окончания разбора, а async разрешает исполнить загруженный скрипт, как только он будет готов. На точный момент также влияют зависимости от таблиц стилей и модульные скрипты.
  2. Вычисление стилей. Для участвующих в отображении узлов определяются итоговые значения свойств с учётом каскада, наследования и текущего состояния.
  3. Раскладка. Движок вычисляет размеры и положение фрагментов документа. Изменение геометрии одного элемента может затронуть зависимые части дерева, но браузер старается ограничить область пересчёта.
  4. Отрисовка. Визуальные свойства превращаются в команды рисования и растеризованные области. Они могут быть разделены на несколько слоёв.
  5. Композиция. Подготовленные слои объединяются в кадр, часто с участием отдельного потока композиции и GPU. Изменения transform и opacity нередко можно применить без новой раскладки и отрисовки, но создание слоя и аппаратное ускорение не гарантируются одним названием свойства.

Изменение DOM или стилей помечает часть прежних результатов недействительной. Стоимость зависит от области пересчёта и от того, потребовались ли стили, раскладка, отрисовка или только композиция. Графическая сторона последней стадии подробнее рассмотрена в статье «GPU и графический конвейер».

3. Один поток и цикл событий

Скрипты окна, DOM и значительная часть стилей и раскладки обслуживаются главным потоком соответствующего контекста исполнения. Это не единственный поток браузера: сеть, композиция, растеризация, декодирование и сборка мусора могут использовать другие потоки и процессы. Порядок доступной JavaScript работы задаёт цикл событий с несколькими источниками задач и очередью микрозадач.

  • Задача исполняется до конца относительно другого JavaScript в том же агенте. Пока обработчик не вернул управление, следующий обработчик не начнётся и документ обычно не получит новую возможность отрисовки. На экране 60 Гц интервал между возможностями составляет около 16,7 мс, но стандарт не требует рисовать после каждой задачи или поддерживать именно эту частоту.
  • Асинхронная операция не равна синхронному ожиданию. Сеть, таймер или файловый API выполняет работу средствами среды, а результат позднее ставит продолжение в очередь. Коллбэки, промисы и async/await организуют это продолжение; подробнее они разобраны в статье «Современный JavaScript и TypeScript».
  • Микрозадачи выполняются в контрольных точках. Реакции промисов относятся к микрозадачам. После задачи среда обычно исчерпывает очередь микрозадач перед следующей возможностью обновить изображение. Цепочка, постоянно добавляющая микрозадачи, поэтому способна задержать интерфейс.
  • Worker создаёт отдельный агент исполнения без прямого доступа к DOM. Обычный способ обмена — сообщения с копированием или передачей данных. При доступном SharedArrayBuffer агенты могут разделять память и обязаны синхронизироваться атомарными операциями; сообщения сами по себе не исключают все гонки. Общие модели конкурентности рассмотрены в отдельной статье.

4. Как исполняется JavaScript

Движки V8, JavaScriptCore и SpiderMonkey используют несколько уровней исполнения, но их устройство и решения меняются независимо. Например, современный V8 может разобрать код в байткод интерпретатора Ignition, быстро скомпилировать его базовым компилятором Sparkplug, а часто выполняемые участки передать оптимизирующим компиляторам. Они используют сведения о наблюдавшихся типах и формах объектов; если предположение перестаёт выполняться, код может быть деоптимизирован. Из этого не следует, что любое изменение формы оставляет функцию в интерпретаторе или что прикладной код следует подгонять под внутренности конкретной версии. Сначала нужны профиль и измерение.

Память JavaScript управляется трассирующим сборщиком. V8, например, сочетает поколения с параллельными и конкурентными фазами, но другой движок вправе выбрать иную организацию. Сборщик освобождает недостижимые объекты; подписка, замыкание или неограниченный кэш могут по-прежнему удерживать ненужный граф. Механизмы и способы наблюдения разобраны в статье «Как современные языки управляют памятью».

5. Процессы, песочница и границы источников

Современные браузеры разделяют работу между процессами, но точная схема зависит от продукта, платформы и ограничений ресурсов. В Chromium есть основной процесс браузера, процессы рендереров и отдельные службы, в том числе сетевые и графические. Site Isolation стремится не помещать документы разных сайтов в один рендерер; это не тождественно правилу «одна вкладка — один процесс», потому что вкладка может содержать межсайтовые фреймы, а несколько документов одного сайта могут переиспользовать процесс. Рендереры работают с ограниченными полномочиями. Песочница и изоляция сайтов уменьшают последствия уязвимости, но остаются слоями защиты, а не доказательством невозможности выхода из процесса.

Правило одного источника (same-origin policy) ограничивает взаимодействие документов и сценариев разных источников; источник обычно задаётся схемой, именем хоста и портом. Оно разделяет DOM и хранилища и запрещает сценариям многие межисточниковые чтения. При этом переходы, отправка форм и встраивание некоторых ресурсов могут быть разрешены. CORS позволяет серверу сообщить браузеру, каким источникам разрешено читать ответ программно; он не отменяет проверку прав на сервере.

XSS и CSRF связаны с этой границей по-разному. При XSS чужой код выполняется с полномочиями уязвимого источника. При CSRF браузер побуждают отправить допустимый для протокола запрос к сайту, где у пользователя есть сеанс; отправка учётных данных зависит от вида запроса, режима fetch, настроек cookie и политики SameSite. Поэтому защита от XSS включает контекстное кодирование или санитизацию данных и CSP как дополнительный слой, а защита от CSRF — отсутствие изменений состояния в безопасных HTTP-методах, CSRF-токены, проверку контекста запроса и подходящие cookie. CORS сам по себе не является защитой от всех таких атак.

6. Практикум: наблюдение в инструментах разработчика

Лучше выполнять задания на собственной или специально открытой тестовой странице, предварительно сохранив несохранённые данные. Названия панелей и событий различаются между версиями браузеров.

Задание 1. Конвейер в записи. Запишите загрузку страницы в панели производительности. Найдите события разбора HTML, вычисления стилей, раскладки, отрисовки и композиции, если браузер показывает их отдельно. Отметьте, какие стадии перекрываются с загрузкой ресурсов и какие повторяются после исполнения скрипта. Не ожидайте одной непрерывной последовательности из пяти блоков.

Задание 2. Длинная задача. Во время записи выполните в консоли контролируемое ожидание около секунды: const end = performance.now() + 1000; while (performance.now() < end) {}. Проверьте реакцию страницы на ввод и найдите задачу на временной шкале. Затем разбейте искусственную работу на части с уступкой циклу событий и сравните отзывчивость и полное время.

Задание 3. Раскладка и композиция. Сравните две анимации одинакового движения: изменением left у позиционированного элемента и свойством transform. Включите подсветку перерисовок и запишите обе. Опишите наблюдавшиеся стадии, но не объявляйте второй вариант композиционным без подтверждения записи: повышение элемента в слой зависит от реализации.

Задание 4. Микрозадачи и задачи. Выполните setTimeout(() => console.log('задача'), 0); Promise.resolve().then(() => console.log('микрозадача')); console.log('синхронно'); и объясните порядок. Затем создайте ограниченную цепочку из нескольких тысяч микрозадач и проверьте, когда браузер сможет отрисовать заранее запрошенный requestAnimationFrame.

Задание 5. Процессы и источники. В диспетчере задач браузера найдите доступные процессы рендереров, сети и GPU и сопоставьте их с открытыми документами и межсайтовым фреймом. Затем выполните fetch к ресурсу другого источника без разрешающего CORS-заголовка. Сравните ошибку JavaScript с записью в панели Network: простой запрос мог уйти, а предварительно проверяемый — остановиться после OPTIONS. Отдельно учтите, что fetch по умолчанию не посылает cookie произвольному другому источнику.

Задание 6*. Кэш и приоритеты. При включённом кэше сравните обычную загрузку, перезагрузку и повторный переход. Найдите ответы из памяти или диска, условные запросы и статус 304. Выпишите наблюдаемые приоритеты ресурсов и проверьте, меняются ли они после обнаружения ресурса в документе. Не путайте настройку DevTools «Disable cache» с поведением обычного пользователя.

Итоги

  • Загрузка сочетает кэши, сеть, постепенное обнаружение ресурсов и приоритеты. Свежий кэш может исключить сеть, устаревший — потребовать проверки.
  • Парсинг, стили, раскладка, отрисовка и композиция образуют логический конвейер, но браузер выполняет его постепенно и пересчитывает только затронутые части, насколько это возможно.
  • JavaScript-задача исполняется до конца в своём агенте; микрозадачи выполняются в контрольных точках. Worker даёт другой агент, а разделяемая память возвращает необходимость синхронизации.
  • JavaScript-движки используют несколько уровней исполнения и трассирующую сборку мусора, однако их конкретные компиляторы и алгоритмы не являются частью стандарта языка.
  • Многопроцессная архитектура, песочница, изоляция сайтов и правило одного источника образуют несколько слоёв защиты. CORS, XSS и CSRF решают разные задачи и не должны смешиваться.

Литература

  1. HTML Standard: Event loops и Web workers — задачи, микрозадачи, возможности отрисовки и агенты Worker.
  2. RFC 9111: HTTP Caching и RFC 9218: Extensible Prioritization Scheme for HTTP — свежесть, проверка и приоритеты запросов.
  3. Документы Chromium о многопроцессной архитектуре и Site Isolation — пример конкретной реализации изоляции.
  4. Материалы V8 о компиляторе Sparkplug и сборке мусора — пример многоуровневого исполнения и поколенческой кучи.
  5. Fetch Standard и раздел HTML Standard об источниках — режимы запросов, учётные данные и межисточниковые ограничения.
  6. Оглавление раздела «Веб-технологии»; следующие материалы цикла: «JSON и дизайн API», «Современный JavaScript и TypeScript», «Как устроен фронтенд-фреймворк» и «Веб-производительность».
404 Not Found

404 Not Found


nginx/1.24.0 (Ubuntu)

Связь с редакцией