[Перевод] Как работает JS: движки рендеринга веб-страниц и советы по оптимизации их производительности

Сегодня, в переводе одиннадцатой части серии материалов, посвящённых JavaScript, мы поговорим о подсистемах браузера, ответственных за рендеринг веб-страниц. Они играют ключевую роль в деле преобразования описаний документов, выполненных с помощью HTML и CSS, в то, что мы видим на экране.

image

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

Обзор


Создавая веб-приложения, мы не пишем изолированный JS-код, который занимается исключительно какими-то собственными «внутренними» делами. Этот код выполняется в окружении, предоставляемом ему браузером, взаимодействует с ним. Понимание устройства этого окружения, того, как оно работает, из каких частей состоит, позволяет разработчику создавать более качественные программы, даёт ему возможность предусмотреть возникновение возможных проблем с приложением, которое вышло в свет.

На рисунке ниже показаны основные компоненты браузера. Давайте поговорим о том, какую роль они играют в процессе обработки веб-страниц.

a0dcf9bbfb167e561c78dde6a8e88611.png


Основные компоненты браузера

  • Пользовательский интерфейс (User Interface). Этот компонент браузера включает в себя адресную строку, кнопки «Вперёд» и «Назад», команды для работы с закладками, и так далее. В целом, это всё то, что выводит на экран браузер — за исключением той области его окна, где находится отображаемая им веб-страница.
  • Движок браузера (Browser Engine). Он занимается поддержкой взаимодействия между пользовательским интерфейсом и движком рендеринга.
  • Движок рендеринга (Rendering Engine). Эта подсистема отвечает за показ веб-страницы. Движок рендеринга обрабатывает HTML и CSS и выводит то, что у него получилось, на экран.
  • Сетевая подсистема (Networking). Эта подсистема ответственна за сетевое взаимодействие браузера с внешним миром, в частности, например, её средствами выполняются XHR-запросы. Она поддерживает платформенно-независимый интерфейс, за которым скрываются конкретные реализации различных сетевых механизмов, специфичные для различных платформ. Здесь можно почитать подробности об этой подсистеме.
  • Подсистема поддержки пользовательского интерфейса (UI Backend). Эта подсистема отвечает за вывод базовых компонентов интерфейса, таких, как окна и элементы управления, вроде чекбоксов. Здесь браузеру предоставляется универсальный интерфейс, не зависящий от платформы, на которой он работает, а в основе этой подсистемы лежат возможности формирования элементов пользовательского интерфейса, предоставляемые конкретной операционной системой.
  • JavaScript-движок (JavaScript Engine). Мы разбирали JS-движок в одном из предыдущих материалов этой серии. Именно здесь осуществляется выполнение JS-кода.
  • Подсистема постоянного хранения данных (Data Persistence). Если приложению нужны возможности локального хранения данных, оно может пользоваться различными механизмами, предоставляемыми этой подсистемой. Среди них, например, такие API, как localStorage, IndexedDB, WebSQL и FileSystem.


В этом материале мы сосредоточимся на движке рендеринга. Именно эта подсистема браузера занимается разбором и визуализацией HTML и CSS. А это — именно те технологии, с которыми постоянно взаимодействует код веб-приложений, написанный на JavaScript.

О различных движках рендеринга


Главная задача движка рендеринга заключается в том, чтобы вывести запрошенную страницу в окне браузера. Движок может выводить HTML-документы, XML-документы, изображения. При использовании дополнительных плагинов движок может визуализировать и материалы других типов, например — PDF-документы.

Мы знаем, что существуют различные JS-движки, которые используют различные браузеры. То же самое справедливо и для движков рендеринга. Вот несколько популярных движков:

  • Gecko — используется в браузере Firefox.
  • WebKit — применяется в браузере Safari.
  • Blink — интегрирован в браузеры Chrome и Opera (с 15-й версии).


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


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

f244720436405799957af75a44adc510.png


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

Вот основные этапы этого процесса:

  • Обработка HTML для создания дерева DOM.
  • Создание дерева рендеринга.
  • Расчёт параметров расположения элементов дерева рендеринга на экране, формирование макета страницы.
  • Визуализация (отрисовка) дерева рендеринга.


Рассмотрим эти и другие шаги, выполняемые при визуализации веб-страниц, подробнее.

Создание дерева DOM


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

66f4b3fadfe72ed0f956485ed7da5b72.png


Дерево DOM

Каждый элемент этого дерева, содержащий вложенные элементы, является для них родительским. Это справедливо для всех уровней вложенности.

Создание дерева CSSOM


CSSOM (CSS Object Model) — это объектная модель CSS. Когда браузер занимается созданием дерева DOM страницы, он находит в разделе head тег link, который ссылается на внешний CSS-файл, скажем, имеющий имя theme.css. Ожидая, что этот ресурс может понадобиться ему для рендеринга страницы, браузер выполняет запрос на загрузку данного файла. Этот файл содержит в себе обычный текст, представляющий собой описание стилей, применяемых к элементам страницы.

