Всем привет! Меня зовут Александр Борисов и я разрабатываю Modest — открытый движок HTML-рендера на «голом» Си без использования внешних зависимостей (далее движок). Сразу хочется пояснить, что значит «без внешних зависимостей» — весь код пишется с нуля, код ни где не заимствован.
После моей последней публикации прошло не мало времени. За это время многое изменилось и я хочу поделиться с вами достижениями в разработке.
О Проекте
Основная идея проекта заключается в его неприхотливости, а это значит:
— Возможность скомпилировать/установить на любом устройстве где есть Си
— Скорость работы
— Минимально возможное потребление ресурсов
Один из конечных продуктов данной разработки является по настоящему быстрый, лёгкий, полнофункциональный браузер.
Обо всём по порядку
Возможность скомпилировать/установить на любом устройстве где есть Си
Идея заключается не только в том, чтобы иметь возможность скомпилировать/установить на любой железке, ведь Си есть практически везде, но и иметь возможность подключить движок к другому языку программирования раскрывая для этого языка полный спектр API движка. Говоря проще, иметь возможность легко сделать binding (обвязку) для вашего языка программирования.
Что же нам дадут обвязки на других языках?
Простое и понятное API движка даст нам возможность напрямую работать с HTML Tree, CSS, Render Tree/Layers (Дерево отрисовки/слои) через наш любимый язык программирования, без использования JavaScript.
На практике это будет означать следующее:
- Высокая скорость доступа, создания, изменения элементов/слоёв.
- Лёгкость создания интерфейсов, игр, приложений через знакомый вам язык программирования с использованием всех возможностей HTML/DOM Events/CSS.
- Берём готовую обвязку для Perl и меняем её
- Добавляем в обработку тега script тип Perl: <script type="Per">
- Разбираем хтмл в Perl
- Когда парсер встречает тег script с типом Perl то он выполняет этот код в текущем интерпретаторе
Скрипт на Perl получился такой:
use utf8;
use strict;
use warnings;
use HTML::MyHTML::Fun;
my $html = q~
<div>
<span>text</span>
</div>
<script type="Perl">
# $MyHTML_TREE global var
my $nodes = $MyHTML_TREE->get_elements_by_tag_name("span");
foreach my $node (@$nodes) {
$node->delete($MyHTML_TREE);
}
</script>
<span>footer</span>
~;
# parse HTML
my $myhtml = HTML::MyHTML->new(MyHTML_OPTIONS_DEFAULT, 1);
my $tree = $myhtml->new_tree();
$myhtml->parse($tree, MyHTML_ENCODING_UTF_8, $html);
Результат обработки:
<html>
<head>
<body>
<div>
<script type="Perl">
<-text>: ...
<span>
<-text>: footer
Скорость работы
Ждать никто не любит, а я особенно. Один из ключевых моментов разработки — это обеспечение быстрой обработки той или иной части HTML/CSS/Render. К примеру, среднее время обработки типичной хтмл страницы равна 0.001 сек., то есть 1мс, а это 1000 страниц в секунду. Разбор CSS файла bootstrap и его селекторов обходится в 1.5мс. На данный момент мы имеем самый быстрый из полноценный парсер HTML и CSS. И это не предел.
Минимально возможное потребление ресурсов
Если, со скоростью работы всё более или менее понятно, то вот с потреблением ресурсов всё значительно сложнее. Спецификации обычно «советую/требуют» всё хранить в памяти. Точнее не так, все рассуждения идут так, словно у нас уже всё под рукой, всё создано.
В чём же это проявляется?
Возьмём, к примеру, спецификацию CSS синтаксиса. Она нам говорит, что мы должны создать токены на каждый символ/последовательность символов и разложить их по группам, создать группы токенов. Говоря прямо, — мы должны создавать токены на каждый символ не входящий в общие правила токенизации (delim-token), а так же на каждые: ";", ":", "(", ")", "," и прочие, полный список правил можно увидеть тут.
Согласитесь, что создавать токен на каждую запятую или точку с запятой довольно расточительное занятие. При этом, стоит отметить, что в правилах токенизации символов присутствуют условия вроде таких: имея текущий символ посмотрите, что следующие Н равны Х, иначе создайте Y.
Позже, когда все токены будут созданы их нужно разобрать. То есть, из последовательности созданных токенов выделить группы. И именно эти группы используют модули CSS, которых не мало.
Вот тут и начинается интересное. Мы должны полностью соответствовать спецификации, но сделать так чтобы не создавать токены на каждый чих. Не мало читая спецификации и раздумывая о том, как не создавать «лишних» токенов и экономить память, сформировались следующие условия:
1) Разбор CSS должен поддерживать куски (chunks), то есть разбор потока (stream parsing). Этого спецификация не требует, но это важно для дальнейшего развития.
Именно из-за этого условия мы не знаем когда закончится поток данных, для нас он бесконечный. То есть, в любой момент времени мы гарантированно имеем только текущий и все предыдущие символы, но понятия не имеем что дальше, и соответственно не можем посмотреть по условию «если пришел символ А то смотрим есть ли дальше открывающая скобка».
2) Не создаем все токены, создаём только один, который в дальнейшем будет постоянно перезаписываться. В любой момент времени, мы имеем только один токен, текущий токен. Соответственно, мы не можем посмотреть предыдущий или следующий токен, их нет. По началу это казалось проблемой, так как в спецификации не мало условий вроде «если пришел токен Х, то смотрите следующие три токена, и если они не H то тогда Y».
Всё выше описанное реализовано в MyCSS. Уже сейчас MyCSS успешно разбирает селекторы и некоторые CSS свойства потребляя минимальное количество памяти. Соответственно, если мы постоянно не ходим за «кусочками» памяти, то и скорость у нас возрастает значительно.
И в довесок ко всему выше сказанному, парсер MyCSS сохранил понятность, гибкость, лёгкость в дальнейшей разработке модулей к нему.
К слову, в MyHTML всё реализовано ровно наоборот. Там упор на создание токенов и дальнейшую работу с ними. Это наглядно показывает, что в таком деле как написание отрисовщика HTML нельзя использовать «серебряную пулю». Везде нужен индивидуальный подход. Ну, и конечно же, всё это нельзя создать без полного понимания спецификаций и того, что в спецификации требуют.
Текущая структура проекта
На данный момент реализованны следующие части проекта:
- MyHTML — парсер HTML
- MyCSS — парсер CSS (Selectors, Values, Namespace, Property)
- MyFONT — парсер .otf и .fft файлов. Получение метрик для глифов: width, height, baseline, x-height и прочие. Стоит отметить, что тут речь идёт о размерах символов, как в браузерах. Смотрите пример.
О селекторах
Писать отдельную статью о селекторах я не вижу смысла, а похвастаться хочется. Уже сейчас можно использовать селекторы для нахождения нод в дереве:
div > :nth-child(2n+1):not(:has(a))
или списком церез запятую
.header, :nth-child(2n+2 of div:not([id])) >> :not(:has(> [class ~= "bukabyaka" i]))
Работают они довольно шустро. Выше, первый, приведенный селектор на типичной статье хабра отрабатывает за 0.00015 сек., то есть за 0.15мс, и находит 247 элементов. В это время входит разбор CSS, разбор и создание селекторов, поиск по дереву. Можно создать селектор заранее и использовать многократно, что позволит уменьшить время работы.
На данный момент, поддерживаются все селекторы из спецификации четверной версии (Selectors 4) за исключением:
- Всех псевдо-элементов (pseudo-elements)
- :dir, :lang, :scope, «Time-dimensional Pseudo-classes», :drop
- :nth-column, :nth-last-column
Первые и вторые будут разрабатываться/добавляться по мере разработки движка. До третьих руки не дошли, но, конечно же, они так же будут реализованы в ближайшем будущем.
Будущее проекта
Оно (будущее) видится очень светлым. Проектом занимаюсь в рабочее время. Мой работодатель разрешил мне тратить всё рабочее время на проект, конечно, если что-то просят сделать приходится отвлекаться.
Сейчас, я приступил к созданию дерева отрисовки (Render Tree, Layers). То есть, уже в недалёком будущем можно будет получать рассчитанные метрики хтмл нод, такие как width, height, font-size, border-color и прочие.
Идей очень много, а «бензина» во мне ещё больше! Спасибо за внимание!
P.S.: Если у кого-нибудь возникнет желание помочь/поучаствовать в реализации проекта то можно смело писать на почту.
Ссылки:
» Modest
» Примеры Modest
» Примеры MyHTML
» Примеры MyCSS
» Обвязка MyHTML для Perl
Автор: lastmac