Взгляд изнутри на современный веб-браузер (часть 3)

Mariko Kosaka

Внутренняя работа процесса рендеринга

Это третья часть из четырех серий блогов, посвященная тому, как работают браузеры. Ранее мы рассмотрели многопроцессную архитектуру и поток навигации . В этом посте мы рассмотрим, что происходит внутри процесса рендеринга.

Процесс рендеринга затрагивает многие аспекты веб-производительности. Поскольку внутри процесса рендеринга происходит много всего, эта статья представляет собой лишь общий обзор. Если вы хотите копнуть глубже, в разделе «Производительность» раздела «Основы веб-технологий» есть гораздо больше ресурсов.

Процессы рендеринга обрабатывают веб-содержимое

Процесс рендеринга отвечает за все, что происходит внутри вкладки. В процессе рендеринга основной поток обрабатывает большую часть кода, который вы отправляете пользователю. Иногда части вашего JavaScript обрабатываются рабочими потоками, если вы используете веб-воркера или сервис-воркера. Потоки компоновщика и растра также запускаются внутри процессов рендеринга для эффективного и плавного рендеринга страницы.

Основная задача процесса рендеринга — превратить HTML, CSS и JavaScript в веб-страницу, с которой пользователь может взаимодействовать.

Процесс рендеринга
Рис. 1. Процесс рендеринга с основным потоком, рабочими потоками, потоком наборщика и растровым потоком внутри.

Разбор

Строительство ДОМ

Когда процесс рендеринга получает сообщение фиксации для навигации и начинает получать данные HTML, основной поток начинает анализировать текстовую строку (HTML) и превращать ее в объектную модель документа ( DOM ).

DOM — это внутреннее представление страницы в браузере, а также структура данных и API, с которыми веб-разработчик может взаимодействовать через JavaScript.

Синтаксический анализ HTML-документа в DOM определяется стандартом HTML . Возможно, вы заметили, что передача HTML в браузер никогда не выдает ошибку. Например, отсутствие закрывающего тега </p> является допустимым HTML. Ошибочная разметка типа Hi! <b>I'm <i>Chrome</b>!</i> (тег b закрывается перед тегом i) рассматривается так, как если бы вы написали Hi! <b>I'm <i>Chrome</i></b><i>!</i> . Это связано с тем, что спецификация HTML предназначена для корректной обработки этих ошибок. Если вам интересно, как это делается, вы можете прочитать раздел « Введение в обработку ошибок и странные случаи в синтаксическом анализаторе » спецификации HTML.

Загрузка субресурса

Веб-сайт обычно использует внешние ресурсы, такие как изображения, CSS и JavaScript. Эти файлы необходимо загрузить из сети или кэша. Основной поток может запрашивать их один за другим по мере их обнаружения во время анализа для построения DOM, но для ускорения параллельно запускается «сканер предварительной загрузки». Если в HTML-документе есть такие элементы, как <img> или <link> , сканер предварительной загрузки просматривает токены, сгенерированные анализатором HTML, и отправляет запросы в сетевой поток в процессе браузера.

ДОМ
Рисунок 2. Основной поток, анализирующий HTML и строящий дерево DOM.

JavaScript может блокировать синтаксический анализ

Когда анализатор HTML находит тег <script> , он приостанавливает анализ HTML-документа и должен загрузить, проанализировать и выполнить код JavaScript. Почему? потому что JavaScript может изменить форму документа, используя такие вещи, как document.write() , который меняет всю структуру DOM ( обзор модели синтаксического анализа в спецификации HTML имеет красивую диаграмму). Вот почему анализатору HTML приходится ждать запуска JavaScript, прежде чем он сможет возобновить анализ HTML-документа. Если вам интересно, что происходит при выполнении JavaScript, команда V8 проводит обсуждения и сообщения в блогах по этому поводу .

Подсказка браузеру, как вы хотите загружать ресурсы

Существует множество способов, которыми веб-разработчики могут отправлять подсказки браузеру для правильной загрузки ресурсов. Если ваш JavaScript не использует document.write() , вы можете добавить атрибут async или defer в тег <script> . Затем браузер загружает и запускает код JavaScript асинхронно и не блокирует синтаксический анализ. Вы также можете использовать модуль JavaScript, если это удобно. <link rel="preload"> — способ сообщить браузеру, что ресурс определенно необходим для текущей навигации и вы хотели бы его загрузить как можно скорее. Подробнее об этом можно прочитать в статье «Приоритезация ресурсов – помощь браузера» .

Расчет стиля

Иметь DOM недостаточно, чтобы знать, как будет выглядеть страница, поскольку мы можем стилизовать элементы страницы с помощью CSS. Основной поток анализирует CSS и определяет вычисляемый стиль для каждого узла DOM. Это информация о том, какой стиль применяется к каждому элементу на основе селекторов CSS. Вы можете увидеть эту информацию в computed разделе DevTools.