Как и в случае с HTML, движку нужно конвертировать CSS в нечто, с чем может работать браузер — в CSSOM. В результате получается дерево CSSOM, представленное на следующем рисунке.

99ab91ff1e81ccace7b5d62a7dccc219.png


Дерево CSSOM

Знаете, почему CSSOM имеет древовидную структуру? Когда выполняется формирование итогового набора стилей для элемента страницы, браузер начинает с наиболее общих правил, применимых к этому элементу, представленному узлом DOM (например, если узел является потомком элемента body, к нему применяются все стили, заданные для body), а затем рекурсивно уточняет вычисленные стили, применяя более специфические правила.

Разберём пример, который представлен на предыдущем рисунке. Любой текст, содержащийся внутри тега span, который помещён в элемент body, выводится красным цветом и имеет размер шрифта, равный 16px. Эти стили унаследованы от элемента body. Если элемент span является потомком элемента p, значит его содержимое не выводится в соответствии с применённым к нему более специфичным стилем.

Кроме того, обратите внимание на то, что вышеприведённое дерево не является полным CSSOM-деревом. Тут показаны лишь стили, которые мы, в нашем CSS-файле, решили переопределить. У каждого браузера имеется стандартный набор стилей, применяемый по умолчанию, известный ещё как «стили пользовательского агента» (user agent styles). Именно результаты применения этих стилей можно видеть на странице, не имеющей связанных с ней CSS-правил. Наши же стили просто переопределяют некоторые из стандартных стилей браузера.

Создание дерева рендеринга


Инструкции о внешнем виде элементов, представленные в HTML, скомбинированные с информацией об их стилизации из дерева CSSOM, используются для формирования дерева рендеринга.

Что это такое? Это — дерево визуальных элементов, созданных в том порядке, в котором они будут выводиться на экран. Это — визуальное представление HTML-кода страницы, отражающее влияние соответствующих этой странице CSS-правил. Цель этого дерева заключается в том, чтобы обеспечить вывод элементов правильном порядке.

Узел дерева рендеринга известен в движке WebKit как «renderer» или «render object» (мы будем называть их «объектами рендеринга»).

Вот как будет выглядеть дерево рендеринга для деревьев DOM и CSSOM, показанных выше.

95c00c07f371356b64169c68e8c42920.png


Дерево рендеринга

Вот общее описание действий браузера, выполняемых им при создании дерева рендеринга.

  • Начиная с корня дерева DOM, браузер обходит каждый видимый узел. Некоторые узлы невидимы (например — теги, содержащие ссылки на скрипты, мета-теги, и так далее), их браузер пропускает, так как они не влияют на внешний вид страницы. Некоторые узлы скрыты средствами CSS, браузер так же не включает их в дерево рендеринга. Например, узел span из нашего примера не выводится в дереве рендеринга, так как у нас имеется явным образом заданное правило, устанавливающего для него свойство display: none.
  • Для каждого видимого узла браузер находит подходящие CSSOM-правила и применяет их.
  • В результате формируется структура, содержащая видимые узлы и вычисленные для них стили.


Для того, чтобы лучше понять то, о чём тут идёт речь, можете взглянуть на исходный код класса RenderObject из WebKit. Каждый объект рендеринга представляет собой прямоугольную область, обычно соответствующую CSS-блоку узла. Сведения об этом блоке включают его геометрические характеристики, такие, как ширина, высота и позиция.

Формирование макета страницы


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

HTML использует потоковую модель компоновки. Это означает, что чаще всего система может вычислить геометрические параметры элементов за один проход. Тут используется координатная система, основанная на корневом объекте рендеринга, в ней применяются координаты left и top.

Формирование макета — это рекурсивный процесс. Он начинается в корневом объекте, который соответствует элементу документа . Процесс выполняется рекурсивно по всей иерархической структуре объекта рендеринга, производится вычисление размеров и положения для каждого элемента, который в этом нуждается.

Позиция корневого объекта рендеринга — 0,0. Его размеры соответствуют размерам видимой части окна браузера (это называют «областью просмотра», viewport).

Процесс формирования макета означает задание каждому узлу точной позиции, в которой он должен появиться на странице.

Визуализация дерева рендеринга


На данном этапе осуществляется обход дерева рендеринга и вызов методов paint() объектов рендеринга, которые и выполняют вывод графического представления объектов на экран.

Визуализация, или отрисовка, может быть глобальной или инкрементной (так же выполняется и формирование макета страницы).

  • Глобальная отрисовка означает повторный вывод всего дерева рендеринга.
  • Инкрементная отрисовка выполняется в ситуации, когда меняются лишь некоторые из объектов рендеринга, причём так, что это не влияет на всё дерево. Подсистема рендеринга делает недействительными прямоугольные области на экране. Это приводит к тому, что операционная система воспринимает их как участки, содержимое которых нужно обновить и сгенерировать для них событие paint. Операционная система выполняет перерисовку областей интеллектуально, объединяя несколько областей в одну.


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

Порядок обработки JS-скриптов и CSS-файлов


Разбор и выполнение скрипта осуществляется сразу же после того, как система обработки кода страницы достигнет тега