Внутренняя работа процесса рендеринга
Это третья часть из четырех серий блогов, посвященная тому, как работают браузеры. Ранее мы рассмотрели многопроцессную архитектуру и поток навигации . В этом посте мы рассмотрим, что происходит внутри процесса рендеринга.
Процесс рендеринга затрагивает многие аспекты веб-производительности. Поскольку внутри процесса рендеринга происходит много всего, эта статья представляет собой лишь общий обзор. Если вы хотите копнуть глубже, в разделе «Производительность» раздела «Основы веб-технологий» есть гораздо больше ресурсов.
Процессы рендеринга обрабатывают веб-содержимое
Процесс рендеринга отвечает за все, что происходит внутри вкладки. В процессе рендеринга основной поток обрабатывает большую часть кода, который вы отправляете пользователю. Иногда части вашего JavaScript обрабатываются рабочими потоками, если вы используете веб-воркера или сервис-воркера. Потоки компоновщика и растра также запускаются внутри процессов рендеринга для эффективного и плавного рендеринга страницы.
Основная задача процесса рендеринга — превратить HTML, CSS и JavaScript в веб-страницу, с которой пользователь может взаимодействовать.
![Процесс рендеринга](https://developer.chrome.com/static/blog/inside-browser-part3/image/renderer-process-df424472d0633.png?authuser=5&hl=ru)
Разбор
Строительство ДОМ
Когда процесс рендеринга получает сообщение фиксации для навигации и начинает получать данные 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, и отправляет запросы в сетевой поток в процессе браузера.
![ДОМ](https://developer.chrome.com/static/blog/inside-browser-part3/image/dom-ffeed8e96a6e8.png?authuser=5&hl=ru)
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.
![Компьютерный стиль](https://developer.chrome.com/static/blog/inside-browser-part3/image/computed-style-5e3c65a01f3f1.png?authuser=5&hl=ru)
Даже если вы не предоставляете CSS, каждый узел DOM имеет вычисленный стиль. Тег <h1>
отображается больше, чем тег <h2>
, а поля определяются для каждого элемента. Это связано с тем, что в браузере имеется таблица стилей по умолчанию. Если вы хотите узнать, что такое CSS по умолчанию в Chrome, вы можете посмотреть исходный код здесь .
Макет
Теперь процесс рендеринга знает структуру документа и стили для каждого узла, но этого недостаточно для рендеринга страницы. Представьте, что вы пытаетесь описать картину своему другу по телефону. «Там большой красный круг и маленький синий квадрат» недостаточно для того, чтобы ваш друг знал, как именно будет выглядеть картина.
![игра человеческого факса](https://developer.chrome.com/static/blog/inside-browser-part3/image/game-human-fax-machine-17f10d8b400ac.png?authuser=5&hl=ru)
Компоновка — это процесс поиска геометрии элементов. Основной поток проходит через DOM и вычисляемые стили и создает дерево макета, которое содержит такую информацию, как координаты xy и размеры ограничивающего прямоугольника. Дерево макета может иметь структуру, аналогичную дереву DOM, но оно содержит только информацию, связанную с тем, что видно на странице. Если применяется display: none
, этот элемент не является частью дерева макета (однако элемент с visibility: hidden
находится в дереве макета). Аналогично, если применяется псевдоэлемент с содержимым типа p::before{content:"Hi!"}
, он включается в дерево макета, даже если его нет в DOM.
![макет](https://developer.chrome.com/static/blog/inside-browser-part3/image/layout-9d8ed8c743f45.png?authuser=5&hl=ru)
Определение макета страницы — сложная задача. Даже самый простой макет страницы, такой как расположение блоков сверху вниз, должен учитывать размер шрифта и место разрыва строки, поскольку это влияет на размер и форму абзаца; что затем влияет на то, где должен быть следующий абзац.
CSS может перемещать элемент в сторону, маскировать элемент переполнения и изменять направление письма. Вы можете себе представить, перед этим этапом макетирования стоит огромная задача. В Chrome над версткой работает целая команда инженеров. Если вы хотите узнать подробности их работы, несколько выступлений с конференции BlinkOn записаны и их довольно интересно посмотреть.
Краска
![рисование игры](https://developer.chrome.com/static/blog/inside-browser-part3/image/drawing-game-7902ad8a91d2f.png?authuser=5&hl=ru)
Иметь DOM, стиль и макет по-прежнему недостаточно для рендеринга страницы. Допустим, вы пытаетесь воспроизвести картину. Вы знаете размер, форму и расположение элементов, но вам все равно придется судить, в каком порядке вы их рисуете.
Например, для определенных элементов может быть установлен z-index
, в этом случае рисование в порядке элементов, записанных в HTML, приведет к некорректному рендерингу.
![Ошибка z-индекса](https://developer.chrome.com/static/blog/inside-browser-part3/image/z-index-fail-2529cf989dc65.png?authuser=5&hl=ru)
На этом этапе рисования основной поток проходит по дереву макета для создания записей рисования. Запись рисования — это запись процесса рисования, например «сначала фон, затем текст, затем прямоугольник». Если вы рисовали элемент <canvas>
с помощью JavaScript, этот процесс может быть вам знаком.
![рисовать пластинки](https://developer.chrome.com/static/blog/inside-browser-part3/image/paint-records-151165f18a91e.png?authuser=5&hl=ru)
Обновление конвейера рендеринга обходится дорого
Самое важное, что нужно понимать в конвейере рендеринга, — это то, что на каждом этапе результат предыдущей операции используется для создания новых данных. Например, если что-то изменится в дереве макета, то порядок рисования необходимо заново сгенерировать для затронутых частей документа.
Если вы анимируете элементы, браузер должен выполнять эти операции между каждым кадром. Большинство наших дисплеев обновляют экран 60 раз в секунду (60 кадров в секунду); анимация будет казаться плавной для человеческого глаза, когда вы перемещаете объекты по экрану в каждом кадре. Однако если анимация пропускает промежуточные кадры, страница будет выглядеть «дерганой».
![Джейдж Джанк из-за пропущенных кадров](https://developer.chrome.com/static/blog/inside-browser-part3/image/jage-jank-missing-frames-187496b8fbc8e.png?authuser=5&hl=ru)
Даже если ваши операции рендеринга идут в ногу с обновлением экрана, эти вычисления выполняются в основном потоке, а это означает, что они могут быть заблокированы, когда ваше приложение выполняет JavaScript.
![Jage Junk с помощью JavaScript](https://developer.chrome.com/static/blog/inside-browser-part3/image/jage-jank-javascript-b19c0193934a3.png?authuser=5&hl=ru)
Вы можете разделить операцию JavaScript на небольшие фрагменты и запланировать их выполнение в каждом кадре, используя requestAnimationFrame()
. Дополнительную информацию по этой теме см. в разделе Оптимизация выполнения JavaScript . Вы также можете запустить свой JavaScript в Web Workers , чтобы избежать блокировки основного потока.
![запросить кадр анимации](https://developer.chrome.com/static/blog/inside-browser-part3/image/request-animation-frame-3b04e26660019.png?authuser=5&hl=ru)
Композитинг
Как бы вы нарисовали страницу?
Теперь, когда браузер знает структуру документа, стиль каждого элемента, геометрию страницы и порядок рисования, как он рисует страницу? Преобразование этой информации в пиксели на экране называется растеризацией.
Возможно, самым простым способом справиться с этой задачей было бы растрировать части внутри области просмотра. Если пользователь прокручивает страницу, переместите растровый фрейм и заполните недостающие части дополнительным растрированием. Вот как Chrome обрабатывал растеризацию, когда он был впервые выпущен. Однако современный браузер запускает более сложный процесс, называемый композицией.
Что такое композитинг
Компоновка — это метод разделения частей страницы на слои, их растеризации по отдельности и объединения страницы в отдельный поток, называемый потоком компоновщика. Если произойдет прокрутка, поскольку слои уже растрированы, все, что нужно сделать, — это скомпоновать новый кадр. Анимации можно добиться таким же образом, перемещая слои и создавая новый кадр.
Вы можете увидеть, как ваш веб-сайт разделен на слои в DevTools, используя панель «Слои» .
Разделение на слои
Чтобы выяснить, какие элементы должны находиться в каких слоях, основной поток проходит через дерево макета, чтобы создать дерево слоев (эта часть называется «Обновить дерево слоев» на панели производительности DevTools). Если определенные части страницы, которые должны быть отдельным слоем (например, боковое меню), не получают его, вы можете намекнуть браузеру, используя атрибут will-change
в CSS.
![дерево слоев](https://developer.chrome.com/static/blog/inside-browser-part3/image/layer-tree-cc92336966c73.png?authuser=5&hl=ru)
У вас может возникнуть соблазн добавить слои к каждому элементу, но композиция из избыточного количества слоев может привести к более медленной работе, чем растеризация небольших частей страницы в каждом кадре, поэтому крайне важно измерить производительность рендеринга вашего приложения. Дополнительные сведения по этой теме см. в разделах «Придерживаться свойств только для композитора» и «Управление количеством слоев» .
Растр и композит вне основного потока
После создания дерева слоев и определения порядка рисования основной поток передает эту информацию в поток набора текста. Затем поток наборщика растрирует каждый слой. Слой может быть большим, как вся длина страницы, поэтому поток наборщика делит его на фрагменты и отправляет каждый фрагмент в растровые потоки. Растровые потоки растрируют каждый тайл и сохраняют их в памяти графического процессора.
![растр](https://developer.chrome.com/static/blog/inside-browser-part3/image/raster-9dfd7af5a9554.png?authuser=5&hl=ru)
Поток композитора может определять приоритетность различных растровых потоков, чтобы объекты в области просмотра (или поблизости) могли быть растрированы в первую очередь. Слой также имеет несколько фрагментов для разных разрешений, чтобы обрабатывать такие вещи, как увеличение масштаба.
После того как тайлы растрируются, поток компоновщика собирает информацию о тайлах, называемую четырехугольниками рисования, для создания кадра компоновщика .
Нарисуйте четверные | Содержит такую информацию, как расположение плитки в памяти и место на странице для ее рисования с учетом компоновки страницы. |
Рамка композитора | Коллекция четырехугольников, представляющих рамку страницы. |
Затем кадр композитора передается процессу браузера через IPC. На этом этапе можно добавить еще один кадр композитора из потока пользовательского интерфейса для изменения пользовательского интерфейса браузера или из других процессов рендеринга для расширений. Эти кадры наборщика отправляются в графический процессор для отображения на экране. Если происходит событие прокрутки, поток наборщика создает еще один кадр наборщика для отправки в графический процессор.
![композит](https://developer.chrome.com/static/blog/inside-browser-part3/image/composit-266744978ac93.png?authuser=5&hl=ru)
Преимущество композитинга в том, что он выполняется без участия основного потока. Потоку композитора не нужно ждать расчета стиля или выполнения JavaScript. Вот почему компоновка только анимации считается лучшей для плавной работы. Если макет или покраску необходимо рассчитать заново, то придется задействовать основную нить.
Заворачивать
В этом посте мы рассмотрели конвейер рендеринга от синтаксического анализа до композиции. Надеемся, теперь у вас есть возможность узнать больше об оптимизации производительности веб-сайта.
В следующем и последнем посте этой серии мы более подробно рассмотрим поток композитора и посмотрим, что происходит, когда вводится пользовательский ввод, например перемещение и click
mouse move
.
Вам понравился пост? Если у вас есть какие-либо вопросы или предложения по поводу будущего поста, я буду рад услышать ваше мнение в разделе комментариев ниже или на @kosamari в Твиттере.