Как работают браузеры?
Основное предназначение браузера — отображение запрашиваемых ресурсов. Расположение ресурса определяется с помощью URI (унифицированого идентификатора ресурса).
Рассмотрим основные компоненты браузера:
- User Interface (Пользовательский интерфейс)— адресная строка, кнопки «Вперед»/»Назад» и остальные элементы, через которые пользователь может взаимодействовать с браузером.
- Browser Engine (Механизм браузера) — управляет взаимодействием User Interface и Rendering Engine
- Rendering Engine (Модуль отображения) — выполняет синтаксический анализ кода запрашиваемого ресурса и выводит содержимое на экран. По умолчанию он отображает HTML, XML документы и картинки, для отображения других файлов, например pdf, используются специальные модули. В Chrome и Safari модулем отображения является Webkit, а в Firefox — Gecko. Подробней о работе Rendering Engine рассмотрим далее.
- Networking (Сетевые компоненты) — выполнение сетевых вызовов (HTTP запросы)
- UI Backend — отрисовка основных виджетов — окна, поля со списками.
- Интерпретатор JS — синтаксический анализ и выполнение JS
- Data Persistence (Хранилище данных) — браузер хранит данные в формате «ключ: значение» на жестком диске для различных сайтов (cookie, sessionStorage).
Что же происходит когда мы ищем нужный ресурс в браузере?
Когда пользователь вводит данные в адресной строке, браузер, получив строку к примеру «searchpage.com.ua», должен узнать IP-адрес. User Interface после ввода url передает управление элементу Browser Engine. Для того, что бы сделать запрос по указанному в адресной строке url, браузеру нужно узнать адрес IP сервера.
Компьютеры в сети интернет имеют доменное имя (например, site.com) и IP-адрес (например, 127.0.0.1). Основной задачей DNS сервера является трансляция доменных имен в IP-адреса и обратно. DNS имеет иерархическую структуру имен, где есть корень дерева («.»), далее расположены домены первого уровня (com, net, org и т.д.) и домены государств (ua, ru и т.д.), далее домены второго, третьего уровня… Когда клиент посылает запрос этому серверу, сервер или отвечает на запрос (если знает ответ) или пересылает запрос вышестоящему серверу, а когда ответ найден он опускается низ по цепочке серверов к источнику запроса. Процесс получения IP адреса называется DNS lookup.
Для того, что бы узнать IP-адрес ресурса «searchpage.com.ua» начинается поиск с локального кеша DNS и с локальных хостов (файл /etc/hosts), если там запись не найдена — локальная система обращается к интернет провайдеру, который тоже имеет свой кеш DNS, если и там не найден — запрос идет дальше по цепочке серверов.
В нашем примере для сайта «searchpage.com.ua» запросы будут идти приблизительно так: . (корневой DNS) -> ua (DNS зоны “ua”) -> com (DNS зоны “com”)-> searchpage.
Далее Browser Engine смотрит, нет ли в ее кеше данных соответвующих ресурсу по искомому IP-адресу, если такого закешированого ресурса не найдено — передается управление Rendering Engine, который, при помощи компонента Network, посылает GET запросы за ресурсом HTML на указанный IP. Браузер получает запрашиваемый HTML файл с указанными заголовками, если присутствуют заголовки для кеширования — он сохранит их в свой локальный кеш. Далее браузер начинает парсинг полученного ресурса при помощи Rendering Engine.
Модуль отображения (Rendering Engine)
Rendering Engine получает содержимое запрашиваемого документа обычно фрагментами по 8 КБ.
Сначала он проводит синтаксический анализ и парсинг HTML документа и приводит параметры в узлы для построения DOM дерева (Дерева содержания). СSS документы тоже обрабатываются и служат для построения CSSOM дерева, а так же там собирается вся информация о стилях, найдена в других источниках, например, inline стили в html. После DOM дерево и CSSOM дерево объединяются в одно, этот процесс называется Attachment, для создания Render Tree. Дерево отображения (Render Tree) состоит из объектов отображения (Render Objects). В нем элементы отображения располагаются в том порядке в котором их необходимо вывести на экран.
Из полученного Render Tree происходит компоновка (layout), в процессе которой каждому узлу дерева отображения присваиваются координаты места, где он должен появится. Затем с помощью UI Backend происходит отрисовка элементов.
Ниже представлена схема работы модуля отображения WebKit:
Синтаксический анализ HTML
Синтаксический анализ — это преобразование документа в пригодную для чтения структуру. Результатом синтаксического анализа является дерево узлов, представляющее структуру документа — Синтаксическое дерево. В случае анализа HTML документа — в результате синтаксического анализа мы получим синтаксическое дерево (DOM).
Объектная модель документа (DOM) — это фундаментальный прикладной программный интерфейс, обеспечивающий возможность работы с содержимым HTML & XML документов таким внешним объектам как код JS.
Синтаксический анализ состоит из двух этапов — лексического анализа и построения дерева. В процессе лексического анализа входная последовательность символов разбивается на токены — открывающие и закрывающие теги, названия и значения атрибутов. Лексичесткий анализатор обнаруживает токен, передает его в конструктор дерева и переходит дальше к поиску новых токенов, пока входная последовательность символов не будет закончена.
DOM состоит из узлов, представляющих элементы или теги, такие как <p>
или <body>
, и узлы представляющие строки текста.
Рассмотрим пример как бы выглядело DOM дерево для такого html кода:
<html>
<head>
<title>Title</title>
</head>
<body>
<p>Demo Text</p>
<div>
<img src="example.png">
</div>
</body>
</html>
Каждый прямоугольник, представленный на DOM дереве является объектом Node. На схеме представлены узлы типа Document, Element (p, div …) и Text — они являются подклассами класса Node.
В процессе лексического анализа элементы добавляются не только в DOM, а так же и в стек открытых элементов, что позволяет исправлять неправильно вложенные или закрытые теги, тем самым, корректируя отображения ресурса пользователю.
Порядок обработки CSS & JS
Обработка CSS
При обработке стилей браузер строит подобную древовидную структуру, как было рассмотрено выше. В файле стилей имеется селектор (div, #id, .cssClass
) и значение. Браузер читает селекторы справа налево, например, селектор типа div ul > a
будет обработан в такой поочередности: сначала браузер найдет все элементы a
на странице, после ul
и отфильтрует массив ul до тех, дочерними элементами которых являются a
и т.д. пока не дойдет до крайнего левого селектора.
В результате синтаксического анализа CSS будет построено дерево на основе всех найденных стилей, рассмотрим маленький пример:
body {
font-size: 16px;
}
h1 {
text-align: center;
}
В следствии обработки такого css будет построено CSSOM дерево:
После построения дерева CSS браузеру нужно сопоставить его с DOM деревом элементов. В Webkit процесс сопоставления DOM дерева и стилей называется Attachment. Совмещение происходит синхронно — при добавлении нового элемента DOM дерева для него вызывается метод attach
.
Обработка Javascript
Javascript код выполняется сразу как был обнаружен на странице тег <script>
, при этом синтаксический анализ документа останавливается до выполнения скрипта. Если речь идет о внешних источниках, которые нужно загрузить, синтаксический анализ документа точно так же ставится на паузу, но уже для загрузки и выполнения кода.
Чтобы влиять на время выполнения скрипта, можно использовать атрибуты defer
или async
. Они позволяют не останавливать синтаксический анализ документа для загрузки скрипта.
<script type="text/javascript" defer src="./index.js"></script>
<script type="text/javascript" async src="./index.js"></script>
defer
и async
дают возможность выполнять загрузку скрипта асинхронно, он будет выполнен как только загрузится, а блокировки системного анализа не произойдет.
Отличием defer
от async
является то, что async
выполнит скрипт как только он загрузился, а defer
при этом сохранит порядок в котором они были подключены на страницу.
Если присутствуют оба атрибута — defer
будет проигнорирован.
Так как стили не вносят изменение в DOM — останавливать синтаксическую обработку для их загрузки нет необходимости, но скрипты JS могут пытаться использовать еще не загруженные стили, таким образом в WebKit скрипты останавливаются только в том случае, если просят данные о стилях, а браузер имеет в наличии еще необработанные таблицы стилей, а Gecko останавливает все скрипты если обнаруживает, что какая-то таблица стилей была еще не обработана.
Render Tree
Во время обработки HTML и CSS создается еще одно дерево — дерево отображения или Render Tree, в котором элементы располагаются в том порядке. в котором они будут выведены на экран. Render Tree состоит из объектов отображения, которые в свою очереди представляют из себя прямоугольную область, где содержатся геометрические данные — высота, ширина, размеры рамок и отступов. Тип окна объекта отображения зависит от свойства display
.
Один объект синтаксического DOM дерева может иметь 1 или больше объектов дерева отображения, так к примеру элемент select
будет иметь 3 объекта дерева отображения — объект отображения области, раскрывающийся список и кнопка. Стоит отметить, что если текст не помещается в одну строку — каждой строке будет создан новый объект отображения. Элементам со стилем display: none
не будет создано объекта отображения в Render Tree.
Для построения дерева стилей нужно рассчитать свойство каждого элемента, достав стили с внешних файлов, inline стилей и стилей заданных атрибутами (например width, bgcolor…).
Компоновка (Layout)
Layout — это расчет размера и положения объекта.
В большинстве случаев компоновка выполняется за 1 проход, так как последующие элементы не влияют на размеры уже рассчитанных элементов, но бывают и исключения, к примеру таблицы.
Layout проходит в несколько циклов и начинается с объекта html
, который получает начальные координаты 0:0, а размеры соответствуют видимой части экрана.
Существует 2 вида компоновки — инкрементная и глобальная. Глобальная компоновка выполняется для всего Render Tree, а инкрементная только для части Render Tree — тех объектов которые помечены как «dirty».
Что бы не выполнять глобальную перекомпоновку при небольших изменениях была придумана система «грязных битов» — измененный объект отображения и его дочерние элементы помечаются флагами dirty
и children are dirty
. Флаг children are dirty
означает, что перекомпоновка нужна не самому объекту, а одному из его дочерних элементов.
Глобальную перекомпоновку могут вызвать какие-то глобальные изменения, которые используются во всех объектах, например, изменение шрифта или размеров экрана.
Инкрементная перекомпоновка выполняется асинхронно по мере обнаружения «грязных битов».
Способ компоновки окна определяется следующими способами:
- Типом окна
- Размером окна
- Схемой позиционирования
- Внешней информацией (например размеры экрана)
Тип окна элементов отображения зависят от свойства display: block, inline, none. Блочное окно имеет собственный блок, строчное окно не имеет собственного блока и помещается внутрь контейнера,
В зависимости от свойств position
и float
существуют разные схемы позицинирования:
- Стандартная — объект размещается в документе согласно своему положению в дереве
- Плавающая — сначала объект размещается в документе согласно своему положению в дереве, а после сдвигается в крайнее левое или крайнее правое положение
- Относительная — положение объекта в дереве отображения отличается от его положения в дереве DOM
Display (Отрисовка)
На этапе отрисовки каждому объекту отображения будет вызван метод paint
, в результате которого он будет отображен на экран.
Пространство, где отображается сформированная структура называется холст (canvas). В действительности он бесконечен, однако браузеры обычно определяют для него ширину исходя из размеров области просмотра.
Окна делятся на стеки, где сначала отрисовываются элементы на заднем плане, а затем на переднем. Порядок стеков определяется свойством z-index и соответствует третьей оси расположения объектов.
Источники:
«How browsers work» By Tali Garsiel and Paul Irish
«Javascript. Подробное руководство» Дэвид Флэнаган
Medium
Blog imena.ua