Компьютерный стиль
Рисунок 3. Основной поток анализирует CSS для добавления вычисляемого стиля.

Даже если вы не предоставляете CSS, каждый узел DOM имеет вычисленный стиль. Тег <h1> отображается больше, чем тег <h2> , а поля определяются для каждого элемента. Это связано с тем, что в браузере имеется таблица стилей по умолчанию. Если вы хотите узнать, что представляет собой CSS по умолчанию в Chrome, вы можете посмотреть исходный код здесь .

Макет

Теперь процесс рендеринга знает структуру документа и стили для каждого узла, но этого недостаточно для рендеринга страницы. Представьте, что вы пытаетесь описать картину своему другу по телефону. «Там большой красный круг и маленький синий квадрат» недостаточно для того, чтобы ваш друг знал, как именно будет выглядеть картина.

игра человеческого факса
Рисунок 4: Человек, стоящий перед картиной, телефонная линия подключена к другому человеку.

Компоновка — это процесс поиска геометрии элементов. Основной поток проходит через DOM и вычисляемые стили и создает дерево макета, которое содержит такую ​​информацию, как координаты xy и размеры ограничивающего прямоугольника. Дерево макета может иметь структуру, аналогичную дереву DOM, но оно содержит только информацию, связанную с тем, что видно на странице. Если применяется display: none , этот элемент не является частью дерева макета (однако элемент с visibility: hidden находится в дереве макета). Аналогично, если применяется псевдокласс с содержимым типа p::before{content:"Hi!"} , он включается в дерево макета, даже если его нет в DOM.

макет
Рисунок 5. Основной поток просматривает дерево DOM с вычисленными стилями и создает дерево макета.
Рисунок 6. Макет поля для абзаца, перемещающегося из-за изменения разрыва строки.

Определение макета страницы — сложная задача. Даже самый простой макет страницы, такой как расположение блоков сверху вниз, должен учитывать размер шрифта и место разрыва строки, поскольку это влияет на размер и форму абзаца; что затем влияет на то, где должен быть следующий абзац.

CSS может перемещать элемент в сторону, маскировать элемент переполнения и изменять направление письма. Вы можете себе представить, перед этим этапом макетирования стоит огромная задача. В Chrome над версткой работает целая команда инженеров. Если вы хотите узнать подробности их работы, несколько выступлений с конференции BlinkOn записаны и их довольно интересно посмотреть.

Краска

рисование игры
Рисунок 7: Человек перед холстом держит кисть и задается вопросом, следует ли ему сначала нарисовать круг или сначала квадрат.

Иметь DOM, стиль и макет по-прежнему недостаточно для рендеринга страницы. Допустим, вы пытаетесь воспроизвести картину. Вы знаете размер, форму и расположение элементов, но вам все равно придется судить, в каком порядке вы их рисуете.

Например, для определенных элементов может быть установлен z-index , в этом случае рисование в порядке элементов, записанных в HTML, приведет к некорректному рендерингу.

Ошибка z-индекса
Рис. 8. Элементы страницы отображаются в порядке разметки HTML, что приводит к неправильному отображению изображения, поскольку z-index не учитывается.

На этом этапе рисования основной поток проходит по дереву макета для создания записей рисования. Запись рисования — это запись процесса рисования, например «сначала фон, затем текст, затем прямоугольник». Если вы рисовали элемент <canvas> с помощью JavaScript, этот процесс может быть вам знаком.

рисовать пластинки
Рис. 9. Основной поток, проходящий через дерево макета и создающий записи рисования.

Обновление конвейера рендеринга обходится дорого

Рис. 10. Деревья DOM+Style, Layout и Paint в порядке их создания.

Самое важное, что нужно понимать в конвейере рендеринга, — это то, что на каждом этапе результат предыдущей операции используется для создания новых данных. Например, если что-то изменится в дереве макета, то порядок рисования необходимо заново сгенерировать для затронутых частей документа.

Если вы анимируете элементы, браузер должен выполнять эти операции между каждым кадром. Большинство наших дисплеев обновляют экран 60 раз в секунду (60 кадров в секунду); анимация будет казаться плавной для человеческого глаза, когда вы перемещаете объекты по экрану в каждом кадре. Однако если анимация пропускает промежуточные кадры, страница будет выглядеть «дерганой».

Джейдж Джанк из-за пропущенных кадров
Рисунок 11. Кадры анимации на временной шкале.

Даже если ваши операции рендеринга идут в ногу с обновлением экрана, эти вычисления выполняются в основном потоке, а это означает, что они могут быть заблокированы, когда ваше приложение выполняет JavaScript.

