Последнее время с браузерами всё было просто и предсказуемо: есть Google Chrome для всех, есть Safari для пользователей Mac OS и Edge для Windows, есть Firefox, Opera, Brave и российский Яндекс Браузер, которые живут на тех же движках, что и предыдущие. Но в 2022 году вышел новый браузер Arc, который произвёл мини-революцию и быстро набрал популярность. Получается, что даже в этой области, где всё уже привычно и стандартно, можно создать новый браузер, который понравится пользователям.
Чтобы создать свой браузер, можно написать всё с нуля или использовать готовые компоненты. Но для начала нужно разобраться, как устроены браузеры, что делает движок и что нужно предусмотреть. Сегодня — как раз об этом.
Если вы только в общих чертах представляете, как работает браузер, почитайте нашу статью о том, что происходит, когда мы открываем сайт, Потом возвращайтесь — многое станет понятнее.
Из чего состоит веб-браузер
Обычно архитектуру браузера условно представляют в виде таких основных компонентов:
- пользовательский интерфейс (User Interface, или UI);
- движок браузера (Browser Engine);
- рендеринг-движок (Rendering Engine);
- сетевой слой (Networking Layer);
- JavaScript-интерпретатор (JavaScript Interpreter);
- бэкенд пользовательского интерфейса (UI Backend);
- хранилище данных (Data Persistence).
Теперь разберёмся, что делает каждый компонент, за что отвечает и чем управляет.
Пользовательский интерфейс — набор элементов для взаимодействия непосредственно с браузером, а не с веб-страницами, которые он отображает. Это строка для ввода адреса сайта, кнопки «Назад», «Вперёд» и «Обновить страницу», панель закладок и другие подобные элементы. Проще говоря, интерфейс браузера — это то, что мы видим, когда запускаем браузер, но ещё не открыли ни одной страницы.
Движок браузера — самый важный и сложный компонент. Он отвечает за взаимодействие между пользовательским интерфейсом и рендеринг-движком и координирует работу между рендеринг-движком и другими компонентами браузера. Движок браузера управляет загрузкой страниц, обработкой и выполнением скриптов, сетевыми запросами, сессиями и так далее. Можно сказать, что движок — это мозговой центр браузера, без которого всё остальное перестанет работать.
Рендеринг-движок отвечает за отображение запрошенной веб-страницы. Он читает файлы HTML со структурой страницы, CSS со стилями оформления и JavaScript с интерактивными сценариями, а затем рендерит, то есть отрисовывает, содержимое этих файлов на экране. В результате мы видим визуальное представление страницы, которое может динамически меняться от наших действий.
Сетевой слой отвечает за все операции, которыми браузер занимается в сети: общается с веб-серверами и получает от них файлы, поддерживает соединения, проверяет сертификаты безопасности сайтов и сообщает о возможных угрозах. Сетевой слой передаёт загруженные ресурсы рендеринг-движку и кэширует их в памяти для более быстрой загрузки сайта при повторном посещении. Он также управляет куками и токенами аутентификации, с помощью которых нас узнают сайты.
JavaScript-интерпретатор отвечает за выполнение JavaScript-кода на страницах: анимации, интерактивные элементы и другой динамический контент. JS-интерпретатор обрабатывает события на странице так, что она реагирует на наши клики, ввод текста, прокрутку и наведение курсора мыши.
Бэкенд пользовательского интерфейса обрабатывает запросы от интерфейса и отправляет ответы. Например, когда пользователь вводит адрес сайта, бэкенд направляет этот запрос в сетевой модуль и движок браузера, чтобы загрузить нужную страницу.
Хранилище данных содержит данные пользователя: файлы куки, историю посещений и кэшированные файлы. В современных браузерах хранилище также используется для работы с локальными базами данных и офлайн-режимом.
Теперь важное замечание. На самом деле браузерный движок включает рендеринг-движок, сетевой слой, JavaScript-интерпретатор и бэкенд UI. Но их часто выделяют отдельно из-за того, насколько разные задачи они выполняют. А ещё так сложилось, что рендеринг-движки и JavaScript-интерпретаторы имеют свои названия. Например, в Chromium есть рендеринг-движок Blink и JS-интерпретатор V8. WebKit в Safari сначала был просто рендеринг-движком, а затем вырос до браузерного движка.
Чтобы создать свой полноценный браузер, нужно собрать вместе и настроить все эти компоненты. Разве что, если хотите «безголовый браузер» для разработки или тестирования, пользовательский интерфейс не нужен.
Какие процессы происходят в браузере во время его работы
Чтобы браузер работал быстро, стабильно и безопасно, задачи разделяются между разными процессами, которые могут одновременно взаимодействовать с различными компонентами. Приведём несколько примеров.
Основной процесс создаёт и завершает все остальные процессы, а также координирует их работу. Он обеспечивает взаимодействие браузера с операционной системой, включая доступ к файловой системе, сети и аппаратным ресурсам. Основной процесс связан с пользовательским интерфейсом, сетевым слоем, бэкендом UI и хранилищем данных. Если завершить основной процесс, браузер закроется вместе со всеми вкладками.
Рендер-процесс управляет отрисовкой страниц, преобразуя HTML, CSS и другие ресурсы в визуальное представление, и интерпретирует и исполняет JavaScript-код. Вкладки браузера обычно работают в отдельных рендер-процессах, чтобы изолировать их друг от друга. Если одна вкладка зависнет, это не повлияет на остальные. Рендер-процесс связан с рендеринг-движком, JavaScript-интерпретатором, сетевым слоем, бэкендом UI и хранилищем данных.
Процесс GPU помогает разгрузить основной и рендер-процессы. Он обрабатывает графику, которая требует аппаратного ускорения, например сложные визуальные эффекты. Процесс GPU связан с пользовательским интерфейсом браузера и рендеринг-движком.
Сетевой процесс управляет всеми сетевыми операциями, включая загрузку страниц, ресурсов и мультимедиа-файлов. Связан с сетевым слоем и хранилищем данных.
Процесс обработки событий отвечает за обработку пользовательских действий: клики, ввод текста, прокрутку и другие. Эти события он передаёт в соответствующие рендер-процессы. Процесс обработки событий связан с пользовательским интерфейсом, рендеринг-движком, JavaScript-интерпретатором и бэкендом UI.
👉 Короче, нет такого, что компоненты браузера обособлены и процессы крутятся в них замкнуто. Всё переплетено.
Где взять готовые компоненты для создания браузера
Самый простой способ создать свой браузер — собрать его на базе готового проекта с открытым исходным кодом. Для этого весь код копируют, а затем меняют и дополняют по своему усмотрению, соблюдая лицензии. Вот с каких открытых проектов можно начать:
- Chromium — свободный браузер, на основе которого сделаны Google Chrome, Opera, Arc и многие другие браузеры. Включает мощный рендеринг-движок Blink и JavaScript-интерпретатор V8, которые можно использовать отдельно. На базе движка Blink сделан Яндекс Браузер.
- WebKit — движок, который используется в браузерах Safari и старых версиях Google Chrome. Включает JS-интерпретатор JavaScriptCore, который можно использовать отдельно.
- Gecko — движок, разработанный в Mozilla, используется в Firefox. Включает JS-интерпретатор SpiderMonkey, который можно использовать отдельно.
- Servo — экспериментальный движок, разработанный в Mozilla Research.
- Electron — фреймворк для создания приложений, который работает на движке Chromium и использует библиотеку Node.js для серверного уровня.
- Qt WebEngine — компонент фреймворка Qt для C++, основанный на Chromium. Позволяет создавать браузеры и другие веб-приложения.
У этих проектов есть подробная документация и инструкции по получению исходного кода. Выбор зависит от целей и требований к браузеру. Например, если браузер будет ориентирован на macOS или Linux, то лучше выбрать WebKit.
Если хотите сделать браузер для разработчиков с удобными инструментами для отладки, то лучше выбрать Chromium, так как в него уже встроены нужные инструменты разработчика и можно добавлять свои функции и расширения.
Для легковесного браузера, который будет работать на слабых устройствах, можно выбрать Servo — он заточен на высокую производительность и безопасность (но инструментами разработчика там пользоваться будет сложно).
На чём написать свой движок для браузера
Если вы всё-таки хотите написать свой движок, то сначала нужно выбрать язык. В основном браузерные движки пишут на C++ и Rust.
С++ как низкоуровневый язык позволяет писать код, который выполняется очень быстро. В отличие от языков, которые требуют интерпретаторов, например Python или JavaScript, C++ компилируется непосредственно в машинный код. Это позволяет напрямую взаимодействовать с операционной системой и точно контролировать, как и когда выделяется и освобождается память. На C++ можно написать мощный и производительный движок, как Chromium, Blink, Gecko и WebKit.
Rust тоже компилируется в машинный код, а ещё использует систему владения и заимствования для управления памятью. У каждого фрагмента данных, например переменной или структуры, есть только один владелец, например функция. После того как она выполняется, память автоматически освобождается. При заимствовании доступ к данным одалживается другим частям кода с условием: либо несколько частей одновременно их читают, либо только одна часть их меняет. Это минимизирует риск утечки памяти и идеально подходит для многопоточных приложений. На Rust можно написать надёжный движок, как Servo от Mozilla.
На этих языках есть готовые библиотеки, которые можно использовать для выполнения различных задач браузера, например:
- чтение HTML и сбор страниц — Gumbo на C++ и HTML5ever на Rust;
- чтение CSS и применение стилей — libcss на C++ и rust-cssparser на Rust;
- работа с сетью — libcurl для HTTP-запросов и libuv для асинхронной загрузки на C++ и Hyper на Rust;
- обработка пользовательских событий — libev или libuv на C++ и winit на Rust.
Что нужно предусмотреть
Поскольку браузер обрабатывает большое количество данных — от рендеринга и выполнения JavaScript до загрузки мультимедиа и хранения данных пользователя, то браузер должен эффективно использовать оперативную память, чтобы не зависать и не тормозить. В C++ и Rust есть встроенные механизмы для управления памятью, но браузеру могут потребоваться дополнительные алгоритмы, например для управления памятью в процессе рендеринга или работы с большими данными. Для этих целей можно использовать библиотеки для управления кэшем: jemalloc на C/C++ или moka на Rust.
Данные в браузере должны передаваться безопасно, чтобы злоумышленники не могли перехватить пароли или другую чувствительную информацию. Для этого в браузере должны быть такие штуки:
- шифрование локально хранимых данных (можно сделать с помощью OpenSSL для C/C++ или rust-crypto для Rust);
- возможность использовать HTTPS для защиты передачи данных (libcurl на C/C++ и reqwest на Rust);
- защита от XSS- и CSRF-атак с помощью Content Security Policy.
Чтобы пользователи могли сохранять свои предпочтения в браузере, а браузер — хранить кэш, куки и пароли, нужно реализовать механизм хранения. Для этого можно использовать SQLite на C/C++, который применяется в большинстве современных браузеров. Это легковесная и мощная база данных, которая поддерживает хранение данных локально на устройстве пользователя.
Далее нужно реализовать систему автоматического обновления, чтобы браузер всегда оставался актуальным, получал новые функции и был защищён от уязвимостей. Для этого понадобится создать сервер обновлений и механизм проверки версий, загрузки и установки обновлений. Можно использовать Squirrel — кроссплатформенное решение для автоматических обновлений.
В собственный браузер также можно добавить инструменты разработчика: либо интегрировать готовые решения (Chromium DevTools), либо создать упрощённые версии, используя доступные API.
Для тестирования и отладки пригодятся фреймворки Selenium или Puppeteer (для браузеров на основе Chromium). Для отладки также можно использовать встроенные возможности языка, например GNU Debugger для C++, или специализированные инструменты для поиска утечек памяти.
Что дальше
В следующий раз соберём собственный браузер из готовых компонентов. Подпишитесь, чтобы не пропустить продолжение.