Jage Junk с помощью JavaScript
Рис. 12. Кадры анимации на временной шкале, но один кадр заблокирован JavaScript.

Вы можете разделить операцию JavaScript на небольшие фрагменты и запланировать их выполнение в каждом кадре, используя requestAnimationFrame() . Дополнительную информацию по этой теме см. в разделе Оптимизация выполнения JavaScript . Вы также можете запустить свой JavaScript в Web Workers , чтобы избежать блокировки основного потока.

запросить кадр анимации
Рисунок 13. Небольшие фрагменты JavaScript, выполняемые на временной шкале с кадром анимации.

Композитинг

Как бы вы нарисовали страницу?

Рис. 14. Анимация процесса наивного растрирования.

Теперь, когда браузер знает структуру документа, стиль каждого элемента, геометрию страницы и порядок рисования, как он рисует страницу? Преобразование этой информации в пиксели на экране называется растеризацией.

Возможно, самым простым способом справиться с этой задачей было бы растрировать части внутри области просмотра. Если пользователь прокручивает страницу, переместите растровый фрейм и заполните недостающие части, растрируя еще больше. Вот как Chrome обрабатывал растеризацию, когда он был впервые выпущен. Однако современный браузер запускает более сложный процесс, называемый композицией.

Что такое композитинг

Рисунок 15: Анимация процесса композитинга.

Компоновка — это метод разделения частей страницы на слои, их растеризации по отдельности и объединения страницы в отдельный поток, называемый потоком компоновщика. Если произойдет прокрутка, поскольку слои уже растрированы, все, что нужно сделать, — это скомпоновать новый кадр. Анимации можно добиться таким же образом, перемещая слои и создавая новый кадр.

Вы можете увидеть, как ваш веб-сайт разделен на слои в DevTools, используя панель «Слои» .

Разделение на слои

Чтобы выяснить, какие элементы должны находиться в каких слоях, основной поток проходит через дерево макета, чтобы создать дерево слоев (эта часть называется «Обновить дерево слоев» на панели производительности DevTools). Если определенные части страницы, которые должны быть отдельным слоем (например, боковое меню), не получают его, вы можете намекнуть браузеру, используя атрибут will-change в CSS.

дерево слоев
Рис. 16. Основной поток, проходящий через дерево макета и создающий дерево слоев.

У вас может возникнуть соблазн добавить слои к каждому элементу, но композиция из избыточного количества слоев может привести к более медленной работе, чем растеризация небольших частей страницы в каждом кадре, поэтому крайне важно измерить производительность рендеринга вашего приложения. Дополнительные сведения по этой теме см. в разделах «Придерживаться свойств только для композитора» и «Управление количеством слоев» .

Растр и композит вне основного потока

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

растр
Рис. 17. Растровые потоки, создающие растровое изображение тайлов и отправляющие его в графический процессор.

Поток компоновщика может определять приоритеты различных растровых потоков, чтобы объекты в области просмотра (или поблизости) могли быть растрированы в первую очередь. Слой также имеет несколько фрагментов для разных разрешений, чтобы обрабатывать такие действия, как увеличение масштаба.

После растрирования тайлов поток компоновщика собирает информацию о тайлах, называемую четырехугольниками рисования , для создания кадра компоновщика .

Нарисуйте четверные Содержит такую ​​информацию, как расположение плитки в памяти и место на странице для ее рисования с учетом компоновки страницы.
Рамка композитора Коллекция четырехугольников, представляющих собой рамку страницы.

Затем кадр композитора передается процессу браузера через IPC. На этом этапе можно добавить еще один кадр композитора из потока пользовательского интерфейса для изменения пользовательского интерфейса браузера или из других процессов рендеринга для расширений. Эти кадры наборщика отправляются в графический процессор для отображения на экране. Если происходит событие прокрутки, поток наборщика создает еще один кадр наборщика для отправки в графический процессор.

композит
Рис. 18. Поток композитинга, создающий кадр композитинга. Кадр отправляется в процесс браузера, а затем в графический процессор.

Преимущество композитинга в том, что он выполняется без участия основного потока. Потоку композитора не нужно ждать расчета стиля или выполнения JavaScript. Вот почему компоновка только анимации считается лучшей для плавной работы. Если макет или покраску необходимо рассчитать заново, то придется задействовать основную нить.

Заворачивать

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

В следующем и последнем посте этой серии мы более подробно рассмотрим поток композитора и посмотрим, что происходит, когда вводится пользовательский ввод, например перемещение и click mouse move .

Вам понравился пост? Если у вас есть какие-либо вопросы или предложения по поводу будущего поста, я буду рад услышать ваше мнение в разделе комментариев ниже или на @kosamari в Твиттере.

Далее: Ввод поступает в композитор